Ansible::checkCommand()   A
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 6
nop 2
dl 0
loc 25
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/*
3
 * This file is part of the php-ansible package.
4
 *
5
 * (c) Marc Aschmann <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Asm\Ansible;
12
13
use Asm\Ansible\Command\AnsibleGalaxy;
14
use Asm\Ansible\Command\AnsibleGalaxyInterface;
15
use Asm\Ansible\Command\AnsiblePlaybook;
16
use Asm\Ansible\Command\AnsiblePlaybookInterface;
17
use Asm\Ansible\Exception\CommandException;
18
use Asm\Ansible\Process\ProcessBuilder;
19
use Asm\Ansible\Process\ProcessBuilderInterface;
20
use Asm\Ansible\Utils\Env;
21
use Psr\Log\LoggerAwareInterface;
22
use Psr\Log\LoggerAwareTrait;
23
use Psr\Log\NullLogger;
24
25
/**
26
 * Ansible command factory
27
 *
28
 * @package Asm\Ansible
29
 * @author Marc Aschmann <[email protected]>
30
 */
31
final class Ansible implements LoggerAwareInterface
32
{
33
    const DEFAULT_TIMEOUT = 300;
34
35
    /**
36
     * Adds a local $logger instance and the setter.
37
     */
38
    use LoggerAwareTrait;
39
40
    /**
41
     * @var string
42
     */
43
    private $playbookCommand;
44
45
    /**
46
     * @var string
47
     */
48
    private $galaxyCommand;
49
50
    /**
51
     * @var string
52
     */
53
    private $ansibleBaseDir;
54
55
    /**
56
     * @var integer
57
     */
58
    private $timeout;
59
60
    /**
61
     * @param string $ansibleBaseDir base directory of ansible project structure
62
     * @param string $playbookCommand path to playbook executable, default ansible-playbook
63
     * @param string $galaxyCommand path to galaxy executable, default ansible-galaxy
64
     */
65
    public function __construct(string $ansibleBaseDir, string $playbookCommand = '', string $galaxyCommand = '')
66
    {
67
        $this->ansibleBaseDir = $this->checkDir($ansibleBaseDir);
68
        $this->playbookCommand = $this->checkCommand($playbookCommand, 'ansible-playbook');
69
        $this->galaxyCommand = $this->checkCommand($galaxyCommand, 'ansible-galaxy');
70
71
        $this->timeout = Ansible::DEFAULT_TIMEOUT;
72
        $this->logger= new NullLogger();
73
    }
74
75
    /**
76
     * AnsiblePlaybook instance creator
77
     *
78
     * @return AnsiblePlaybookInterface
79
     */
80
    public function playbook(): AnsiblePlaybookInterface
81
    {
82
        return new AnsiblePlaybook(
83
            $this->createProcess($this->playbookCommand),
84
            $this->logger
85
        );
86
    }
87
88
    /**
89
     * AnsibleGalaxy instance creator
90
     *
91
     * @return AnsibleGalaxyInterface
92
     */
93
    public function galaxy(): AnsibleGalaxyInterface
94
    {
95
        return new AnsibleGalaxy(
96
            $this->createProcess($this->galaxyCommand),
97
            $this->logger
98
        );
99
    }
100
101
    /**
102
     * Set process timeout in seconds.
103
     *
104
     * @param int $timeout
105
     * @return Ansible
106
     */
107
    public function setTimeout(int $timeout): Ansible
108
    {
109
        $this->timeout = $timeout;
110
111
        return $this;
112
    }
113
114
    /**
115
     * @param string $prefix base command
116
     * @return ProcessBuilderInterface
117
     */
118
    private function createProcess(string  $prefix): ProcessBuilderInterface
119
    {
120
        $process = new ProcessBuilder($prefix, $this->ansibleBaseDir);
121
122
        return $process->setTimeout($this->timeout);
123
    }
124
125
    /**
126
     * @param string $command
127
     * @param string $default
128
     * @return string
129
     * @throws CommandException
130
     */
131
    private function checkCommand(string $command, string $default): string
132
    {
133
        // normally ansible is in /usr/local/bin/*
134
        if (empty($command)) {
135
            if (Env::isWindows())
136
                return $default;
137
138
            // not testable without ansible installation
139
            if (null === shell_exec('which ' . $default)) {
140
                throw new CommandException(sprintf('No "%s" executable present in PATH!', $default));
141
            }
142
143
            return $default;
144
        }
145
146
        // Here: we have a given command, just need to check it exists and it's executable
147
        if (!is_file($command)) {
148
            throw new CommandException(sprintf('Command "%s" does not exist!', $command));
149
        }
150
151
        if (!$this->isExecutable($command)) {
152
            throw new CommandException(sprintf('Command "%s" is not executable!', $command));
153
        }
154
155
        return $command;
156
    }
157
158
    /**
159
     * @param string $dir directory to check
160
     * @return string
161
     * @throws CommandException
162
     */
163
    private function checkDir(string $dir): string
164
    {
165
        if (!is_dir($dir)) {
166
            throw new CommandException('Ansible project root ' . $dir . ' not found!');
167
        }
168
169
        return $dir;
170
    }
171
172
    /**
173
     * @param string $command
174
     * @return bool
175
     */
176
    private function isExecutable($command): bool
177
    {
178
        if (empty($command))
179
            return false;
180
181
        if (!Env::isWindows())
182
            return is_executable($command);
183
184
        foreach (['exe', 'com', 'bat', 'cmd', 'ps1'] as $ext) {
185
            if (strtolower(substr($command, -3, 3)) === $ext)
186
                return true;
187
        }
188
189
        return false;
190
    }
191
}
192