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.
Test Failed
Push — master ( 444b11...55afa9 )
by Anton
18:50 queued 16:47
created

functions.php ➔ commandExist()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 1
cts 1
cp 1
crap 1
rs 10
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 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 ...$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
 * @param string ...$hostnames
69
 * @return Localhost|Localhost[]|Proxy
70
 */
71
function localhost(...$hostnames)
72
{
73 9
    $deployer = Deployer::get();
74 9
    $hostnames = Range::expand($hostnames);
75
76 9
    if (count($hostnames) <= 1) {
77 9
        $host = count($hostnames) === 1 ? new Localhost($hostnames[0]) : new Localhost();
78 9
        $deployer->hosts->set($host->alias(), $host);
79 9
        return $host;
80
    } else {
81
        $hosts = array_map(function ($hostname) use ($deployer) {
82
            $host = new Localhost($hostname);
83
            $deployer->hosts->set($host->alias(), $host);
84
            return $host;
85
        }, $hostnames);
86
        return new Proxy($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 1
    return Deployer::get()->hosts->get($alias);
99
}
100
101
/**
102
 * Get current host.
103
 *
104
 * @return Host
105
 */
106
function currentHost()
107
{
108 6
    return Context::get()->getHost();
109
}
110
111
112
/**
113
 * Load list of hosts from file
114
 *
115
 * @param string $file
116
 * @return Proxy
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->alias(), $host);
127
    }
128
129
    return new Proxy($hosts);
130
}
131
132
/**
133
 * Set task description.
134
 *
135
 * @param string $title
136
 * @return string
137
 */
138
function desc($title = null)
139
{
140 18
    static $store = null;
141
142 18
    if ($title === null) {
143 18
        return $store;
144
    } else {
145 8
        return $store = $title;
146
    }
147
}
148
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\Task
157
 */
158
function task($name, $body = null)
159
{
160 18
    $deployer = Deployer::get();
161
162 18
    if (empty($body)) {
163 3
        return $deployer->tasks->get($name);
164
    }
165
166 18
    if (is_callable($body)) {
167 18
        $task = new T($name, $body);
168 9
    } elseif (is_array($body)) {
169 9
        $task = new GroupTask($name, $body);
170
    } else {
171
        throw new \InvalidArgumentException('Task should be a closure or array of other tasks.');
172
    }
173
174 18
    $task->saveSourceLocation();
175 18
    $deployer->tasks->set($name, $task);
176
177 18
    if (!empty(desc())) {
178 8
        $task->desc(desc());
179 8
        desc(''); // Clear title.
180
    }
181
182 18
    return $task;
183
}
184
185
/**
186
 * Call that task before specified task runs.
187
 *
188
 * @param string $task The task before $that should be run.
189
 * @param string|callable $todo The task to be run.
190
 * @return T|void
191
 */
192
function before($task, $todo)
193
{
194 1
    if (is_callable($todo)) {
195 1
        $newTask = task("$task:before", $todo);
196 1
        before($task, "$task:before");
197 1
        return $newTask;
198
    }
199 1
    task($task)->addBefore($todo);
200 1
}
201
202
/**
203
 * Call that task after specified task runs.
204
 *
205
 * @param string $task The task after $that should be run.
206
 * @param string|callable $todo The task to be run.
207
 * @return T|void
208
 */
209
function after($task, $todo)
210
{
211 1 View Code Duplication
    if (is_callable($todo)) {
212 1
        $newTask = task("$task:after", $todo);
213 1
        after($task, "$task:after");
214 1
        return $newTask;
215
    }
216 1
    task($task)->addAfter($todo);
217 1
}
218
219
/**
220
 * 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 $todo The task to be run.
224
 * @return T|void
225
 */
226
function fail($task, $todo)
227
{
228 8 View Code Duplication
    if (is_callable($todo)) {
229
        $newTask = task("$task:fail", $todo);
230
        fail($task, "$task:fail");
231
        return $newTask;
232
    }
233 8
    $deployer = Deployer::get();
234 8
    $deployer->fail->set($task, $todo);
235 8
}
236
237
/**
238
 * 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 8
    Deployer::get()->inputDefinition->addOption(
249 8
        new InputOption($name, $shortcut, $mode, $description, $default)
250
    );
251 8
}
252
253
/**
254
 * Change the current working directory.
255
 *
256
 * @param string $path
257
 */
258
function cd($path)
259
{
260 5
    set('working_path', parse($path));
261 5
}
262
263
/**
264
 * 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
 * Run command.
282
 *
283
 * @param string $command
284
 * @param array $options
285
 * @return string
286
 */
287
function run($command, $options = [])
288
{
289
    $run = function ($command, $options) {
290 6
        $host = Context::get()->getHost();
291
292 6
        $command = parse($command);
293 6
        $workingPath = get('working_path', '');
294
295 6
        if (!empty($workingPath)) {
296 5
            $command = "cd $workingPath && ($command)";
297
        }
298
299 6
        $env = get('env', []) + ($options['env'] ?? []);
300 6
        if (!empty($env)) {
301
            $env = array_to_string($env);
302
            $command = "export $env; $command";
303
        }
304
305 6
        if ($host instanceof Localhost) {
306 6
            $process = Deployer::get()->processRunner;
307 6
            $output = $process->run($host, $command, $options);
308
        } else {
309
            $client = Deployer::get()->sshClient;
310
            $output = $client->run($host, $command, $options);
311
        }
312
313 6
        return rtrim($output);
314 6
    };
315
316 6
    if (preg_match('/^sudo\b/', $command)) {
317
        try {
318
            return $run($command, $options);
319
        } catch (RunException $exception) {
320
            $askpass = get('sudo_askpass', '/tmp/dep_sudo_pass');
321
            $password = get('sudo_pass', false);
322
            if ($password === false) {
323
                writeln("<fg=green;options=bold>run</> $command");
324
                $password = askHiddenResponse('Password:');
325
            }
326
            $run("echo -e '#!/bin/sh\necho \"%secret%\"' > $askpass", array_merge($options, ['secret' => $password]));
327
            $run("chmod a+x $askpass", $options);
328
            $run(sprintf('export SUDO_ASKPASS=%s; %s', $askpass, preg_replace('/^sudo\b/', 'sudo -A', $command)), $options);
329
            $run("rm $askpass", $options);
330
        }
331
    } else {
332 6
        return $run($command, $options);
333
    }
334
}
335
336
337
/**
338
 * Execute commands on local machine
339
 *
340
 * @param string $command Command to run locally.
341
 * @param array $options
342
 * @return string Output of command.
343
 */
344
function runLocally($command, $options = [])
345
{
346 5
    $process = Deployer::get()->processRunner;
347 5
    $command = parse($command);
348
349 5
    $env = get('env', []) + ($options['env'] ?? []);
350 5
    if (!empty($env)) {
351
        $env = array_to_string($env);
352
        $command = "export $env; $command";
353
    }
354
355 5
    $output = $process->run(new Localhost(), $command, $options);
356
357 5
    return rtrim($output);
358
}
359
360
/**
361
 * Run test command.
362
 * Example:
363
 *
364
 *     test('[ -d {{release_path}} ]')
365
 *
366
 * @param string $command
367
 * @return bool
368
 */
369
function test($command)
370
{
371 5
    return run("if $command; then echo 'true'; fi") === 'true';
372
}
373
374
/**
375
 * Run test command locally.
376
 * Example:
377
 *
378
 *     testLocally('[ -d {{local_release_path}} ]')
379
 *
380
 * @param string $command
381
 * @return bool
382
 */
383
function testLocally($command)
384
{
385
    return runLocally("if $command; then echo 'true'; fi") === 'true';
386
}
387
388
/**
389
 * Iterate other hosts, allowing to call run func in callback.
390
 *
391
 * @experimental
392
 * @param Host|Host[] $hosts
393
 * @param callable $callback
394
 */
395
function on($hosts, callable $callback)
396
{
397
    $deployer = Deployer::get();
398
399
    if (!is_array($hosts) && !($hosts instanceof \Traversable)) {
400
        $hosts = [$hosts];
401
    }
402
403
    foreach ($hosts as $host) {
404
        if ($host instanceof Host) {
405
            $host->getConfig()->load();
406
            Context::push(new Context($host, input(), output()));
407
            try {
408
                $callback($host);
409
                $host->getConfig()->save();
410
            } catch (GracefulShutdownException $e) {
411
                $deployer->messenger->renderException($e, $host);
412
            } finally {
413
                Context::pop();
414
            }
415
        } else {
416
            throw new \InvalidArgumentException("Function on can iterate only on Host instances.");
417
        }
418
    }
419
}
420
421
/**
422
 * Run task
423
 *
424
 * @experimental
425
 * @param string $task
426
 */
427
function invoke($task)
428
{
429
    $hosts = [Context::get()->getHost()];
430
    $tasks = Deployer::get()->scriptManager->getTasks($task, $hosts);
0 ignored issues
show
Unused Code introduced by
The call to ScriptManager::getTasks() has too many arguments starting with $hosts.

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.

Loading history...
431
432
    $executor = Deployer::get()->seriesExecutor;
0 ignored issues
show
Bug introduced by
The property seriesExecutor does not seem to exist. Did you mean executor?

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.

Loading history...
433
    $executor->run($tasks, $hosts);
434
}
435
436
/*
437
 * Upload file or directory to host.
438
 */
439 View Code Duplication
function upload(string $source, string $destination, $config = [])
0 ignored issues
show
Duplication introduced by
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...
440
{
441
    $rsync = Deployer::get()->rsync;
442
    $host = currentHost();
443
    $source = parse($source);
444
    $destination = parse($destination);
445
446
    if ($host instanceof Localhost) {
447
        $rsync->call($host, $source, $destination, $config);
448
    } else {
449
        $rsync->call($host, $source, "{$host->hostname()}:$destination", $config);
450
    }
451
}
452
453
/*
454
 * Download file or directory from host
455
 */
456 View Code Duplication
function download(string $source, string $destination, $config = [])
0 ignored issues
show
Duplication introduced by
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...
457
{
458
    $rsync = Deployer::get()->rsync;
459
    $host = currentHost();
460
    $source = parse($source);
461
    $destination = parse($destination);
462
463
    if ($host instanceof Localhost) {
464
        $rsync->call($host, $source, $destination, $config);
465
    } else {
466
        $rsync->call($host, "{$host->hostname()}:$source", $destination, $config);
467
    }
468
}
469
470
/**
471
 * Writes an info message.
472
 * @param string $message
473
 */
474
function info($message)
475
{
476 5
    output()->writeln("<fg=green;options=bold>info</> " . parse($message));
477 5
}
478
479
/**
480
 * Writes an warning message.
481
 * @param string $message
482
 */
483
function warning($message)
484
{
485
    writeln("<fg=yellow;options=bold>warning</> <comment>" . parse($message) . "</comment>");
486
}
487
488
/**
489
 * Writes a message to the output and adds a newline at the end.
490
 * @param string|array $message
491
 * @param int $options
492
 */
493
function writeln($message, $options = 0)
494
{
495 1
    $host = currentHost();
496 1
    output()->writeln("[{$host->tag()}] " . parse($message), $options);
497 1
}
498
499
/**
500
 * Writes a message to the output.
501
 * @param string $message
502
 * @param int $options
503
 */
504
function write($message, $options = 0)
505
{
506
    output()->write(parse($message), $options);
507
}
508
509
/**
510
 * Setup configuration option.
511
 *
512
 * @param string $name
513
 * @param mixed $value
514
 */
515 View Code Duplication
function set($name, $value)
0 ignored issues
show
Duplication introduced by
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...
516
{
517 8
    if (!Context::has()) {
518 8
        Deployer::get()->config->set($name, $value);
519
    } else {
520 5
        Context::get()->getConfig()->set($name, $value);
521
    }
522 8
}
523
524
/**
525
 * Merge new config params to existing config array.
526
 *
527
 * @param string $name
528
 * @param array $array
529
 */
530 View Code Duplication
function add($name, $array)
0 ignored issues
show
Duplication introduced by
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...
531
{
532
    if (!Context::has()) {
533
        Deployer::get()->config->add($name, $array);
534
    } else {
535
        Context::get()->getConfig()->add($name, $array);
536
    }
537
}
538
539
/**
540
 * Get configuration value.
541
 *
542
 * @param string $name
543
 * @param mixed|null $default
544
 * @return mixed
545
 */
546 View Code Duplication
function get($name, $default = null)
0 ignored issues
show
Duplication introduced by
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...
547
{
548 7
    if (!Context::has()) {
549
        return Deployer::get()->config->get($name, $default);
550
    } else {
551 7
        return Context::get()->getConfig()->get($name, $default);
552
    }
553
}
554
555
/**
556
 * Check if there is such configuration option.
557
 *
558
 * @param string $name
559
 * @return boolean
560
 */
561 View Code Duplication
function has($name)
0 ignored issues
show
Duplication introduced by
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...
562
{
563 4
    if (!Context::has()) {
564
        return Deployer::get()->config->has($name);
565
    } else {
566 4
        return Context::get()->getConfig()->has($name);
567
    }
568
}
569
570
/**
571
 * @param string $message
572
 * @param string|null $default
573
 * @param string[]|null $autocomplete
574
 * @return string
575
 */
576
function ask($message, $default = null, $autocomplete = null)
577
{
578
    Context::required(__FUNCTION__);
579
580
    if (output()->isQuiet()) {
581
        return $default;
582
    }
583
584
    /** @var QuestionHelper $helper */
585
    $helper = Deployer::get()->getHelper('question');
586
587
    $tag = currentHost()->tag();
588
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
589
590
    $question = new Question($message, $default);
591
    if (!empty($autocomplete)) {
592
        $question->setAutocompleterValues($autocomplete);
593
    }
594
595
    try {
596
        return $helper->ask(input(), output(), $question);
597
    } catch (MissingInputException $exception) {
598
        throw new Exception("Failed to read input from stdin.\nMake sure what you are asking for input not from parallel task.", $exception->getCode(), $exception);
599
    }
600
}
601
602
/**
603
 * @param string $message
604
 * @param string[] $availableChoices
605
 * @param string|null $default
606
 * @param bool|false $multiselect
607
 * @return string|string[]
608
 */
609
function askChoice($message, array $availableChoices, $default = null, $multiselect = false)
610
{
611
    Context::required(__FUNCTION__);
612
613
    if (empty($availableChoices)) {
614
        throw new \InvalidArgumentException('Available choices should not be empty');
615
    }
616
617
    if ($default !== null && !array_key_exists($default, $availableChoices)) {
618
        throw new \InvalidArgumentException('Default choice is not available');
619
    }
620
621
    if (output()->isQuiet()) {
622
        if ($default === null) {
623
            $default = key($availableChoices);
624
        }
625
        return [$default => $availableChoices[$default]];
626
    }
627
628
    $helper = Deployer::get()->getHelper('question');
629
630
    $tag = currentHost()->tag();
631
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
632
633
    $question = new ChoiceQuestion($message, $availableChoices, $default);
634
    $question->setMultiselect($multiselect);
635
636
    return $helper->ask(input(), output(), $question);
637
}
638
639
/**
640
 * @param string $message
641
 * @param bool $default
642
 * @return bool
643
 */
644
function askConfirmation($message, $default = false)
645
{
646
    Context::required(__FUNCTION__);
647
648
    if (output()->isQuiet()) {
649
        return $default;
650
    }
651
652
    $helper = Deployer::get()->getHelper('question');
653
654
    $yesOrNo = $default ? 'Y/n' : 'y/N';
655
    $tag = currentHost()->tag();
656
    $message = "[$tag] <question>$message</question> [$yesOrNo] ";
657
658
    $question = new ConfirmationQuestion($message, $default);
659
660
    return $helper->ask(input(), output(), $question);
661
}
662
663
/**
664
 * @param string $message
665
 * @return string
666
 */
667
function askHiddenResponse($message)
668
{
669
    Context::required(__FUNCTION__);
670
671
    if (output()->isQuiet()) {
672
        return '';
673
    }
674
675
    $helper = Deployer::get()->getHelper('question');
676
677
    $tag = currentHost()->tag();
678
    $message = "[$tag] <question>$message</question> ";
679
680
    $question = new Question($message);
681
    $question->setHidden(true);
682
    $question->setHiddenFallback(false);
683
684
    return $helper->ask(input(), output(), $question);
685
}
686
687
/**
688
 * @return InputInterface
689
 */
690
function input()
691
{
692 4
    return Context::get()->getInput();
693
}
694
695
696
/**
697
 * @return OutputInterface
698
 */
699
function output()
700
{
701 6
    return Context::get()->getOutput();
702
}
703
704
/**
705
 * Check if command exists
706
 *
707
 * @param string $command
708
 * @return bool
709
 */
710
function commandExist($command)
711
{
712 3
    return test("hash $command 2>/dev/null");
713
}
714
715
function commandSupportsOption($command, $option)
716
{
717 5
    return test("[[ $(man $command 2>&1 || $command -h 2>&1 || $command --help 2>&1) =~ '$option' ]]");
718
}
719
720
/**
721
 * Parse set values.
722
 *
723
 * @param string $value
724
 * @return string
725
 */
726
function parse($value)
727
{
728 8
    return Context::get()->getConfig()->parse($value);
729
}
730
731
function locateBinaryPath($name)
732
{
733 4
    $nameEscaped = escapeshellarg($name);
734
735
    // Try `command`, should cover all Bourne-like shells
736
    // Try `which`, should cover most other cases
737
    // Fallback to `type` command, if the rest fails
738 4
    $path = run("command -v $nameEscaped || which $nameEscaped || type -p $nameEscaped");
739 4
    if ($path) {
740
        // Deal with issue when `type -p` outputs something like `type -ap` in some implementations
741 4
        return trim(str_replace("$name is", "", $path));
742
    }
743
744
    throw new \RuntimeException("Can't locate [$nameEscaped] - neither of [command|which|type] commands are available");
745
}
746