124903a6e2631161d7b52cde37bfb83cfe58ed30
[yaffs-website] / vendor / drush / drush / src / Commands / core / RsyncCommands.php
1 <?php
2 namespace Drush\Commands\core;
3
4 use Consolidation\AnnotatedCommand\CommandData;
5 use Drush\Commands\DrushCommands;
6 use Drush\Drush;
7 use Drush\Exceptions\UserAbortException;
8 use Consolidation\SiteAlias\HostPath;
9 use Consolidation\SiteAlias\SiteAliasManagerAwareInterface;
10 use Consolidation\SiteAlias\SiteAliasManagerAwareTrait;
11 use Drush\Backend\BackendPathEvaluator;
12 use Drush\Config\ConfigLocator;
13 use Symfony\Component\Console\Event\ConsoleCommandEvent;
14
15 class RsyncCommands extends DrushCommands implements SiteAliasManagerAwareInterface
16 {
17     use SiteAliasManagerAwareTrait;
18
19     /**
20      * These are arguments after the aliases and paths have been evaluated.
21      * @see validate().
22      */
23     /** @var HostPath */
24     public $sourceEvaluatedPath;
25     /** @var HostPath */
26     public $targetEvaluatedPath;
27     /** @var BackendPathEvaluator */
28     protected $pathEvaluator;
29
30     public function __construct()
31     {
32         // TODO: once the BackendInvoke service exists, inject it here
33         // and use it to get the path evaluator
34         $this->pathEvaluator = new BackendPathEvaluator();
35     }
36
37     /**
38      * Rsync Drupal code or files to/from another server using ssh.
39      *
40      * @command core:rsync
41      * @param $source A site alias and optional path. See rsync documentation and example.site.yml.
42      * @param $target A site alias and optional path. See rsync documentation and example.site.yml.',
43      * @param $extra Additional parameters after the ssh statement.
44      * @optionset_ssh
45      * @option exclude-paths List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).
46      * @option include-paths List of paths to include, seperated by : (Unix-based systems) or ; (Windows).
47      * @option mode The unary flags to pass to rsync; --mode=rultz implies rsync -rultz.  Default is -akz.
48      * @usage drush rsync @dev @stage
49      *   Rsync Drupal root from Drush alias dev to the alias stage.
50      * @usage drush rsync ./ @stage:%files/img
51      *   Rsync all files in the current directory to the 'img' directory in the file storage folder on the Drush alias stage.
52      * @usage drush rsync @dev @stage -- --exclude=*.sql --delete
53      *   Rsync Drupal root from the Drush alias dev to the alias stage, excluding all .sql files and delete all files on the destination that are no longer on the source.
54      * @usage drush rsync @dev @stage --ssh-options="-o StrictHostKeyChecking=no" -- --delete
55      *   Customize how rsync connects with remote host via SSH. rsync options like --delete are placed after a --.
56      * @aliases rsync,core-rsync
57      * @topics docs:aliases
58      */
59     public function rsync($source, $target, array $extra, $options = ['exclude-paths' => self::REQ, 'include-paths' => self::REQ, 'mode' => 'akz'])
60     {
61         // Prompt for confirmation. This is destructive.
62         if (!\Drush\Drush::simulate()) {
63             $this->output()->writeln(dt("You will delete files in !target and replace with data from !source", ['!source' => $this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash(), '!target' => $this->targetEvaluatedPath->fullyQualifiedPath()]));
64             if (!$this->io()->confirm(dt('Do you want to continue?'))) {
65                 throw new UserAbortException();
66             }
67         }
68
69         $rsync_options = $this->rsyncOptions($options);
70         $parameters = array_merge([$rsync_options], $extra);
71         $parameters[] = $this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash();
72         $parameters[] = $this->targetEvaluatedPath->fullyQualifiedPath();
73
74         $ssh_options = Drush::config()->get('ssh.options', '');
75         $exec = "rsync -e 'ssh $ssh_options'". ' '. implode(' ', array_filter($parameters));
76         $exec_result = drush_op_system($exec);
77
78         if ($exec_result == 0) {
79             drush_backend_set_result($this->targetEvaluatedPath->fullyQualifiedPath());
80         } else {
81             throw new \Exception(dt("Could not rsync from !source to !dest", ['!source' => $this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash(), '!dest' => $this->targetEvaluatedPath->fullyQualifiedPath()]));
82         }
83     }
84
85     public function rsyncOptions($options)
86     {
87         $verbose = $paths = '';
88         // Process --include-paths and --exclude-paths options the same way
89         foreach (['include', 'exclude'] as $include_exclude) {
90             // Get the option --include-paths or --exclude-paths and explode to an array of paths
91             // that we will translate into an --include or --exclude option to pass to rsync
92             $inc_ex_path = explode(PATH_SEPARATOR, @$options[$include_exclude . '-paths']);
93             foreach ($inc_ex_path as $one_path_to_inc_ex) {
94                 if (!empty($one_path_to_inc_ex)) {
95                     $paths .= ' --' . $include_exclude . '="' . $one_path_to_inc_ex . '"';
96                 }
97             }
98         }
99
100         $mode = '-'. $options['mode'];
101         if ($this->output()->isVerbose()) {
102             $mode .= 'v';
103             $verbose = ' --stats --progress';
104         }
105
106         return implode(' ', array_filter([$mode, $verbose, $paths]));
107     }
108
109     /**
110      * Evaluate the path aliases in the source and destination
111      * parameters. We do this in the pre-command-event so that
112      * we can set up the configuration object to include options
113      * from the source and target aliases, if any, so that these
114      * values may participate in configuration injection.
115      *
116      * @hook command-event core:rsync
117      * @param ConsoleCommandEvent $event
118      * @throws \Exception
119      * @return void
120      */
121     public function preCommandEvent(ConsoleCommandEvent $event)
122     {
123         $input = $event->getInput();
124         $this->sourceEvaluatedPath = $this->injectAliasPathParameterOptions($input, 'source');
125         $this->targetEvaluatedPath = $this->injectAliasPathParameterOptions($input, 'target');
126     }
127
128     protected function injectAliasPathParameterOptions($input, $parameterName)
129     {
130         // The Drush configuration object is a ConfigOverlay; fetch the alias
131         // context, that already has the options et. al. from the
132         // site-selection alias ('drush @site rsync ...'), @self.
133         $aliasConfigContext = $this->getConfig()->getContext(ConfigLocator::ALIAS_CONTEXT);
134         $manager = $this->siteAliasManager();
135
136         $aliasName = $input->getArgument($parameterName);
137         $evaluatedPath = HostPath::create($manager, $aliasName);
138         $this->pathEvaluator->evaluate($evaluatedPath);
139
140         $aliasRecord = $evaluatedPath->getAliasRecord();
141
142         // If the path is remote, then we will also inject the global
143         // options into the alias config context so that we pick up
144         // things like ssh-options.
145         if ($aliasRecord->isRemote()) {
146             $aliasConfigContext->combine($aliasRecord->export());
147         }
148
149         return $evaluatedPath;
150     }
151
152     /**
153      * Validate that passed aliases are valid.
154      *
155      * @hook validate core-rsync
156      * @param \Consolidation\AnnotatedCommand\CommandData $commandData
157      * @throws \Exception
158      * @return void
159      */
160     public function validate(CommandData $commandData)
161     {
162         if ($this->sourceEvaluatedPath->isRemote() && $this->targetEvaluatedPath->isRemote()) {
163             $msg = dt("Cannot specify two remote aliases. Instead, use one of the following alternate options:\n\n    `drush {source} rsync @self {target}`\n    `drush {source} rsync @self {fulltarget}\n\nUse the second form if the site alias definitions are not available at {source}.", ['source' => $source, 'target' => $target, 'fulltarget' => $this->targetEvaluatedPath->fullyQualifiedPath()]);
164             throw new \Exception($msg);
165         }
166     }
167 }