Passed
Push — test ( d8d4a5...bb43f9 )
by Tom
03:01
created

AbstractionLayerImpl::remove()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 6
eloc 16
c 2
b 0
f 1
nc 4
nop 2
dl 0
loc 32
ccs 15
cts 15
cp 1
crap 6
rs 9.1111
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines\Runner\Docker;
6
7
use Ktomk\Pipelines\Cli\Exec;
8
use Ktomk\Pipelines\Cli\ExecTester;
9
use Ktomk\Pipelines\Lib;
10
11
/**
12
 * Class DockerAbstractionLayer Implementation
13
 *
14
 * Implement all of the docker interaction (first of all from runners'
15
 * side) with the docker cli (the docker client binary) with a defined
16
 * interface. Any (new) interaction (inter!) with docker client
17
 * programmed against the interface so it can become part of the
18
 * runner component (for the interface) and inverse the dependency
19
 * against the hard working implementation.
20
 *
21
 * First implementation of the abstraction layer, as runner driven,
22
 * placed in there but perhaps must not belong into there but a
23
 * component of it's own (still exec driven, yet no passthru() in use,
24
 * hope to keep it, but it's a more leaking abstraction for pipelines,
25
 * so let's see how clean this can be done, in CLI context, passthru()
26
 * is especially useful for the overall pipelines utility, maybe when
27
 * breaking with it due to null-byte prevention leakage, see KNOWN BUGS)
28
 *
29
 * @package Ktomk\Pipelines\Runner\Docker
30
 */
31
class AbstractionLayerImpl implements AbstractionLayer
32
{
33
    /**
34
     * @var bool
35
     */
36
    private $throws;
37
38
    /**
39
     * @var Exec
40
     */
41
    private $exec;
42
43
    /**
44
     * DockerAbstractionLayer constructor.
45
     *
46
     * @param Exec $exec
47
     * @param bool $throws optional
48
     */
49 7
    public function __construct(Exec $exec, $throws = null)
50
    {
51 7
        $this->exec = $exec;
52 7
        $this->throws($throws);
53 7
    }
54
55
    /**
56
     * @param string $id
57
     * @param array|string[] $arguments
58
     *
59
     * @return null|string null on error (non-zero exit status),
60
     *                     string with standard output (rtrim-med) on success
61
     */
62 1
    public function execute($id, array $arguments)
63
    {
64 1
        $status = $this->exec->capture(
65 1
            $cmd = Lib::cmd(
66 1
                'docker',
67 1
                Lib::merge('exec', $id, $arguments)
68
            ),
69 1
            array(),
70
            $out,
71
            $err
72
        );
73
74 1
        if (0 !== $status) {
75 1
            $this->notifyNonZero($cmd, $status, $out, $err);
76
77 1
            return null;
78
        }
79
80 1
        return rtrim($out);
81
    }
82
83 1
    public function kill($idOrName)
84
    {
85 1
        $status = $this->exec->capture(
86 1
            $cmd = Lib::cmd(
87 1
                'docker',
88 1
                Lib::merge('kill', $idOrName)
89
            ),
90 1
            array(),
91
            $out,
92
            $err
93
        );
94
95 1
        if (0 !== $status) {
96 1
            $this->notifyNonZero($cmd, $status, $out, $err);
97
98 1
            return null;
99
        }
100
101 1
        return rtrim($out);
102
    }
103
104
    /**
105
     * remove a container
106
     *
107
     * @param string $idOrName docker container
108
     * @param bool $force optional
109
     *
110
     * @return null|string
111
     */
112 1
    public function remove($idOrName, $force = true)
113
    {
114 1
        $status = $this->exec->capture(
115 1
            $cmd = Lib::cmd(
116 1
                'docker',
117 1
                Lib::merge('rm', $force ? '-f' : null, $idOrName)
118
            ),
119 1
            array(),
120
            $out,
121
            $err
122
        );
123
124
        // idempotent removal for throw behaviour, removing an nonexistent is not an exception, never
125 1
        if (1 === $status) {
126 1
            return null;
127
        }
128
129 1
        if (0 !== $status) {
130 1
            $this->notifyNonZero($cmd, $status, $out, $err);
131
132 1
            return null;
133
        }
134
135 1
        $buffer = rtrim($out);
136
137
        // idempotent removal for return behaviour, removing an nonexistent is not an exception, never
138
        // later docker exits 0, no out and err is that no such container to remove
139 1
        if ('' === $buffer && '' !== $err) {
140 1
            return null;
141
        }
142
143 1
        return $buffer;
144
    }
145
146 1
    public function start($image, array $arguments, array $runArguments = array())
147
    {
148 1
        $status = $this->exec->capture(
149 1
            $cmd = Lib::cmd(
150 1
                'docker',
151 1
                Lib::merge('run', array('--detach', '--entrypoint', '/bin/sh', '-it'), $arguments, $image, $runArguments)
152
            ),
153 1
            array(),
154
            $out,
155
            $err
156
        );
157
158 1
        if (0 !== $status) {
159 1
            $this->notifyNonZero($cmd, $status, $out, $err);
160
161 1
            return null;
162
        }
163
164 1
        return rtrim($out);
165
    }
166
167
    /* tar methods */
168
169 1
    public function importTar($tar, $id, $path)
170
    {
171 1
        $status = $this->exec->capture(
172 1
            $cmd = Lib::cmd(
173 1
                sprintf('<%s docker', Lib::quoteArg($tar)),
174
                array(
175 1
                    'cp',
176 1
                    '-',
177 1
                    sprintf('%s:%s', $id, $path),
178
                )
179
            ),
180 1
            array(),
181
            $out,
182
            $err
183
        );
184
185 1
        if (0 !== $status) {
186 1
            $this->notifyNonZero($cmd, $status, $out, $err);
187
188 1
            return null;
189
        }
190
191 1
        return true;
192
    }
193
194 1
    public function exportTar($id, $path, $tar)
195
    {
196 1
        $status = $this->exec->capture(
197 1
            $cmd = Lib::cmd(
198 1
                sprintf('>%s docker', Lib::quoteArg($tar)),
199
                array(
200 1
                    'cp',
201 1
                    sprintf('%s:%s', $id, $path),
202 1
                    '-',
203
                )
204
            ),
205 1
            array(),
206
            $out,
207
            $err
208
        );
209
210 1
        if (0 !== $status) {
211 1
            $this->notifyNonZero($cmd, $status, $out, $err);
212
213 1
            return null;
214
        }
215
216 1
        return $tar;
217
    }
218
219
    /* layer error/exception handling */
220
221 7
    public function throws($throws = null)
222
    {
223 7
        $this->throws = null === $throws
224 7
            ? $this->exec instanceof ExecTester
225 6
            : $throws;
226 7
    }
227
228
    /**
229
     * notify non zero status
230
     *
231
     * internal representation to throw, leaking as some non-zero values
232
     * for some of the abstractions are expected to simplify the interface
233
     * in which case the notification is not called.
234
     *
235
     * @param string $cmd
236
     * @param int $status
237
     * @param string $out
238
     * @param string $err
239
     */
240 6
    private function notifyNonZero($cmd, $status, $out, $err)
241
    {
242 6
        if (!$this->throws) {
243 6
            return;
244
        }
245
246 2
        throw new \RuntimeException(
247 2
            sprintf(
248 2
                "Failed to execute\n\n  %s\n\ngot status %d and error:\n\n  %s\n\nwith output:\n\n %s\n",
249
                $cmd,
250
                $status,
251
                $err,
252
                $out
253
            )
254
        );
255
    }
256
}
257