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])) { |
|
|
|
|
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'])) { |
|
|
|
|
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
|
|
|
|
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.