Passed
Push — master ( 71afca...9e5fe0 )
by Tom
04:18
created

CacheIo::captureStepCaches()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 15
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 28
ccs 16
cts 16
cp 1
crap 5
rs 9.4555
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\Streams;
9
use Ktomk\Pipelines\File\Pipeline\Step;
10
use Ktomk\Pipelines\LibFs;
11
use Ktomk\Pipelines\LibFsPath;
12
use Ktomk\Pipelines\Runner\Runner;
13
14
/**
15
 * Class CacheHandler
16
 *
17
 * Directory path related cache operations for docker containers when
18
 * running pipelines.
19
 *
20
 * Delegates between local cache storage (directory based, one tar
21
 * archive per cache) and (running) step-runner docker container.
22
 *
23
 * @package Ktomk\Pipelines\Runner\Docker
24
 */
25
class CacheIo
26
{
27
    /**
28
     * @var string
29
     */
30
    private $id;
31
32
    /**
33
     * @var string
34
     */
35
    private $cachesDirectory;
36
37
    /**
38
     * @var Streams
39
     */
40
    private $streams;
41
42
    /**
43
     * @var Exec
44
     */
45
    private $exec;
46
47
    /**
48
     * @var string
49
     */
50
    private $clonePath;
51
52
    /**
53
     * @var bool
54
     */
55
    private $noCache = false;
56
57
    /**
58
     * @var AbstractionLayer
59
     */
60
    private $dal;
61
62
    /**
63
     * @param Runner $runner
64
     * @param $id
65
     *
66
     * @return CacheIo
67
     */
68 3
    public static function createByRunner(Runner $runner, $id)
69
    {
70 3
        return new self(
71 3
            self::runnerCachesDirectory($runner),
72
            $id,
73 3
            $runner->getRunOpts()->getOption('step.clone-path'),
74 3
            $runner->getStreams(),
75 3
            $runner->getExec(),
76 3
            $runner->getFlags()->noCache()
77
        );
78
    }
79
80
    /**
81
     * @param Runner $runner
82
     *
83
     * @return string path of base-directory in which the pipelines project tar files are located
84
     */
85 4
    public static function runnerCachesDirectory(Runner $runner)
86
    {
87 4
        return (string)$runner->getDirectories()->getBaseDirectory(
88 4
            'XDG_CACHE_HOME',
89 4
            sprintf(
90 4
                'caches/%s',
91 4
                $runner->getProject()
92
            )
93
        );
94
    }
95
96
    /**
97
     * CacheIo constructor.
98
     *
99
     * @param string $caches path to directory where tar files are stored
100
     * @param string $id container
101
     * @param string $clonePath
102
     * @param Streams $streams
103
     * @param Exec $exec
104
     * @param bool $noCache
105
     */
106 10
    public function __construct($caches, $id, $clonePath, Streams $streams, Exec $exec, $noCache)
107
    {
108 10
        $this->id = $id;
109 10
        $this->setCachesDirectory($caches);
110 9
        $this->clonePath = $clonePath;
111 9
        $this->streams = $streams;
112 9
        $this->exec = $exec;
113 9
        $this->dal = new AbstractionLayerImpl($exec);
114 9
        $this->noCache = $noCache;
115 9
    }
116
117
    /**
118
     * Deploy step caches into container
119
     *
120
     * @param Step $step
121
     */
122 5
    public function deployStepCaches(Step $step)
123
    {
124 5
        $cachesDirectory = $this->cachesDirectory;
125
126
        // skip deploying caches if there are no caches
127 5
        if ($this->skip($this->noCache, $cachesDirectory, $step)) {
128 3
            return;
129
        }
130
131 2
        $id = $this->id;
132 2
        $streams = $this->streams;
133
134 2
        $streams->out("\x1D+++ populating caches...\n");
135
136 2
        foreach ($step->getCaches() as $name => $path) {
137 2
            $tarFile = sprintf('%s/%s.tar', $cachesDirectory, $name);
138 2
            $tarExists = LibFs::isReadableFile($tarFile);
139 2
            $streams->out(sprintf(" - %s %s (%s)\n", $name, $path, $tarExists ? 'hit' : 'miss'));
140
141 2
            if (!$tarExists) {
142 1
                continue;
143
            }
144
145 1
            $containerPath = $this->mapCachePath($path);
146
147 1
            $this->dal->execute($id, array('mkdir', '-p', $containerPath));
148 1
            $this->dal->importTar($tarFile, $id, $containerPath);
149
        }
150 2
    }
151
152
    /**
153
     * Capture cache from container
154
     *
155
     * @param Step $step
156
     * @param bool $capture
157
     */
158 6
    public function captureStepCaches(Step $step, $capture)
159
    {
160
        // skip capturing if disabled
161 6
        if (!$capture) {
162 1
            return;
163
        }
164
165 5
        $cachesDirectory = $this->cachesDirectory;
166
167
        // skip capturing caches if there are no caches
168 5
        if ($this->skip($this->noCache, $cachesDirectory, $step)) {
169 3
            return;
170
        }
171
172 2
        $id = $this->id;
173 2
        $streams = $this->streams;
174
175 2
        $streams->out("\x1D+++ updating caches from container...\n");
176
177 2
        $cachesDirectory = LibFs::mkDir($cachesDirectory);
178
179 2
        foreach ($step->getCaches() as $name => $path) {
180 2
            $tarFile = sprintf('%s/%s.tar', $cachesDirectory, $name);
181 2
            $tarExists = LibFs::isReadableFile($tarFile);
182 2
            $streams->out(sprintf(" - %s %s (%s)\n", $name, $path, $tarExists ? 'update' : 'create'));
183
184 2
            $containerPath = $this->mapCachePath($path);
185 2
            $this->dal->exportTar($id, $containerPath . '/.', $tarFile);
186
        }
187 2
    }
188
189
    /**
190
     * Map cache path to container path
191
     *
192
     * A cache path in a cache definition can be with information for
193
     * container context, e.g. $HOME, ~ or just being relative to the
194
     * clone directory.
195
     *
196
     * This requires mapping for the "real" path
197
     *
198
     * @param string $path component of cache definition
199
     *
200
     * @return string
201
     */
202 4
    public function mapCachePath($path)
203
    {
204 4
        $id = $this->id;
205
206 4
        $containerPath = $path;
207
208
        // let the container map the path expression
209 4
        $out = $this->dal->execute($id, array('/bin/sh', '-c', sprintf('echo %s', $containerPath)));
210 4
        if (0 < strlen((string)$out) && ('/' === $out[0])) {
211 1
            $containerPath = trim($out);
212
        }
213
214
        // fallback to '~' -> /root assumption (can fail easy as root might not be the user)
215 4
        if ('~' === $containerPath[0]) {
216 2
            $containerPath = '/root/' . ltrim(substr($containerPath, 1), '/');
217
        }
218
219
        // handle relative paths
220 4
        if (!LibFsPath::isAbsolute($containerPath)) {
221 1
            $containerPath = LibFsPath::normalizeSegments(
222 1
                $this->clonePath . '/' . $containerPath
223
            );
224
        }
225
226 4
        return $containerPath;
227
    }
228
229
    /**
230
     * @param bool $noCache
231
     * @param string $cachesDirectory
232
     * @param Step $step
233
     *
234
     * @return bool|void
235
     */
236 5
    private function skip($noCache, $cachesDirectory, Step $step)
237
    {
238 5
        if ($noCache) {
239 1
            return true;
240
        }
241
242
        // caches directory is empty?
243 4
        if (empty($cachesDirectory)) {
244 1
            return true;
245
        }
246
247
        // step has caches?
248 3
        $caches = $step->getCaches()->getIterator();
249 3
        if (!count($caches)) {
250 1
            return true;
251
        }
252
253 2
        return false;
254
    }
255
256
    /**
257
     * @param string $path
258
     */
259 10
    private function setCachesDirectory($path)
260
    {
261 10
        if (!empty($path) && !LibFsPath::isAbsolute($path)) {
262 1
            throw new \InvalidArgumentException(sprintf('Caches directory: Not an absolute path: %s', $path));
263
        }
264
265 9
        $this->cachesDirectory = (string)$path;
266 9
    }
267
}
268