Passed
Push — master ( d133cd...1b99f0 )
by Tom
04:05
created

Env::addPrReference()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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