functions.php ➔ upload()   B
last analyzed

Complexity

Conditions 6
Paths 9

Size

Total Lines 26

Duplication

Lines 26
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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