Passed
Push — master ( 0cfc7e...b88fc1 )
by Tom
03:33 queued 42s
created

File::validateImage()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

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