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