Passed
Push — master ( 3b22a1...94154d )
by Tom
03:44
created

File::getPipelines()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines;
6
7
use InvalidArgumentException;
8
use Ktomk\Pipelines\File\BbplMatch;
9
use Ktomk\Pipelines\File\Image;
10
use Ktomk\Pipelines\File\ImageName;
11
use Ktomk\Pipelines\File\ParseException;
12
use Ktomk\Pipelines\Runner\Reference;
13
14
/**
15
 * Bitbucket Pipelines file
16
 */
17
class File
18
{
19
    const FILE_NAME = 'bitbucket-pipelines.yml';
20
21
    const DEFAULT_IMAGE = 'atlassian/default-image:latest';
22
23
    /**
24
     * default clone depth
25
     */
26
    const DEFAULT_CLONE = 50;
27
28
    /**
29
     * @var array
30
     */
31
    private $array;
32
33
    /**
34
     * @var array
35
     */
36
    private $pipelines;
37
38
    /**
39
     * File constructor.
40
     *
41
     * @param array $array
42
     * @throws \Ktomk\Pipelines\File\ParseException
43
     */
44 22
    public function __construct(array $array)
45
    {
46
        // quick validation: pipelines require
47 22
        if (!isset($array['pipelines']) || !is_array($array['pipelines'])) {
48 1
            ParseException::__("Missing required property 'pipelines'");
49
        }
50
51
        // quick validation: image name
52 21
        self::validateImage($array);
53
54 19
        $this->pipelines = $this->parsePipelineReferences($array['pipelines']);
55
56 16
        $this->array = $array;
57 16
    }
58
59
    /**
60
     * @param $path
61
     * @throws \Ktomk\Pipelines\File\ParseException
62
     * @return File
63
     */
64 7
    public static function createFromFile($path)
65
    {
66 7
        $result = Yaml::file($path);
67 7
        if (!$result) {
68 1
            ParseException::__(sprintf("YAML error: %s; verify the file contains valid YAML", $path));
69
        }
70
71 6
        return new self($result);
72
    }
73
74
    /**
75
     * if an 'image' entry is set, validate it is a string or a section.
76
     *
77
     * TODO(tk): move into Image class
78
     *
79
     * @param array $array
80
     * @throw ParseException if the image name is invalid
81
     * @throws \Ktomk\Pipelines\File\ParseException
82
     */
83 24
    public static function validateImage(array $array)
84
    {
85 24
        if (!array_key_exists('image', $array)) {
86 18
            return;
87
        }
88
89 6
        $image = $array['image'];
90
91 6
        if (is_array($image) && isset($image['name'])) {
92 3
            if (!ImageName::validate($image['name'])) {
93 1
                ParseException::__(sprintf(
94 1
                    "'image' invalid Docker image name: '%s'",
95 1
                    $image['name']
96
                ));
97
            }
98
99 2
            return;
100
        }
101
102 4
        if (!is_string($image)) {
103 1
            ParseException::__("'image' requires a Docker image name");
104
        }
105 3
        if (!ImageName::validate($image)) {
106 1
            ParseException::__(
107 1
                sprintf("'image' invalid Docker image name: '%s'", $image)
108
            );
109
        }
110 2
    }
111
112
    /**
113
     * @throws \Ktomk\Pipelines\File\ParseException
114
     * @return Image
115
     */
116 3
    public function getImage()
117
    {
118 3
        $imageData = isset($this->array['image'])
119 1
            ? $this->array['image']
120 3
            : self::DEFAULT_IMAGE;
121
122 3
        return new Image($imageData);
123
    }
124
125 2
    public function getClone()
126
    {
127 2
        return isset($this->array['clone'])
128 1
            ? $this->array['clone']
129 2
            : self::DEFAULT_CLONE;
130
    }
131
132
    /**
133
     * @throws \InvalidArgumentException
134
     * @return null|Pipeline
135
     */
136 4
    public function getDefault()
137
    {
138 4
        return $this->getById('default');
139
    }
140
141
    /**
142
     * returns the id of the default pipeline in file or null if there is none
143
     *
144
     * @return null|string
145
     */
146 2
    public function getIdDefault()
147
    {
148 2
        $id = 'default';
149
150 2
        if (!isset($this->pipelines[$id])) {
151 1
            return null;
152
        }
153
154 1
        return $id;
155
    }
156
157
    /**
158
     * Searches the pipeline that matches the reference
159
     *
160
     * @param Reference $reference
161
     * @throws \UnexpectedValueException
162
     * @throws \InvalidArgumentException
163
     * @return null|string id if found, null otherwise
164
     */
165 1
    public function searchIdByReference(Reference $reference)
166
    {
167 1
        if (null === $reference->getType()) {
168 1
            return $this->getIdDefault();
169
        }
170
171 1
        return $this->searchIdByTypeReference(
172 1
            $reference->getPipelinesType(),
173 1
            $reference->getName()
174
        );
175
    }
176
177
    /**
178
     * Searches a reference
179
     *
180
     * @param Reference $reference
181
     * @throws \Ktomk\Pipelines\File\ParseException
182
     * @throws \UnexpectedValueException
183
     * @throws \InvalidArgumentException
184
     * @return null|Pipeline
185
     */
186 7
    public function searchReference(Reference $reference)
187
    {
188 7
        if (null === $type = $reference->getPipelinesType()) {
189 2
            return $this->getDefault();
190
        }
191
192 5
        return $this->searchTypeReference($type, $reference->getName());
193
    }
194
195
    /**
196
     * Searches a reference within type, returns found one, if
197
     * none is found, the default pipeline or null if there is
198
     * no default pipeline.
199
     *
200
     * @param string $type of pipeline, can be branches, tags or bookmarks
201
     * @param string $reference
202
     * @throws \Ktomk\Pipelines\File\ParseException
203
     * @throws \UnexpectedValueException
204
     * @throws \InvalidArgumentException
205
     * @return null|Pipeline
206
     */
207 8
    public function searchTypeReference($type, $reference)
208
    {
209 8
        $id = $this->searchIdByTypeReference($type, $reference);
210
211 7
        return null !== $id ? $this->getById($id) : null;
212
    }
213
214
    /**
215
     * @return array
216
     */
217 2
    public function getPipelineIds()
218
    {
219 2
        return array_keys($this->pipelines);
220
    }
221
222
    /**
223
     * @throws \InvalidArgumentException
224
     * @return array|Pipeline[]
225
     */
226 1
    public function getPipelines()
227
    {
228 1
        $pipelines = array();
229
230 1
        foreach ($this->getPipelineIds() as $id) {
231 1
            $pipelines[$id] = $this->getById($id);
232
        }
233
234 1
        return $pipelines;
235
    }
236
237
    /**
238
     * @param string $id
239
     * @throws InvalidArgumentException
240
     * @throws ParseException
241
     * @return null|Pipeline
242
     */
243 11
    public function getById($id)
244
    {
245 11
        if (!preg_match('~^(default|(branches|tags|bookmarks|custom)/[^\x00-\x1F]*)$~', $id)) {
246 1
            throw new InvalidArgumentException(sprintf("Invalid id '%s'", $id));
247
        }
248
249 10
        if (!isset($this->pipelines[$id])) {
250 2
            return null;
251
        }
252
253 8
        $ref = $this->pipelines[$id];
254 8
        if ($ref[2] instanceof Pipeline) {
255 2
            return $ref[2];
256
        }
257
258
        // bind to instance if yet an array
259 8
        if (!is_array($ref[2])) {
260 1
            ParseException::__(sprintf("%s: named pipeline required", $id));
261
        }
262 7
        $pipeline = new Pipeline($this, $ref[2]);
263 7
        $ref[2] = $pipeline;
264
265 7
        return $pipeline;
266
    }
267
268 2
    public function getIdFrom(Pipeline $pipeline)
269
    {
270 2
        foreach ($this->pipelines as $id => $reference) {
271 2
            if ($pipeline === $reference[2]) {
272 2
                return $id;
273
            }
274
        }
275
276 1
        return null;
277
    }
278
279
    /**
280
     * @param $type
281
     * @param $reference
282
     * @throws \UnexpectedValueException
283
     * @throws \InvalidArgumentException
284
     * @return null|string
285
     */
286 5
    private function searchIdByTypeReference($type, $reference)
287
    {
288 5
        $this->validateType($type);
289
290 4
        if (!isset($this->array['pipelines'][$type])) {
291 2
            return $this->getIdDefault();
292
        }
293 3
        $array = &$this->array['pipelines'][$type];
294
295
        # check for direct (non-pattern) match
296 3
        if (isset($array[$reference])) {
297 2
            return "${type}/${reference}";
298
        }
299
300
        # get entry with largest pattern to match
301 2
        $patterns = array_keys($array);
302 2
        unset($array);
303
304 2
        $match = null;
305 2
        foreach ($patterns as $pattern) {
306 2
            $result = BbplMatch::match($pattern, $reference);
307 2
            if ($result and (null === $match or strlen($pattern) > strlen($match))) {
308 2
                $match = $pattern;
309
            }
310
        }
311 2
        if (null !== $match) {
312 1
            return "${type}/${match}";
313
        }
314
315 1
        return $this->getIdDefault();
316
    }
317
318
    /**
319
     * @param array $array
320
     * @throws \Ktomk\Pipelines\File\ParseException
321
     * @return array
322
     */
323
    private function parsePipelineReferences(array &$array)
324
    {
325
        // quick validation: pipeline sections
326 19
        $sections = array('branches', 'tags', 'bookmarks', 'custom');
327 19
        $count = 0;
328 19
        foreach ($sections as $section) {
329 19
            if (isset($array[$section])) {
330 19
                $count++;
331
            }
332
        }
333 19
        if (!$count && !isset($array['default'])) {
334 1
            ParseException::__("'pipelines' requires at least a default, branches, tags, bookmarks or custom section");
335
        }
336
337 18
        $references = array();
338
339 18
        $section = 'default';
340 18
        if (isset($array[$section])) {
341 12
            if (!is_array($array[$section])) {
342 1
                ParseException::__("'${section}' requires a list of steps");
343
            }
344 11
            $references[$section] = array(
345 11
                $section,
346
                null,
347 11
                &$array[$section],
348
            );
349
        }
350
351 17
        foreach ($array as $section => $refs) {
352 17
            if (!in_array($section, $sections, true)) {
353 11
                continue;
354
            }
355 13
            if (!is_array($refs)) {
356 1
                ParseException::__("'${section}' requires a list");
357
            }
358 12
            foreach ($refs as $pattern => $pipeline) {
359 10
                $references["${section}/${pattern}"] = array(
360 10
                    $section,
361 10
                    $pattern,
362 12
                    &$array[$section][$pattern],
363
                );
364
            }
365
        }
366
367 16
        return $references;
368
    }
369
370
    /**
371
     * @param $type
372
     * @throws \InvalidArgumentException
373
     */
374
    private function validateType($type)
375
    {
376 5
        $scopes = array('branches', 'tags', 'bookmarks');
377 5
        if (!in_array($type, $scopes, true)) {
378 1
            throw new InvalidArgumentException(sprintf("Invalid type '%s'", $type));
379
        }
380 4
    }
381
}
382