Passed
Push — master ( b7ade4...e7afcb )
by Andrés
01:01 queued 12s
created

Runtime::isWindows()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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 12
    public function isWindows()
75
    {
76 12
        return stripos(PHP_OS, 'WIN') === 0;
77
    }
78
79
    /**
80
     * Generate the Release ID
81
     *
82
     * @return Runtime
83
     */
84 1
    public function generateReleaseId()
85
    {
86 1
        $this->setReleaseId(date('YmdHis'));
87 1
        return $this;
88
    }
89
90
    /**
91
     * Sets the Release ID
92
     *
93
     * @param string $releaseId Release ID
94
     * @return Runtime
95
     */
96 16
    public function setReleaseId($releaseId)
97
    {
98 16
        $this->releaseId = $releaseId;
99 16
        return $this;
100
    }
101
102
    /**
103
     * Retrieve the current Release ID
104
     *
105
     * @return null|string Release ID
106
     */
107 44
    public function getReleaseId()
108
    {
109 44
        return $this->releaseId;
110
    }
111
112
    /**
113
     * Sets the Runtime in Rollback mode On or Off
114
     *
115
     * @param bool $inRollback
116
     * @return Runtime
117
     */
118 1
    public function setRollback($inRollback)
119
    {
120 1
        $this->rollback = $inRollback;
121 1
        return $this;
122
    }
123
124
    /**
125
     * Indicates if Runtime is in rollback
126
     *
127
     * @return bool
128
     */
129 35
    public function inRollback()
130
    {
131 35
        return $this->rollback;
132
    }
133
134
    /**
135
     * Sets a value in the Vars bag
136
     *
137
     * @param string $key Variable name
138
     * @param string $value Variable value
139
     * @return Runtime
140
     */
141 26
    public function setVar($key, $value)
142
    {
143 26
        $this->vars[$key] = $value;
144 26
        return $this;
145
    }
146
147
    /**
148
     * Retrieve a value from the Vars bag
149
     *
150
     * @param string $key Variable name
151
     * @param mixed $default Variable default value, returned if not found
152
     * @return string
153
     */
154 28
    public function getVar($key, $default = null)
155
    {
156 28
        if (array_key_exists($key, $this->vars)) {
157 26
            return $this->vars[$key];
158
        }
159
160 28
        return $default;
161
    }
162
163
    /**
164
     * Sets the Logger instance
165
     *
166
     * @param LoggerInterface $logger Logger instance
167
     * @return Runtime
168
     */
169 52
    public function setLogger(LoggerInterface $logger = null)
170
    {
171 52
        $this->logger = $logger;
172 52
        return $this;
173
    }
174
175
    /**
176
     * Sets the Magallanes Configuration to the Runtime
177
     *
178
     * @param array $configuration Configuration
179
     * @return Runtime
180
     */
181 87
    public function setConfiguration($configuration)
182
    {
183 87
        $this->configuration = $configuration;
184 87
        return $this;
185
    }
186
187
    /**
188
     * Retrieve the Configuration
189
     *
190
     * @return array
191
     */
192 1
    public function getConfiguration()
193
    {
194 1
        return $this->configuration;
195
    }
196
197
    /**
198
     * Retrieves the Configuration Option for a specific section in the configuration
199
     *
200
     * @param string $key Section name
201
     * @param mixed $default Default value
202
     * @return mixed
203
     */
204 56
    public function getConfigOption($key, $default = null)
205
    {
206 56
        if (array_key_exists($key, $this->configuration)) {
207 45
            return $this->configuration[$key];
208
        }
209
210 47
        return $default;
211
    }
212
213
    /**
214
     * Returns the Configuration Option for a specific section the current Environment
215
     *
216
     * @param string $key Section/Parameter name
217
     * @param mixed $default Default value
218
     * @return mixed
219
     */
220 59
    public function getEnvOption($key, $default = null)
221
    {
222 59
        if (!array_key_exists('environments', $this->configuration) || !is_array($this->configuration['environments'])) {
223 2
            return $default;
224
        }
225
226 57
        if (!array_key_exists($this->environment, $this->configuration['environments'])) {
227 1
            return $default;
228
        }
229
230 56 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...
231 43
            return $this->configuration['environments'][$this->environment][$key];
232
        }
233
234 54
        return $default;
235
    }
236
237
    /**
238
     * Shortcut to get the the configuration option for a specific environment and merge it with
239
     * the global one (environment specific overrides the global one if present).
240
     *
241
     * @param       $key
242
     * @param array $defaultEnv
243
     *
244
     * @return array
245
     */
246 37
    public function getMergedOption($key, $defaultEnv = [])
