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') |
|
|
|
|
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
|
|
|
/** |
126
|
|
|
* Restart running containers |
127
|
|
|
* |
128
|
|
|
* @param mixed $composeFiles The compose files names |
129
|
|
|
* @param integer $timeout If we want attempt to pull a newer version of the from image |
130
|
|
|
*/ |
131
|
|
View Code Duplication |
public function restart($composeFiles = array(), $timeout = 10) |
|
|
|
|
132
|
|
|
{ |
133
|
|
|
$command = 'restart'; |
134
|
|
|
|
135
|
|
|
if ($timeout != 10) { |
136
|
|
|
$command .= ' --timeout='.$timeout; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
return $this->processResult( |
140
|
|
|
$this->execute( |
141
|
|
|
$this->formatCommand($command, $this->createComposeFileCollection($composeFiles)) |
142
|
|
|
) |
143
|
|
|
); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Run service with command |
148
|
|
|
* |
149
|
|
|
* @param string $service Service name |
150
|
|
|
* @param string $command Command to pass to service |
151
|
|
|
* @param mixed $composeFiles The compose files names |
152
|
|
|
*/ |
153
|
|
|
public function run($service, $command, $composeFiles = array()) |
154
|
|
|
{ |
155
|
|
|
$command = 'run --rm ' . $service . ' ' . $command; |
156
|
|
|
$result = $this->execute( |
157
|
|
|
$this->formatCommand($command, $this->createComposeFileCollection($composeFiles)) |
158
|
|
|
); |
159
|
|
|
|
160
|
|
|
if ($result['code'] == 1 && strpos($result['output'], 'No such service') != false) { |
|
|
|
|
161
|
|
|
throw new NoSuchServiceException($result['output']); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
return $this->processResult($result); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* List containers |
169
|
|
|
* |
170
|
|
|
* @param mixed $composeFiles The compose files names |
171
|
|
|
*/ |
172
|
|
|
public function ps($composeFiles = array()) |
173
|
|
|
{ |
174
|
|
|
return $this->processResult( |
175
|
|
|
$this->execute( |
176
|
|
|
$this->formatCommand('ps', $this->createComposeFileCollection($composeFiles)) |
177
|
|
|
) |
178
|
|
|
); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Process result with returned code and output |
183
|
|
|
* |
184
|
|
|
* @param array $result The result of command with output and returnCode |
185
|
|
|
* |
186
|
|
|
* @throws DockerInstallationMissingException When returned code is 127 |
187
|
|
|
* @throws ComposeFileNotFoundException When no compose file precise and docker-compose.yml not found |
188
|
|
|
* @throws DockerHostConnexionErrorException When we can't connect to docker host |
189
|
|
|
* @throws \Exception When an unknown error is returned |
190
|
|
|
*/ |
191
|
|
|
private function processResult($result) |
192
|
|
|
{ |
193
|
|
|
if ($result['code'] === 127) { |
194
|
|
|
throw new DockerInstallationMissingException(); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
if ($result['code'] === 1) { |
198
|
|
|
if (!strpos($result['output'], 'DOCKER_HOST')) { |
199
|
|
|
if (!strpos($result['output'], 'docker-compose.yml')) { |
200
|
|
|
throw new Exception($result['output']); |
201
|
|
|
} else { |
202
|
|
|
throw new ComposeFileNotFoundException(); |
203
|
|
|
} |
204
|
|
|
} else { |
205
|
|
|
throw new DockerHostConnexionErrorException(); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return $result['output']; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Create the composeFileCollection from the type of value given |
214
|
|
|
* |
215
|
|
|
* @param mixed $composeFiles The docker-compose files (can be array, string or ComposeFile) |
216
|
|
|
* |
217
|
|
|
* @return ComposeFileCollection |
218
|
|
|
*/ |
219
|
|
|
private function createComposeFileCollection($composeFiles) |
220
|
|
|
{ |
221
|
|
|
if (!$composeFiles instanceof ComposeFileCollection) { |
222
|
|
|
if (!is_array($composeFiles)) { |
223
|
|
|
return new ComposeFileCollection([$composeFiles]); |
224
|
|
|
} else { |
225
|
|
|
return new ComposeFileCollection($composeFiles); |
226
|
|
|
} |
227
|
|
|
} else { |
228
|
|
|
return $composeFiles; |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Format the command to execute |
234
|
|
|
* |
235
|
|
|
* @param string $subcommand The subcommand to pass to docker-compose command |
236
|
|
|
* @param ComposeFileCollection $composeFiles The compose files to precise in the command |
237
|
|
|
*/ |
238
|
|
|
private function formatCommand($subcommand, ComposeFileCollection $composeFiles) |
239
|
|
|
{ |
240
|
|
|
$project = ''; |
241
|
|
|
$networking = ''; |
242
|
|
|
$networkDriver = ''; |
243
|
|
|
|
244
|
|
|
# Add project name, and network options |
245
|
|
|
if ($composeFiles->getProjectName() != null) { |
246
|
|
|
$project = ' --project-name ' . $composeFiles->getProjectName(); |
247
|
|
|
if ($composeFiles->isNetworking()) { |
248
|
|
|
$networking = ' --x-networking'; |
249
|
|
|
if ($composeFiles->getNetworkDriver() != null) { |
250
|
|
|
$networkDriver = ' --x-network-driver ' . $composeFiles->getNetworkDriver(); |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
# Add files names |
256
|
|
|
$preciseFiles = ''; |
257
|
|
|
foreach ($composeFiles->getAll() as $composeFile) { |
258
|
|
|
$preciseFiles .= ' -f ' . $composeFile->getFileName(); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
$command = 'docker-compose' . $preciseFiles . $networking . $networkDriver . $project . ' ' . $subcommand; |
262
|
|
|
|
263
|
|
|
return $command; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Execute docker-compose commande |
268
|
|
|
* @codeCoverageIgnore |
269
|
|
|
* @param string $command The command to execute |
270
|
|
|
*/ |
271
|
|
|
protected function execute($command) |
272
|
|
|
{ |
273
|
|
|
$exec = new Command($command); |
274
|
|
|
|
275
|
|
|
if ($exec->execute()) { |
276
|
|
|
$output = $exec->getOutput(); |
277
|
|
|
} else { |
278
|
|
|
$output = $exec->getError(); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
return array( |
282
|
|
|
'output' => $output, |
283
|
|
|
'code' => $exec->getExitCode() |
284
|
|
|
); |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
|
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.