Passed
Push — master ( 48c4ea...d64961 )
by Tom
02:50
created

StepContainer::execRunServiceContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 5
dl 0
loc 21
ccs 13
cts 13
cp 1
crap 1
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines\Runner;
6
7
use Ktomk\Pipelines\Cli\Docker;
8
use Ktomk\Pipelines\Cli\Exec;
9
use Ktomk\Pipelines\Cli\Streams;
10
use Ktomk\Pipelines\File\Definitions\Service;
11
use Ktomk\Pipelines\File\Pipeline\Step;
12
use Ktomk\Pipelines\Lib;
13
use Ktomk\Pipelines\Runner\Docker\ImageLogin;
14
15
/**
16
 * Class StepContainer
17
 *
18
 * @package Ktomk\Pipelines\Runner
19
 */
20
class StepContainer
21
{
22
    /**
23
     * @var null|string id of the (running) container
24
     */
25
    private $id;
26
27
    /**
28
     * @var null|string name of the container
29
     */
30
    private $name;
31
32
    /**
33
     * @var Step
34
     */
35
    private $step;
36
37
    /**
38
     * @var Exec
39
     */
40
    private $exec;
41
42
    /**
43
     * @param Step $step
44
     * @param null|Exec $exec
45
     *
46
     * @return StepContainer
47
     */
48 6
    public static function create(Step $step, Exec $exec = null)
49
    {
50 6
        if (null === $exec) {
51 2
            $exec = new Exec();
52
        }
53
54 6
        return new self($step, $exec);
55
    }
56
57
    /**
58
     * @param Step $step
59
     * @param string $prefix
60
     * @param string $project name
61
     *
62
     * @return string
63
     */
64 1
    public static function createName(Step $step, $prefix, $project)
65
    {
66 1
        return self::create($step)->generateName($prefix, $project);
67
    }
68
69
    /**
70
     * kill and remove static implementation
71
     *
72
     * @param Exec $exec
73
     * @param string|string[] $idOrIds container id(s) or name(s)
74
     * @param bool $kill
75
     * @param bool $remove
76
     *
77
     * @return void
78
     */
79 4
    public static function execKillAndRemove(Exec $exec, $idOrIds, $kill, $remove)
80
    {
81 4
        if ($kill) {
82 2
            Docker::create($exec)->getProcessManager()->kill($idOrIds);
83
        }
84
85 4
        if ($remove) {
86 3
            Docker::create($exec)->getProcessManager()->remove($idOrIds);
87
        }
88 4
    }
89
90
    /**
91
     * @param Exec $exec
92
     * @param array $args
93
     *
94
     * @return array array(int $status, string $out, string $err, string|null $id)
95
     */
96 4
    public static function execRun(Exec $exec, array $args)
97
    {
98 4
        $status = $exec->capture('docker', Lib::merge('run', $args), $out, $err);
99
100 4
        $id = null;
101 4
        if (0 === $status) {
102 4
            $id = rtrim($out) ?: null;
103
        }
104
105 4
        return array($status, $out, $err, $id);
106
    }
107
108
    /**
109
     * @param Exec $exec
110
     * @param Streams $streams
111
     * @param string|string[] $idOrIds
112
     * @param int $status
113
     * @param Flags $flags
114
     * @param string $message
115
     * @param string $id
116
     *
117
     * @return void
118
     *
119
     * @see StepRunner::shutdownStepContainer
120
     */
121 4
    public static function execShutdownContainer(Exec $exec, Streams $streams, $idOrIds, $status, Flags $flags, $message)
122
    {
123
        # keep container on error
124 4
        if (0 !== $status && $flags->keepOnError()) {
125 2
            $streams->err(sprintf("error, %s\n", $message));
126
127 2
            return;
128
        }
129
130
        # keep or kill/remove container
131 2
        self::execKillAndRemove($exec, $idOrIds, $flags->killContainer(), $flags->removeContainer());
132
133 2
        if ($flags->keep()) {
134 1
            $streams->out(sprintf("%s\n", $message));
135
        }
136 2
    }
137
138
    /**
139
     * generate step container name
140
     *
141
     * example: pipelines-1.pipeline-features-and-introspection.default.app
142
     *              ^    `^`                  ^                `    ^  ` ^
143
     *              |     |                   |                     |    |
144
     *              | step number        step name           pipeline id |
145
     *           prefix                                                project
146
     *
147
     * @param string $prefix
148
     * @param Step $step
149
     * @param string $project
150
     *
151
     * @return string
152
     *
153
     * @see StepContainer::generateName()
154
     */
155 9
    public static function generateStepName($prefix, Step $step, $project)
156
    {
157 9
        $idContainerSlug = preg_replace('([^a-zA-Z0-9_.-]+)', '-', $step->getPipeline()->getId());
158 9
        if ('' === $idContainerSlug) {
159 9
            $idContainerSlug = 'null';
160
        }
161 9
        $nameSlug = preg_replace(array('( )', '([^a-zA-Z0-9_.-]+)'), array('-', ''), $step->getName());
162 9
        if ('' === $nameSlug) {
163 9
            $nameSlug = 'no-name';
164
        }
165
166 9
        $stepNumber = $step->getIndex() + 1;
167
168 9
        return $prefix . '-' . implode(
169 9
            '.',
170
            array(
171 9
                    $stepNumber,
172 9
                    $nameSlug,
173 9
                    trim($idContainerSlug, '-'),
174 9
                    $project,
175
                )
176
        );
177
    }
178
179
    /**
180
     * generate service container name
181
     *
182
     * exmaple: pipelines-service-redis.pipelines
183
     *              ^    `   ^   `  ^  `   ^
184
     *              |        |      |      |
185
     *              |    "service"  |   project
186
     *           prefix       service name
187
     *
188
     * @param string $prefix
189
     * @param string $serviceName
190
     * @param string $project
191
     *
192
     * @return string
193
     *
194
     * @see StepRunner::obtainServicesNetwork()
195
     */
196 2
    public static function generateServiceName($prefix, $serviceName, $project)
197
    {
198 2
        $nameSlug = preg_replace(array('( )', '([^a-zA-Z0-9_.-]+)'), array('-', ''), $serviceName);
199
200 2
        return $prefix . '-service-' . implode(
201 2
            '.',
202
            array(
203 2
                    $nameSlug,
204 2
                    $project,
205
                )
206
        );
207
    }
208
209
    /**
210
     * @param Exec $exec
211
     * @param Service $service
212
     * @param callable $resolver
213
     * @param string $prefix
214
     * @param string $project
215
     *
216
     * @return array
217
     */
218 1
    public static function execRunServiceContainer(Exec $exec, Service $service, $resolver, $prefix, $project)
219
    {
220 1
        $network = array('--network', 'host');
221 1
        $image = $service->getImage();
222 1
        ImageLogin::loginImage($exec, $resolver, null, $image);
223
224 1
        $containerName = self::generateServiceName($prefix, $service->getName(), $project);
225
226 1
        $variables = $resolver($service->getVariables());
227
228
        $args = array(
229 1
            $network, '--name',
230 1
            $containerName,
231 1
            '--detach',
232 1
            Env::createArgVarDefinitions('-e', $variables),
233 1
            $image->getName(),
234
        );
235
236 1
        $status = $exec->capture('docker', Lib::merge('run', $args), $out, $err);
237
238 1
        return array($status, $network);
239
    }
240
241
    /**
242
     * @param Exec $exec
243
     * @param Service $service
244
     * @param callable $resolver
245
     * @param string $prefix
246
     * @param string $project
247
     *
248
     * @return array
249
     */
250 1
    public static function execRunServiceContainerAttached(Exec $exec, Service $service, $resolver, $prefix, $project)
251
    {
252 1
        $network = array('--network', 'host');
253 1
        $image = $service->getImage();
254 1
        ImageLogin::loginImage($exec, $resolver, null, $image);
255
256 1
        $containerName = self::generateServiceName($prefix, $service->getName(), $project);
257
258 1
        $variables = $resolver($service->getVariables());
259
260
        $args = array(
261 1
            $network, '--name',
262 1
            $containerName,
263 1
            '--rm',
264 1
            Env::createArgVarDefinitions('-e', $variables),
265 1
            $image->getName(),
266
        );
267
268 1
        $status = $exec->pass('docker', Lib::merge('run', $args));
269
270 1
        return array($status, $network);
271
    }
272
273
    /**
274
     * StepContainer constructor.
275
     *
276
     * @param Step $step
277
     * @param Exec $exec
278
     */
279 14
    public function __construct(Step $step, Exec $exec)
280
    {
281 14
        $this->step = $step;
282 14
        $this->exec = $exec;
283 14
    }
284
285
    /**
286
     * generate step container name
287
     *
288
     * @param string $prefix for the name (normally "pipelines")
289
     * @param string $project name
290
     *
291
     * @return string
292
     */
293 9
    public function generateName($prefix, $project)
294
    {
295 9
        return $this->name = self::generateStepName($prefix, $this->step, $project);
296
    }
297
298
    /**
299
     * the display id
300
     *
301
     *   side-effect: if id is null, this signals a dry-run which is made
302
     * visible by the string "*dry-run*"
303
     *
304
     * @return string
305
     */
306 8
    public function getDisplayId()
307
    {
308 8
        return isset($this->id) ? $this->id : '*dry-run*';
309
    }
310
311
    /**
312
     * @return null|string ID of (once) running container or null if not yet running
313
     */
314 3
    public function getId()
315
    {
316 3
        return $this->id;
317
    }
318
319
    /**
320
     * @return null|string name of the container, NULL if no name generated yet
321
     */
322 3
    public function getName()
323
    {
324 3
        return $this->name;
325
    }
326
327
    /**
328
     * @param bool $keep a container on true, kill on false (if it exists)
329
     *
330
     * @return null|string
331
     */
332 7
    public function keepOrKill($keep)
333
    {
334 7
        $name = $this->name;
335 7
        if (null === $name) {
336 1
            throw new \BadMethodCallException('Container has no name yet');
337
        }
338
339 6
        $processManager = Docker::create($this->exec)->getProcessManager();
340
341 6
        if (false === $keep) {
342 2
            $processManager->zapContainersByName($name);
343
344 2
            return $this->id = null;
345
        }
346
347 5
        return $this->id = $processManager->findContainerIdByName($name);
348
    }
349
350
    /**
351
     * @param bool $kill
352
     * @param bool $remove
353
     *
354
     * @return void
355
     */
356 2
    public function killAndRemove($kill, $remove)
357
    {
358 2
        $id = $this->getDisplayId();
359
360 2
        self::execKillAndRemove($this->exec, $id, $kill, $remove);
361 2
    }
362
363
    /**
364
     * @param array $args
365
     *
366
     * @return array array(int $status, string $out, string $err)
367
     */
368 4
    public function run(array $args)
369
    {
370 4
        $execRun = self::execRun($this->exec, $args);
371 4
        $this->id = array_pop($execRun);
372
373 4
        return $execRun;
374
    }
375
}
376