1
|
|
|
<?php |
2
|
|
|
/* (c) Anton Medvedev <[email protected]> |
3
|
|
|
* |
4
|
|
|
* For the full copyright and license information, please view the LICENSE |
5
|
|
|
* file that was distributed with this source code. |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Deployer; |
9
|
|
|
|
10
|
|
|
use Deployer\Exception\Exception; |
11
|
|
|
use Deployer\Exception\GracefulShutdownException; |
12
|
|
|
use Deployer\Exception\RunException; |
13
|
|
|
use Deployer\Host\FileLoader; |
14
|
|
|
use Deployer\Host\Host; |
15
|
|
|
use Deployer\Host\Localhost; |
16
|
|
|
use Deployer\Host\Range; |
17
|
|
|
use Deployer\Support\Proxy; |
18
|
|
|
use Deployer\Task\Context; |
19
|
|
|
use Deployer\Task\GroupTask; |
20
|
|
|
use Deployer\Task\Task as T; |
21
|
|
|
use Deployer\Utility\Httpie; |
22
|
|
|
use Symfony\Component\Console\Exception\MissingInputException; |
23
|
|
|
use Symfony\Component\Console\Helper\QuestionHelper; |
24
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
25
|
|
|
use Symfony\Component\Console\Input\InputOption; |
26
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
27
|
|
|
use Symfony\Component\Console\Question\ChoiceQuestion; |
28
|
|
|
use Symfony\Component\Console\Question\ConfirmationQuestion; |
29
|
|
|
use Symfony\Component\Console\Question\Question; |
30
|
|
|
use function Deployer\Support\array_merge_alternate; |
31
|
|
|
use function Deployer\Support\array_to_string; |
32
|
|
|
use function Deployer\Support\str_contains; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param string ...$hostname |
36
|
|
|
* @return Host|Host[]|Proxy |
37
|
|
|
*/ |
38
|
|
|
function host(...$hostname) |
39
|
|
|
{ |
40
|
1 |
|
$deployer = Deployer::get(); |
41
|
1 |
|
$aliases = Range::expand($hostname); |
42
|
|
|
|
43
|
1 |
|
foreach ($aliases as $alias) { |
44
|
1 |
|
if ($deployer->hosts->has($alias)) { |
45
|
|
|
$host = $deployer->hosts->get($alias); |
46
|
|
|
throw new \InvalidArgumentException( |
47
|
|
|
"Host \"{$host->getTag()}\" already exists.\n" . |
48
|
|
|
"If you want to override configuration options, get host with <fg=yellow>getHost</> function.\n" . |
49
|
|
|
"\n" . |
50
|
|
|
" <fg=yellow>getHost</>(<fg=green>'{$alias}'</>);" . |
51
|
|
|
"\n" |
52
|
|
|
); |
53
|
|
|
} |
54
|
|
|
} |
55
|
|
|
|
56
|
1 |
|
if (count($aliases) === 1) { |
57
|
1 |
|
$host = new Host($aliases[0]); |
58
|
1 |
|
$deployer->hosts->set($aliases[0], $host); |
59
|
1 |
|
return $host; |
60
|
|
|
} else { |
61
|
|
|
$hosts = array_map(function ($hostname) use ($deployer) { |
62
|
1 |
|
$host = new Host($hostname); |
63
|
1 |
|
$deployer->hosts->set($hostname, $host); |
64
|
1 |
|
return $host; |
65
|
1 |
|
}, $aliases); |
66
|
1 |
|
return new Proxy($hosts); |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @param string ...$hostnames |
72
|
|
|
* @return Localhost|Localhost[]|Proxy |
73
|
|
|
*/ |
74
|
|
|
function localhost(...$hostnames) |
75
|
|
|
{ |
76
|
12 |
|
$deployer = Deployer::get(); |
77
|
12 |
|
$hostnames = Range::expand($hostnames); |
78
|
|
|
|
79
|
12 |
|
if (count($hostnames) <= 1) { |
80
|
12 |
|
$host = count($hostnames) === 1 ? new Localhost($hostnames[0]) : new Localhost(); |
81
|
12 |
|
$deployer->hosts->set($host->getAlias(), $host); |
82
|
12 |
|
return $host; |
83
|
|
|
} else { |
84
|
|
|
$hosts = array_map(function ($hostname) use ($deployer) { |
85
|
|
|
$host = new Localhost($hostname); |
86
|
|
|
$deployer->hosts->set($host->getAlias(), $host); |
87
|
|
|
return $host; |
88
|
|
|
}, $hostnames); |
89
|
|
|
return new Proxy($hosts); |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Get host by host alias. |
95
|
|
|
* |
96
|
|
|
* @param string $alias |
97
|
|
|
* @return Host |
98
|
|
|
*/ |
99
|
|
|
function getHost(string $alias) |
100
|
|
|
{ |
101
|
1 |
|
return Deployer::get()->hosts->get($alias); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Get current host. |
106
|
|
|
* |
107
|
|
|
* @return Host |
108
|
|
|
*/ |
109
|
|
|
function currentHost() |
110
|
|
|
{ |
111
|
7 |
|
return Context::get()->getHost(); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Load list of hosts from file |
117
|
|
|
* |
118
|
|
|
* @param string $file |
119
|
|
|
* @return Proxy |
120
|
|
|
*/ |
121
|
|
|
function inventory($file) |
122
|
|
|
{ |
123
|
|
|
$deployer = Deployer::get(); |
124
|
|
|
$fileLoader = new FileLoader(); |
125
|
|
|
$fileLoader->load($file); |
126
|
|
|
|
127
|
|
|
$hosts = $fileLoader->getHosts(); |
128
|
|
|
foreach ($hosts as $host) { |
129
|
|
|
$deployer->hosts->set($host->getAlias(), $host); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return new Proxy($hosts); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Set task description. |
137
|
|
|
* |
138
|
|
|
* @param string $title |
139
|
|
|
* @return string |
140
|
|
|
*/ |
141
|
|
|
function desc($title = null) |
142
|
|
|
{ |
143
|
19 |
|
static $store = null; |
144
|
|
|
|
145
|
19 |
|
if ($title === null) { |
146
|
19 |
|
return $store; |
147
|
|
|
} else { |
148
|
11 |
|
return $store = $title; |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Define a new task and save to tasks list. |
154
|
|
|
* |
155
|
|
|
* Alternatively get a defined task. |
156
|
|
|
* |
157
|
|
|
* @param string $name Name of current task. |
158
|
|
|
* @param callable|array|string|null $body Callable task, array of other tasks names or nothing to get a defined tasks |
159
|
|
|
* @return Task\Task |
160
|
|
|
*/ |
161
|
|
|
function task($name, $body = null) |
162
|
|
|
{ |
163
|
19 |
|
$deployer = Deployer::get(); |
164
|
|
|
|
165
|
19 |
|
if (empty($body)) { |
166
|
14 |
|
return $deployer->tasks->get($name); |
167
|
|
|
} |
168
|
|
|
|
169
|
19 |
|
if (is_callable($body)) { |
170
|
19 |
|
$task = new T($name, $body); |
171
|
12 |
|
} elseif (is_array($body)) { |
172
|
12 |
|
$task = new GroupTask($name, $body); |
173
|
|
|
} else { |
174
|
|
|
throw new \InvalidArgumentException('Task should be a closure or array of other tasks.'); |
175
|
|
|
} |
176
|
|
|
|
177
|
19 |
|
$task->saveSourceLocation(); |
178
|
19 |
|
$deployer->tasks->set($name, $task); |
179
|
|
|
|
180
|
19 |
|
if (!empty(desc())) { |
181
|
11 |
|
$task->desc(desc()); |
182
|
11 |
|
desc(''); // Clear title. |
183
|
|
|
} |
184
|
|
|
|
185
|
19 |
|
return $task; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Call that task before specified task runs. |
190
|
|
|
* |
191
|
|
|
* @param string $task The task before $that should be run. |
192
|
|
|
* @param string|callable $do The task to be run. |
193
|
|
|
* @return T|void |
194
|
|
|
*/ |
195
|
|
|
function before($task, $do) |
196
|
|
|
{ |
197
|
1 |
|
if (is_callable($do)) { |
198
|
1 |
|
$newTask = task("before:$task", $do); |
199
|
1 |
|
before($task, "before:$task"); |
200
|
1 |
|
return $newTask; |
201
|
|
|
} |
202
|
1 |
|
task($task)->addBefore($do); |
203
|
1 |
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Call that task after specified task runs. |
207
|
|
|
* |
208
|
|
|
* @param string $task The task after $that should be run. |
209
|
|
|
* @param string|callable $do The task to be run. |
210
|
|
|
* @return T|void |
211
|
|
|
*/ |
212
|
|
|
function after($task, $do) |
213
|
|
|
{ |
214
|
12 |
View Code Duplication |
if (is_callable($do)) { |
215
|
1 |
|
$newTask = task("after:$task", $do); |
216
|
1 |
|
after($task, "after:$task"); |
217
|
1 |
|
return $newTask; |
218
|
|
|
} |
219
|
12 |
|
task($task)->addAfter($do); |
220
|
12 |
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Setup which task run on failure of first. |
224
|
|
|
* |
225
|
|
|
* @param string $task The task which need to fail so $that should be run. |
226
|
|
|
* @param string $do The task to be run. |
227
|
|
|
* @return T|void |
228
|
|
|
*/ |
229
|
|
|
function fail($task, $do) |
230
|
|
|
{ |
231
|
11 |
View Code Duplication |
if (is_callable($do)) { |
232
|
|
|
$newTask = task("fail:$task", $do); |
233
|
|
|
fail($task, "fail:$task"); |
234
|
|
|
return $newTask; |
235
|
|
|
} |
236
|
11 |
|
$deployer = Deployer::get(); |
237
|
11 |
|
$deployer->fail->set($task, $do); |
238
|
11 |
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Add users options. |
242
|
|
|
* |
243
|
|
|
* @param string $name The option name |
244
|
|
|
* @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts |
245
|
|
|
* @param int|null $mode The option mode: One of the VALUE_* constants |
246
|
|
|
* @param string $description A description text |
247
|
|
|
* @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE) |
248
|
|
|
*/ |
249
|
|
|
function option($name, $shortcut = null, $mode = null, $description = '', $default = null) |
250
|
|
|
{ |
251
|
11 |
|
Deployer::get()->inputDefinition->addOption( |
252
|
11 |
|
new InputOption($name, $shortcut, $mode, $description, $default) |
253
|
|
|
); |
254
|
11 |
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Change the current working directory. |
258
|
|
|
* |
259
|
|
|
* @param string $path |
260
|
|
|
*/ |
261
|
|
|
function cd($path) |
262
|
|
|
{ |
263
|
6 |
|
set('working_path', parse($path)); |
264
|
6 |
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Execute a callback within a specific directory and revert back to the initial working directory. |
268
|
|
|
* |
269
|
|
|
* @param string $path |
270
|
|
|
* @param callable $callback |
271
|
|
|
*/ |
272
|
|
|
function within($path, $callback) |
273
|
|
|
{ |
274
|
|
|
$lastWorkingPath = get('working_path', ''); |
275
|
|
|
try { |
276
|
|
|
set('working_path', parse($path)); |
277
|
|
|
$callback(); |
278
|
|
|
} finally { |
279
|
|
|
set('working_path', $lastWorkingPath); |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Run command. |
285
|
|
|
* |
286
|
|
|
* @param string $command |
287
|
|
|
* @param array $options |
288
|
|
|
* @return string |
289
|
|
|
*/ |
290
|
|
|
function run($command, $options = []) |
291
|
|
|
{ |
292
|
|
|
$run = function ($command, $options) { |
293
|
9 |
|
$host = Context::get()->getHost(); |
294
|
|
|
|
295
|
9 |
|
$command = parse($command); |
296
|
9 |
|
$workingPath = get('working_path', ''); |
297
|
|
|
|
298
|
9 |
|
if (!empty($workingPath)) { |
299
|
6 |
|
$command = "cd $workingPath && ($command)"; |
300
|
|
|
} |
301
|
|
|
|
302
|
9 |
|
$env = array_merge_alternate(get('env', []), $options['env'] ?? []); |
303
|
9 |
|
if (!empty($env)) { |
304
|
|
|
$env = array_to_string($env); |
305
|
|
|
$command = "export $env; $command"; |
306
|
|
|
} |
307
|
|
|
|
308
|
9 |
|
if ($host instanceof Localhost) { |
309
|
9 |
|
$process = Deployer::get()->processRunner; |
310
|
9 |
|
$output = $process->run($host, $command, $options); |
311
|
|
|
} else { |
312
|
|
|
$client = Deployer::get()->sshClient; |
313
|
|
|
$output = $client->run($host, $command, $options); |
314
|
|
|
} |
315
|
|
|
|
316
|
9 |
|
return rtrim($output); |
317
|
9 |
|
}; |
318
|
|
|
|
319
|
9 |
|
if (preg_match('/^sudo\b/', $command)) { |
320
|
|
|
try { |
321
|
|
|
return $run($command, $options); |
322
|
|
|
} catch (RunException $exception) { |
323
|
|
|
$askpass = get('sudo_askpass', '/tmp/dep_sudo_pass'); |
324
|
|
|
$password = get('sudo_pass', false); |
325
|
|
|
if ($password === false) { |
326
|
|
|
writeln("<fg=green;options=bold>run</> $command"); |
327
|
|
|
$password = askHiddenResponse('Password:'); |
328
|
|
|
} |
329
|
|
|
$run("echo -e '#!/bin/sh\necho \"%sudo_pass%\"' > $askpass", array_merge($options, ['sudo_pass' => $password])); |
330
|
|
|
$run("chmod a+x $askpass", $options); |
331
|
|
|
$run(sprintf('export SUDO_ASKPASS=%s; %s', $askpass, preg_replace('/^sudo\b/', 'sudo -A', $command)), $options); |
332
|
|
|
$run("rm $askpass", $options); |
333
|
|
|
} |
334
|
|
|
} else { |
335
|
9 |
|
return $run($command, $options); |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Execute commands on local machine |
342
|
|
|
* |
343
|
|
|
* @param string $command Command to run locally. |
344
|
|
|
* @param array $options |
345
|
|
|
* @return string Output of command. |
346
|
|
|
*/ |
347
|
|
|
function runLocally($command, $options = []) |
348
|
|
|
{ |
349
|
6 |
|
$process = Deployer::get()->processRunner; |
350
|
6 |
|
$command = parse($command); |
351
|
|
|
|
352
|
6 |
|
$env = array_merge_alternate(get('env', []), $options['env'] ?? []); |
353
|
6 |
|
if (!empty($env)) { |
354
|
1 |
|
$env = array_to_string($env); |
355
|
1 |
|
$command = "export $env; $command"; |
356
|
|
|
} |
357
|
|
|
|
358
|
6 |
|
$output = $process->run(new Localhost(), $command, $options); |
359
|
|
|
|
360
|
6 |
|
return rtrim($output); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Run test command. |
365
|
|
|
* Example: |
366
|
|
|
* |
367
|
|
|
* test('[ -d {{release_path}} ]') |
368
|
|
|
* |
369
|
|
|
* @param string $command |
370
|
|
|
* @return bool |
371
|
|
|
*/ |
372
|
|
|
function test($command) |
373
|
|
|
{ |
374
|
8 |
|
return run("if $command; then echo 'true'; fi") === 'true'; |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* Run test command locally. |
379
|
|
|
* Example: |
380
|
|
|
* |
381
|
|
|
* testLocally('[ -d {{local_release_path}} ]') |
382
|
|
|
* |
383
|
|
|
* @param string $command |
384
|
|
|
* @return bool |
385
|
|
|
*/ |
386
|
|
|
function testLocally($command) |
387
|
|
|
{ |
388
|
|
|
return runLocally("if $command; then echo 'true'; fi") === 'true'; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Iterate other hosts, allowing to call run func in callback. |
393
|
|
|
* |
394
|
|
|
* @experimental |
395
|
|
|
* @param Host|Host[] $hosts |
396
|
|
|
* @param callable $callback |
397
|
|
|
*/ |
398
|
|
|
function on($hosts, callable $callback) |
399
|
|
|
{ |
400
|
|
|
$deployer = Deployer::get(); |
401
|
|
|
|
402
|
|
|
if (!is_array($hosts) && !($hosts instanceof \Traversable)) { |
403
|
|
|
$hosts = [$hosts]; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
foreach ($hosts as $host) { |
407
|
|
|
if ($host instanceof Host) { |
408
|
|
|
$host->getConfig()->load(); |
409
|
|
|
Context::push(new Context($host, input(), output())); |
410
|
|
|
try { |
411
|
|
|
$callback($host); |
412
|
|
|
$host->getConfig()->save(); |
413
|
|
|
} catch (GracefulShutdownException $e) { |
414
|
|
|
$deployer->messenger->renderException($e, $host); |
415
|
|
|
} finally { |
416
|
|
|
Context::pop(); |
417
|
|
|
} |
418
|
|
|
} else { |
419
|
|
|
throw new \InvalidArgumentException("Function on can iterate only on Host instances."); |
420
|
|
|
} |
421
|
|
|
} |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Run task |
426
|
|
|
* |
427
|
|
|
* @experimental |
428
|
|
|
* @param string $task |
429
|
|
|
*/ |
430
|
|
|
function invoke($task) |
431
|
|
|
{ |
432
|
|
|
$hosts = [Context::get()->getHost()]; |
433
|
|
|
$tasks = Deployer::get()->scriptManager->getTasks($task, $hosts); |
434
|
|
|
|
435
|
|
|
$master = Deployer::get()->master; |
436
|
|
|
$master->run($tasks, $hosts); |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
/* |
440
|
|
|
* Upload file or directory to host. |
441
|
|
|
*/ |
442
|
|
View Code Duplication |
function upload(string $source, string $destination, $config = []) |
|
|
|
|
443
|
|
|
{ |
444
|
|
|
$rsync = Deployer::get()->rsync; |
445
|
|
|
$host = currentHost(); |
446
|
|
|
$source = parse($source); |
447
|
|
|
$destination = parse($destination); |
448
|
|
|
|
449
|
|
|
if ($host instanceof Localhost) { |
450
|
|
|
$rsync->call($host, $source, $destination, $config); |
451
|
|
|
} else { |
452
|
|
|
$rsync->call($host, $source, "{$host->getConnectionString()}:$destination", $config); |
453
|
|
|
} |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
/* |
457
|
|
|
* Download file or directory from host |
458
|
|
|
*/ |
459
|
|
View Code Duplication |
function download(string $source, string $destination, $config = []) |
|
|
|
|
460
|
|
|
{ |
461
|
|
|
$rsync = Deployer::get()->rsync; |
462
|
|
|
$host = currentHost(); |
463
|
|
|
$source = parse($source); |
464
|
|
|
$destination = parse($destination); |
465
|
|
|
|
466
|
|
|
if ($host instanceof Localhost) { |
467
|
|
|
$rsync->call($host, $source, $destination, $config); |
468
|
|
|
} else { |
469
|
|
|
$rsync->call($host, "{$host->getConnectionString()}:$source", $destination, $config); |
470
|
|
|
} |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* Writes an info message. |
475
|
|
|
* @param string $message |
476
|
|
|
*/ |
477
|
|
|
function info($message) |
478
|
|
|
{ |
479
|
4 |
|
writeln("<fg=green;options=bold>info</> " . parse($message)); |
480
|
4 |
|
} |
481
|
|
|
|
482
|
|
|
/** |
483
|
|
|
* Writes an warning message. |
484
|
|
|
* @param string $message |
485
|
|
|
*/ |
486
|
|
|
function warning($message) |
487
|
|
|
{ |
488
|
|
|
writeln("<fg=yellow;options=bold>warning</> <comment>" . parse($message) . "</comment>"); |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Writes a message to the output and adds a newline at the end. |
493
|
|
|
* @param string|array $message |
494
|
|
|
* @param int $options |
495
|
|
|
*/ |
496
|
|
|
function writeln($message, $options = 0) |
497
|
|
|
{ |
498
|
7 |
|
$host = currentHost(); |
499
|
7 |
|
output()->writeln("[{$host->getTag()}] " . parse($message), $options); |
500
|
7 |
|
} |
501
|
|
|
|
502
|
|
|
/** |
503
|
|
|
* Writes a message to the output. |
504
|
|
|
* @param string $message |
505
|
|
|
* @param int $options |
506
|
|
|
*/ |
507
|
|
|
function write($message, $options = 0) |
508
|
|
|
{ |
509
|
|
|
output()->write(parse($message), false, $options); |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
/** |
513
|
|
|
* Parse set values. |
514
|
|
|
* |
515
|
|
|
* @param string $value |
516
|
|
|
* @return string |
517
|
|
|
*/ |
518
|
|
|
function parse($value) |
519
|
|
|
{ |
520
|
11 |
|
return Context::get()->getConfig()->parse($value); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
/** |
524
|
|
|
* Setup configuration option. |
525
|
|
|
* |
526
|
|
|
* @param string $name |
527
|
|
|
* @param mixed $value |
528
|
|
|
*/ |
529
|
|
View Code Duplication |
function set($name, $value) |
|
|
|
|
530
|
|
|
{ |
531
|
11 |
|
if (!Context::has()) { |
532
|
11 |
|
Deployer::get()->config->set($name, $value); |
533
|
|
|
} else { |
534
|
6 |
|
Context::get()->getConfig()->set($name, $value); |
535
|
|
|
} |
536
|
11 |
|
} |
537
|
|
|
|
538
|
|
|
/** |
539
|
|
|
* Merge new config params to existing config array. |
540
|
|
|
* |
541
|
|
|
* @param string $name |
542
|
|
|
* @param array $array |
543
|
|
|
*/ |
544
|
|
View Code Duplication |
function add($name, $array) |
|
|
|
|
545
|
|
|
{ |
546
|
|
|
if (!Context::has()) { |
547
|
|
|
Deployer::get()->config->add($name, $array); |
548
|
|
|
} else { |
549
|
|
|
Context::get()->getConfig()->add($name, $array); |
550
|
|
|
} |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
/** |
554
|
|
|
* Get configuration value. |
555
|
|
|
* |
556
|
|
|
* @param string $name |
557
|
|
|
* @param mixed|null $default |
558
|
|
|
* @return mixed |
559
|
|
|
*/ |
560
|
|
View Code Duplication |
function get($name, $default = null) |
|
|
|
|
561
|
|
|
{ |
562
|
11 |
|
if (!Context::has()) { |
563
|
|
|
return Deployer::get()->config->get($name, $default); |
564
|
|
|
} else { |
565
|
11 |
|
return Context::get()->getConfig()->get($name, $default); |
566
|
|
|
} |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
/** |
570
|
|
|
* Check if there is such configuration option. |
571
|
|
|
* |
572
|
|
|
* @param string $name |
573
|
|
|
* @return boolean |
574
|
|
|
*/ |
575
|
|
View Code Duplication |
function has($name) |
|
|
|
|
576
|
|
|
{ |
577
|
4 |
|
if (!Context::has()) { |
578
|
|
|
return Deployer::get()->config->has($name); |
579
|
|
|
} else { |
580
|
4 |
|
return Context::get()->getConfig()->has($name); |
581
|
|
|
} |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
/** |
585
|
|
|
* @param string $message |
586
|
|
|
* @param string|null $default |
587
|
|
|
* @param string[]|null $autocomplete |
588
|
|
|
* @return string |
589
|
|
|
*/ |
590
|
|
|
function ask($message, $default = null, $autocomplete = null) |
591
|
|
|
{ |
592
|
|
|
Context::required(__FUNCTION__); |
593
|
|
|
|
594
|
|
|
if (output()->isQuiet()) { |
595
|
|
|
return $default; |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
if (Deployer::isWorker()) { |
599
|
|
|
return Deployer::proxyCallToMaster(__FUNCTION__, ...func_get_args()); |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
/** @var QuestionHelper $helper */ |
603
|
|
|
$helper = Deployer::get()->getHelper('question'); |
604
|
|
|
|
605
|
|
|
$tag = currentHost()->getTag(); |
606
|
|
|
$message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) "); |
607
|
|
|
|
608
|
|
|
$question = new Question($message, $default); |
609
|
|
|
if (!empty($autocomplete)) { |
610
|
|
|
$question->setAutocompleterValues($autocomplete); |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
return $helper->ask(input(), output(), $question); |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* @param string $message |
618
|
|
|
* @param string[] $availableChoices |
619
|
|
|
* @param string|null $default |
620
|
|
|
* @param bool|false $multiselect |
621
|
|
|
* @return string|string[] |
622
|
|
|
*/ |
623
|
|
|
function askChoice($message, array $availableChoices, $default = null, $multiselect = false) |
624
|
|
|
{ |
625
|
|
|
Context::required(__FUNCTION__); |
626
|
|
|
|
627
|
|
|
if (empty($availableChoices)) { |
628
|
|
|
throw new \InvalidArgumentException('Available choices should not be empty'); |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
if ($default !== null && !array_key_exists($default, $availableChoices)) { |
632
|
|
|
throw new \InvalidArgumentException('Default choice is not available'); |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
if (output()->isQuiet()) { |
636
|
|
|
if ($default === null) { |
637
|
|
|
$default = key($availableChoices); |
638
|
|
|
} |
639
|
|
|
return [$default => $availableChoices[$default]]; |
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
if (Deployer::isWorker()) { |
643
|
|
|
return Deployer::proxyCallToMaster(__FUNCTION__, ...func_get_args()); |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
$helper = Deployer::get()->getHelper('question'); |
647
|
|
|
|
648
|
|
|
$tag = currentHost()->getTag(); |
649
|
|
|
$message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) "); |
650
|
|
|
|
651
|
|
|
$question = new ChoiceQuestion($message, $availableChoices, $default); |
652
|
|
|
$question->setMultiselect($multiselect); |
653
|
|
|
|
654
|
|
|
return $helper->ask(input(), output(), $question); |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
/** |
658
|
|
|
* @param string $message |
659
|
|
|
* @param bool $default |
660
|
|
|
* @return bool |
661
|
|
|
*/ |
662
|
|
|
function askConfirmation($message, $default = false) |
663
|
|
|
{ |
664
|
|
|
Context::required(__FUNCTION__); |
665
|
|
|
|
666
|
|
|
if (output()->isQuiet()) { |
667
|
|
|
return $default; |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
if (Deployer::isWorker()) { |
671
|
|
|
return Deployer::proxyCallToMaster(__FUNCTION__, ...func_get_args()); |
672
|
|
|
} |
673
|
|
|
|
674
|
|
|
$helper = Deployer::get()->getHelper('question'); |
675
|
|
|
|
676
|
|
|
$yesOrNo = $default ? 'Y/n' : 'y/N'; |
677
|
|
|
$tag = currentHost()->getTag(); |
678
|
|
|
$message = "[$tag] <question>$message</question> [$yesOrNo] "; |
679
|
|
|
|
680
|
|
|
$question = new ConfirmationQuestion($message, $default); |
681
|
|
|
|
682
|
|
|
return $helper->ask(input(), output(), $question); |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
/** |
686
|
|
|
* @param string $message |
687
|
|
|
* @return string |
688
|
|
|
*/ |
689
|
|
|
function askHiddenResponse(string $message) |
690
|
|
|
{ |
691
|
|
|
Context::required(__FUNCTION__); |
692
|
|
|
|
693
|
|
|
if (output()->isQuiet()) { |
694
|
|
|
return ''; |
695
|
|
|
} |
696
|
|
|
|
697
|
|
|
if (Deployer::isWorker()) { |
698
|
|
|
return Deployer::proxyCallToMaster(__FUNCTION__, ...func_get_args()); |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
$helper = Deployer::get()->getHelper('question'); |
702
|
|
|
|
703
|
|
|
$tag = currentHost()->getTag(); |
704
|
|
|
$message = "[$tag] <question>$message</question> "; |
705
|
|
|
|
706
|
|
|
$question = new Question($message); |
707
|
|
|
$question->setHidden(true); |
708
|
|
|
$question->setHiddenFallback(false); |
709
|
|
|
|
710
|
|
|
return $helper->ask(input(), output(), $question); |
711
|
|
|
} |
712
|
|
|
|
713
|
|
|
/** |
714
|
|
|
* @return InputInterface |
715
|
|
|
*/ |
716
|
|
|
function input() |
717
|
|
|
{ |
718
|
4 |
|
return Context::get()->getInput(); |
719
|
|
|
} |
720
|
|
|
|
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* @return OutputInterface |
724
|
|
|
*/ |
725
|
|
|
function output() |
726
|
|
|
{ |
727
|
7 |
|
return Context::get()->getOutput(); |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* Check if command exists |
732
|
|
|
* |
733
|
|
|
* @param string $command |
734
|
|
|
* @return bool |
735
|
|
|
*/ |
736
|
|
|
function commandExist($command) |
737
|
|
|
{ |
738
|
3 |
|
return test("hash $command 2>/dev/null"); |
739
|
|
|
} |
740
|
|
|
|
741
|
|
|
function commandSupportsOption($command, $option) |
742
|
|
|
{ |
743
|
5 |
|
$man = run("(man $command 2>&1 || $command -h 2>&1 || $command --help 2>&1) | grep -- $option || true"); |
744
|
5 |
|
if (empty($man)) { |
745
|
|
|
return false; |
746
|
|
|
} |
747
|
5 |
|
return str_contains($man, $option); |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
function locateBinaryPath($name) |
751
|
|
|
{ |
752
|
4 |
|
$nameEscaped = escapeshellarg($name); |
753
|
|
|
|
754
|
|
|
// Try `command`, should cover all Bourne-like shells |
755
|
|
|
// Try `which`, should cover most other cases |
756
|
|
|
// Fallback to `type` command, if the rest fails |
757
|
4 |
|
$path = run("command -v $nameEscaped || which $nameEscaped || type -p $nameEscaped"); |
758
|
4 |
|
if (empty($path)) { |
759
|
|
|
throw new \RuntimeException("Can't locate [$nameEscaped] - neither of [command|which|type] commands are available"); |
760
|
|
|
} |
761
|
|
|
|
762
|
|
|
// Deal with issue when `type -p` outputs something like `type -ap` in some implementations |
763
|
4 |
|
return trim(str_replace("$name is", "", $path)); |
764
|
|
|
|
765
|
|
|
} |
766
|
|
|
|
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.