247
    {
248 37
        $userGlobalOptions = $this->getConfigOption($key, $defaultEnv);
249 37
        $userEnvOptions = $this->getEnvOption($key, $defaultEnv);
250
251 37
        return array_merge(
252 37
            (is_array($userGlobalOptions) ? $userGlobalOptions : []),
253 37
            (is_array($userEnvOptions) ? $userEnvOptions : [])
254
        );
255
    }
256
257
    /**
258
     * Overwrites an Environment Configuration Option
259
     *
260
     * @param string $key
261
     * @param mixed $value
262
     * @return Runtime
263
     */
264 1
    public function setEnvOption($key, $value)
265
    {
266 1
        if (array_key_exists('environments', $this->configuration) && is_array($this->configuration['environments'])) {
267 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...
268 1
                $this->configuration['environments'][$this->environment][$key] = $value;
269
            }
270
        }
271
272 1
        return $this;
273
    }
274
275
    /**
276
     * Sets the working Environment
277
     *
278
     * @param string $environment Environment name
279
     * @return Runtime
280
     * @throws RuntimeException
281
     */
282 85
    public function setEnvironment($environment)
283
    {
284 85
        if (array_key_exists('environments', $this->configuration) && array_key_exists($environment, $this->configuration['environments'])) {
285 82
            $this->environment = $environment;
286 82
            return $this;
287
        }
288
289 3
        throw new RuntimeException(sprintf('The environment "%s" does not exists.', $environment), 100);
290
    }
291
292
    /**
293
     * Returns the current working Environment
294
     *
295
     * @return null|string
296
     */
297 62
    public function getEnvironment()
298
    {
299 62
        return $this->environment;
300
    }
301
302
    /**
303
     * Sets the working stage
304
     *
305
     * @param string $stage Stage code
306
     * @return Runtime
307
     */
308 38
    public function setStage($stage)
309
    {
310 38
        $this->stage = $stage;
311 38
        return $this;
312
    }
313
314
    /**
315
     * Retrieve the current working Stage
316
     *
317
     * @return string
318
     */
319 62
    public function getStage()
320
    {
321 62
        return $this->stage;
322
    }
323
324
    /**
325
     * Retrieve the defined Tasks for the current Environment and Stage
326
     *
327
     * @return array
328
     */
329 38
    public function getTasks()
330
    {
331 38
        if (!array_key_exists('environments', $this->configuration) || !is_array($this->configuration['environments'])) {
332 1
            return [];
333
        }
334
335 37
        if (!array_key_exists($this->environment, $this->configuration['environments'])) {
336 1
            return [];
337
        }
338
339 36
        if (array_key_exists($this->stage, $this->configuration['environments'][$this->environment])) {
340 35
            if (is_array($this->configuration['environments'][$this->environment][$this->stage])) {
341 35
                return $this->configuration['environments'][$this->environment][$this->stage];
342
            }
343
        }
344
345 26
        return [];
346
    }
347
348
    /**
349
     * Sets the working Host
350
     *
351
     * @param string $host Host name
352
     * @return Runtime
353
     */
354 37
    public function setWorkingHost($host)
355
    {
356 37
        $this->workingHost = $host;
357 37
        return $this;
358
    }
359
360
    /**
361
     * Retrieve the working Host
362
     *
363
     * @return null|string
364
     */
365 66
    public function getWorkingHost()
366
    {
367 66
        return $this->workingHost;
368
    }
369
370
    /**
371
     * Logs a Message into the Logger
372
     *
373
     * @param string $message Log message
374
     * @param string $level Log Level
375
     */
376 43
    public function log($message, $level = LogLevel::DEBUG)
377
    {
378 43
        if ($this->logger instanceof LoggerInterface) {
379 41
            $this->logger->log($level, $message);
380
        }
381 43
    }
382
383
    /**
384
     * Executes a command, it will be run Locally or Remotely based on the working Stage
385
     *
386
     * @param string $cmd Command to execute
387
     * @param int $timeout Seconds to wait
388
     * @return Process
389
     */
390 50
    public function runCommand($cmd, $timeout = 120)
391
    {
392 50
        switch ($this->getStage()) {
393 50
            case self::ON_DEPLOY:
394 48
            case self::ON_RELEASE:
395 48
            case self::POST_RELEASE:
396 16
                return $this->runRemoteCommand($cmd, true, $timeout);
397
            default:
398 48
                return $this->runLocalCommand($cmd, $timeout);
399
        }
400
    }
401
402
    /**
403
     * Execute a command locally
404
     *
405
     * @param string $cmd Command to execute
406
     * @param int $timeout Seconds to wait
407
     * @return Process
408
     */
409 1
    public function runLocalCommand($cmd, $timeout = 120)
