Env   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 372
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 101
dl 0
loc 372
ccs 96
cts 96
cp 1
rs 9.52
c 2
b 0
f 1
wmc 36

19 Methods

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