Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
78.57% |
11 / 14 |
CRAP | |
79.17% |
76 / 96 |
| Docker | |
0.00% |
0 / 1 |
|
78.57% |
11 / 14 |
46.08 | |
79.17% |
76 / 96 |
| __construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
| setDockerInstance | |
0.00% |
0 / 1 |
7.04 | |
90.91% |
10 / 11 |
|||
| run | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
| removeOldService | |
0.00% |
0 / 1 |
12.82 | |
69.57% |
16 / 23 |
|||
| create | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| createServiceConfig | |
100.00% |
1 / 1 |
1 | |
100.00% |
6 / 6 |
|||
| createNetworkList | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
| createTask | |
100.00% |
1 / 1 |
1 | |
100.00% |
4 / 4 |
|||
| createContainerSpec | |
100.00% |
1 / 1 |
1 | |
100.00% |
8 / 8 |
|||
| createMountPoint | |
100.00% |
1 / 1 |
2 | |
100.00% |
9 / 9 |
|||
| createDns | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
| createRestartPolicy | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
| createReplicationMode | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
| getRunningContainerByName | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 12 |
|||
| 1 | <?php |
| 2 | |
| 3 | namespace Qmp\Laravel\DockerLauncher; |
| 4 | |
| 5 | use Illuminate\Support\Facades\Log; |
| 6 | use Qmp\Laravel\DockerLauncher\Docker\DockerConfig; |
| 7 | use Qmp\Laravel\DockerLauncher\Exceptions\DockerException; |
| 8 | use Docker\API\Model\Mount; |
| 9 | use Docker\API\Model\ServicesCreatePostBody; |
| 10 | use Docker\API\Model\ServiceSpecMode; |
| 11 | use Docker\API\Model\ServiceSpecModeReplicated; |
| 12 | use Docker\API\Model\TaskSpec; |
| 13 | use Docker\API\Model\TaskSpecContainerSpec; |
| 14 | use Docker\API\Model\TaskSpecContainerSpecDNSConfig; |
| 15 | use Docker\API\Model\TaskSpecRestartPolicy; |
| 16 | use Docker\Docker as DockerClient; |
| 17 | |
| 18 | use Docker\API\Model\ServicesCreatePostResponse201; |
| 19 | use Psr\Http\Message\ResponseInterface; |
| 20 | |
| 21 | class Docker |
| 22 | { |
| 23 | /** |
| 24 | * @var DockerConfig |
| 25 | */ |
| 26 | private $config; |
| 27 | |
| 28 | /** |
| 29 | * @var int |
| 30 | */ |
| 31 | private $replicasNb = 1; |
| 32 | |
| 33 | /** |
| 34 | * @var DockerClient |
| 35 | */ |
| 36 | private $docker; |
| 37 | |
| 38 | /** |
| 39 | * Docker constructor. |
| 40 | * @param DockerConfig $config |
| 41 | * @param $docker |
| 42 | */ |
| 43 | public function __construct(DockerConfig $config, $docker) |
| 44 | { |
| 45 | $this->config = $config; |
| 46 | $this->docker = $docker; |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * @param string $scheduleName |
| 51 | * @param string $command |
| 52 | * @param string $image |
| 53 | * @param string $output |
| 54 | * @param string $launcherCmd |
| 55 | * @param array $mount |
| 56 | */ |
| 57 | public function setDockerInstance(string $scheduleName, string $command, string $image = '', string $output = '', string $launcherCmd = '', array $mount = []) |
| 58 | { |
| 59 | $this->config->setScheduleName($scheduleName); |
| 60 | $this->config->setCommand($command); |
| 61 | |
| 62 | if (!empty($image)) { |
| 63 | $this->config->setDockerImage($image); |
| 64 | } |
| 65 | |
| 66 | if (!empty($output)) { |
| 67 | $this->config->setOutputFile($output); |
| 68 | } |
| 69 | |
| 70 | if (!empty($launcherCmd)) { |
| 71 | $this->config->setCommandLauncher($launcherCmd); |
| 72 | } |
| 73 | |
| 74 | if (!empty($mount['source']) && !empty($mount['target']) && !empty($mount['type'])) { |
| 75 | $this->config->setMountPoint($mount['source'], $mount['target'], $mount['type']); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | public function run() |
| 80 | { |
| 81 | $this->removeOldService(); |
| 82 | $this->create(); |
| 83 | } |
| 84 | |
| 85 | public function removeOldService($wait = false, $leftTries = 30) |
| 86 | { |
| 87 | $loop = false; |
| 88 | $taskList = $this->docker->taskList( |
| 89 | [ |
| 90 | 'filters' => json_encode(['service' => [$this->config->getScheduleName() => true]]) |
| 91 | ] |
| 92 | ); |
| 93 | |
| 94 | $removeOldService = $taskList !== null; |
| 95 | |
| 96 | if ($removeOldService) { |
| 97 | foreach ($taskList as $task) { |
| 98 | if ( |
| 99 | $task->getDesiredState() !== 'shutdown' |
| 100 | && $task->getStatus()->getState() !== 'complete' |
| 101 | && $task->getStatus()->getState() !== 'failed' |
| 102 | && $task->getStatus()->getMessage() !== 'finished' |
| 103 | ) { |
| 104 | |
| 105 | if($wait === false) { |
| 106 | Log::debug($task->getStatus()->getState()); |
| 107 | throw new DockerException('Task is not finished !', ['taskList' => $taskList]); |
| 108 | } else { |
| 109 | $loop = true; |
| 110 | break; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | } |
| 115 | if($loop) { |
| 116 | $left = $leftTries--; |
| 117 | if($left > 0) { |
| 118 | sleep(1); |
| 119 | Log::debug('Task is not finished, check again ...'); |
| 120 | $this->removeOldService(true, $left); |
| 121 | } |
| 122 | } else { |
| 123 | $this->docker->serviceDelete($this->config->getScheduleName()); |
| 124 | } |
| 125 | |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | /** |
| 130 | * @return ServicesCreatePostResponse201|null|ResponseInterface |
| 131 | */ |
| 132 | public function create() |
| 133 | { |
| 134 | return $this->docker->serviceCreate($this->createServiceConfig()); |
| 135 | } |
| 136 | |
| 137 | /** |
| 138 | * @return ServicesCreatePostBody |
| 139 | */ |
| 140 | public function createServiceConfig() |
| 141 | { |
| 142 | $serviceConfig = new ServicesCreatePostBody(); |
| 143 | $serviceConfig->setName($this->config->getScheduleName()); |
| 144 | $serviceConfig->setNetworks($this->createNetworkList()); |
| 145 | $serviceConfig->setTaskTemplate($this->createTask()); |
| 146 | $serviceConfig->setMode($this->createReplicationMode()); |
| 147 | |
| 148 | return $serviceConfig; |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * @return array |
| 153 | */ |
| 154 | private function createNetworkList() |
| 155 | { |
| 156 | return array_map(function ($item) { |
| 157 | return ['Target' => $item->getName()]; |
| 158 | }, array_filter($this->docker->networkList(), function ($item) { |
| 159 | return $item->getScope() === 'swarm' && $item->getDriver() === 'overlay' && in_array($item->getName(), $this->config->getAuthorizedNetworksList()); |
| 160 | })); |
| 161 | } |
| 162 | |
| 163 | /** |
| 164 | * @return TaskSpec |
| 165 | */ |
| 166 | private function createTask() |
| 167 | { |
| 168 | $taskSpec = new TaskSpec(); |
| 169 | $taskSpec->setContainerSpec($this->createContainerSpec()); |
| 170 | $taskSpec->setRestartPolicy($this->createRestartPolicy()); |
| 171 | |
| 172 | return $taskSpec; |
| 173 | } |
| 174 | |
| 175 | /** |
| 176 | * @return TaskSpecContainerSpec |
| 177 | */ |
| 178 | private function createContainerSpec() |
| 179 | { |
| 180 | $containerSpec = new TaskSpecContainerSpec(); |
| 181 | $containerSpec->setImage($this->config->getDockerImage()); |
| 182 | $containerSpec->setCommand($this->config->getBashCommand()); |
| 183 | $containerSpec->setArgs($this->config->getBashArgs()); |
| 184 | $containerSpec->setMounts($this->createMountPoint()); |
| 185 | $containerSpec->setDNSConfig($this->createDns()); |
| 186 | $containerSpec->setEnv($this->config->getEnvs()); |
| 187 | |
| 188 | return $containerSpec; |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * @return array |
| 193 | */ |
| 194 | private function createMountPoint() |
| 195 | { |
| 196 | $mountInfos = $this->config->getMountPoints(); |
| 197 | $mountPoints = []; |
| 198 | |
| 199 | foreach($mountInfos as $mount) { |
| 200 | $mountPoint = new Mount(); |
| 201 | $mountPoint->setSource($mount['source']); |
| 202 | $mountPoint->setTarget($mount['target']); |
| 203 | $mountPoint->setType($mount['type']); |
| 204 | |
| 205 | $mountPoints[] = $mountPoint; |
| 206 | } |
| 207 | |
| 208 | return $mountPoints; |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * @return TaskSpecContainerSpecDNSConfig |
| 213 | */ |
| 214 | private function createDns() |
| 215 | { |
| 216 | $dnsConfig = new TaskSpecContainerSpecDNSConfig(); |
| 217 | $dnsConfig->setNameservers(['8.8.8.8', '8.8.4.4']); |
| 218 | |
| 219 | return $dnsConfig; |
| 220 | } |
| 221 | |
| 222 | /** |
| 223 | * @return TaskSpecRestartPolicy |
| 224 | */ |
| 225 | private function createRestartPolicy() |
| 226 | { |
| 227 | $restartPolicy = new TaskSpecRestartPolicy(); |
| 228 | $restartPolicy->setCondition($this->config->getRestartPolicy()); |
| 229 | |
| 230 | return $restartPolicy; |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * @return ServiceSpecMode |
| 235 | */ |
| 236 | private function createReplicationMode() |
| 237 | { |
| 238 | $replicas = new ServiceSpecModeReplicated(); |
| 239 | $replicas->setReplicas($this->replicasNb); |
| 240 | $replicationMode = new ServiceSpecMode(); |
| 241 | $replicationMode->setReplicated($replicas); |
| 242 | |
| 243 | return $replicationMode; |
| 244 | } |
| 245 | |
| 246 | public function getRunningContainerByName(string $name) |
| 247 | { |
| 248 | $containers = collect($this->docker->containerList([ |
| 249 | 'all' => true |
| 250 | ])); |
| 251 | |
| 252 | $filters = $containers->filter(function ($container) use ($name) { |
| 253 | $labels = $container->getLabels(); |
| 254 | |
| 255 | if(isset($labels['com.docker.swarm.service.name'])) { |
| 256 | return $container->getState() !== 'exited' && preg_match('/'. $name .'/', $labels['com.docker.swarm.service.name']); |
| 257 | } |
| 258 | }); |
| 259 | |
| 260 | return $filters->map(function ($container) { |
| 261 | $labels = $container->getLabels(); |
| 262 | |
| 263 | if(isset($labels['com.docker.swarm.service.name'])) { |
| 264 | return $labels['com.docker.swarm.service.name']; |
| 265 | } |
| 266 | }); |
| 267 | |
| 268 | } |
| 269 | } |