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.
Passed
Push — master ( 6a3289...514d99 )
by Anton
02:27
created

src/functions.php (2 issues)

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\GracefulShutdownException;
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\ObjectProxy;
17
use Deployer\Task\Context;
18
use Deployer\Task\GroupTask;
19
use Deployer\Task\Task;
20
use Symfony\Component\Console\Helper\QuestionHelper;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Console\Question\ChoiceQuestion;
25
use Symfony\Component\Console\Question\ConfirmationQuestion;
26
use Symfony\Component\Console\Question\Question;
27
use function Deployer\Support\array_merge_alternate;
28
use function Deployer\Support\array_to_string;
29
use function Deployer\Support\str_contains;
30
31
/**
32
 * @param string ...$hostname
33
 * @return Host|Host[]|ObjectProxy
34
 */
35
function host(...$hostname)
36
{
37
    $deployer = Deployer::get();
38
    $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 1
            throw new \InvalidArgumentException(
44 1
                "Host \"{$host->getTag()}\" 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
    if (count($aliases) === 1) {
54
        $host = new Host($aliases[0]);
55
        $deployer->hosts->set($aliases[0], $host);
56 1
        return $host;
57 1
    } else {
58 1
        $hosts = array_map(function ($hostname) use ($deployer) {
59 1
            $host = new Host($hostname);
60
            $deployer->hosts->set($hostname, $host);
61
            return $host;
62 1
        }, $aliases);
63 1
        return new ObjectProxy($hosts);
64 1
    }
65 1
}
66 1
67
/**
68
 * @param string ...$hostnames
69
 * @return Localhost|Localhost[]|ObjectProxy
70
 */
71
function localhost(...$hostnames)
72
{
73
    $deployer = Deployer::get();
74
    $hostnames = Range::expand($hostnames);
75
76 13
    if (count($hostnames) <= 1) {
77 13
        $host = count($hostnames) === 1 ? new Localhost($hostnames[0]) : new Localhost();
78
        $deployer->hosts->set($host->getAlias(), $host);
79 13
        return $host;
80 13
    } else {
81 13
        $hosts = array_map(function ($hostname) use ($deployer) {
82 13
            $host = new Localhost($hostname);
83
            $deployer->hosts->set($host->getAlias(), $host);
84
            return $host;
85
        }, $hostnames);
86
        return new ObjectProxy($hosts);
87
    }
88
}
89
90
/**
91
 * Get host by host alias.
92
 *
93
 * @param string $alias
94
 * @return Host
95
 */
96
function getHost(string $alias)
97
{
98
    return Deployer::get()->hosts->get($alias);
99
}
100
101 5
/**
102
 * Get current host.
103
 *
104
 * @return Host
105
 */
106
function currentHost()
107
{
108
    return Context::get()->getHost();
109
}
110
111 8
112
/**
113
 * Load list of hosts from file
114
 *
115
 * @param string $file
116
 * @return ObjectProxy
117
 */
118
function inventory($file)
119
{
120
    $deployer = Deployer::get();
121
    $fileLoader = new FileLoader();
122
    $fileLoader->load($file);
123
124
    $hosts = $fileLoader->getHosts();
125
    foreach ($hosts as $host) {
126
        $deployer->hosts->set($host->getAlias(), $host);
127
    }
128
129
    return new ObjectProxy($hosts);
130
}
131
132
/**
133
 * Set task description.
134
 *
135
 * @param string $title
136
 * @return string
137
 */
138
function desc($title = null)
139
{
140
    static $store = null;
141
142
    if ($title === null) {
143 15
        return $store;
144
    } else {
145 15
        return $store = $title;
146 15
    }
147
}
148 8
149
/**
150
 * Define a new task and save to tasks list.
151
 *
152
 * Alternatively get a defined task.
153
 *
154
 * @param string $name Name of current task.
155
 * @param callable|array|string|null $body Callable task, array of other tasks names or nothing to get a defined tasks
156
 * @return Task
157
 */
158
function task($name, $body = null)
159
{
160
    $deployer = Deployer::get();
161
162
    if (empty($body)) {
163 15
        return $deployer->tasks->get($name);
164
    }
165 15
166 15
    if (is_callable($body)) {
167
        $task = new Task($name, $body);
168
    } elseif (is_array($body)) {
169 15
        $task = new GroupTask($name, $body);
170 15
    } else {
171 9
        throw new \InvalidArgumentException('Task should be a closure or array of other tasks.');
172 9
    }
173
174
    $task->saveSourceLocation();
175
    $deployer->tasks->set($name, $task);
176
177 15
    if (!empty(desc())) {
178 15
        $task->desc(desc());
179
        desc(''); // Clear title.
180 15
    }
181 8
182 8
    return $task;
183
}
184
185 15
/**
186
 * Call that task before specified task runs.
187
 *
188
 * @param string $task The task before $that should be run.
189
 * @param string|callable $do The task to be run.
190
 * @return Task|void
191
 */
192
function before($task, $do)
193
{
194
    if (is_callable($do)) {
195
        $newTask = task("before:$task", $do);
196
        before($task, "before:$task");
197 1
        return $newTask;
198 1
    }
199 1
    task($task)->addBefore($do);
200 1
}
201
202 1
/**
203 1
 * Call that task after specified task runs.
204
 *
205
 * @param string $task The task after $that should be run.
206
 * @param string|callable $do The task to be run.
207
 * @return Task|void
208
 */
209
function after($task, $do)
210
{
211
    if (is_callable($do)) {
212
        $newTask = task("after:$task", $do);
213
        after($task, "after:$task");
214 13
        return $newTask;
215 5
    }
216 5
    task($task)->addAfter($do);
217 5
}
218
219 13
/**
220 13
 * Setup which task run on failure of first.
221
 *
222
 * @param string $task The task which need to fail so $that should be run.
223
 * @param string $do The task to be run.
224
 * @return Task|void
225
 */
226
function fail($task, $do)
227
{
228
    if (is_callable($do)) {
229
        $newTask = task("fail:$task", $do);
230
        fail($task, "fail:$task");
231 8
        return $newTask;
232
    }
233
    $deployer = Deployer::get();
234
    $deployer->fail->set($task, $do);
235
}
236 8
237 8
/**
238 8
 * Add users options.
239
 *
240
 * @param string $name The option name
241
 * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
242
 * @param int|null $mode The option mode: One of the VALUE_* constants
243
 * @param string $description A description text
244
 * @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
245
 */
246
function option($name, $shortcut = null, $mode = null, $description = '', $default = null)
247
{
248
    Deployer::get()->inputDefinition->addOption(
249
        new InputOption($name, $shortcut, $mode, $description, $default)
250
    );
251 8
}
252 8
253
/**
254 8
 * Change the current working directory.
255
 *
256
 * @param string $path
257
 */
258
function cd($path)
259
{
260
    set('working_path', parse($path));
261
}
262
263 6
/**
264 6
 * Execute a callback within a specific directory and revert back to the initial working directory.
265
 *
266
 * @param string $path
267
 * @param callable $callback
268
 */
269
function within($path, $callback)
270
{
271
    $lastWorkingPath = get('working_path', '');
272
    try {
273
        set('working_path', parse($path));
274
        $callback();
275
    } finally {
276
        set('working_path', $lastWorkingPath);
277
    }
278
}
279
280
/**
281
 * Executes given command on remote host.
282
 *
283
 * Options:
284
 * - `timeout` - Sets the process timeout (max. runtime). The timeout in seconds (default: 300 sec).
285
 * - `secret` - Placeholder `%secret%` can be used in command. Placeholder will be replaced with this value and will not appear in any logs.
286
 *
287
 * Examples:
288
 *
289
 * ```php
290
 * run('echo hello world');
291
 * run('cd {{deploy_path}} && git status');
292
 * run('password %secret%', ['secret' => getenv('CI_SECRET')]);
293 8
 * ```
294
 *
295 8
 * ```php
296 8
 * $path = run('readlink {{deploy_path}}/current');
297
 * run("echo $path");
298 8
 * ```
299 6
 *
300
 * @param string $command
301
 * @param array $options
302 8
 * @return string
303 8
 */
304
function run($command, $options = [])
305
{
306
    $run = function ($command, $options) {
307
        $host = Context::get()->getHost();
308 8
309 8
        $command = parse($command);
310 8
        $workingPath = get('working_path', '');
311
312
        if (!empty($workingPath)) {
313
            $command = "cd $workingPath && ($command)";
314
        }
315
316 8
        $env = array_merge_alternate(get('env', []), $options['env'] ?? []);
0 ignored issues
show
It seems like get('env', array()) can also be of type string; however, parameter $original of Deployer\Support\array_merge_alternate() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

316
        $env = array_merge_alternate(/** @scrutinizer ignore-type */ get('env', []), $options['env'] ?? []);
Loading history...
317 8
        if (!empty($env)) {
318
            $env = array_to_string($env);
319 8
            $command = "export $env; $command";
320
        }
321
322
        if ($host instanceof Localhost) {
323
            $process = Deployer::get()->processRunner;
324
            $output = $process->run($host, $command, $options);
325
        } else {
326
            $client = Deployer::get()->sshClient;
327
            $output = $client->run($host, $command, $options);
328
        }
329
330
        return rtrim($output);
331
    };
332
333
    if (preg_match('/^sudo\b/', $command)) {
334
        try {
335 8
            return $run($command, $options);
336
        } catch (RunException $exception) {
337
            $askpass = get('sudo_askpass', '/tmp/dep_sudo_pass');
338
            $password = get('sudo_pass', false);
339
            if ($password === false) {
340
                writeln("<fg=green;options=bold>run</> $command");
341
                $password = askHiddenResponse('Password:');
342
            }
343
            $run("echo -e '#!/bin/sh\necho \"%sudo_pass%\"' > $askpass", array_merge($options, ['sudo_pass' => $password]));
344
            $run("chmod a+x $askpass", $options);
345
            $run(sprintf('export SUDO_ASKPASS=%s; %s', $askpass, preg_replace('/^sudo\b/', 'sudo -A', $command)), $options);
346
            $run("rm $askpass", $options);
347
        }
348
    } else {
349 6
        return $run($command, $options);
350 6
    }
351
}
352 6
353 6
354 1
/**
355 1
 * Execute commands on local machine
356
 *
357
 * @param string $command Command to run locally.
358 6
 * @param array $options
359
 * @return string Output of command.
360 6
 */
361
function runLocally($command, $options = [])
362
{
363
    $process = Deployer::get()->processRunner;
364
    $command = parse($command);
365
366
    $env = array_merge_alternate(get('env', []), $options['env'] ?? []);
0 ignored issues
show
It seems like get('env', array()) can also be of type string; however, parameter $original of Deployer\Support\array_merge_alternate() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

366
    $env = array_merge_alternate(/** @scrutinizer ignore-type */ get('env', []), $options['env'] ?? []);
Loading history...
367
    if (!empty($env)) {
368
        $env = array_to_string($env);
369
        $command = "export $env; $command";
370
    }
371
372
    $output = $process->run(new Localhost(), $command, $options);
373
374 8
    return rtrim($output);
375
}
376
377
/**
378
 * Run test command.
379
 * Example:
380
 *
381
 * ```php
382
 * if (test('[ -d {{release_path}} ]')) {
383
 * ...
384
 * }
385
 * ```
386
 *
387
 * @param string $command
388
 * @return bool
389
 */
390
function test($command)
391
{
392
    return run("if $command; then echo 'true'; fi") === 'true';
393
}
394
395
/**
396
 * Run test command locally.
397
 * Example:
398
 *
399
 *     testLocally('[ -d {{local_release_path}} ]')
400
 *
401
 * @param string $command
402
 * @return bool
403
 */
404
function testLocally($command)
405
{
406
    return runLocally("if $command; then echo 'true'; fi") === 'true';
407
}
408
409
/**
410
 * Iterate other hosts, allowing to call run func in callback.
411
 *
412
 * @experimental
413
 * @param Host|Host[] $hosts
414
 * @param callable $callback
415
 */
416
function on($hosts, callable $callback)
417
{
418
    $deployer = Deployer::get();
419
420
    if (!is_array($hosts) && !($hosts instanceof \Traversable)) {
421
        $hosts = [$hosts];
422
    }
423
424
    foreach ($hosts as $host) {
425
        if ($host instanceof Host) {
426
            $host->config()->load();
427
            Context::push(new Context($host, input(), output()));
428
            try {
429
                $callback($host);
430
                $host->config()->save();
431
            } catch (GracefulShutdownException $e) {
432
                $deployer->messenger->renderException($e, $host);
433
            } finally {
434
                Context::pop();
435
            }
436
        } else {
437
            throw new \InvalidArgumentException("Function on can iterate only on Host instances.");
438
        }
439
    }
440
}
441
442
/**
443
 * Run task
444
 *
445
 * @experimental
446
 * @param string $task
447
 */
448
function invoke($task)
449
{
450
    $hosts = [Context::get()->getHost()];
451
    $tasks = Deployer::get()->scriptManager->getTasks($task, $hosts);
452
453
    $master = Deployer::get()->master;
454
    $master->run($tasks, $hosts);
455
}
456
457
/**
458
 * Upload file or directory to host.
459
 *
460
 * > You may have noticed that there is a trailing slash (/) at the end of the first argument in the above command, this is necessary to mean “the contents of build“.
461
 * >
462
 * > The alternative, without the trailing slash, would place build, including the directory, within public. This would create a hierarchy that looks like: {{release_path}}/public/build
463
 *
464
 * @param string $source
465
 * @param string $destination
466
 * @param array $config
467
 * @throws RunException
468
 */
469
function upload(string $source, string $destination, $config = [])
470
{
471
    $rsync = Deployer::get()->rsync;
472
    $host = currentHost();
473
    $source = parse($source);
474
    $destination = parse($destination);
475
476
    if ($host instanceof Localhost) {
477
        $rsync->call($host, $source, $destination, $config);
478
    } else {
479 4
        $rsync->call($host, $source, "{$host->getConnectionString()}:$destination", $config);
480 4
    }
481
}
482
483
/**
484
 * Download file or directory from host
485
 *
486
 * @param string $source
487
 * @param string $destination
488
 * @param array $config
489
 * @throws RunException
490
 */
491
function download(string $source, string $destination, $config = [])
492
{
493
    $rsync = Deployer::get()->rsync;
494
    $host = currentHost();
495
    $source = parse($source);
496
    $destination = parse($destination);
497
498 7
    if ($host instanceof Localhost) {
499 7
        $rsync->call($host, $source, $destination, $config);
500 7
    } else {
501
        $rsync->call($host, "{$host->getConnectionString()}:$source", $destination, $config);
502
    }
503
}
504
505
/**
506
 * Writes an info message.
507
 * @param string $message
508
 */
509
function info($message)
510
{
511
    writeln("<fg=green;options=bold>info</> " . parse($message));
512
}
513
514
/**
515
 * Writes an warning message.
516
 * @param string $message
517
 */
518
function warning($message)
519
{
520 10
    writeln("<fg=yellow;options=bold>warning</> <comment>" . parse($message) . "</comment>");
521
}
522
523
/**
524
 * Writes a message to the output and adds a newline at the end.
525
 * @param string|array $message
526
 * @param int $options
527
 */
528
function writeln($message, $options = 0)
529
{
530
    $host = currentHost();
531 12
    output()->writeln("[{$host->getTag()}] " . parse($message), $options);
532 12
}
533
534 6
/**
535
 * Writes a message to the output.
536 12
 * @param string $message
537
 * @param int $options
538
 */
539
function write($message, $options = 0)
540
{
541
    output()->write(parse($message), false, $options);
542
}
543
544
/**
545
 * Parse set values.
546
 *
547
 * @param string $value
548
 * @return string
549
 */
550
function parse($value)
551
{
552
    return Context::get()->getConfig()->parse($value);
553
}
554
555
/**
556
 * Setup configuration option.
557
 *
558
 * @param string $name
559
 * @param mixed $value
560
 */
561
function set($name, $value)
562 10
{
563
    if (!Context::has()) {
564
        Deployer::get()->config->set($name, $value);
565 10
    } else {
566
        Context::get()->getConfig()->set($name, $value);
567
    }
568
}
569
570
/**
571
 * Merge new config params to existing config array.
572
 *
573
 * @param string $name
574
 * @param array $array
575
 */
576
function add($name, $array)
577 4
{
578
    if (!Context::has()) {
579
        Deployer::get()->config->add($name, $array);
580 4
    } else {
581
        Context::get()->getConfig()->add($name, $array);
582
    }
583
}
584
585
/**
586
 * Get configuration value.
587
 *
588
 * @param string $name
589
 * @param mixed|null $default
590
 * @return mixed
591
 */
592 1
function get($name, $default = null)
593
{
594 1
    if (!Context::has()) {
595
        return Deployer::get()->config->get($name, $default);
596
    } else {
597
        return Context::get()->getConfig()->get($name, $default);
598 1
    }
599
}
600
601
/**
602
 * Check if there is such configuration option.
603 1
 *
604
 * @param string $name
605 1
 * @return boolean
606 1
 */
607
function has($name)
608 1
{
609 1
    if (!Context::has()) {
610
        return Deployer::get()->config->has($name);
611
    } else {
612
        return Context::get()->getConfig()->has($name);
613 1
    }
614
}
615
616
/**
617
 * @param string $message
618
 * @param string|null $default
619
 * @param string[]|null $autocomplete
620
 * @return string
621
 */
622
function ask($message, $default = null, $autocomplete = null)
623
{
624
    Context::required(__FUNCTION__);
625
626
    if (output()->isQuiet()) {
627
        return $default;
628
    }
629
630
    if (Deployer::isWorker()) {
631
        return Deployer::proxyCallToMaster(currentHost(), __FUNCTION__, ...func_get_args());
632
    }
633
634
    /** @var QuestionHelper $helper */
635
    $helper = Deployer::get()->getHelper('question');
636
637
    $tag = currentHost()->getTag();
638
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
639
640
    $question = new Question($message, $default);
641
    if (!empty($autocomplete)) {
642
        $question->setAutocompleterValues($autocomplete);
643
    }
644
645
    return $helper->ask(input(), output(), $question);
646
}
647
648
/**
649
 * @param string $message
650
 * @param string[] $availableChoices
651
 * @param string|null $default
652
 * @param bool|false $multiselect
653
 * @return string|string[]
654
 */
655
function askChoice($message, array $availableChoices, $default = null, $multiselect = false)
656
{
657
    Context::required(__FUNCTION__);
658
659
    if (empty($availableChoices)) {
660
        throw new \InvalidArgumentException('Available choices should not be empty');
661
    }
662
663
    if ($default !== null && !array_key_exists($default, $availableChoices)) {
664
        throw new \InvalidArgumentException('Default choice is not available');
665
    }
666
667
    if (output()->isQuiet()) {
668
        if ($default === null) {
669
            $default = key($availableChoices);
670
        }
671
        return [$default => $availableChoices[$default]];
672
    }
673
674
    if (Deployer::isWorker()) {
675
        return Deployer::proxyCallToMaster(currentHost(), __FUNCTION__, ...func_get_args());
676
    }
677
678
    $helper = Deployer::get()->getHelper('question');
679
680
    $tag = currentHost()->getTag();
681
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
682
683
    $question = new ChoiceQuestion($message, $availableChoices, $default);
684
    $question->setMultiselect($multiselect);
685
686
    return $helper->ask(input(), output(), $question);
687
}
688
689
/**
690
 * @param string $message
691
 * @param bool $default
692
 * @return bool
693
 */
694
function askConfirmation($message, $default = false)
695
{
696
    Context::required(__FUNCTION__);
697
698
    if (output()->isQuiet()) {
699
        return $default;
700
    }
701
702
    if (Deployer::isWorker()) {
703
        return Deployer::proxyCallToMaster(currentHost(), __FUNCTION__, ...func_get_args());
704
    }
705
706
    $helper = Deployer::get()->getHelper('question');
707
708
    $yesOrNo = $default ? 'Y/n' : 'y/N';
709
    $tag = currentHost()->getTag();
710
    $message = "[$tag] <question>$message</question> [$yesOrNo] ";
711
712
    $question = new ConfirmationQuestion($message, $default);
713
714
    return $helper->ask(input(), output(), $question);
715
}
716
717
/**
718 5
 * @param string $message
719
 * @return string
720
 */
721
function askHiddenResponse(string $message)
722
{
723
    Context::required(__FUNCTION__);
724
725
    if (output()->isQuiet()) {
726
        return '';
727 8
    }
728
729
    if (Deployer::isWorker()) {
730
        return Deployer::proxyCallToMaster(currentHost(), __FUNCTION__, ...func_get_args());
731
    }
732
733
    $helper = Deployer::get()->getHelper('question');
734
735
    $tag = currentHost()->getTag();
736
    $message = "[$tag] <question>$message</question> ";
737
738 3
    $question = new Question($message);
739
    $question->setHidden(true);
740
    $question->setHiddenFallback(false);
741
742
    return $helper->ask(input(), output(), $question);
743 5
}
744 5
745
/**
746
 * @return InputInterface
747 5
 */
748
function input()
749
{
750
    return Context::get()->getInput();
751
}
752 4
753
754
/**
755
 * @return OutputInterface
756
 */
757 4
function output()
758 4
{
759
    return Context::get()->getOutput();
760
}
761
762
/**
763 4
 * Check if command exists
764
 *
765
 * @param string $command
766
 * @return bool
767
 */
768
function commandExist($command)
769
{
770
    return test("hash $command 2>/dev/null");
771
}
772
773
function commandSupportsOption($command, $option)
774
{
775
    $man = run("(man $command 2>&1 || $command -h 2>&1 || $command --help 2>&1) | grep -- $option || true");
776
    if (empty($man)) {
777
        return false;
778
    }
779
    return str_contains($man, $option);
780
}
781
782
function locateBinaryPath($name)
783
{
784
    $nameEscaped = escapeshellarg($name);
785
786
    // Try `command`, should cover all Bourne-like shells
787
    // Try `which`, should cover most other cases
788
    // Fallback to `type` command, if the rest fails
789
    $path = run("command -v $nameEscaped || which $nameEscaped || type -p $nameEscaped");
790
    if (empty($path)) {
791
        throw new \RuntimeException("Can't locate [$nameEscaped] - neither of [command|which|type] commands are available");
792
    }
793
794
    // Deal with issue when `type -p` outputs something like `type -ap` in some implementations
795
    return trim(str_replace("$name is", "", $path));
796
797
}
798