Passed
Branch test (244668)
by Tom
03:44
created

Runner::runStep()   F

Complexity

Conditions 15
Paths 512

Size

Total Lines 103
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 67
CRAP Score 15.0007

Importance

Changes 0
Metric Value
cc 15
eloc 69
nc 512
nop 2
dl 0
loc 103
ccs 67
cts 68
cp 0.9853
crap 15.0007
rs 3.0055
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines;
6
7
use Ktomk\Pipelines\Cli\Docker;
8
use Ktomk\Pipelines\Cli\Exec;
9
use Ktomk\Pipelines\Cli\Streams;
10
use Ktomk\Pipelines\Runner\Env;
11
12
/**
13
 * Pipeline runner with docker under the hood
14
 */
15
class Runner
16
{
17
    const FLAGS = 3;
18
    const FLAG_DOCKER_REMOVE = 1;
19
    const FLAG_DOCKER_KILL = 2;
20
    const FLAG_DEPLOY_COPY = 4; # copy working dir into container
21
22
    const STATUS_NO_STEPS = 1;
23
    const STATUS_RECURSION_DETECTED = 127;
24
25
    /**
26
     * @var string
27
     */
28
    private $prefix;
29
30
    /**
31
     * @var string
32
     */
33
    private $directory;
34
35
    /**
36
     * @var Exec
37
     */
38
    private $exec;
39
40
    /**
41
     * @var int
42
     */
43
    private $flags;
44
45
    /**
46
     * @var Env
47
     */
48
    private $env;
49
    /**
50
     * @var Streams
51
     */
52
    private $streams;
53
54
    /**
55
     * DockerSession constructor.
56
     *
57
     * @param string $prefix
58
     * @param string $directory source repository root
59
     * @param Exec $exec
60
     * @param int $flags [optional]
61
     * @param Env $env [optional]
62
     * @param Streams $streams [optional]
63
     */
64 7
    public function __construct(
65
        $prefix,
66
        $directory,
67
        Exec $exec,
68
        $flags = null,
69
        Env $env = null,
70
        Streams $streams = null
71
    )
72
    {
73 7
        $this->prefix = $prefix;
74
75 7
        $this->directory = $directory;
76 7
        $this->exec = $exec;
77 7
        $this->flags = $flags === null ? self::FLAGS : $flags;
78 7
        $this->env = null === $env ? Env::create() : $env;
79 7
        $this->streams = null === $streams ? Streams::create() : $streams;
80 7
    }
81
82 7
    public function run(Pipeline $pipeline)
83
    {
84 7
        $hasId = $this->env->setPipelinesId($pipeline->getId()); # TODO give Env an addPipeline() method (compare addReference)
85 7
        if ($hasId) {
86 1
            $this->streams->err(sprintf(
87 1
                "fatal: won't start pipeline '%s'; pipeline inside pipelines recursion detected\n",
88 1
                $pipeline->getId()
89
            ));
90 1
            return self::STATUS_RECURSION_DETECTED;
91
        }
92
93 6
        $steps = $pipeline->getSteps();
94 6
        foreach ($steps as $index => $step) {
95 5
            $status = $this->runStep($step, $index);
96 5
            if ($status !== 0) {
97 5
                return $status;
98
            }
99
        }
100
101 3
        if (!isset($status)) {
102 1
            $this->streams->err("error: pipeline with no step to execute\n");
103 1
            return self::STATUS_NO_STEPS;
104
        }
105
106 2
        return $status;
107
    }
108
109
    /**
110
     * @param Step $step
111
     * @param int $index
112
     * @return int exit status
113
     */
114 5
    public function runStep(Step $step, $index)
115
    {
116 5
        $prefix = $this->prefix;
117 5
        $dir = $this->directory;
118 5
        $env = $this->env;
119 5
        $exec = $this->exec;
120 5
        $streams = $this->streams;
121
122 5
        $docker = new Docker($exec);
123
124 5
        $name = $prefix . '-' . Lib::generateUuid();
125 5
        $image = $step->getImage();
126 5
        $env->setContainerName($name);
127
128
        # launch container
129 5
        $streams->out(sprintf(
130 5
            "\x1D+++ step #%d\n\n    name...........: %s\n    effective-image: %s\n    container......: %s\n",
131 5
            $index,
132 5
            $step->getName() ? '"' . $step->getName() . '"' : '(unnamed)',
133 5
            $step->getImage(),
134 5
            $name
135
        ));
136
137 5
        $copy = (bool)($this->flags & self::FLAG_DEPLOY_COPY);
138
139
        // docker client inside docker
140
        // FIXME give controlling options, this is serious /!\
141 5
        $mountDockerSock = array();
142 5
        if (file_exists('/var/run/docker.sock')) {
143
            $mountDockerSock = array(
144
                '-v', '/var/run/docker.sock:/var/run/docker.sock',
145
            );
146
        }
147
148 5
        $parentName = $env->getValue('PIPELINES_PARENT_CONTAINER_NAME');
149 5
        $checkMount = $mountDockerSock && null !== $parentName;
150 5
        $deviceDir = $checkMount ? $docker->hostDevice($parentName, $dir) : $dir;
0 ignored issues
show
introduced by
The condition $checkMount can never be true.
Loading history...
151
152 5
        $mountWorkingDirectory = $copy
153 2
            ? array()
154 5
            : array('--volume', "$deviceDir:/app");
155
156 5
        $status = $exec->capture('docker', array(
157 5
            'run', '-i', '--name', $name,
158 5
            $env->getArgs('-e'),
159 5
            $mountWorkingDirectory, '-e', 'BITBUCKET_CLONE_DIR=/app',
160 5
            $mountDockerSock,
161 5
            '--workdir', '/app', '--detach', $image
162 5
        ), $out, $err);
163 5
        if ($status !== 0) {
164 1
            $streams->out(sprintf("    container-id...: %s\n\n", '*failure*'));
165 1
            $streams->out(sprintf("fatal: setting up the container failed.\n"));
166 1
            $streams->err(sprintf("%s\n", $err));
167 1
            $streams->out(sprintf("%s\n", $out));
168 1
            $streams->out(sprintf("exit status: %d\n", $status));
169 1
            return $status;
170
        }
171 4
        $id = rtrim($out) ?: "*dry-run*"; # side-effect: internal exploit of no output with true exit status
172 4
        $streams->out(sprintf("    container-id...: %s\n\n", substr($id, 0, 12)));
173
174
        # TODO: different deployments, mount (default), mount-ro, copy
175 4
        if ($copy) {
176 2
            $streams->out("\x1D+++ copying files into container...\n");
177 2
            $status = $exec->pass('docker', array(
178 2
                    'cp', '-a', $dir . '/.', $id . ':/app')
179
            );
180 2
            if ($status !== 0) {
181 1
                $streams->err('Deploy copy failure\n');
182 1
                return $status;
183
            }
184 1
            $streams("");
185
        }
186
187 3
        $script = $step->getScript();
188 3
        foreach ($script as $line => $command) {
189 3
            $streams->out(sprintf("\x1D+ %s\n", $command));
190 3
            $status = $exec->pass('docker', array(
191 3
                'exec', '-i', $name, '/bin/sh', '-c', $command,
192
            ));
193 3
            $streams->out(sprintf("\n"));
194 3
            if ($status !== 0) {
195 3
                break;
196
            }
197
        }
198
199 3
        if (0 !== $status) {
200
            # keep container
201 1
            $this->streams->err(sprintf(
202 1
                "exit status %d, keeping container id %s\n",
203 1
                $status,
204 1
                substr($id, 0, 12)
205
            ));
206
        } else {
207
            # remove container
208 2
            if ($this->flags & self::FLAG_DOCKER_KILL) {
209 1
                $exec->capture('docker', array('kill', $name));
210
            }
211 2
            if ($this->flags & self::FLAG_DOCKER_REMOVE) {
212 1
                $exec->capture('docker', array('rm', $name));
213
            }
214
        }
215
216 3
        return $status;
217
    }
218
}
219