410
    {
411 1
        $this->log($cmd, LogLevel::INFO);
412
413 1
        $process = Process::fromShellCommandline($cmd);
414 1
        $process->setTimeout($timeout);
415 1
        $process->run();
416
417 1
        $this->log($process->getOutput(), LogLevel::DEBUG);
418 1
        if (!$process->isSuccessful()) {
419 1
            $this->log($process->getErrorOutput(), LogLevel::ERROR);
420
        }
421
422 1
        return $process;
423
    }
424
425
    /**
426
     * Executes a command remotely, if jail is true, it will run inside the Host Path and the Release (if available)
427
     *
428
     * @param string $cmd Command to execute
429
     * @param bool $jail Jail the command
430
     * @param int $timeout Seconds to wait
431
     * @return Process
432
     */
433 26
    public function runRemoteCommand($cmd, $jail, $timeout = 120)
434
    {
435 26
        $user = $this->getEnvOption('user', $this->getCurrentUser());
436 26
        $sudo = $this->getEnvOption('sudo', false);
437 26
        $host = $this->getHostName();
438 26
        $sshConfig = $this->getSSHConfig();
439
440 26
        $cmdDelegate = $cmd;
441 26
        if ($sudo === true) {
442 1
            $cmdDelegate = sprintf('sudo %s', $cmd);
443
        }
444
445 26
        $hostPath = rtrim($this->getEnvOption('host_path'), '/');
446 26
        if ($jail && $this->getReleaseId() !== null) {
447 8
            $cmdDelegate = sprintf('cd %s/releases/%s && %s', $hostPath, $this->getReleaseId(), $cmdDelegate);
448 26
        } elseif ($jail) {
449 8
            $cmdDelegate = sprintf('cd %s && %s', $hostPath, $cmdDelegate);
450
        }
451
452 26
        $cmdRemote = str_replace('"', '\"', $cmdDelegate);
453 26
        $cmdLocal = sprintf('ssh -p %d %s %s@%s "%s"', $sshConfig['port'], $sshConfig['flags'], $user, $host, $cmdRemote);
454
455 26
        return $this->runLocalCommand($cmdLocal, $timeout);
456
    }
457
458
    /**
459
     * Get the SSH configuration based on the environment
460
     *
461
     * @return array
462
     */
463 39
    public function getSSHConfig()
464
    {
465 39
        $sshConfig = $this->getEnvOption('ssh', ['port' => 22, 'flags' => '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no']);
466
467 39
        if ($this->getHostPort() !== null) {
468 3
            $sshConfig['port'] = $this->getHostPort();
469
        }
470
471 39
        if (!array_key_exists('port', $sshConfig)) {
472 1
            $sshConfig['port'] = '22';
473
        }
474
475 39
        if (!array_key_exists('flags', $sshConfig)) {
476 2
            $sshConfig['flags'] = '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no';
477
        }
478
479 39
        if (!array_key_exists('timeout', $sshConfig)) {
480 38
            $sshConfig['timeout'] = 300;
481
        }
482
483 39
        return $sshConfig;
484
    }
485
486
    /**
487
     * Get the current Host Port or default ssh port
488
     *
489
     * @return integer
490
     */
491 39
    public function getHostPort()
492
    {
493 39
        $info = explode(':', $this->getWorkingHost());
494 39
        return isset($info[1]) ? $info[1] : null;
495
    }
496
497
    /**
498
     * Get the current Host Name
499
     *
500
     * @return string
501
     */
502 62
    public function getHostName()
503
    {
504 62
        if (strpos($this->getWorkingHost(), ':') === false) {
505 62
            return $this->getWorkingHost();
506
        }
507
508 2
        $info = explode(':', $this->getWorkingHost());
509 2
        return $info[0];
510
    }
511
512
    /**
513
     * Gets a Temporal File name
514
     *
515
     * @return string
516
     */
517 1
    public function getTempFile()
518
    {
519 1
        return tempnam(sys_get_temp_dir(), 'mage');
520
    }
521
522
    /**
523
     * Get the current user
524
     *
525
     * @return string
526
     */
527 36
    public function getCurrentUser()
528
    {
529 36
        $userData = posix_getpwuid(posix_geteuid());
530 36
        return $userData['name'];
531
    }
532
533
    /**
534
     * Shortcut for getting Branch information
535
     *
536
     * @return boolean|string
537
     */
538 36
    public function getBranch()
539
    {
540 36
        return $this->getEnvOption('branch', false);
541
    }
542
543
    /**
544
     * Guesses the Deploy Strategy to use
545
     *
546
     * @return StrategyInterface
547
     */
548 41
    public function guessStrategy()
549
    {
550 41
        $strategy = new RsyncStrategy();
551
552 41
        if ($this->getEnvOption('releases', false)) {
553 15
            $strategy = new ReleasesStrategy();
554
        }
555
556 41
        $strategy->setRuntime($this);
557 41
        return $strategy;
558
    }
559
}
560