GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( deffcb...b633e9 )
by Anton
02:17
created

functions.php ➔ ask()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.3906

Importance

Changes 0
Metric Value
cc 5
nc 6
nop 3
dl 0
loc 25
ccs 9
cts 12
cp 0.75
crap 5.3906
rs 9.2088
c 0
b 0
f 0
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 13
    $deployer = Deployer::get();
77 13
    $hostnames = Range::expand($hostnames);
78
79 13
    if (count($hostnames) <= 1) {
80 13
        $host = count($hostnames) === 1 ? new Localhost($hostnames[0]) : new Localhost();
81 13
        $deployer->hosts->set($host->getAlias(), $host);
82 13
        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 2
    return Deployer::get()->hosts->get($alias);
102
}
103
104
/**
105
 * Get current host.
106
 *
107
 * @return Host
108
 */
109
function currentHost()
110
{
111 9
    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 20
    static $store = null;
144
145 20
    if ($title === null) {
146 20
        return $store;
147
    } else {
148 12
        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 20
    $deployer = Deployer::get();
164
165 20
    if (empty($body)) {
166 15
        return $deployer->tasks->get($name);
167
    }
168
169 20
    if (is_callable($body)) {
170 20
        $task = new T($name, $body);
171 13
    } elseif (is_array($body)) {
172 13
        $task = new GroupTask($name, $body);
173
    } else {
174
        throw new \InvalidArgumentException('Task should be a closure or array of other tasks.');
175
    }
176
177 20
    $task->saveSourceLocation();
178 20
    $deployer->tasks->set($name, $task);
179
180 20
    if (!empty(desc())) {
181 12
        $task->desc(desc());
182 12
        desc(''); // Clear title.
183
    }
184
185 20
    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 13 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 13
    task($task)->addAfter($do);
220 13
}
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 12 View Code Duplication
    if (is_callable($do)) {
232
        $newTask = task("fail:$task", $do);
233
        fail($task, "fail:$task");
234
        return $newTask;
235
    }
236 12
    $deployer = Deployer::get();
237 12
    $deployer->fail->set($task, $do);
238 12
}
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 12
    Deployer::get()->inputDefinition->addOption(
252 12
        new InputOption($name, $shortcut, $mode, $description, $default)
253
    );
254 12
}
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 = [])
0 ignored issues
show
Duplication introduced by Anton Medvedev
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 = [])
0 ignored issues
show
Duplication introduced by Anton Medvedev
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 5
    writeln("<fg=green;options=bold>info</> " . parse($message));
480 5
}
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 8
    $host = currentHost();
499 8
    output()->writeln("[{$host->getTag()}] " . parse($message), $options);
500 8
}
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 12
    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)
0 ignored issues
show
Duplication introduced by Anton Medvedev
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
530
{
531 12
    if (!Context::has()) {
532 12
        Deployer::get()->config->set($name, $value);
533
    } else {
534 6
        Context::get()->getConfig()->set($name, $value);
535
    }
536 12
}
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)
0 ignored issues
show
Duplication introduced by Anton Medvedev
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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)
0 ignored issues
show
Duplication introduced by Anton Medvedev
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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)
0 ignored issues
show
Duplication introduced by Anton Medvedev
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 1
    Context::required(__FUNCTION__);
593
594 1
    if (output()->isQuiet()) {
595
        return $default;
596
    }
597
598 1
    if (Deployer::isWorker()) {
599
        return Deployer::proxyCallToMaster(__FUNCTION__, ...func_get_args());
600
    }
601
602
    /** @var QuestionHelper $helper */
603 1
    $helper = Deployer::get()->getHelper('question');
604
605 1
    $tag = currentHost()->getTag();
606 1
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
607
608 1
    $question = new Question($message, $default);
609 1
    if (!empty($autocomplete)) {
610
        $question->setAutocompleterValues($autocomplete);
611
    }
612
613 1
    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 5
    return Context::get()->getInput();
719
}
720
721
722
/**
723
 * @return OutputInterface
724
 */
725
function output()
726
{
727 9
    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