Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
78.57% covered (success)
78.57%
11 / 14
CRAP
79.17% covered (success)
79.17%
76 / 96
Docker
0.00% covered (danger)
0.00%
0 / 1
78.57% covered (success)
78.57%
11 / 14
46.08
79.17% covered (success)
79.17%
76 / 96
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 setDockerInstance
0.00% covered (danger)
0.00%
0 / 1
7.04
90.91% covered (success)
90.91%
10 / 11
 run
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 removeOldService
0.00% covered (danger)
0.00%
0 / 1
12.82
69.57% covered (warning)
69.57%
16 / 23
 create
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 createServiceConfig
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
6 / 6
 createNetworkList
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 createTask
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 createContainerSpec
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
8 / 8
 createMountPoint
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
9 / 9
 createDns
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 createRestartPolicy
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 createReplicationMode
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 getRunningContainerByName
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 12
1<?php
2
3namespace Qmp\Laravel\DockerLauncher;
4
5use Illuminate\Support\Facades\Log;
6use Qmp\Laravel\DockerLauncher\Docker\DockerConfig;
7use Qmp\Laravel\DockerLauncher\Exceptions\DockerException;
8use Docker\API\Model\Mount;
9use Docker\API\Model\ServicesCreatePostBody;
10use Docker\API\Model\ServiceSpecMode;
11use Docker\API\Model\ServiceSpecModeReplicated;
12use Docker\API\Model\TaskSpec;
13use Docker\API\Model\TaskSpecContainerSpec;
14use Docker\API\Model\TaskSpecContainerSpecDNSConfig;
15use Docker\API\Model\TaskSpecRestartPolicy;
16use Docker\Docker as DockerClient;
17
18use Docker\API\Model\ServicesCreatePostResponse201;
19use Psr\Http\Message\ResponseInterface;
20
21class 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}