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 ( f46b7f...632562 )
by Anton
02:07
created

functions.php ➔ run()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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