Completed
Pull Request — master (#12)
by ophelie
02:29
created

ComposeManager::formatCommand()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 3
Metric Value
c 4
b 0
f 3
dl 0
loc 27
rs 8.439
cc 5
eloc 15
nc 8
nop 2
1
<?php
2
3
namespace DockerCompose\Manager;
4
5
use DockerCompose\Exception\ComposeFileNotFoundException;
6
use DockerCompose\Exception\DockerHostConnexionErrorException;
7
use DockerCompose\Exception\DockerInstallationMissingException;
8
use DockerCompose\Exception\NoSuchServiceException;
9
use DockerCompose\ComposeFileCollection;
10
use mikehaertl\shellcommand\Command;
11
use Exception;
12
13
/**
14
 * DockerCompose\Manager\ComposeManager
15
 */
16
class ComposeManager
17
{
18
19
    /**
20
     * Start service containers
21
     *
22
     * @param mixed $composeFiles The compose files names
23
     */
24
    public function start($composeFiles = array())
25
    {
26
        return $this->processResult(
27
            $this->execute(
28
                $this->formatCommand('up -d', $this->createComposeFileCollection($composeFiles))
29
            )
30
        );
31
    }
32
33
    /**
34
     * Stop service containers
35
     *
36
     * @param mixed $composeFiles The compose files names
37
     */
38
    public function stop($composeFiles = array())
39
    {
40
        return $this->processResult(
41
            $this->execute(
42
                $this->formatCommand('stop', $this->createComposeFileCollection($composeFiles))
43
            )
44
        );
45
    }
46
47
    /**
48
     * Stop service containers
49
     *
50
     * @param mixed   $composeFiles  The compose files names
51
     * @param boolean $force         If the remove need to be force (default=false)
52
     * @param boolean $removeVolumes If we need to remove the volumes (default=false)
53
     */
54
    public function remove($composeFiles = array(), $force = false, $removeVolumes = false)
55
    {
56
        $command = 'rm';
57
        if ($force) {
58
            $command .= ' --force';
59
        }
60
61
        if ($removeVolumes) {
62
            $command .= ' -v';
63
        }
64
65
        return $this->processResult(
66
            $this->execute(
67
                $this->formatCommand($command, $this->createComposeFileCollection($composeFiles))
68
            )
69
        );
70
    }
71
72
     /**
73
     * Stop service containers
74
     *
75
     * @param mixed   $composeFiles  The compose files names
76
     * @param string  $signal        Optionnal to precise SIGNAL to send to the container for SIGKILL replacement.
77
     */
78 View Code Duplication
    public function kill($composeFiles = array(), $signal = 'SIGKILL')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
79
    {
80
        $command = 'kill';
81
82
        if ($signal !== 'SIGKILL') {
83
            $command .= ' -s ' . $signal;
84
        }
85
86
        return $this->processResult(
87
            $this->execute(
88
                $this->formatCommand($command, $this->createComposeFileCollection($composeFiles))
89
            )
90
        );
91
    }
92
93
    /**
94
     * Build service images
95
     *
96
     * @param mixed   $composeFiles  The compose files names
97
     * @param boolean $pull          If we want attempt to pull a newer version of the from image
98
     * @param boolean $forceRemove   If we want remove the intermediate containers
99
     * @param bollean $cache         If we can use the cache when building the image
100
     */
101
    public function build($composeFiles = array(), $pull = true, $forceRemove = false, $cache = true)
102
    {
103
        $command = 'build';
104
105
        if ($pull) {
106
            $command .= ' --pull';
107
        }
108
109
        if ($forceRemove) {
110
            $command .= ' --force-rm';
111
        }
112
113
        if (!$cache) {
114
            $command .= ' --no-cache';
115
        }
116
117
        return $this->processResult(
118
            $this->execute(
119
                $this->formatCommand($command, $this->createComposeFileCollection($composeFiles))
120
            )
121
        );
122
    }
123
124
    /**
125
     * Build service images
126
     *
127
     * @param mixed   $composeFiles  The compose files names
128
     */
129
    public function pull($composeFiles = array())
130
    {
131
        $command = 'pull';
132
133
        return $this->processResult(
134
            $this->execute(
135
                $this->formatCommand($command, $this->createComposeFileCollection($composeFiles))
136
            )
137
        );
138
    }
139
140
141
    /**
142
     * Restart running containers
143
     *
144
     * @param mixed   $composeFiles  The compose files names
145
     * @param integer $timeout       If we want attempt to pull a newer version of the from image
146
     */
147 View Code Duplication
    public function restart($composeFiles = array(), $timeout = 10)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
148
    {
149
        $command = 'restart';
150
151
        if ($timeout != 10) {
152
            $command .= ' --timeout='.$timeout;
153
        }
154
155
        return $this->processResult(
156
            $this->execute(
157
                $this->formatCommand($command, $this->createComposeFileCollection($composeFiles))
158
            )
159
        );
160
    }
161
162
    /**
163
     * Run service with command
164
     *
165
     * @param string $service Service name
166
     * @param string $command Command to pass to service
167
     * @param mixed   $composeFiles  The compose files names
168
     */
169
    public function run($service, $command, $composeFiles = array())
170
    {
171
        $command = 'run --rm ' . $service . ' ' . $command;
172
        $result = $this->execute(
173
            $this->formatCommand($command, $this->createComposeFileCollection($composeFiles))
174
        );
175
176
        if ($result['code'] == 1 && strpos($result['output'], 'No such service') != false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($result['output'], 'No such service') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
177
            throw new NoSuchServiceException($result['output']);
178
        }
179
180
        return $this->processResult($result);
181
    }
182
183
    /**
184
     * List containers
185
     *
186
     * @param mixed $composeFiles The compose files names
187
     */
188
    public function ps($composeFiles = array())
189
    {
190
        return $this->processResult(
191
            $this->execute(
192
                $this->formatCommand('ps', $this->createComposeFileCollection($composeFiles))
193
            )
194
        );
195
    }
196
197
    /**
198
     * List IP containers
199
     *
200
     * @param mixed $composeFiles The compose files names
201
     */
202 View Code Duplication
    public function ips($composeFiles = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204
        $command = $this->formatCommand('ps', $this->createComposeFileCollection($composeFiles));
205
206
        $command = 'for CONTAINER in $(' . $command . ' -q); ';
207
        $command .= 'do echo "$(docker inspect --format \' {{ .Name }} \' $CONTAINER)\t';
208
        $command .= '$(docker inspect --format \' {{ .NetworkSettings.IPAddress }} \' $CONTAINER)"; done';
209
210
        return $this->processResult($this->execute($command));
211
    }
212
213
    /**
214
     * Process result with returned code and output
215
     *
216
     * @param array $result The result of command with output and returnCode
217
     *
218
     * @throws DockerInstallationMissingException When returned code is 127
219
     * @throws ComposeFileNotFoundException When no compose file precise and docker-compose.yml not found
220
     * @throws DockerHostConnexionErrorException When we can't connect to docker host
221
     * @throws \Exception When an unknown error is returned
222
     */
223
    private function processResult($result)
224
    {
225
        if ($result['code'] === 127) {
226
            throw new DockerInstallationMissingException();
227
        }
228
229
        if ($result['code'] === 1) {
230
            if (!strpos($result['output'], 'DOCKER_HOST')) {
231
                if (!strpos($result['output'], 'docker-compose.yml')) {
232
                    throw new Exception($result['output']);
233
                } else {
234
                    throw new ComposeFileNotFoundException();
235
                }
236
            } else {
237
                throw new DockerHostConnexionErrorException();
238
            }
239
        }
240
241
        return $result['output'];
242
    }
243
244
    /**
245
     * Create the composeFileCollection from the type of value given
246
     *
247
     * @param mixed $composeFiles The docker-compose files (can be array, string or ComposeFile)
248
     *
249
     * @return ComposeFileCollection
250
     */
251
    private function createComposeFileCollection($composeFiles)
252
    {
253
        if (!$composeFiles instanceof ComposeFileCollection) {
254
            if (!is_array($composeFiles)) {
255
                return new ComposeFileCollection([$composeFiles]);
256
            } else {
257
                return new ComposeFileCollection($composeFiles);
258
            }
259
        } else {
260
            return $composeFiles;
261
        }
262
    }
263
264
    /**
265
     * Format the command to execute
266
     *
267
     * @param string                $subcommand   The subcommand to pass to docker-compose command
268
     * @param ComposeFileCollection $composeFiles The compose files to precise in the command
269
     */
270
    private function formatCommand($subcommand, ComposeFileCollection $composeFiles)
271
    {
272
        $project = '';
273
        $networking = '';
274
        $networkDriver = '';
275
276
        # Add project name, and network options
277
        if ($composeFiles->getProjectName() != null) {
278
            $project = ' --project-name ' . $composeFiles->getProjectName();
279
            if ($composeFiles->isNetworking()) {
280
                $networking = ' --x-networking';
281
                if ($composeFiles->getNetworkDriver() != null) {
282
                    $networkDriver = ' --x-network-driver ' . $composeFiles->getNetworkDriver();
283
                }
284
            }
285
        }
286
287
        # Add files names
288
        $preciseFiles = '';
289
        foreach ($composeFiles->getAll() as $composeFile) {
290
            $preciseFiles .= ' -f ' . $composeFile->getFileName();
291
        }
292
293
        $command = 'docker-compose' . $preciseFiles . $networking . $networkDriver . $project . ' ' . $subcommand;
294
295
        return $command;
296
    }
297
298
    /**
299
     * Execute docker-compose commande
300
     * @codeCoverageIgnore
301
     * @param string                $command      The command to execute
302
     */
303
    protected function execute($command)
304
    {
305
        $exec = new Command($command);
306
307
        if ($exec->execute()) {
308
            $output = $exec->getOutput();
309
        } else {
310
            $output = $exec->getError();
311
        }
312
313
        return array(
314
            'output' => $output,
315
            'code' => $exec->getExitCode()
316
        );
317
    }
318
}
319