Test Failed
Pull Request — master (#448)
by
unknown
05:18
created

Runtime   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 525
Duplicated Lines 2.29 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 65
lcom 2
cbo 5
dl 12
loc 525
ccs 0
cts 230
cp 0
rs 3.2
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getConfigOption() 0 8 2
A getEnvOption() 6 16 5
A getMergedOption() 0 10 3
A setEnvOption() 3 10 4
A getHostPort() 0 5 2
A getHostName() 0 9 2
A getBranch() 0 4 1
A guessStrategy() 0 11 2
A generateReleaseId() 0 5 1
A setReleaseId() 0 5 1
A getReleaseId() 0 4 1
A setRollback() 0 5 1
A inRollback() 0 4 1
A setVar() 0 5 1
A getVar() 0 8 2
A setLogger() 0 5 1
A setConfiguration() 0 5 1
A getConfiguration() 0 4 1
A setEnvironment() 0 9 3
A getEnvironment() 0 4 1
A setStage() 0 5 1
A getStage() 0 4 1
A getTasks() 3 18 6
A setWorkingHost() 0 5 1
A getWorkingHost() 0 4 1
A log() 0 6 2
A runCommand() 0 11 4
A runLocalCommand() 0 15 2
A runRemoteCommand() 0 24 5
A getSSHConfig() 0 18 4
A getTempFile() 0 4 1
A getCurrentUser() 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Runtime often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Runtime, and based on these observations, apply Extract Interface, too.

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
    public function generateReleaseId()
80
    {
81
        $this->setReleaseId(date('YmdHis'));
82
        return $this;
83
    }
84
85
    /**
86
     * Sets the Release ID
87
     *
88
     * @param string $releaseId Release ID
89
     * @return Runtime
90
     */
91
    public function setReleaseId($releaseId)
92
    {
93
        $this->releaseId = $releaseId;
94
        return $this;
95
    }
96
97
    /**
98
     * Retrieve the current Release ID
99
     *
100
     * @return null|string Release ID
101
     */
102
    public function getReleaseId()
103
    {
104
        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
    public function setRollback($inRollback)
114
    {
115
        $this->rollback = $inRollback;
116
        return $this;
117
    }
118
119
    /**
120
     * Indicates if Runtime is in rollback
121
     *
122
     * @return bool
123
     */
124
    public function inRollback()
125
    {
126
        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
    public function setVar($key, $value)
137
    {
138
        $this->vars[$key] = $value;
139
        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
    public function getVar($key, $default = null)
150
    {
151
        if (array_key_exists($key, $this->vars)) {
152
            return $this->vars[$key];
153
        }
154
155
        return $default;
156
    }
157
158
    /**
159
     * Sets the Logger instance
160
     *
161
     * @param LoggerInterface $logger Logger instance
162
     * @return Runtime
163
     */
164
    public function setLogger(LoggerInterface $logger = null)
165
    {
166
        $this->logger = $logger;
167
        return $this;
168
    }
169
170
    /**
171
     * Sets the Magallanes Configuration to the Runtime
172
     *
173
     * @param array $configuration Configuration
174
     * @return Runtime
175
     */
176
    public function setConfiguration($configuration)
177
    {
178
        $this->configuration = $configuration;
179
        return $this;
180
    }
181
182
    /**
183
     * Retrieve the Configuration
184
     *
185
     * @return array
186
     */
187
    public function getConfiguration()
188
    {
189
        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
    public function getConfigOption($key, $default = null)
200
    {
201
        if (array_key_exists($key, $this->configuration)) {
202
            return $this->configuration[$key];
203
        }
204
205
        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
    public function getEnvOption($key, $default = null)
216
    {
217 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
            return $default;
219
        }
220
221
        if (!array_key_exists($this->environment, $this->configuration['environments'])) {
222
            return $default;
223
        }
224
225 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
            return $this->configuration['environments'][$this->environment][$key];
227
        }
228
229
        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
    public function getMergedOption($key, $defaultEnv = [])
242
    {
243
        $userGlobalOptions = $this->getConfigOption($key, $defaultEnv);
244
        $userEnvOptions = $this->getEnvOption($key, $defaultEnv);
245
246
        return array_merge(
247
            (is_array($userGlobalOptions) ? $userGlobalOptions : []),
248
            (is_array($userEnvOptions) ? $userEnvOptions : [])
249
        );
250
    }
251
252
    /**
253
     * Overwrites an Environment Configuration Option
254
     *
255
     * @param string $key
256
     * @param mixed $value
257
     * @return Runtime
258
     */
259
    public function setEnvOption($key, $value)
260
    {
261
        if (array_key_exists('environments', $this->configuration) && is_array($this->configuration['environments'])) {
262 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
                $this->configuration['environments'][$this->environment][$key] = $value;
264
            }
265
        }
266
267
        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
    public function setEnvironment($environment)
278
    {
279
        if (array_key_exists('environments', $this->configuration) && array_key_exists($environment, $this->configuration['environments'])) {
280
            $this->environment = $environment;
281
            return $this;
282
        }
283
284
        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
    public function getEnvironment()
293
    {
294
        return $this->environment;
295
    }
296
297
    /**
298
     * Sets the working stage
299
     *
300
     * @param string $stage Stage code
301
     * @return Runtime
302
     */
303
    public function setStage($stage)
304
    {
305
        $this->stage = $stage;
306
        return $this;
307
    }
308
309
    /**
310
     * Retrieve the current working Stage
311
     *
312
     * @return string
313
     */
314
    public function getStage()
315
    {
316
        return $this->stage;
317
    }
318
319
    /**
320
     * Retrieve the defined Tasks for the current Environment and Stage
321
     *
322
     * @return array
323
     */
324
    public function getTasks()
325
    {
326 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
            return [];
328
        }
329
330
        if (!array_key_exists($this->environment, $this->configuration['environments'])) {
331
            return [];
332
        }
333
334
        if (array_key_exists($this->stage, $this->configuration['environments'][$this->environment])) {
335
            if (is_array($this->configuration['environments'][$this->environment][$this->stage])) {
336
                return $this->configuration['environments'][$this->environment][$this->stage];
337
            }
338
        }
339
340
        return [];
341
    }
342
343
    /**
344
     * Sets the working Host
345
     *
346
     * @param string $host Host name
347
     * @return Runtime
348
     */
349
    public function setWorkingHost($host)
350
    {
351
        $this->workingHost = $host;
352
        return $this;
353
    }
354
355
    /**
356
     * Retrieve the working Host
357
     *
358
     * @return null|string
359
     */
360
    public function getWorkingHost()
361
    {
362
        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
    public function log($message, $level = LogLevel::DEBUG)
372
    {
373
        if ($this->logger instanceof LoggerInterface) {
374
            $this->logger->log($level, $message);
375
        }
376
    }
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
    public function runCommand($cmd, $timeout = 120)
386
    {
387
        switch ($this->getStage()) {
388
            case self::ON_DEPLOY:
389
            case self::ON_RELEASE:
390
            case self::POST_RELEASE:
391
                return $this->runRemoteCommand($cmd, true, $timeout);
392
            default:
393
                return $this->runLocalCommand($cmd, $timeout);
394
        }
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
    public function runLocalCommand($cmd, $timeout = 120)
405
    {
406
        $this->log($cmd, LogLevel::INFO);
407
408
        $process = Process::fromShellCommandline($cmd);
409
        $process->setTimeout($timeout);
410
        $process->run();
411
412
        $this->log($process->getOutput(), LogLevel::DEBUG);
413
        if (!$process->isSuccessful()) {
414
            $this->log($process->getErrorOutput(), LogLevel::ERROR);
415
        }
416
417
        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
    public function runRemoteCommand($cmd, $jail, $timeout = 120)
429
    {
430
        $user = $this->getEnvOption('user', $this->getCurrentUser());
431
        $sudo = $this->getEnvOption('sudo', false);
432
        $host = $this->getHostName();
433
        $sshConfig = $this->getSSHConfig();
434
435
        $cmdDelegate = $cmd;
436
        if ($sudo === true) {
437
            $cmdDelegate = sprintf('sudo %s', $cmd);
438
        }
439
440
        $hostPath = rtrim($this->getEnvOption('host_path'), '/');
441
        if ($jail && $this->getReleaseId() !== null) {
442
            $cmdDelegate = sprintf('cd %s/releases/%s && %s', $hostPath, $this->getReleaseId(), $cmdDelegate);
443
        } elseif ($jail) {
444
            $cmdDelegate = sprintf('cd %s && %s', $hostPath, $cmdDelegate);
445
        }
446
447
        $cmdRemote = str_replace('"', '\"', $cmdDelegate);
448
        $cmdLocal = sprintf('ssh -p %d %s %s@%s "%s"', $sshConfig['port'], $sshConfig['flags'], $user, $host, $cmdRemote);
449
450
        return $this->runLocalCommand($cmdLocal, $timeout);
451
    }
452
453
    /**
454
     * Get the SSH configuration based on the environment
455
     *
456
     * @return array
457
     */
458
    public function getSSHConfig()
459
    {
460
        $sshConfig = $this->getEnvOption('ssh', ['port' => 22, 'flags' => '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no']);
461
462
        if ($this->getHostPort() !== null) {
463
            $sshConfig['port'] = $this->getHostPort();
464
        }
465
466
        if (!array_key_exists('port', $sshConfig)) {
467
            $sshConfig['port'] = '22';
468
        }
469
470
        if (!array_key_exists('flags', $sshConfig)) {
471
            $sshConfig['flags'] = '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no';
472
        }
473
474
        return $sshConfig;
475
    }
476
477
    /**
478
     * Get the current Host Port or default ssh port
479
     *
480
     * @return integer
481
     */
482
    public function getHostPort()
483
    {
484
        $info = explode(':', $this->getWorkingHost());
485
        return isset($info[1]) ? $info[1] : null;
486
    }
487
488
    /**
489
     * Get the current Host Name
490
     *
491
     * @return string
492
     */
493
    public function getHostName()
494
    {
495
        if (strpos($this->getWorkingHost(), ':') === false) {
496
            return $this->getWorkingHost();
497
        }
498
499
        $info = explode(':', $this->getWorkingHost());
500
        return $info[0];
501
    }
502
503
    /**
504
     * Gets a Temporal File name
505
     *
506
     * @return string
507
     */
508
    public function getTempFile()
509
    {
510
        return tempnam(sys_get_temp_dir(), 'mage');
511
    }
512
513
    /**
514
     * Get the current user
515
     *
516
     * @return string
517
     */
518
    public function getCurrentUser()
519
    {
520
        $userData = posix_getpwuid(posix_geteuid());
521
        return $userData['name'];
522
    }
523
524
    /**
525
     * Shortcut for getting Branch information
526
     *
527
     * @return boolean|string
528
     */
529
    public function getBranch()
530
    {
531
        return $this->getEnvOption('branch', false);
532
    }
533
534
    /**
535
     * Guesses the Deploy Strategy to use
536
     *
537
     * @return StrategyInterface
538
     */
539
    public function guessStrategy()
540
    {
541
        $strategy = new RsyncStrategy();
542
543
        if ($this->getEnvOption('releases', false)) {
544
            $strategy = new ReleasesStrategy();
545
        }
546
547
        $strategy->setRuntime($this);
548
        return $strategy;
549
    }
550
}
551