Passed
Push — master ( 74010f...cd30de )
by Tom
03:00
created

Env::createEx()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines\Runner;
6
7
use Ktomk\Pipelines\Cli\Args\Args;
8
use Ktomk\Pipelines\Cli\Args\Collector;
9
use Ktomk\Pipelines\Lib;
10
11
/**
12
 * Pipeline environment collaborator
13
 */
14
class Env
15
{
16
    /**
17
     * @var array pipelines (bitbucket) environment variables
18
     */
19
    private $vars = array();
20
21
    /**
22
     * collected arguments
23
     *
24
     * @var array
25
     */
26
    private $collected = array();
27
28
    /**
29
     * environment variables to inherit from
30
     *
31
     * @var array
32
     */
33
    private $inherit = array();
34
35
    /**
36
     * @var null|EnvResolver
37
     */
38
    private $resolver;
39
40
    /**
41
     * create with no default initialization
42
     *
43
     * @param null|array $inherit
44
     *
45
     * @return Env
46
     */
47 2
    public static function create(array $inherit = null)
48
    {
49 2
        $env = new self();
50 2
        $env->initInherit((array)$inherit);
51
52 2
        return $env;
53
    }
54
55
    /**
56
     * @param null|array $inherit
57
     *
58
     * @return Env
59
     */
60 17
    public static function createEx(array $inherit = null)
61
    {
62 17
        $env = new self();
63 17
        $env->initDefaultVars((array)$inherit);
64
65 17
        return $env;
66
    }
67
68
    /**
69
     * @param array $vars
70
     *
71
     * @return array w/ a string variable definition (name=value) per value
72
     */
73 13
    public static function createVarDefinitions(array $vars)
74
    {
75 13
        $array = array();
76
77 13
        foreach ($vars as $name => $value) {
78 11
            if (isset($value)) {
79 11
                $array[] = sprintf('%s=%s', $name, $value);
80
            }
81
        }
82
83 13
        return $array;
84
    }
85
86
    /**
87
     * @param string $option "-e" typically for Docker binary
88
     * @param array $vars variables as hashmap
89
     *
90
     * @return array of options (from $option) and values, ['-e', 'val1', '-e', 'val2', ...]
91
     */
92 13
    public static function createArgVarDefinitions($option, array $vars)
93
    {
94 13
        $args = array();
95
96 13
        foreach (self::createVarDefinitions($vars) as $definition) {
97 11
            $args[] = $option;
98 11
            $args[] = $definition;
99
        }
100
101 13
        return $args;
102
    }
103
104
    /**
105
     * Initialize default environment used in a Bitbucket Pipeline
106
     *
107
     * As the runner mimics some values, defaults are available
108
     *
109
     * @param array $inherit Environment variables to inherit from
110
     *
111
     * @return void
112
     */
113 21
    public function initDefaultVars(array $inherit)
114
    {
115 21
        $this->initInherit($inherit);
116
117
        $inheritable = array(
118 21
            'BITBUCKET_BOOKMARK' => null,
119
            'BITBUCKET_BRANCH' => null,
120 21
            'BITBUCKET_BUILD_NUMBER' => '0',
121 21
            'BITBUCKET_COMMIT' => '0000000000000000000000000000000000000000',
122 21
            'BITBUCKET_REPO_OWNER' => '' . Lib::r($inherit['USER'], 'nobody'),
123 21
            'BITBUCKET_REPO_SLUG' => 'local-has-no-slug',
124
            'BITBUCKET_TAG' => null,
125 21
            'CI' => 'true',
126
            'PIPELINES_CONTAINER_NAME' => null,
127
            'PIPELINES_IDS' => null,
128
            'PIPELINES_PARENT_CONTAINER_NAME' => null,
129
            'PIPELINES_PIP_CONTAINER_NAME' => null,
130
            'PIPELINES_PROJECT_PATH' => null,
131
        );
132
133
        $invariant = array(
134 21
            'PIPELINES_ID' => null,
135
        );
136
137 21
        foreach ($inheritable as $name => $value) {
138 21
            isset($inherit[$name]) ? $inheritable[$name] = $inherit[$name] : null;
139
        }
140
141 21
        $var = $invariant + $inheritable;
142 21
        ksort($var);
143
144 21
        $this->vars = $var;
145 21
    }
146
147
    /**
148
     * Map reference to environment variable setting
149
     *
150
     * Only add the BITBUCKET_BOOKMARK/_BRANCH/_TAG variable
151
     * if not yet set.
152
     *
153
     * @param Reference $ref
154
     *
155
     * @return void
156
     */
157 4
    public function addReference(Reference $ref)
158
    {
159 4
        if (null === $type = $ref->getType()) {
160 2
            return;
161
        }
162
163
        $map = array(
164 4
            'bookmark' => 'BITBUCKET_BOOKMARK',
165
            'branch' => 'BITBUCKET_BRANCH',
166
            'tag' => 'BITBUCKET_TAG',
167
            'pr' => 'BITBUCKET_BRANCH',
168
        );
169
170 4
        if (!isset($map[$type])) {
171 1
            throw new \UnexpectedValueException(sprintf('Unknown reference type: "%s"', $type));
172
        }
173
174 3
        if ($this->addPrReference($ref)) {
175 1
            return;
176
        }
177
178 2
        $this->addVar($map[$type], $ref->getName());
179 2
    }
180
181
    /**
182
     * add a variable if not yet set (real add new)
183
     *
184
     * @param string $name
185
     * @param string $value
186
     *
187
     * @return void
188
     */
189 3
    public function addVar($name, $value)
190
    {
191 3
        if (!isset($this->vars[$name])) {
192 2
            $this->vars[$name] = $value;
193
        }
194 3
    }
195
196
    /**
197
     * @param Reference $reference
198
     *
199
     * @return bool
200
     */
201 3
    public function addPrReference(Reference $reference)
202
    {
203 3
        if ('pr' !== $reference->getType()) {
204 2
            return false;
205
        }
206
207 1
        $var = array_combine(
208 1
            array('BITBUCKET_BRANCH', 'BITBUCKET_PR_DESTINATION_BRANCH'),
209 1
            explode(':', $reference->getName(), 2) + array(null, null)
210
        );
211
212 1
        foreach ($var as $name => $value) {
213 1
            isset($value) && $this->addVar($name, $value);
214
        }
215
216 1
        return true;
217
    }
218
219
    /**
220
     * @param string $name of container
221
     *
222
     * @return void
223
     */
224 2
    public function setContainerName($name)
225
    {
226 2
        if (isset($this->vars['PIPELINES_CONTAINER_NAME'])) {
227 2
            $this->vars['PIPELINES_PARENT_CONTAINER_NAME']
228 2
                = $this->vars['PIPELINES_CONTAINER_NAME'];
229
        }
230
231 2
        $this->vars['PIPELINES_CONTAINER_NAME'] = $name;
232 2
        $this->setFirstPipelineVariable('PIPELINES_PIP_CONTAINER_NAME', $name);
233 2
    }
234
235
    /**
236
     * set the pipelines environment's running pipeline id
237
     *
238
     * @param string $id of pipeline, e.g. "default" or "branch/feature/*"
239
     *
240
     * @return bool whether was used before (endless pipelines in pipelines loop)
241
     */
242 2
    public function setPipelinesId($id)
243
    {
244 2
        $list = (string)$this->getValue('PIPELINES_IDS');
245 2
        $hashes = preg_split('~\s+~', $list, -1, PREG_SPLIT_NO_EMPTY);
246 2
        $hashes = array_map('strtolower', /** @scrutinizer ignore-type */ $hashes);
247
248 2
        $idHash = md5($id);
249 2
        $hasId = in_array($idHash, $hashes, true);
250 2
        $hashes[] = $idHash;
251
252 2
        $this->vars['PIPELINES_ID'] = $id;
253 2
        $this->vars['PIPELINES_IDS'] = implode(' ', $hashes);
254
255 2
        return $hasId;
256
    }
257
258
    /**
259
     * set PIPELINES_PROJECT_PATH
260
     *
261
     * can never be overwritten, must be set by pipelines itself for the
262
     * initial pipeline. will be taken over into each sub-pipeline.
263
     *
264
     * @param string $path absolute path to the project directory (deploy source path)
265
     *
266
     * @return void
267
     */
268 1
    public function setPipelinesProjectPath($path)
269
    {
270
        // TODO $path must be absolute
271 1
        $this->setFirstPipelineVariable('PIPELINES_PROJECT_PATH', $path);
272 1
    }
273
274
    /**
275
     * @param string $option "-e" typically for Docker binary
276
     *
277
     * @return array of options (from $option) and values, ['-e', 'val1', '-e', 'val2', ...]
278
     */
279 13
    public function getArgs($option)
280
    {
281 13
        return Lib::merge(
282 13
            $this->collected,
283 13
            self::createArgVarDefinitions($option, $this->vars)
284
        );
285
    }
286
287
    /**
288
     * get a variables' value from the inherited
289
     * environment or null if not set
290
     *
291
     * @param string $name
292
     *
293
     * @return null|string
294
     */
295 1
    public function getInheritValue($name)
296
    {
297 1
        return isset($this->inherit[$name])
298 1
            ? $this->inherit[$name]
299 1
            : null;
300
    }
301
302
    /**
303
     * get a variables value or null if not set
304
     *
305
     * @param string $name
306
     *
307
     * @return null|string
308
     */
309 7
    public function getValue($name)
310
    {
311 7
        return isset($this->vars[$name])
312 6
            ? $this->vars[$name]
313 7
            : null;
314
    }
315
316
    /**
317
     * collect option arguments
318
     *
319
     * those options to be passed to docker client, normally -e,
320
     * --env and --env-file.
321
     *
322
     * @param Args $args
323
     * @param string|string[] $option
324
     *
325
     * @throws \InvalidArgumentException
326
     * @throws \Ktomk\Pipelines\Cli\ArgsException
327
     *
328
     * @return void
329
     */
330 2
    public function collect(Args $args, $option)
331
    {
332 2
        $collector = new Collector($args);
333 2
        $collector->collect($option);
334 2
        $this->collected = array_merge($this->collected, $collector->getArgs());
335
336 2
        $this->getResolver()->addArguments($collector);
337 2
    }
338
339
    /**
340
     * @param array $paths
341
     *
342
     * @throws \InvalidArgumentException
343
     *
344
     * @return void
345
     */
346 2
    public function collectFiles(array $paths)
347
    {
348 2
        $resolver = $this->getResolver();
349 2
        foreach ($paths as $path) {
350 2
            if ($resolver->addFileIfExists($path)) {
351 2
                $this->collected[] = '--env-file';
352 2
                $this->collected[] = $path;
353
            }
354
        }
355 2
    }
356
357
    /**
358
     * @return EnvResolver
359
     */
360 6
    public function getResolver()
361
    {
362 6
        if (null === $this->resolver) {
363 6
            $this->resolver = new EnvResolver($this->inherit);
364
        }
365
366 6
        return $this->resolver;
367
    }
368
369
    /**
370
     * get all variables collected so far
371
     *
372
     * @return array
373
     */
374 2
    public function getVariables()
375
    {
376 2
        return (array)$this->getResolver()->getVariables();
377
    }
378
379
    /**
380
     * @param array $inherit
381
     *
382
     * @return void
383
     */
384 22
    private function initInherit(array $inherit)
385
    {
386 22
        $this->inherit = array_filter($inherit, 'is_string');
387 22
    }
388
389
    /**
390
     * set an environment variable only if not yet set and in the first
391
     * pipeline.
392
     *
393
     * @param string $name
394
     * @param string $value
395
     *
396
     * @return void
397
     */
398 3
    private function setFirstPipelineVariable($name, $value)
399
    {
400 3
        if (isset($this->vars[$name])
401 3
            || !isset($this->vars['PIPELINES_ID'], $this->vars['PIPELINES_IDS'])
402 3
            || $this->vars['PIPELINES_IDS'] !== md5($this->vars['PIPELINES_ID'])
403
        ) {
404 3
            return;
405
        }
406
407 1
        $this->vars[$name] = $value;
408 1
    }
409
}
410