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