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 Exception; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* DockerCompose\Manager\ComposeManager |
14
|
|
|
*/ |
15
|
|
|
class ComposeManager |
16
|
|
|
{ |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Start service containers |
20
|
|
|
* |
21
|
|
|
* @param mixed $composeFiles The compose files names |
22
|
|
|
*/ |
23
|
|
View Code Duplication |
public function start($composeFiles = array()) |
|
|
|
|
24
|
|
|
{ |
25
|
|
|
$composeFiles = $this->createComposeFileCollection($composeFiles); |
26
|
|
|
$result = $this->execute( |
27
|
|
|
$this->formatCommand('up -d', $composeFiles) |
28
|
|
|
); |
29
|
|
|
|
30
|
|
|
return $this->processResult($result); |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Stop service containers |
35
|
|
|
* |
36
|
|
|
* @param mixed $composeFiles The compose files names |
37
|
|
|
*/ |
38
|
|
View Code Duplication |
public function stop($composeFiles = array()) |
|
|
|
|
39
|
|
|
{ |
40
|
|
|
$composeFiles = $this->createComposeFileCollection($composeFiles); |
41
|
|
|
$result = $this->execute( |
42
|
|
|
$this->formatCommand('stop', $composeFiles) |
43
|
|
|
); |
44
|
|
|
|
45
|
|
|
return $this->processResult($result); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Stop service containers |
50
|
|
|
* |
51
|
|
|
* @param mixed $composeFiles The compose files names |
52
|
|
|
* @param boolean $force If the remove need to be force (default=false) |
53
|
|
|
* @param boolean $removeVolumes If we need to remove the volumes (default=false) |
54
|
|
|
*/ |
55
|
|
|
public function remove($composeFiles = array(), $force = false, $removeVolumes = false) |
56
|
|
|
{ |
57
|
|
|
$composeFiles = $this->createComposeFileCollection($composeFiles); |
58
|
|
|
$command = 'rm'; |
59
|
|
|
if ($force) { |
60
|
|
|
$command .= ' --force'; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
if ($removeVolumes) { |
64
|
|
|
$command .= ' -v'; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
$result = $this->execute( |
68
|
|
|
$this->formatCommand($command, $composeFiles) |
69
|
|
|
); |
70
|
|
|
|
71
|
|
|
return $this->processResult($result); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Run service with command |
76
|
|
|
* |
77
|
|
|
* @param string $service Service name |
78
|
|
|
* @param string $command Command to pass to service |
79
|
|
|
* @param mixed $composeFiles The compose files names |
80
|
|
|
*/ |
81
|
|
|
public function run($service, $command, $composeFiles = array()) |
82
|
|
|
{ |
83
|
|
|
$composeFiles = $this->createComposeFileCollection($composeFiles); |
84
|
|
|
$command = 'run --rm ' . $service . ' ' . $command; |
85
|
|
|
$result = $this->execute( |
86
|
|
|
$this->formatCommand($command, $composeFiles) |
87
|
|
|
); |
88
|
|
|
|
89
|
|
|
if ($result['code'] == 1 && strpos($result['output'], 'No such service') != false) { |
|
|
|
|
90
|
|
|
throw new NoSuchServiceException($result['output']); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
return $this->processResult($result); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Process result with returned code and output |
98
|
|
|
* |
99
|
|
|
* @param array $result The result of command with output and returnCode |
100
|
|
|
* |
101
|
|
|
* @throws DockerInstallationMissingException When returned code is 127 |
102
|
|
|
* @throws ComposeFileNotFoundException When no compose file precise and docker-compose.yml not found |
103
|
|
|
* @throws DockerHostConnexionErrorException When we can't connect to docker host |
104
|
|
|
* @throws \Exception When an unknown error is returned |
105
|
|
|
*/ |
106
|
|
|
private function processResult($result) |
107
|
|
|
{ |
108
|
|
|
if ($result['code'] === 127) { |
109
|
|
|
throw new DockerInstallationMissingException(); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
if ($result['code'] === 1) { |
113
|
|
|
if (!strpos($result['output'], 'DOCKER_HOST')) { |
114
|
|
|
if (!strpos($result['output'], 'docker-compose.yml')) { |
115
|
|
|
throw new Exception($result['output']); |
116
|
|
|
} else { |
117
|
|
|
throw new ComposeFileNotFoundException(); |
118
|
|
|
} |
119
|
|
|
} else { |
120
|
|
|
throw new DockerHostConnexionErrorException(); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
return $result['output']; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Create the composeFileCollection from the type of value given |
129
|
|
|
* |
130
|
|
|
* @param mixed $composeFiles The docker-compose files (can be array, string or ComposeFile) |
131
|
|
|
* |
132
|
|
|
* @return ComposeFileCollection |
133
|
|
|
*/ |
134
|
|
|
private function createComposeFileCollection($composeFiles) |
135
|
|
|
{ |
136
|
|
|
if (!$composeFiles instanceof ComposeFileCollection) { |
137
|
|
|
if (!is_array($composeFiles)) { |
138
|
|
|
return new ComposeFileCollection([$composeFiles]); |
139
|
|
|
} else { |
140
|
|
|
return new ComposeFileCollection($composeFiles); |
141
|
|
|
} |
142
|
|
|
} else { |
143
|
|
|
return $composeFiles; |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Format the command to execute |
149
|
|
|
* |
150
|
|
|
* @param string $subcommand The subcommand to pass to docker-compose command |
151
|
|
|
* @param ComposeFileCollection $composeFiles The compose files to precise in the command |
152
|
|
|
*/ |
153
|
|
|
private function formatCommand($subcommand, ComposeFileCollection $composeFiles) |
154
|
|
|
{ |
155
|
|
|
$project = ''; |
156
|
|
|
$networking = ''; |
157
|
|
|
$networkDriver = ''; |
158
|
|
|
# Add project name, and network options |
159
|
|
|
if ($composeFiles->getProjectName() != null) { |
160
|
|
|
$project = ' --project-name ' . $composeFiles->getProjectName(); |
161
|
|
|
if ($composeFiles->isNetworking()) { |
162
|
|
|
$networking = ' --x-networking'; |
163
|
|
|
if ($composeFiles->getNetworkDriver() != null) { |
164
|
|
|
$networkDriver = ' --x-network-driver ' . $composeFiles->getNetworkDriver(); |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
# Add files names |
170
|
|
|
$preciseFiles = ''; |
171
|
|
|
foreach ($composeFiles->getAll() as $composeFile) { |
172
|
|
|
$preciseFiles .= ' -f ' . $composeFile->getFileName(); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
$command = 'docker-compose' . $preciseFiles . $networking . $networkDriver . $project . ' ' . $subcommand; |
176
|
|
|
|
177
|
|
|
return $command; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Execute docker-compose commande |
182
|
|
|
* @codeCoverageIgnore |
183
|
|
|
* @param string $command The command to execute |
184
|
|
|
*/ |
185
|
|
|
protected function execute($command) |
186
|
|
|
{ |
187
|
|
|
$command = system($command . ' > output 2>&1', $retval); |
|
|
|
|
188
|
|
|
|
189
|
|
|
$output = fopen('output', 'r'); |
190
|
|
|
$output = fread($output, filesize('output')); |
191
|
|
|
unlink('output'); |
192
|
|
|
|
193
|
|
|
return array( |
194
|
|
|
'output' => $output, |
195
|
|
|
'code' => $retval |
196
|
|
|
); |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
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.