Passed
Push — master ( 55fc69...722b69 )
by Anton
02:20
created

functions.php ➔ on()   C

Complexity

Conditions 7
Paths 32

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7.0283

Importance

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