Passed
Push — master ( 94214e...f603ad )
by Andrés
58s
created

Runtime::getMergedOption()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 3
eloc 6
nc 1
nop 2
crap 3
1
<?php
2
/*
3
 * This file is part of the Magallanes package.
4
 *
5
 * (c) Andrés Montañez <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Mage\Runtime;
12
13
use Mage\Deploy\Strategy\ReleasesStrategy;
14
use Mage\Deploy\Strategy\RsyncStrategy;
15
use Mage\Deploy\Strategy\StrategyInterface;
16
use Psr\Log\LoggerInterface;
17
use Psr\Log\LogLevel;
18
use Symfony\Component\Process\Process;
19
use Mage\Runtime\Exception\RuntimeException;
20
21
/**
22
 * Runtime is a container of all run in time configuration, stages of progress, hosts being deployed, etc.
23
 *
24
 * @author Andrés Montañez <[email protected]>
25
 */
26
class Runtime
27
{
28
    const PRE_DEPLOY = 'pre-deploy';
29
    const ON_DEPLOY = 'on-deploy';
30
    const POST_DEPLOY = 'post-deploy';
31
    const ON_RELEASE = 'on-release';
32
    const POST_RELEASE = 'post-release';
33
34
    /**
35
     * @var array Magallanes configuration
36
     */
37
    protected $configuration = [];
38
39
    /**
40
     * @var string|null Environment being deployed
41
     */
42
    protected $environment;
43
44
    /**
45
     * @var string Stage of Deployment
46
     */
47
    protected $stage;
48
49
    /**
50
     * @var string|null The host being deployed to
51
     */
52
    protected $workingHost = null;
53
54
    /**
55
     * @var string|null The Release ID
56
     */
57
    protected $releaseId = null;
58
59
    /**
60
     * @var array Hold a bag of variables for sharing information between tasks, if needed
61
     */
62
    protected $vars = [];
63
64
    /**
65
     * @var LoggerInterface|null The logger instance
66
     */
67
    protected $logger;
68
69
    /**
70
     * @var bool Indicates if a Rollback operation is in progress
71
     */
72
    protected $rollback = false;
73
74
    /**
75
     * Generate the Release ID
76
     *
77
     * @return Runtime
78
     */
79 1
    public function generateReleaseId()
80
    {
81 1
        $this->setReleaseId(date('YmdHis'));
82 1
        return $this;
83
    }
84
85
    /**
86
     * Sets the Release ID
87
     *
88
     * @param string $releaseId Release ID
89
     * @return Runtime
90
     */
91 11
    public function setReleaseId($releaseId)
92
    {
93 11
        $this->releaseId = $releaseId;
94 11
        return $this;
95
    }
96
97
    /**
98
     * Retrieve the current Release ID
99
     *
100
     * @return null|string Release ID
101
     */
102 36
    public function getReleaseId()
103
    {
104 36
        return $this->releaseId;
105
    }
106
107
    /**
108
     * Sets the Runtime in Rollback mode On or Off
109
     *
110
     * @param bool $inRollback
111
     * @return Runtime
112
     */
113 1
    public function setRollback($inRollback)
114
    {
115 1
        $this->rollback = $inRollback;
116 1
        return $this;
117
    }
118
119
    /**
120
     * Indicates if Runtime is in rollback
121
     *
122
     * @return bool
123
     */
124 26
    public function inRollback()
125
    {
126 26
        return $this->rollback;
127
    }
128
129
    /**
130
     * Sets a value in the Vars bag
131
     *
132
     * @param string $key Variable name
133
     * @param string $value Variable value
134
     * @return Runtime
135
     */
136 19
    public function setVar($key, $value)
137
    {
138 19
        $this->vars[$key] = $value;
139 19
        return $this;
140
    }
141
142
    /**
143
     * Retrieve a value from the Vars bag
144
     *
145
     * @param string $key Variable name
146
     * @param mixed $default Variable default value, returned if not found
147
     * @return string
148
     */
149 21
    public function getVar($key, $default = null)
150
    {
151 21
        if (array_key_exists($key, $this->vars)) {
152 19
            return $this->vars[$key];
153
        }
154
155 21
        return $default;
156
    }
157
158
    /**
159
     * Sets the Logger instance
160
     *
161
     * @param LoggerInterface $logger Logger instance
162
     * @return Runtime
163
     */
164 40
    public function setLogger(LoggerInterface $logger = null)
165
    {
166 40
        $this->logger = $logger;
167 40
        return $this;
168
    }
169
170
    /**
171
     * Sets the Magallanes Configuration to the Runtime
172
     *
173
     * @param array $configuration Configuration
174
     * @return Runtime
175
     */
176 73
    public function setConfiguration($configuration)
177
    {
178 73
        $this->configuration = $configuration;
179 73
        return $this;
180
    }
181
182
    /**
183
     * Retrieve the Configuration
184
     *
185
     * @return array
186
     */
187 1
    public function getConfiguration()
188
    {
189 1
        return $this->configuration;
190
    }
191
192
    /**
193
     * Retrieves the Configuration Option for a specific section in the configuration
194
     *
195
     * @param string $key Section name
196
     * @param mixed $default Default value
197
     * @return mixed
198
     */
199 38
    public function getConfigOption($key, $default = null)
200
    {
201 38
        if (array_key_exists($key, $this->configuration)) {
202 33
            return $this->configuration[$key];
203
        }
204
205 20
        return $default;
206
    }
207
208
    /**
209
     * Returns the Configuration Option for a specific section the current Environment
210
     *
211
     * @param string $key Section/Parameter name
212
     * @param mixed $default Default value
213
     * @return mixed
214
     */
215 44
    public function getEnvOption($key, $default = null)
216
    {
217 44 View Code Duplication
        if (!array_key_exists('environments', $this->configuration) || !is_array($this->configuration['environments'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
218 1
            return $default;
219
        }
220
221 43
        if (!array_key_exists($this->environment, $this->configuration['environments'])) {
222 1
            return $default;
223
        }
224
225 42 View Code Duplication
        if (array_key_exists($key, $this->configuration['environments'][$this->environment])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226 34
            return $this->configuration['environments'][$this->environment][$key];
227
        }
228
229 40
        return $default;
230
    }
231
232
    /**
233
     * Shortcut to get the the configuration option for a specific environment and merge it with
234
     * the global one (environment specific overrides the global one if present).
235
     *
236
     * @param       $key
237
     * @param array $defaultEnv
238
     *
239
     * @return array
240
     */
241 22
    public function getMergedOption($key, $defaultEnv = [])
242
    {
243 22
        $userGlobalOptions = $this->getConfigOption($key, $defaultEnv);
244 22
        $userEnvOptions = $this->getEnvOption($key, $defaultEnv);
245
246 22
        return array_merge(
247 22
            (is_array($userGlobalOptions) ? $userGlobalOptions : []),
248 22
            (is_array($userEnvOptions) ? $userEnvOptions : [])
249 22
        );
250
    }
251
252
    /**
253
     * Overwrites an Environment Configuration Option
254
     *
255
     * @param string $key
256
     * @param mixed $value
257
     * @return Runtime
258
     */
259 1
    public function setEnvOption($key, $value)
260
    {
261 1
        if (array_key_exists('environments', $this->configuration) && is_array($this->configuration['environments'])) {
262 1 View Code Duplication
            if (array_key_exists($this->environment, $this->configuration['environments'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263 1
                $this->configuration['environments'][$this->environment][$key] = $value;
264 1
            }
265 1
        }
266
267 1
        return $this;
268
    }
269
270
    /**
271
     * Sets the working Environment
272
     *
273
     * @param string $environment Environment name
274
     * @return Runtime
275
     * @throws RuntimeException
276
     */
277 71
    public function setEnvironment($environment)
278
    {
279 71
        if (array_key_exists('environments', $this->configuration) && array_key_exists($environment, $this->configuration['environments'])) {
280 68
            $this->environment = $environment;
281 68
            return $this;
282
        }
283
284 3
        throw new RuntimeException(sprintf('The environment "%s" does not exists.', $environment), 100);
285
    }
286
287
    /**
288
     * Returns the current working Environment
289
     *
290
     * @return null|string
291
     */
292 53
    public function getEnvironment()
293
    {
294 53
        return $this->environment;
295
    }
296
297
    /**
298
     * Sets the working stage
299
     *
300
     * @param string $stage Stage code
301
     * @return Runtime
302
     */
303 29
    public function setStage($stage)
304
    {
305 29
        $this->stage = $stage;
306 29
        return $this;
307
    }
308
309
    /**
310
     * Retrieve the current working Stage
311
     *
312
     * @return string
313
     */
314 52
    public function getStage()
315
    {
316 52
        return $this->stage;
317
    }
318
319
    /**
320
     * Retrieve the defined Tasks for the current Environment and Stage
321
     *
322
     * @return array
323
     */
324 29
    public function getTasks()
325
    {
326 29 View Code Duplication
        if (!array_key_exists('environments', $this->configuration) || !is_array($this->configuration['environments'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327 1
            return [];
328
        }
329
330 28
        if (!array_key_exists($this->environment, $this->configuration['environments'])) {
331 1
            return [];
332
        }
333
334 27
        if (array_key_exists($this->stage, $this->configuration['environments'][$this->environment])) {
335 26
            if (is_array($this->configuration['environments'][$this->environment][$this->stage])) {
336 26
                return $this->configuration['environments'][$this->environment][$this->stage];
337
            }
338 10
        }
339
340 17
        return [];
341
    }
342
343
    /**
344
     * Sets the working Host
345
     *
346
     * @param string $host Host name
347
     * @return Runtime
348
     */
349 27
    public function setWorkingHost($host)
350
    {
351 27
        $this->workingHost = $host;
352 27
        return $this;
353
    }
354
355
    /**
356
     * Retrieve the working Host
357
     *
358
     * @return null|string
359
     */
360 53
    public function getWorkingHost()
361
    {
362 53
        return $this->workingHost;
363
    }
364
365
    /**
366
     * Logs a Message into the Logger
367
     *
368
     * @param string $message Log message
369
     * @param string $level Log Level
370
     */
371 34
    public function log($message, $level = LogLevel::DEBUG)
372
    {
373 34
        if ($this->logger instanceof LoggerInterface) {
374 33
            $this->logger->log($level, $message);
375 33
        }
376 34
    }
377
378
    /**
379
     * Executes a command, it will be run Locally or Remotely based on the working Stage
380
     *
381
     * @param string $cmd Command to execute
382
     * @param int $timeout Seconds to wait
383
     * @return Process
384
     */
385 40
    public function runCommand($cmd, $timeout = 120)
386
    {
387 40
        switch ($this->getStage()) {
388 40
            case self::ON_DEPLOY:
389 40
            case self::ON_RELEASE:
390 40
            case self::POST_RELEASE:
391 8
                return $this->runRemoteCommand($cmd, true, $timeout);
392 39
            default:
393 39
                return $this->runLocalCommand($cmd, $timeout);
394 39
        }
395
    }
396
397
    /**
398
     * Execute a command locally
399
     *
400
     * @param string $cmd Command to execute
401
     * @param int $timeout Seconds to wait
402
     * @return Process
403
     */
404 1
    public function runLocalCommand($cmd, $timeout = 120)
405
    {
406 1
        $this->log($cmd, LogLevel::INFO);
407
408 1
        $process = new Process($cmd);
409 1
        $process->setTimeout($timeout);
410 1
        $process->run();
411
412 1
        $this->log($process->getOutput(), LogLevel::DEBUG);
413 1
        if (!$process->isSuccessful()) {
414 1
            $this->log($process->getErrorOutput(), LogLevel::ERROR);
415 1
        }
416
417 1
        return $process;
418
    }
419
420
    /**
421
     * Executes a command remotely, if jail is true, it will run inside the Host Path and the Release (if available)
422
     *
423
     * @param string $cmd Command to execute
424
     * @param bool $jail Jail the command
425
     * @param int $timeout Seconds to wait
426
     * @return Process
427
     */
428 18
    public function runRemoteCommand($cmd, $jail, $timeout = 120)
429
    {
430 18
        $user = $this->getEnvOption('user', $this->getCurrentUser());
431 18
        $sudo = $this->getEnvOption('sudo', false);
432 18
        $host = $this->getWorkingHost();
433 18
        $sshConfig = $this->getSSHConfig();
434
435 18
        $cmdDelegate = $cmd;
436 18
        if ($sudo === true) {
437 1
            $cmdDelegate = sprintf('sudo %s', $cmd);
438 1
        }
439
440 18
        $hostPath = rtrim($this->getEnvOption('host_path'), '/');
441 18
        if ($jail && $this->getReleaseId() !== null) {
442 3
            $cmdDelegate = sprintf('cd %s/releases/%s && %s', $hostPath, $this->getReleaseId(), $cmdDelegate);
443 18
        } elseif ($jail) {
444 5
            $cmdDelegate = sprintf('cd %s && %s', $hostPath, $cmdDelegate);
445 5
        }
446
447 18
        $cmdRemote = str_replace('"', '\"', $cmdDelegate);
448 18
        $cmdLocal = sprintf('ssh -p %d %s %s@%s "%s"', $sshConfig['port'], $sshConfig['flags'], $user, $host, $cmdRemote);
449
450 18
        return $this->runLocalCommand($cmdLocal, $timeout);
451
    }
452
453
    /**
454
     * Get the SSH configuration based on the environment
455
     *
456
     * @return array
457
     */
458 29
    public function getSSHConfig()
459
    {
460 29
        $sshConfig = $this->getEnvOption('ssh', ['port' => '22', 'flags' => '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no']);
461
462 29
        if (!array_key_exists('port', $sshConfig)) {
463 1
            $sshConfig['port'] = '22';
464 1
        }
465
466 29
        if (!array_key_exists('flags', $sshConfig)) {
467 1
            $sshConfig['flags'] = '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no';
468 1
        }
469
470 29
        return $sshConfig;
471
    }
472
473
    /**
474
     * Gets a Temporal File name
475
     *
476
     * @return string
477
     */
478 1
    public function getTempFile()
479
    {
480 1
        return tempnam(sys_get_temp_dir(), 'mage');
481
    }
482
483
    /**
484
     * Get the current user
485
     *
486
     * @return string
487
     */
488 27
    public function getCurrentUser()
489
    {
490 27
        $userData = posix_getpwuid(posix_geteuid());
491 27
        return $userData['name'];
492
    }
493
494
    /**
495
     * Shortcut for getting Branch information
496
     *
497
     * @return boolean|string
498
     */
499 27
    public function getBranch()
500
    {
501 27
        return $this->getEnvOption('branch', false);
502
    }
503
504
    /**
505
     * Guesses the Deploy Strategy to use
506
     *
507
     * @return StrategyInterface
508
     */
509 29
    public function guessStrategy()
510
    {
511 29
        $strategy = new RsyncStrategy();
512
513 29
        if ($this->getEnvOption('releases', false)) {
514 10
            $strategy = new ReleasesStrategy();
515 10
        }
516
517 29
        $strategy->setRuntime($this);
518 29
        return $strategy;
519
    }
520
}
521