-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
RedispatchHook.php
188 lines (168 loc) · 6.3 KB
/
RedispatchHook.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<?php
namespace Drush\Runtime;
use Consolidation\AnnotatedCommand\Hooks\InitializeHookInterface;
use Drush\Drush;
use Symfony\Component\Console\Input\InputInterface;
use Consolidation\AnnotatedCommand\AnnotationData;
use Drush\Log\LogLevel;
use Robo\Contract\ConfigAwareInterface;
use Robo\Common\ConfigAwareTrait;
/**
* The RedispatchHook is installed as an init hook that runs before
* all commands. If the commandline contains an alias or a site specification
* that points at a remote machine, then we will stop execution of the
* current command and instead run the command remotely.
*/
class RedispatchHook implements InitializeHookInterface, ConfigAwareInterface
{
use ConfigAwareTrait;
/**
* Check to see if it is necessary to redispatch to a remote site.
* We do not redispatch to local sites here; usually, local sites may
* simply be selected and require no redispatch. When a local redispatch
* is needed, it happens in the RedispatchToSiteLocal class.
*
* @param InputInterface $input
* @param AnnotationData $annotationData
*/
public function initialize(InputInterface $input, AnnotationData $annotationData)
{
// See drush_preflight_command_dispatch; also needed are:
// - redispatch to a different site-local Drush on same system
// - site-list handling (REMOVED)
// These redispatches need to be done regardless of the presence
// of a @handle-remote-commands annotation.
// If the command has the @handle-remote-commands annotation, then
// short-circuit redispatches to remote hosts.
if ($annotationData->has('handle-remote-commands')) {
return;
}
return $this->redispatchIfRemote($input);
}
/**
* Check to see if the target of the command is remote. Call redispatch
* if it is.
*
* @param InputInterface $input
*/
public function redispatchIfRemote(InputInterface $input)
{
// Determine if this is a remote command.
// n.b. 'hasOption' only means that the option definition exists, so don't use that here.
$root = $input->getOption('remote-host');
if (!empty($root)) {
return $this->redispatch($input);
}
}
/**
* Called from RemoteCommandProxy::execute() to run remote commands.
*
* @param InputInterface $input
*/
public function redispatch(InputInterface $input)
{
$remote_host = $input->getOption('remote-host');
$remote_user = $input->getOption('remote-user');
// Get the command arguments, and shift off the Drush command.
$redispatchArgs = Drush::config()->get('runtime.argv');
$drush_path = array_shift($redispatchArgs);
$command_name = array_shift($redispatchArgs);
Drush::logger()->debug('Redispatch hook {command}', ['command' => $command_name]);
// Remove argument patterns that should not be propagated
$redispatchArgs = $this->alterArgsForRedispatch($redispatchArgs);
// Fetch the commandline options to pass along to the remote command.
// The options the user provided on the commandline will be included
// in $redispatchArgs. Here, we only need to provide those
// preflight options that should be propagated.
$redispatchOptions = $this->redispatchOptions($input);
$backend_options = [
'drush-script' => $this->getConfig()->get('paths.drush-script', null),
'remote-host' => $remote_host,
'remote-user' => $remote_user,
'additional-global-options' => [],
'integrate' => true,
'backend' => false,
];
if ($input->isInteractive()) {
$backend_options['#tty'] = true;
$backend_options['interactive'] = true;
}
$invocations = [
[
'command' => $command_name,
'args' => $redispatchArgs,
],
];
$common_backend_options = [];
$default_command = null;
$default_site = [
'remote-host' => $remote_host,
'remote-user' => $remote_user,
'root' => $input->getOption('root'),
'uri' => $input->getOption('uri'),
];
$context = null;
$values = drush_backend_invoke_concurrent(
$invocations,
$redispatchOptions,
$backend_options,
$default_command,
$default_site,
$context
);
return $this->exitEarly($values);
}
/**
* Collect the options to use in a redispatch.
*
* @param InputInterface $input
*/
protected function redispatchOptions(InputInterface $input)
{
return [];
$result = [];
$redispatchOptionList = [
'root',
'uri',
];
foreach ($redispatchOptionList as $option) {
$value = $input->hasOption($option) ? $input->getOption($option) : false;
if ($value === true) {
$result[$option] = true;
} elseif (is_string($value) && !empty($value)) {
$result[$option] = $value;
}
}
return $result;
}
/**
* Remove anything that is not necessary for the remote side.
* At the moment this is limited to configuration options
* provided via -D.
*
* @param array $redispatchArgs
*/
protected function alterArgsForRedispatch($redispatchArgs)
{
return array_filter($redispatchArgs, function ($item) {
return strpos($item, '-D') !== 0;
});
}
/**
* Abort the current execution without causing distress to our
* shutdown handler.
*
* @param array $values The results from backend invoke.
*/
protected function exitEarly($values)
{
Drush::logger()->log(LogLevel::DEBUG, 'Redispatch hook exit early');
// TODO: This is how Drush exits from redispatch commands today;
// perhaps this could be somewhat improved, though.
// Note that RemoteCommandProxy::execute() is expecting that
// the redispatch() method will not return, so that will need
// to be altered if this behavior is changed.
drush_set_context('DRUSH_EXECUTION_COMPLETED', true);
exit($values['error_status']);
}
}