Passed
Push — master ( 8db7a0...27923f )
by Andrés
50s
created

Runtime::getHostName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.6666
cc 2
eloc 5
nc 2
nop 0
crap 2
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 13
    public function setReleaseId($releaseId)
92
    {
93 13
        $this->releaseId = $releaseId;
94 13
        return $this;
95
    }
96
97
    /**
98
     * Retrieve the current Release ID
99
     *
100
     * @return null|string Release ID
101
     */
102 41
    public function getReleaseId()
103
    {
104 41
        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 32
    public function inRollback()
125
    {
126 32
        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 23
    public function setVar($key, $value)
137
    {
138 23
        $this->vars[$key] = $value;
139 23
        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 25
    public function getVar($key, $default = null)
150
    {
151 25
        if (array_key_exists($key, $this->vars)) {
152 23
            return $this->vars[$key];
153
        }
154
155 25
        return $default;
156
    }
157
158
    /**
159
     * Sets the Logger instance
160
     *
161
     * @param LoggerInterface $logger Logger instance
162
     * @return Runtime
163
     */
164 49
    public function setLogger(LoggerInterface $logger = null)
165
    {
166 49
        $this->logger = $logger;
167 49
        return $this;
168
    }
169
170
    /**
171
     * Sets the Magallanes Configuration to the Runtime
172
     *
173
     * @param array $configuration Configuration
174
     * @return Runtime
175
     */
176 85
    public function setConfiguration($configuration)
177
    {
178 85
        $this->configuration = $configuration;
179 85
        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 54
    public function getConfigOption($key, $default = null)
200
    {
201 54
        if (array_key_exists($key, $this->configuration)) {
202 42
            return $this->configuration[$key];
203
        }
204
205 45
        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 57
    public function getEnvOption($key, $default = null)
216
    {
217 57
        if (!array_key_exists('environments', $this->configuration) || !is_array($this->configuration['environments'])) {
218 2
            return $default;
219
        }
220
221 55
        if (!array_key_exists($this->environment, $this->configuration['environments'])) {
222 1
            return $default;
223
        }
224
225 54 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 40
            return $this->configuration['environments'][$this->environment][$key];
227
        }
228
229 52
        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 35
    public function getMergedOption($key, $defaultEnv = [])
242
    {
243 35
        $userGlobalOptions = $this->getConfigOption($key, $defaultEnv);
244 35
        $userEnvOptions = $this->getEnvOption($key, $defaultEnv);
245
246 35
        return array_merge(
247 35
            (is_array($userGlobalOptions) ? $userGlobalOptions : []),
248 35
            (is_array($userEnvOptions) ? $userEnvOptions : [])
249 35
        );
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 83
    public function setEnvironment($environment)
278
    {
279 83
        if (array_key_exists('environments', $this->configuration) && array_key_exists($environment, $this->configuration['environments'])) {
280 80
            $this->environment = $environment;
281 80
            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 59
    public function getEnvironment()
293
    {
294 59
        return $this->environment;
295
    }
296
297
    /**
298
     * Sets the working stage
299
     *
300
     * @param string $stage Stage code
301
     * @return Runtime
302
     */
303 35
    public function setStage($stage)
304
    {
305 35
        $this->stage = $stage;
306 35
        return $this;
307
    }
308
309
    /**
310
     * Retrieve the current working Stage
311
     *
312
     * @return string
313
     */
314 61
    public function getStage()
315
    {
316 61
        return $this->stage;
317
    }
318
319
    /**
320
     * Retrieve the defined Tasks for the current Environment and Stage
321
     *
322
     * @return array
323
     */
324 35
    public function getTasks()
325
    {
326 35
        if (!array_key_exists('environments', $this->configuration) || !is_array($this->configuration['environments'])) {
327 1
            return [];
328
        }
329
330 34
        if (!array_key_exists($this->environment, $this->configuration['environments'])) {
331 1
            return [];
332
        }
333
334 33
        if (array_key_exists($this->stage, $this->configuration['environments'][$this->environment])) {
335 32
            if (is_array($this->configuration['environments'][$this->environment][$this->stage])) {
336 32
                return $this->configuration['environments'][$this->environment][$this->stage];
337
            }
338 15
        }
339
340 23
        return [];
341
    }
342
343
    /**
344
     * Sets the working Host
345
     *
346
     * @param string $host Host name
347
     * @return Runtime
348
     */
349 34
    public function setWorkingHost($host)
350
    {
351 34
        $this->workingHost = $host;
352 34
        return $this;
353
    }
354
355
    /**
356
     * Retrieve the working Host
357
     *
358
     * @return null|string
359
     */
360 63
    public function getWorkingHost()
361
    {
362 63
        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 40
    public function log($message, $level = LogLevel::DEBUG)
372
    {
373 40
        if ($this->logger instanceof LoggerInterface) {
374 38
            $this->logger->log($level, $message);
375 38
        }
376 40
    }
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 49
    public function runCommand($cmd, $timeout = 120)
386
    {
387 49
        switch ($this->getStage()) {
388 49
            case self::ON_DEPLOY:
389 49
            case self::ON_RELEASE:
390 49
            case self::POST_RELEASE:
391 13
                return $this->runRemoteCommand($cmd, true, $timeout);
392 47
            default:
393 47
                return $this->runLocalCommand($cmd, $timeout);
394 47
        }
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 23
    public function runRemoteCommand($cmd, $jail, $timeout = 120)
429
    {
430 23
        $user = $this->getEnvOption('user', $this->getCurrentUser());
431 23
        $sudo = $this->getEnvOption('sudo', false);
432 23
        $host = $this->getHostName();
433 23
        $sshConfig = $this->getSSHConfig();
434
435 23
        $cmdDelegate = $cmd;
436 23
        if ($sudo === true) {
437 1
            $cmdDelegate = sprintf('sudo %s', $cmd);
438 1
        }
439
440 23
        $hostPath = rtrim($this->getEnvOption('host_path'), '/');
441 23
        if ($jail && $this->getReleaseId() !== null) {
442 5
            $cmdDelegate = sprintf('cd %s/releases/%s && %s', $hostPath, $this->getReleaseId(), $cmdDelegate);
443 23
        } elseif ($jail) {
444 8
            $cmdDelegate = sprintf('cd %s && %s', $hostPath, $cmdDelegate);
445 8
        }
446
447 23
        $cmdRemote = str_replace('"', '\"', $cmdDelegate);
448 23
        $cmdLocal = sprintf('ssh -p %d %s %s@%s "%s"', $sshConfig['port'], $sshConfig['flags'], $user, $host, $cmdRemote);
449
450 23
        return $this->runLocalCommand($cmdLocal, $timeout);
451
    }
452
453
    /**
454
     * Get the SSH configuration based on the environment
455
     *
456
     * @return array
457
     */
458 36
    public function getSSHConfig()
459
    {
460 36
        $sshConfig = $this->getEnvOption('ssh', ['port' => 22, 'flags' => '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no']);
461
462 36
        if ($this->getHostPort() !== null) {
463 3
            $sshConfig['port'] = $this->getHostPort();
464 3
        }
465
466 36
        if (!array_key_exists('port', $sshConfig)) {
467 1
            $sshConfig['port'] = '22';
468 1
        }
469
470 36
        if (!array_key_exists('flags', $sshConfig)) {
471 1
            $sshConfig['flags'] = '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no';
472 1
        }
473
474 36
        return $sshConfig;
475
    }
476
477
    /**
478
     * Get the current Host Port or default ssh port
479
     *
480
     * @return integer
481
     */
482 36
    public function getHostPort()
483
    {
484 36
        $info = explode(':', $this->getWorkingHost());
485 36
        return isset($info[1]) ? $info[1] : null;
486
    }
487
488
    /**
489
     * Get the current Host Name
490
     *
491
     * @return string
492
     */
493 59
    public function getHostName()
494
    {
495 59
        if (strpos($this->getWorkingHost(), ':') === false) {
496 59
            return $this->getWorkingHost();
497
        }
498
499 2
        $info = explode(':', $this->getWorkingHost());
500 2
        return $info[0];
501
    }
502
503
    /**
504
     * Gets a Temporal File name
505
     *
506
     * @return string
507
     */
508 1
    public function getTempFile()
509
    {
510 1
        return tempnam(sys_get_temp_dir(), 'mage');
511
    }
512
513
    /**
514
     * Get the current user
515
     *
516
     * @return string
517
     */
518 33
    public function getCurrentUser()
519
    {
520 33
        $userData = posix_getpwuid(posix_geteuid());
521 33
        return $userData['name'];
522
    }
523
524
    /**
525
     * Shortcut for getting Branch information
526
     *
527
     * @return boolean|string
528
     */
529 33
    public function getBranch()
530
    {
531 33
        return $this->getEnvOption('branch', false);
532
    }
533
534
    /**
535
     * Guesses the Deploy Strategy to use
536
     *
537
     * @return StrategyInterface
538
     */
539 38
    public function guessStrategy()
540
    {
541 38
        $strategy = new RsyncStrategy();
542
543 38
        if ($this->getEnvOption('releases', false)) {
544 12
            $strategy = new ReleasesStrategy();
545 12
        }
546
547 38
        $strategy->setRuntime($this);
548 38
        return $strategy;
549
    }
550
}
551