1 | <?php |
||
2 | /** |
||
3 | * @file |
||
4 | * Actions for Deployotron. |
||
5 | */ |
||
6 | |||
7 | namespace Deployotron { |
||
8 | |||
9 | 10 | define('COLOR_RED', "\033[1;31;40m\033[1m"); |
|
10 | 10 | define('COLOR_YELLOW', "\033[1;33;40m\033[1m"); |
|
11 | 10 | define('COLOR_GREEN', "\033[1;32;40m\033[1m"); |
|
12 | 10 | define('COLOR_RESET', "\033[0m"); |
|
13 | |||
14 | /** |
||
15 | * Creates actions to run. |
||
16 | */ |
||
17 | class ActionFactory { |
||
18 | static protected $actionMapping = array( |
||
19 | 'deploy' => array( |
||
20 | 'SanityCheck', |
||
21 | 'SiteOffline', |
||
22 | 'BackupDatabase', |
||
23 | 'DeployCode', |
||
24 | 'CreateVersionTxt', |
||
25 | 'UpdateDatabase', |
||
26 | 'ClearCache', |
||
27 | 'SiteOnline', |
||
28 | 'PurgeDatabaseBackups', |
||
29 | 'FlowdockNotificaton', |
||
30 | 'NewRelicNotificaton', |
||
31 | ), |
||
32 | 'omg' => array( |
||
33 | 'SiteOffline', |
||
34 | 'OMGPrepare', |
||
35 | 'DeployCode', |
||
36 | 'CreateVersionTxt', |
||
37 | 'RestoreDatabase', |
||
38 | 'SiteOnline', |
||
39 | 'ClearCache', |
||
40 | ), |
||
41 | ); |
||
42 | |||
43 | /** |
||
44 | * Gathers an array of actions to run. |
||
45 | */ |
||
46 | 10 | static public function getActions($name, $site = NULL) { |
|
47 | 10 | $actions = array(); |
|
48 | 10 | if (isset(static::$actionMapping[$name])) { |
|
49 | 10 | foreach (static::$actionMapping[$name] as $class_name) { |
|
50 | 10 | $class_name = '\\Deployotron\\Actions\\' . $class_name; |
|
51 | 10 | $action = new $class_name($site); |
|
52 | 10 | $actions = array_merge($actions, |
|
53 | 10 | static::getActionCommands('pre', $action, $site), |
|
54 | 10 | array($action), |
|
55 | 10 | static::getActionCommands('post', $action, $site)); |
|
56 | 10 | } |
|
57 | 10 | return $actions; |
|
58 | } |
||
59 | } |
||
60 | |||
61 | /** |
||
62 | * Gets pre/post commands for an action. |
||
63 | */ |
||
64 | 10 | static protected function getActionCommands($type, $action, $site) { |
|
65 | 10 | $actions = array(); |
|
66 | 10 | $switch = $type . '-' . $action->getSwitchSuffix(); |
|
67 | 10 | if ($action->enabled() && !drush_get_option('no-' . $switch, FALSE)) { |
|
68 | // @todo drush_get_option_list() doesn't play well with commas. |
||
69 | 10 | if ($commands = drush_get_option_list($switch, FALSE)) { |
|
70 | 10 | foreach ($commands as $command) { |
|
71 | 10 | if (!empty($command)) { |
|
72 | 1 | $actions[] = new Actions\CommandAction($site, $command); |
|
73 | 1 | } |
|
74 | 10 | } |
|
75 | 10 | } |
|
76 | 10 | } |
|
77 | |||
78 | 10 | return $actions; |
|
79 | } |
||
80 | |||
81 | /** |
||
82 | * Output help on actions and options. |
||
83 | */ |
||
84 | 1 | static public function getHelp() { |
|
85 | 1 | $all_actions = array(); |
|
86 | 1 | drush_print('Commands'); |
|
87 | 1 | drush_print('--------'); |
|
88 | 1 | foreach (static::$actionMapping as $name => $actions) { |
|
89 | 1 | drush_print(wordwrap(dt('@name runs the actions: @actions', array( |
|
90 | 1 | '@name' => $name, |
|
91 | 1 | '@actions' => implode(', ', $actions), |
|
92 | 1 | )))); |
|
93 | 1 | drush_print(); |
|
94 | |||
95 | 1 | $all_actions = array_merge($all_actions, $actions); |
|
96 | 1 | } |
|
97 | 1 | $all_actions = array_unique($all_actions); |
|
98 | 1 | sort($all_actions); |
|
99 | |||
100 | 1 | drush_print('Actions'); |
|
101 | 1 | drush_print('-------'); |
|
102 | 1 | foreach ($all_actions as $class_name) { |
|
103 | 1 | drush_print($class_name . ':'); |
|
104 | 1 | $class_name = '\\Deployotron\\Actions\\' . $class_name; |
|
105 | 1 | $action = new $class_name('@self'); |
|
106 | 1 | drush_print($action->getHelp()); |
|
107 | 1 | drush_print(); |
|
108 | |||
109 | // Print options. |
||
110 | 1 | $options = $action->getOptions(); |
|
111 | 1 | if ($options) { |
|
112 | 1 | drush_print_table(drush_format_help_section($options, 'options')); |
|
113 | 1 | drush_print(); |
|
114 | 1 | } |
|
115 | 1 | drush_print(); |
|
116 | 1 | } |
|
117 | |||
118 | 1 | } |
|
119 | } |
||
120 | |||
121 | /** |
||
122 | * Base class for actions. |
||
123 | */ |
||
124 | abstract class Action { |
||
125 | /** |
||
126 | * Set a default short description. |
||
127 | */ |
||
128 | protected $short = "incompletely implemented"; |
||
129 | |||
130 | /** |
||
131 | * Default run message. |
||
132 | */ |
||
133 | protected $runMessage = "Running action."; |
||
134 | |||
135 | /** |
||
136 | * Options for this action. |
||
137 | */ |
||
138 | protected $options = array(); |
||
139 | |||
140 | /** |
||
141 | * The site this action works on. |
||
142 | */ |
||
143 | protected $site; |
||
144 | |||
145 | /** |
||
146 | * Result object of last drush command. |
||
147 | */ |
||
148 | protected $drushResult; |
||
149 | |||
150 | /** |
||
151 | * Array of the output of the last executed command. |
||
152 | */ |
||
153 | protected $shOutput; |
||
154 | |||
155 | /** |
||
156 | * Return code of the last executed command. |
||
157 | */ |
||
158 | protected $shRc; |
||
159 | |||
160 | /** |
||
161 | * Shas of checkouts of aliases. |
||
162 | */ |
||
163 | static protected $headShas = array(); |
||
164 | |||
165 | /** |
||
166 | * Construct a new Action object. |
||
167 | */ |
||
168 | 11 | public function __construct($site) { |
|
169 | 11 | $this->site = $site; |
|
170 | |||
171 | 11 | if ($this->short == "incompletely implemented") { |
|
172 | 1 | drush_log(dt('Incomplete action, missing short description: @action', array('@action' => get_class($this))), 'warning'); |
|
173 | 1 | } |
|
174 | |||
175 | 11 | if (!isset($this->runMessage)) { |
|
176 | $this->runMessage = 'Running ' . $this->short; |
||
177 | } |
||
178 | 11 | } |
|
179 | |||
180 | /** |
||
181 | * Get help description. |
||
182 | */ |
||
183 | 1 | public function getHelp() { |
|
184 | 1 | if (isset($this->help)) { |
|
185 | 1 | return $this->help; |
|
186 | } |
||
187 | 1 | if (isset($this->description)) { |
|
188 | 1 | return $this->description; |
|
189 | } |
||
190 | 1 | return "No description."; |
|
191 | } |
||
192 | |||
193 | /** |
||
194 | * Get the command line options for this action. |
||
195 | * |
||
196 | * If the command has an enable-switch, the kill-switch and command options |
||
197 | * are classed as sub-options for nice help output. If there's no |
||
198 | * enable-switch, all options are considered regular options. |
||
199 | * |
||
200 | * @return array |
||
201 | * Options and sub-options as defined on commands in hook_drush_command(). |
||
202 | */ |
||
203 | 10 | public function getOptions() { |
|
204 | 10 | $options = array(); |
|
205 | 10 | if (isset($this->killSwitch)) { |
|
206 | $options += array( |
||
207 | 10 | $this->killSwitch => "Don't " . $this->short . (isset($this->enableSwitch) ? ' (overrides --' . $this->enableSwitch . ')' : '') . '.', |
|
208 | ); |
||
209 | 10 | } |
|
210 | |||
211 | 10 | $options += $this->options; |
|
212 | |||
213 | $prest_options = array( |
||
214 | 'pre/post-*' => array( |
||
215 | 10 | 'pre-' . $this->getSwitchSuffix() => "Before " . $this->short . ".", |
|
216 | 10 | 'post-' . $this->getSwitchSuffix() => "After " . $this->short . ".", |
|
217 | 10 | ), |
|
218 | 10 | ); |
|
219 | |||
220 | 10 | if (isset($this->enableSwitch)) { |
|
221 | 10 | $sub_options = $options; |
|
222 | 10 | $options = array(); |
|
223 | // If the action options defines the enable switch, use it so |
||
224 | // description and possible example-values propergate. |
||
225 | 10 | if (isset($sub_options[$this->enableSwitch])) { |
|
226 | 10 | $options[$this->enableSwitch] = $sub_options[$this->enableSwitch]; |
|
227 | 10 | unset($sub_options[$this->enableSwitch]); |
|
228 | 10 | } |
|
229 | else { |
||
230 | $options[$this->enableSwitch] = ucfirst($this->short) . '.'; |
||
231 | } |
||
232 | 10 | $options['pre/post-*'] = "Commands to run before/after action."; |
|
233 | return array( |
||
234 | 10 | 'options' => $options, |
|
235 | 10 | 'sub-options' => (!empty($sub_options) ? array($this->enableSwitch => $sub_options) : array()) + $prest_options, |
|
236 | 10 | ); |
|
237 | } |
||
238 | |||
239 | 10 | $options['pre/post-*'] = "Commands to run before/after action."; |
|
240 | 10 | return array('options' => $options, 'sub-options' => $prest_options); |
|
241 | } |
||
242 | |||
243 | /** |
||
244 | * Get the switch suffix for --pre-/--post- switches. |
||
245 | */ |
||
246 | 10 | public function getSwitchSuffix() { |
|
247 | 10 | if (isset($this->switchSuffix)) { |
|
248 | 10 | return $this->switchSuffix; |
|
249 | } |
||
250 | 10 | elseif (isset($this->enableSwitch)) { |
|
251 | return $this->enableSwitch; |
||
252 | } |
||
253 | 10 | elseif (isset($this->killSwitch)) { |
|
254 | 10 | return preg_replace('/^no-/', '', $this->killSwitch); |
|
255 | } |
||
256 | else { |
||
257 | 10 | return preg_replace(array('/[^a-z0-9 ]/', '/ /'), array('', '-'), strtolower($this->short)); |
|
258 | } |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Get the short description of this action. |
||
263 | * |
||
264 | * If the action has a killswitch, it should work with "Don't " prepended. |
||
265 | * |
||
266 | * @return string |
||
267 | * Human readable short description of action taken. |
||
268 | */ |
||
269 | 8 | public function getShort() { |
|
270 | 8 | return $this->short; |
|
271 | } |
||
272 | |||
273 | /** |
||
274 | * Return the message to print when running this action. |
||
275 | * |
||
276 | * @return string |
||
277 | * The Message (by Grandmaster Flash). |
||
278 | */ |
||
279 | 8 | public function getRunMessage() { |
|
280 | 8 | return $this->runMessage; |
|
281 | } |
||
282 | |||
283 | /** |
||
284 | * Get a description of what the action would do. |
||
285 | * |
||
286 | * If the action subclass defines the $description property, use that, else |
||
287 | * returns a generic message. |
||
288 | * |
||
289 | * If more logic than a static string is needed, subclassesare encouraged to |
||
290 | * override this to provide a more specific description of the effect of the |
||
291 | * action. |
||
292 | * |
||
293 | * Used when the user provides the --confirm switch. |
||
294 | * |
||
295 | * @return string |
||
296 | * Description of the action. |
||
297 | */ |
||
298 | 10 | public function getDescription() { |
|
299 | 10 | if (isset($this->description)) { |
|
300 | 9 | return $this->description; |
|
301 | } |
||
302 | 1 | return dt("Run the @short action.", array('@short' => $this->short)); |
|
303 | } |
||
304 | |||
305 | /** |
||
306 | * Validate that the action can be run. |
||
307 | * |
||
308 | * @return bool |
||
309 | * Whether it can. |
||
310 | */ |
||
311 | 9 | public function validate() { |
|
312 | 9 | return TRUE; |
|
313 | } |
||
314 | |||
315 | /** |
||
316 | * Run the task. |
||
317 | * |
||
318 | * @param array $state |
||
319 | * Persistent state array that actions can use to communicate with |
||
320 | * following actions. |
||
321 | * |
||
322 | * @return bool |
||
323 | * Success or failure. |
||
324 | */ |
||
325 | 1 | public function run(\ArrayObject $state) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
326 | 1 | return FALSE; |
|
327 | } |
||
328 | |||
329 | /** |
||
330 | * Roll back the task. |
||
331 | */ |
||
332 | 2 | public function rollback() { |
|
333 | 2 | return 'no rollback'; |
|
334 | } |
||
335 | |||
336 | /** |
||
337 | * Whether this action is enabled. |
||
338 | */ |
||
339 | 11 | public function enabled() { |
|
340 | // Incomplete actions are always disabled. |
||
341 | 11 | if ($this->short == "incompletely implemented") { |
|
342 | 1 | return FALSE; |
|
343 | } |
||
344 | // Disable if there is a killswitch and it is set. |
||
345 | 10 | if (isset($this->killSwitch) && drush_get_option($this->killSwitch, FALSE)) { |
|
346 | 3 | return FALSE; |
|
347 | } |
||
348 | // If there is an enable switch, let that decide. |
||
349 | 10 | if (isset($this->enableSwitch)) { |
|
350 | 10 | return drush_get_option($this->enableSwitch, FALSE); |
|
351 | } |
||
352 | // Else default to enabled. |
||
353 | 10 | return TRUE; |
|
354 | } |
||
355 | |||
356 | /** |
||
357 | * Execute a sh command. |
||
358 | * |
||
359 | * Is run on the site. |
||
360 | * |
||
361 | * @param string $command |
||
362 | * Command to run. |
||
363 | * |
||
364 | * @return bool |
||
365 | * Whether the command succeeded. |
||
366 | */ |
||
367 | 9 | protected function sh($command) { |
|
368 | 9 | $exec = $command; |
|
369 | // Check that there is a remote host before trying to construct a remote |
||
370 | // command. |
||
371 | 9 | $host = drush_remote_host($this->site); |
|
372 | 9 | if (!empty($host)) { |
|
373 | $exec = drush_shell_proc_build($this->site, $command, TRUE); |
||
374 | } |
||
375 | else { |
||
376 | // Else just cd to the root of the alias. This allows us to test the |
||
377 | // code without a remote host. |
||
378 | 9 | $exec = "cd " . drush_escapeshellarg($this->site['root']) . " && " . $exec; |
|
379 | } |
||
380 | 9 | return $this->shLocal($exec); |
|
381 | } |
||
382 | |||
383 | /** |
||
384 | * Get the output of the most recent command. |
||
385 | * |
||
386 | * @return string |
||
387 | * The output. |
||
388 | */ |
||
389 | 9 | protected function shOutput() { |
|
390 | 9 | return implode("\n", $this->shOutputArray()); |
|
391 | } |
||
392 | |||
393 | /** |
||
394 | * Get the output of the most recent command as an array. |
||
395 | * |
||
396 | * @return array |
||
397 | * Lines of output. |
||
398 | */ |
||
399 | 9 | protected function shOutputArray() { |
|
400 | 9 | return $this->shOutput; |
|
401 | } |
||
402 | |||
403 | /** |
||
404 | * Get the return code of the most recent command. |
||
405 | */ |
||
406 | 1 | protected function shRc() { |
|
407 | 1 | return $this->shRc; |
|
408 | } |
||
409 | |||
410 | /** |
||
411 | * Execute a sh locally. |
||
412 | * |
||
413 | * Works much like drush_shell_exec, however it captures the command return |
||
414 | * code too, and doesn't support interactive mode. |
||
415 | * |
||
416 | * @param string $command |
||
417 | * Command to run. |
||
418 | * |
||
419 | * @return bool |
||
420 | * Whether the command succeeded. |
||
421 | */ |
||
422 | 9 | protected function shLocal($command) { |
|
423 | 9 | $this->shOutput = array(); |
|
424 | 9 | $this->shRc = 0; |
|
425 | 9 | $args = func_get_args(); |
|
426 | // Escape args, but not the command. |
||
427 | 9 | for ($x = 1; $x < count($args); $x++) { |
|
428 | $args[$x] = drush_escapeshellarg($args[$x]); |
||
429 | } |
||
430 | // Mimic drush_shell_exec(), which can take a single or multiple args. |
||
431 | 9 | if (count($args) == 1) { |
|
432 | 9 | $command = $args[0]; |
|
433 | 9 | } |
|
434 | else { |
||
435 | $command = call_user_func_array('sprintf', $args); |
||
436 | } |
||
437 | |||
438 | 9 | if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) { |
|
439 | drush_print('Executing: ' . $command, 0, STDERR); |
||
440 | } |
||
441 | 9 | if (!drush_get_context('DRUSH_SIMULATE')) { |
|
442 | 9 | exec($command . ' 2>&1', $this->shOutput, $this->shRc); |
|
443 | |||
444 | 9 | if (drush_get_context('DRUSH_DEBUG')) { |
|
445 | foreach ($this->shOutput as $line) { |
||
446 | drush_print($line, 2); |
||
447 | } |
||
448 | } |
||
449 | 9 | } |
|
450 | |||
451 | // Exit code 0 means success. |
||
452 | 9 | return ($this->shRc == 0); |
|
453 | } |
||
454 | |||
455 | /** |
||
456 | * Find the tag pointed to by a SHA. |
||
457 | * |
||
458 | * @param string $sha |
||
459 | * The SHA to lookup. |
||
460 | * |
||
461 | * @return bool |
||
462 | * Whether the command succeeded. |
||
463 | */ |
||
464 | protected function gitPointsAt ($sha) { |
||
0 ignored issues
–
show
|
|||
465 | $success = $this->shLocal('git tag --points-at ' . $sha); |
||
466 | |||
467 | // If it failed we try a fallback command for older versions of |
||
468 | // git. |
||
469 | if (!$success) { |
||
470 | 7 | $success = $this->shLocal('git show-ref --tags -d | grep ^' . $sha . ' | sed -e \'s,.* refs/tags/,,\' -e \'s/\^{}//\''); |
|
0 ignored issues
–
show
|
|||
471 | 7 | } |
|
472 | 7 | ||
473 | return $success; |
||
474 | } |
||
475 | 7 | ||
476 | /** |
||
477 | * Execute a drush command. |
||
478 | * |
||
479 | * Is run on the site. |
||
480 | * |
||
481 | * @param string $command |
||
482 | * Command to run. |
||
483 | * @param string $args |
||
484 | * Command arguments. |
||
485 | * @param string $options |
||
486 | 9 | * Command arguments. |
|
487 | * |
||
488 | 9 | * @return bool |
|
489 | 9 | * Whether the command succeeded. |
|
490 | 9 | */ |
|
491 | 9 | protected function drush($command, $args = array(), $options = array()) { |
|
492 | $this->drushResult = drush_invoke_process($this->site, $command, $args, $options, TRUE); |
||
493 | 9 | if (!$this->drushResult || $this->drushResult['error_status'] != 0) { |
|
494 | 9 | return FALSE; |
|
495 | 9 | } |
|
496 | return TRUE; |
||
497 | 9 | } |
|
498 | 9 | ||
499 | 2 | /** |
|
500 | 2 | * Get the SHA to deploy. |
|
501 | 9 | * |
|
502 | 1 | * This is based on options from configuration or command. |
|
503 | * |
||
504 | * @return string |
||
505 | * The SHA to deploy. |
||
506 | */ |
||
507 | protected function pickSha($options = array()) { |
||
508 | $options = array( |
||
509 | 9 | 'branch' => drush_get_option('branch', NULL), |
|
510 | 9 | 'tag' => drush_get_option('tag', NULL), |
|
511 | 9 | 'sha' => drush_get_option('sha', NULL), |
|
512 | ); |
||
513 | 1 | ||
514 | 1 | $branch = $options['branch']; |
|
515 | $tag = $options['tag']; |
||
516 | $sha = $options['sha']; |
||
517 | 1 | ||
518 | $posibilities = array_filter(array($branch, $tag, $sha)); |
||
519 | if (count($posibilities) > 1) { |
||
520 | drush_log(dt('More than one of branch/tag/sha specified, using @selected.', array('@selected' => !empty($sha) ? 'sha' : 'tag')), 'warning'); |
||
521 | 9 | } |
|
522 | elseif (count($posibilities) < 1) { |
||
523 | return drush_set_error(dt('You must provide at least one of --branch, --tag or --sha.')); |
||
524 | } |
||
525 | |||
526 | // Use rev-list to ensure we get a full SHA rather than branch/tag/partial |
||
527 | // SHA. Ensures the existence of the branch/tag/SHA, and transforms |
||
528 | // annotated tag SHAs to the commit they're based on (so the repo will |
||
529 | // actually have the SHA we asked for checked out). |
||
530 | 9 | if ($this->shLocal('git rev-list -1 ' . end($posibilities) . ' --')) { |
|
531 | 9 | $sha = trim($this->shOutput()); |
|
532 | 9 | } |
|
533 | 9 | else { |
|
534 | 9 | if (!empty($sha)) { |
|
535 | 9 | return drush_set_error(dt('Unknown SHA.')); |
|
536 | } |
||
537 | 9 | else { |
|
538 | 9 | return drush_set_error(dt('Error finding SHA for ' . (!empty($branch) ? 'branch' : 'tag') . '.')); |
|
539 | 9 | } |
|
540 | } |
||
541 | |||
542 | return $sha; |
||
543 | 9 | } |
|
544 | 9 | ||
545 | 9 | /** |
|
546 | * Get the current git head revision. |
||
547 | * |
||
548 | * @param bool $no_cache |
||
549 | * Dont't use cache, but fetch anyway. |
||
550 | */ |
||
551 | protected function getHead($no_cache = FALSE) { |
||
552 | $name = $this->site['#name']; |
||
553 | if (!isset(self::$headShas[$name]) || $no_cache) { |
||
554 | self::$headShas[$name] = ''; |
||
555 | if ($this->sh('git rev-parse HEAD')) { |
||
556 | $sha = trim($this->shOutput()); |
||
557 | // Confirm that we can see the HEAD locally. |
||
558 | if ($this->shLocal('git show -s ' . $sha)) { |
||
559 | self::$headShas[$name] = $sha; |
||
560 | } |
||
561 | else { |
||
562 | drush_log(dt("Could not find the deployed HEAD (@sha) locally, you pulled recently?", array('@sha' => $sha)), 'warning'); |
||
563 | } |
||
564 | } |
||
565 | 1 | } |
|
566 | 1 | return self::$headShas[$name]; |
|
567 | 1 | } |
|
568 | 1 | } |
|
569 | } |
||
570 | 1 | ||
571 | 1 | /** |
|
572 | 1 | * Use a namespace to keep actions together. |
|
573 | */ |
||
574 | namespace Deployotron\Actions { |
||
575 | use Deployotron\Action; |
||
576 | |||
577 | 1 | /** |
|
578 | 1 | * Generic command action. |
|
579 | 1 | * |
|
580 | 1 | * Automatically instantiated from pre/post-* options. |
|
581 | 1 | */ |
|
582 | class CommandAction extends Action { |
||
583 | 1 | /** |
|
584 | * Create action. |
||
585 | */ |
||
586 | public function __construct($site, $command) { |
||
587 | $this->description = 'Run command: ' . $command; |
||
588 | $this->runMessage = 'Running command: ' . $command; |
||
589 | $this->short = 'run ' . $command; |
||
590 | |||
591 | $this->command = $command; |
||
592 | parent::__construct($site); |
||
593 | } |
||
594 | |||
595 | /** |
||
596 | * {@inheritdoc} |
||
597 | */ |
||
598 | public function run(\ArrayObject $state) { |
||
599 | $success = $this->sh($this->command); |
||
600 | drush_print($this->shOutput()); |
||
601 | if (!$success) { |
||
602 | return drush_set_error(dt('Error running command "@command"', array('@command' => $this->command))); |
||
603 | 9 | } |
|
604 | 9 | return TRUE; |
|
605 | } |
||
606 | } |
||
607 | |||
608 | 9 | /** |
|
609 | * Sanity check. |
||
610 | * |
||
611 | * Check for locally modified files and do a dry-run checkout. |
||
612 | */ |
||
613 | class SanityCheck extends Action { |
||
614 | 9 | protected $runMessage = 'Fetching and sanity checking'; |
|
615 | 9 | protected $short = 'sanity check'; |
|
616 | 9 | protected $help = 'Fetches the new code and checks that the repository is clean.'; |
|
617 | protected $options = array( |
||
618 | 1 | 'insanity' => "Don't check that the server repository is clean. Might cause data loss.", |
|
619 | ); |
||
620 | |||
621 | /** |
||
622 | * {@inheritdoc} |
||
623 | */ |
||
624 | 8 | public function getDescription() { |
|
625 | if (drush_get_option('insanity', FALSE)) { |
||
626 | 8 | return COLOR_YELLOW . dt('Skipping sanity check of deployed repo. Might cause data loss or Git failures.') . COLOR_RESET; |
|
627 | } |
||
628 | |||
629 | return dt('Run git fetch and check that the deployed git repository is clean.'); |
||
630 | } |
||
631 | |||
632 | /** |
||
633 | * {@inheritdoc} |
||
634 | */ |
||
635 | public function validate() { |
||
636 | if ($this->sha = $this->pickSha()) { |
||
637 | return TRUE; |
||
638 | } |
||
639 | 8 | return FALSE; |
|
640 | } |
||
641 | |||
642 | /** |
||
643 | * {@inheritdoc} |
||
644 | */ |
||
645 | public function run(\ArrayObject $state) { |
||
646 | // Fetch the latest changes from upstream. |
||
647 | if (!$this->sh('git fetch')) { |
||
648 | 8 | $message = $this->shOutput(); |
|
649 | if (preg_match('/Permission denied \(publickey\)/', $message)) { |
||
650 | drush_log(dt('Access denied to repository URL.'), 'error'); |
||
651 | drush_log(dt('Ensure that either that the user on the server has access to the repository, or use "ForwardAgent yes" in .ssh/config.'), 'error'); |
||
652 | 8 | } |
|
653 | else { |
||
654 | drush_print($message); |
||
655 | } |
||
656 | return drush_set_error(dt("Could not fetch from remote repository.")); |
||
657 | 8 | } |
|
658 | 1 | ||
659 | 1 | // Check that we can see the sha we want to deploy. |
|
660 | if (!$this->sh('git log -1 ' . $this->sha . ' --')) { |
||
661 | $message = $this->shOutput(); |
||
662 | if (preg_match('/^fatal: bad object/', $message)) { |
||
663 | 8 | return drush_set_error(dt('Could not find SHA, did you forget to push?')); |
|
664 | 8 | } |
|
665 | 1 | drush_print($message); |
|
666 | 1 | return drush_set_error(dt('Could not find SHA remotely.')); |
|
667 | } |
||
668 | |||
669 | if (drush_get_option('insanity', FALSE)) { |
||
670 | 8 | drush_log(dt('Skipping sanity check of deployed repo.'), 'warning'); |
|
671 | 8 | return TRUE; |
|
672 | } |
||
673 | $fallback = FALSE; |
||
674 | // http://stackoverflow.com/questions/3878624/how-do-i-programmatically-determine-if-there-are-uncommited-changes |
||
0 ignored issues
–
show
|
|||
675 | // claims this is the proper way. I'm inclined to agree. |
||
676 | // However, these are newer additions to Git, so fallback to git status |
||
677 | // if they fail. |
||
678 | if (!$this->sh('git diff-files --quiet --ignore-submodules --')) { |
||
679 | if ($this->shRc() != 129) { |
||
680 | return drush_set_error(dt('Remote git checkout not clean.')); |
||
681 | 8 | } |
|
682 | $fallback = TRUE; |
||
683 | } |
||
684 | if (!$fallback) { |
||
685 | if (!$this->sh('git diff-index --cached --quiet HEAD --ignore-submodules --')) { |
||
686 | if ($this->shRc() != 129) { |
||
687 | return drush_set_error(dt('Uncommitted changes in the index.')); |
||
688 | } |
||
689 | $fallback = TRUE; |
||
690 | } |
||
691 | } |
||
692 | if ($fallback) { |
||
693 | if (!$this->sh('git status -s --untracked-files=no')) { |
||
694 | return drush_set_error(dt('Error running git status -s --untracked-files=no.')); |
||
695 | } |
||
696 | $output = trim($this->shOutput()); |
||
697 | if (!empty($output)) { |
||
698 | return drush_set_error(dt('Remote git checkout not clean.')); |
||
699 | } |
||
700 | } |
||
701 | |||
702 | 9 | return TRUE; |
|
703 | 9 | } |
|
704 | 9 | } |
|
705 | |||
706 | /** |
||
707 | * Deploy code. |
||
708 | */ |
||
709 | 9 | class DeployCode extends Action { |
|
710 | 9 | protected $runMessage = 'Deploying'; |
|
711 | protected $killSwitch = 'no-deploy'; |
||
712 | protected $short = 'deploy code'; |
||
713 | 9 | protected $help = 'Checks out a specified branch/tag/sha on the site.'; |
|
714 | 9 | protected $options = array( |
|
715 | 'branch' => 'Branch to check out.', |
||
716 | 'tag' => 'Tag to check out.', |
||
717 | 'sha' => 'SHA to check out.', |
||
718 | 9 | ); |
|
719 | 9 | ||
720 | 9 | /** |
|
721 | 9 | * {@inheritdoc} |
|
722 | 9 | */ |
|
723 | 9 | public function getDescription() { |
|
724 | $head = $this->getHead(); |
||
725 | 9 | if (!$head) { |
|
726 | 9 | return COLOR_YELLOW . dt("Cannot find the deployed HEAD.") . COLOR_RESET; |
|
727 | } |
||
728 | 9 | ||
729 | 9 | // Collect info and log entry for the commit we're deploying. |
|
730 | $this->shLocal('git log --pretty="format:%an <%ae> @ %ci (%cr)%n%n%B" --color --no-walk ' . $this->sha); |
||
731 | 9 | $commit_info = $this->shOutput(); |
|
732 | |||
733 | 9 | // Collect info and log entry for the deployed head. |
|
734 | $this->shLocal('git log --pretty="format:%an <%ae> @ %ci (%cr)%n%n%B" --color --no-walk ' . $head); |
||
735 | $head_info = $this->shOutput(); |
||
736 | |||
737 | // Create diffstat between the deployed commit and the one we're |
||
738 | // deploying. |
||
739 | 9 | $this->shLocal('git diff --color --stat ' . $head . ' ' . $this->sha); |
|
740 | 9 | $stat = $this->shOutput(); |
|
741 | 9 | $stat = explode("\n", $stat); |
|
742 | if (count($stat) > 20) { |
||
743 | $stat = array_merge(array_slice($stat, 0, 8), array('', ' ...', ''), array_slice($stat, -8)); |
||
744 | } |
||
745 | |||
746 | $desc = dt("Check out @sha:\n", array('@sha' => $this->sha)); |
||
747 | $desc .= $commit_info . "\n\n"; |
||
748 | |||
749 | 8 | $desc .= dt("Currently deployed: @sha\n", array('@sha' => $head)); |
|
750 | $desc .= $head_info . "\n\n"; |
||
751 | 8 | ||
752 | 8 | $desc .= dt("All changes:\n!changes", array('!changes' => implode("\n", $stat))); |
|
753 | 8 | ||
754 | return $desc; |
||
755 | 8 | } |
|
756 | 1 | ||
757 | 1 | /** |
|
758 | * {@inheritdoc} |
||
759 | */ |
||
760 | public function validate() { |
||
761 | 8 | if ($this->sha = $this->pickSha()) { |
|
762 | 8 | return TRUE; |
|
763 | 8 | } |
|
764 | return FALSE; |
||
765 | } |
||
766 | |||
767 | 8 | /** |
|
768 | 8 | * {@inheritdoc} |
|
769 | */ |
||
770 | 8 | public function run(\ArrayObject $state) { |
|
771 | // Some useful information for other actions. |
||
772 | $state['requested_branch'] = drush_get_option('branch', NULL); |
||
773 | $state['requested_tag'] = drush_get_option('tag', NULL); |
||
774 | $state['requested_sha'] = drush_get_option('sha', NULL); |
||
775 | |||
776 | 8 | if (!$this->sh('git checkout ' . $this->sha)) { |
|
777 | drush_print($this->shOutput()); |
||
778 | return drush_set_error(dt('Could not checkout code.')); |
||
779 | } |
||
780 | |||
781 | // An extra safety check to make sure that things are as we think. |
||
782 | $deployed_sha = $this->getHead(TRUE); |
||
783 | if ($deployed_sha) { |
||
784 | if ($deployed_sha != $this->sha) { |
||
785 | return drush_set_error(dt('Code not properly deployed, head is at @sha now.', array('@sha' => $deployed_sha))); |
||
786 | } |
||
787 | else { |
||
788 | drush_log(dt('HEAD now at @sha', array('@sha' => $deployed_sha)), 'status'); |
||
789 | $state['deployed_sha'] = $deployed_sha; |
||
790 | } |
||
791 | } |
||
792 | 7 | else { |
|
793 | 7 | drush_print($this->shOutput()); |
|
794 | return drush_set_error(dt('Error confirming that the code update succceded.')); |
||
795 | } |
||
796 | 7 | ||
797 | return TRUE; |
||
798 | } |
||
799 | } |
||
800 | |||
801 | /** |
||
802 | 2 | * Set site offline. |
|
803 | */ |
||
804 | 2 | class SiteOffline extends Action { |
|
805 | 2 | protected $description = 'Set the site offline.'; |
|
806 | 2 | protected $runMessage = 'Setting site offline'; |
|
807 | protected $killSwitch = 'no-offline'; |
||
808 | protected $short = 'set site offline'; |
||
809 | |||
810 | /** |
||
811 | * {@inheritdoc} |
||
812 | */ |
||
813 | public function run(\ArrayObject $state) { |
||
814 | if (!$this->drush('variable-set', array('maintenance_mode', 1))) { |
||
815 | return drush_set_error(dt('Error setting site offline.')); |
||
816 | } |
||
817 | return TRUE; |
||
818 | } |
||
819 | |||
820 | /** |
||
821 | * {@inheritdoc} |
||
822 | 7 | */ |
|
823 | 7 | public function rollback() { |
|
824 | // Use the online action as rollback. |
||
825 | $online = new SiteOnline($this->site); |
||
826 | 7 | $online->run(new \ArrayObject()); |
|
827 | } |
||
828 | } |
||
829 | |||
830 | /** |
||
831 | * Set site online. |
||
832 | */ |
||
833 | class SiteOnline extends Action { |
||
834 | protected $description = 'Set the site online.'; |
||
835 | protected $runMessage = 'Setting site online'; |
||
836 | protected $killSwitch = 'no-offline'; |
||
837 | protected $short = 'set site online'; |
||
838 | protected $switchSuffix = 'online'; |
||
839 | |||
840 | /** |
||
841 | * {@inheritdoc} |
||
842 | */ |
||
843 | public function run(\ArrayObject $state) { |
||
844 | if (!$this->drush('variable-set', array('maintenance_mode', 0))) { |
||
845 | return drush_set_error(dt('Error setting site online.')); |
||
846 | } |
||
847 | return TRUE; |
||
848 | } |
||
849 | |||
850 | /** |
||
851 | * {@inheritdoc} |
||
852 | */ |
||
853 | public function rollback() { |
||
854 | // Use the online action as rollback. |
||
855 | $online = new SiteOffline($this->site); |
||
856 | $online->run(new \ArrayObject()); |
||
857 | 9 | } |
|
858 | 9 | } |
|
859 | |||
860 | /** |
||
861 | * Backup database. |
||
862 | */ |
||
863 | class BackupDatabase extends Action { |
||
864 | 8 | protected $runMessage = 'Dumping database'; |
|
865 | 8 | protected $killSwitch = 'no-dump'; |
|
866 | protected $short = 'dump database'; |
||
867 | protected $help = 'Makes a SQL dump of the site database.'; |
||
868 | protected $options = array( |
||
869 | 'dump-dir' => array( |
||
870 | 'description' => 'Directory for database dumps, defaults to /tmp.', |
||
871 | 7 | 'example-value' => 'path', |
|
872 | 7 | ), |
|
873 | ); |
||
874 | |||
875 | 7 | /** |
|
876 | 7 | * Get the name of a dump. |
|
877 | */ |
||
878 | public static function filename($alias, $date_str, $sha) { |
||
879 | return sprintf("%s/deploy.%s.%s.%s.sql", drush_get_option('dump-dir', '/tmp'), $alias, $date_str, $sha); |
||
880 | } |
||
881 | |||
882 | 8 | /** |
|
883 | * {@inheritdoc} |
||
884 | */ |
||
885 | 8 | public function getDescription() { |
|
886 | 8 | return dt("Dump database to @file.", array('@file' => $this->dumpFilename())); |
|
887 | 8 | } |
|
888 | 8 | ||
889 | /** |
||
890 | 8 | * {@inheritdoc} |
|
891 | */ |
||
892 | public function run(\ArrayObject $state) { |
||
893 | if (!$this->drush('sql-dump', array(), array('no-ordered-dump' => TRUE, 'result-file' => $this->dumpFilename()))) { |
||
894 | return drush_set_error('Error dumping database.'); |
||
895 | } |
||
896 | $state['database_dump'] = $this->dumpFilename(); |
||
897 | return TRUE; |
||
898 | } |
||
899 | |||
900 | /** |
||
901 | * Figure out dump filename. |
||
902 | */ |
||
903 | protected function dumpFilename() { |
||
904 | // Because there can pass time between this is called first and last |
||
905 | // (--confirm primarily). |
||
906 | static $date; |
||
907 | if (!$date) { |
||
908 | $date = date('Y-m-d\TH:i:s'); |
||
909 | } |
||
910 | 1 | ||
911 | 1 | return static::filename($this->site['#name'], $date, $this->getHead()); |
|
912 | } |
||
913 | } |
||
914 | |||
915 | 1 | /** |
|
916 | * Restore database. |
||
917 | */ |
||
918 | class RestoreDatabase extends Action { |
||
919 | protected $runMessage = 'Restoring database'; |
||
920 | protected $short = 'restore database'; |
||
921 | 1 | protected $options = array( |
|
922 | 1 | 'file' => array( |
|
923 | 'description' => 'Database dump file to restore.', |
||
924 | 'example-value' => 'filename', |
||
925 | ), |
||
926 | ); |
||
927 | |||
928 | 1 | /** |
|
929 | 1 | * {@inheritdoc} |
|
930 | */ |
||
931 | public function validate() { |
||
932 | if (!drush_get_option('file', NULL)) { |
||
933 | 1 | return drush_set_error(dt('Missing file.')); |
|
934 | } |
||
935 | |||
936 | return TRUE; |
||
937 | 1 | } |
|
938 | |||
939 | 1 | /** |
|
940 | * {@inheritdoc} |
||
941 | */ |
||
942 | public function getDescription() { |
||
943 | return dt("Restore database from @file.", array('@file' => drush_get_option('file', NULL))); |
||
944 | 1 | } |
|
945 | |||
946 | /** |
||
947 | * {@inheritdoc} |
||
948 | */ |
||
949 | public function run(\ArrayObject $state) { |
||
950 | if (!$this->drush('sql-connect', array(), array('pipe' => TRUE))) { |
||
951 | return drush_set_error('Error getting database connection setup.'); |
||
952 | } |
||
953 | |||
954 | if (!is_string($this->drushResult['object'])) { |
||
955 | return drush_set_error('Weird result from sql-connnect.'); |
||
956 | } |
||
957 | |||
958 | $command = $this->drushResult['object']; |
||
959 | |||
960 | if (!$this->sh($command . " < " . drush_get_option('file', NULL))) { |
||
961 | drush_print($this->shOutput()); |
||
962 | return drush_set_error('Error restoring database.'); |
||
963 | } |
||
964 | |||
965 | return TRUE; |
||
966 | } |
||
967 | } |
||
968 | |||
969 | /** |
||
970 | 9 | * Purge old backups. |
|
971 | 9 | */ |
|
972 | 9 | class PurgeDatabaseBackups extends Action { |
|
973 | 1 | protected $runMessage = 'Purging old database dumps.'; |
|
974 | 1 | protected $short = 'purge old dumps'; |
|
975 | 9 | protected $options = array( |
|
976 | 'num-dumps' => array( |
||
977 | 9 | 'description' => 'Number of database dumps to keep. 0 for unlimited.', |
|
978 | 'example-value' => '5', |
||
979 | ), |
||
980 | 9 | ); |
|
981 | 9 | protected $switchSuffix = 'purge'; |
|
982 | 9 | ||
983 | 9 | /** |
|
984 | * Dumps to delete. |
||
985 | 2 | */ |
|
986 | 2 | protected $deleteDumps = array(); |
|
987 | 2 | ||
988 | 9 | /** |
|
989 | * {@inheritdoc} |
||
990 | 9 | */ |
|
991 | public function validate() { |
||
992 | $dumping = TRUE; |
||
993 | if (drush_get_option('no-dump', FALSE)) { |
||
994 | $dumping = FALSE; |
||
995 | } |
||
996 | 9 | $max_dumps = drush_get_option('num-dumps', 5); |
|
997 | 9 | ||
998 | 2 | if ($max_dumps > 0) { |
|
999 | // If we're dumping a new dump, we need to keep one less than the max to |
||
1000 | // make room for the new one. |
||
1001 | 9 | $keep = $max_dumps - ($dumping ? 1 : 0); |
|
1002 | $this->sh('ls ' . BackupDatabase::filename($this->site['#name'], '*', '*')); |
||
1003 | $dumps = $this->shOutputArray(); |
||
1004 | if (count($dumps) > $keep) { |
||
1005 | // Reverse sort to get the newest first. |
||
1006 | rsort($dumps); |
||
1007 | 8 | $this->deleteDumps = array_slice($dumps, $keep); |
|
1008 | 8 | } |
|
1009 | 2 | } |
|
1010 | 2 | ||
1011 | return TRUE; |
||
1012 | } |
||
1013 | |||
1014 | 8 | /** |
|
1015 | * {@inheritdoc} |
||
1016 | */ |
||
1017 | public function getDescription() { |
||
1018 | if (count($this->deleteDumps)) { |
||
1019 | return dt("Purge the following dump files:\n@files.", array('@files' => implode("\n", $this->deleteDumps))); |
||
1020 | |||
1021 | } |
||
1022 | return dt("Not purging any dumps."); |
||
1023 | } |
||
1024 | |||
1025 | /** |
||
1026 | * {@inheritdoc} |
||
1027 | */ |
||
1028 | public function run(\ArrayObject $state) { |
||
1029 | if (count($this->deleteDumps)) { |
||
1030 | 7 | $this->sh('rm ' . implode(" ", array_map('drush_escapeshellarg', $this->deleteDumps))); |
|
1031 | 7 | } |
|
1032 | |||
1033 | // We don't consider failure to delete dumps serious enough to fail the |
||
1034 | 7 | // deployment. |
|
1035 | return TRUE; |
||
1036 | } |
||
1037 | } |
||
1038 | |||
1039 | /** |
||
1040 | * Update database. |
||
1041 | */ |
||
1042 | class UpdateDatabase extends Action { |
||
1043 | protected $description = 'Runs database updates (as with update.php).'; |
||
1044 | protected $runMessage = 'Running database updates'; |
||
1045 | protected $killSwitch = 'no-updb'; |
||
1046 | protected $short = 'update database schema'; |
||
1047 | |||
1048 | /** |
||
1049 | * {@inheritdoc} |
||
1050 | 7 | */ |
|
1051 | 7 | public function run(\ArrayObject $state) { |
|
1052 | if (!$this->drush('updb', array(), array('yes' => TRUE))) { |
||
1053 | return drush_set_error(dt('Error running database updates.')); |
||
1054 | 7 | } |
|
1055 | return TRUE; |
||
1056 | } |
||
1057 | } |
||
1058 | |||
1059 | /** |
||
1060 | * Clear cache. |
||
1061 | */ |
||
1062 | class ClearCache extends Action { |
||
1063 | protected $description = 'Clear all Drupal caches.'; |
||
1064 | protected $runMessage = 'Clearing caches'; |
||
1065 | protected $short = 'cache clear'; |
||
1066 | protected $killSwitch = 'no-cc-all'; |
||
1067 | |||
1068 | /** |
||
1069 | * {@inheritdoc} |
||
1070 | */ |
||
1071 | public function run(\ArrayObject $state) { |
||
1072 | if (!$this->drush('cc', array('all'), array())) { |
||
1073 | return drush_set_error(dt('Error clearing cache.')); |
||
1074 | } |
||
1075 | return TRUE; |
||
1076 | } |
||
1077 | 1 | } |
|
1078 | |||
1079 | 1 | /** |
|
1080 | 1 | * Prepare OMG. |
|
1081 | 1 | */ |
|
1082 | 1 | class OMGPrepare extends Action { |
|
1083 | 1 | protected $description = 'Prepare restore.'; |
|
1084 | 1 | protected $runMessage = 'Preparing'; |
|
1085 | 1 | protected $short = 'preparing'; |
|
1086 | 1 | protected $help = 'Prepares restoring by looking for dumps to import.'; |
|
1087 | 1 | protected $options = array( |
|
1088 | 'dump-dir' => array( |
||
1089 | 1 | 'description' => 'Directory for database dumps.', |
|
1090 | 1 | 'example-value' => 'path', |
|
1091 | 1 | ), |
|
1092 | 1 | ); |
|
1093 | 1 | protected $switchSuffix = 'prepare'; |
|
1094 | |||
1095 | /** |
||
1096 | * {@inheritdoc} |
||
1097 | 1 | */ |
|
1098 | 1 | public function validate() { |
|
1099 | 1 | // Try to find some dumps and give them as options for restoring. |
|
1100 | $regex = '{^deploy\.' . preg_quote($this->site['#name']) . '\.(\d+-\d+-\d+T\d+:\d+:\d+)\.([0-9a-f]+)\.sql$}'; |
||
1101 | $this->sh('ls ' . drush_get_option('dump-dir', '/tmp')); |
||
1102 | $listing = $this->shOutput(); |
||
1103 | $dumps = array(); |
||
1104 | foreach (array_reverse(explode("\n", $listing)) as $line) { |
||
1105 | if (preg_match($regex, $line, $matches)) { |
||
1106 | $dumps[$matches[1]] = $matches[2]; |
||
1107 | } |
||
1108 | } |
||
1109 | |||
1110 | if (!empty($dumps)) { |
||
1111 | $date = drush_choice($dumps, dt('Please select a dump.'), '!key (!value)'); |
||
1112 | if ($date) { |
||
1113 | 1 | $sha = $dumps[$date]; |
|
1114 | $file = 'deploy.' . $this->site['#name'] . '.' . $date . '.' . $sha . '.sql'; |
||
1115 | 1 | // We simply set the options so the other actions will see them. The |
|
1116 | // DeployCode action will check that the SHA is available locally |
||
1117 | // before validating, so we'll avoid the worst if dumps get mixed up. |
||
1118 | drush_set_option('sha', $sha); |
||
1119 | drush_set_option('file', drush_get_option('dump-dir', '/tmp') . '/' . $file); |
||
1120 | return TRUE; |
||
1121 | } |
||
1122 | else { |
||
1123 | return drush_set_error(dt('Aborting.')); |
||
1124 | } |
||
1125 | } |
||
1126 | else { |
||
1127 | return drush_set_error(dt('No database dumps found.')); |
||
1128 | } |
||
1129 | } |
||
1130 | |||
1131 | /** |
||
1132 | 8 | * {@inheritdoc} |
|
1133 | 8 | */ |
|
1134 | public function run(\ArrayObject $state) { |
||
1135 | 8 | // Doing nothing. |
|
1136 | 8 | return TRUE; |
|
1137 | } |
||
1138 | 8 | } |
|
1139 | 8 | ||
1140 | 8 | /** |
|
1141 | 8 | * Create a VERSION.txt file. |
|
1142 | 5 | */ |
|
1143 | 5 | class CreateVersionTxt extends Action { |
|
1144 | 8 | protected $description = 'Create VERSION.txt.'; |
|
1145 | 7 | protected $runMessage = 'Creating VERSION.txt'; |
|
1146 | 7 | protected $short = 'create VERSION.txt'; |
|
1147 | 8 | protected $killSwitch = 'no-create-version-txt'; |
|
1148 | 8 | protected $switchSuffix = 'version-txt'; |
|
1149 | 8 | ||
1150 | /** |
||
1151 | * {@inheritdoc} |
||
1152 | 8 | */ |
|
1153 | public function run(\ArrayObject $state) { |
||
0 ignored issues
–
show
run uses the super-global variable $_SERVER which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
![]() |
|||
1154 | if (!empty($state['deployed_sha'])) { |
||
1155 | // Ask git which tags points to this commit. |
||
1156 | $this->gitPointsAt($state['deployed_sha']); |
||
1157 | $tags = implode(', ', $this->shOutputArray()); |
||
1158 | 8 | ||
1159 | 8 | $version_txt = array(); |
|
1160 | $version_txt[] = 'Deployment info'; |
||
1161 | 1 | $version_txt[] = '---------------'; |
|
1162 | if (!empty($state['requested_branch'])) { |
||
1163 | 8 | $version_txt[] = 'Branch: ' . $state['requested_branch']; |
|
1164 | } |
||
1165 | if (!empty($tags)) { |
||
1166 | $version_txt[] = 'Tags: ' . $tags; |
||
1167 | } |
||
1168 | $version_txt[] = 'SHA: ' . $state['deployed_sha']; |
||
1169 | $version_txt[] = 'Time of deployment: ' . date('r'); |
||
1170 | $version_txt[] = 'Deployer: ' . $_SERVER['USER'] . '@' . php_uname('n'); |
||
1171 | |||
1172 | // Delete any pre-existing VERSION.txt file and create a new one. |
||
1173 | $this->sh('rm VERSION.txt'); |
||
1174 | // You'd think that echo would do the job, but it's not consistent |
||
1175 | // across shells. Bash and most /bin/echo requires the -n option to |
||
1176 | // expand \n to a newline, while /bin/sh built-in echo doesn't and |
||
1177 | // prints the -n as part of the output. But printf works the same and is |
||
1178 | // POSIX 7, which should cover our bases. |
||
1179 | $this->sh('printf ' . escapeshellarg(implode("\\n", $version_txt) . "\\n") . ' > VERSION.txt'); |
||
1180 | } |
||
1181 | else { |
||
1182 | drush_log(dt('No version deployed, not creating/updating VERSION.txt.'), 'warning'); |
||
1183 | } |
||
1184 | return TRUE; |
||
1185 | } |
||
1186 | 2 | } |
|
1187 | 2 | ||
1188 | 1 | /** |
|
1189 | 1 | * Send a notification to Flowdock. |
|
1190 | */ |
||
1191 | 1 | class FlowdockNotificaton extends Action { |
|
1192 | 1 | protected $description = 'Send Flowdock notification.'; |
|
1193 | protected $runMessage = 'Sending Flowdock notification.'; |
||
1194 | 1 | protected $short = 'send Flowdock notification'; |
|
1195 | 1 | protected $enableSwitch = 'flowdock-token'; |
|
1196 | 1 | protected $options = array( |
|
1197 | 'flowdock-token' => array( |
||
1198 | 'description' => 'Flowdock token.', |
||
1199 | 1 | 'example-value' => 'token', |
|
1200 | 1 | ), |
|
1201 | 1 | ); |
|
1202 | 1 | protected $switchSuffix = 'flowdock'; |
|
1203 | 1 | ||
1204 | 1 | /** |
|
1205 | 1 | * {@inheritdoc} |
|
1206 | */ |
||
1207 | 1 | public function run(\ArrayObject $state) { |
|
1208 | if (!empty($state['deployed_sha'])) { |
||
1209 | 1 | $this->gitPointsAt($state['deployed_sha']); |
|
1210 | 1 | $tags = implode(', ', $this->shOutputArray()); |
|
1211 | 1 | ||
1212 | 1 | $subject = 'Deployment to ' . $this->site['remote-user'] . '@' . |
|
1213 | 1 | $this->site['remote-host'] . ':' . $this->site['root']; |
|
1214 | 1 | ||
1215 | 1 | $body = 'SHA: ' . $state['deployed_sha'] . |
|
1216 | (!empty($state['requested_branch']) ? '<br />Branch: ' . $state['requested_branch'] : '') . |
||
1217 | 1 | (!empty($tags) ? '<br />Tags: ' . $tags : ''); |
|
1218 | |||
1219 | 1 | $data = array( |
|
1220 | 1 | 'source' => 'Deployotron', |
|
1221 | 1 | 'from_address' => '[email protected]', |
|
1222 | 1 | 'subject' => $subject, |
|
1223 | 1 | 'content' => $body, |
|
1224 | 1 | 'tags' => array('deployotron', $this->getFlowdockTag()), |
|
1225 | ); |
||
1226 | 1 | $data = json_encode($data); |
|
1227 | |||
1228 | 2 | $service_url = 'https://api.flowdock.com/v1/messages/team_inbox/' . drush_get_option('flowdock-token', ''); |
|
1229 | |||
1230 | $curl = curl_init($service_url); |
||
1231 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); |
||
1232 | curl_setopt($curl, CURLOPT_POST, TRUE); |
||
1233 | curl_setopt($curl, CURLOPT_POSTFIELDS, $data); |
||
1234 | 1 | curl_setopt($curl, CURLOPT_HTTPHEADER, array( |
|
1235 | 1 | 'Content-Type: application/json', |
|
1236 | 'Content-Length: ' . strlen($data), |
||
1237 | ) |
||
1238 | ); |
||
1239 | |||
1240 | $curl_response = curl_exec($curl); |
||
1241 | if ($curl_response != '{}') { |
||
1242 | drush_log(dt('Unexpected response from Flowdock: !response', array('!response' => $curl_response)), 'warning'); |
||
1243 | } |
||
1244 | curl_close($curl); |
||
1245 | } |
||
1246 | else { |
||
1247 | drush_log(dt('No version deployed, not sending Flowdock notification.'), 'warning'); |
||
1248 | } |
||
1249 | return TRUE; |
||
1250 | } |
||
1251 | |||
1252 | /** |
||
1253 | * Get a Flowdock tag for the site. |
||
1254 | */ |
||
1255 | private function getFlowdockTag() { |
||
1256 | return preg_replace('/[^[:alnum:]_\-]/', '_', $this->site['#name']); |
||
1257 | } |
||
1258 | } |
||
1259 | |||
1260 | /** |
||
1261 | * Send a notification to New Relic. |
||
1262 | */ |
||
1263 | class NewRelicNotificaton extends Action { |
||
1264 | protected $description = 'Send New Relic notification.'; |
||
1265 | protected $runMessage = 'Sending New Relic notification.'; |
||
1266 | protected $short = 'send new relic notification'; |
||
1267 | protected $enableSwitch = 'newrelic-api-key'; |
||
1268 | protected $options = array( |
||
1269 | 'newrelic-app-name' => array( |
||
1270 | 'description' => 'New Relic application name.', |
||
1271 | 'example-value' => 'name', |
||
1272 | ), |
||
1273 | 'newrelic-app-id' => array( |
||
1274 | 'description' => 'New Relic application id.', |
||
1275 | 'example-value' => 'id', |
||
1276 | ), |
||
1277 | 'newrelic-api-key' => array( |
||
1278 | 'description' => 'New Relic API key.', |
||
1279 | 'example-value' => 'key', |
||
1280 | ), |
||
1281 | ); |
||
1282 | protected $switchSuffix = 'newrelic'; |
||
1283 | |||
1284 | /** |
||
1285 | * {@inheritdoc} |
||
1286 | */ |
||
1287 | public function validate() { |
||
1288 | if (!drush_get_option('newrelic-app-name', NULL) && !drush_get_option('newrelic-app-id', NULL)) { |
||
1289 | return drush_set_error('Need at least one of --newrelic-app-name or --newrelic-app-id'); |
||
1290 | } |
||
1291 | // The api-key must have been specified as its the enable switch. |
||
1292 | return TRUE; |
||
1293 | } |
||
1294 | |||
1295 | /** |
||
1296 | * {@inheritdoc} |
||
1297 | */ |
||
1298 | public function run(\ArrayObject $state) { |
||
0 ignored issues
–
show
run uses the super-global variable $_SERVER which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
![]() |
|||
1299 | if (!empty($state['deployed_sha'])) { |
||
1300 | $this->gitPointsAt($state['deployed_sha']); |
||
1301 | $tags = implode(', ', $this->shOutputArray()); |
||
1302 | |||
1303 | $body = 'SHA: ' . $state['deployed_sha'] . |
||
1304 | (!empty($state['requested_branch']) ? "\nBranch: " . $state['requested_branch'] : '') . |
||
1305 | (!empty($tags) ? "\nTags: " . $tags : ''); |
||
1306 | |||
1307 | $deployment = array( |
||
1308 | 'description' => $body, |
||
1309 | 'revision' => $state['deployed_sha'], |
||
1310 | // @todo 'changelog' => '', |
||
1311 | 'user' => $_SERVER['USER'] . '@' . php_uname('n'), |
||
1312 | ); |
||
1313 | |||
1314 | $app_name = drush_get_option('newrelic-app-name', NULL); |
||
1315 | if ($app_name) { |
||
1316 | $deployment['app_name'] = $app_name; |
||
1317 | } |
||
1318 | |||
1319 | $app_id = drush_get_option('newrelic-app-id', NULL); |
||
1320 | if ($app_id) { |
||
1321 | $deployment['application_id'] = $app_id; |
||
1322 | } |
||
1323 | |||
1324 | $data = http_build_query(array('deployment' => $deployment)); |
||
1325 | $service_url = 'https://api.newrelic.com/deployments.xml'; |
||
1326 | |||
1327 | $curl = curl_init($service_url); |
||
1328 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); |
||
1329 | curl_setopt($curl, CURLOPT_POST, TRUE); |
||
1330 | curl_setopt($curl, CURLOPT_POSTFIELDS, $data); |
||
1331 | curl_setopt($curl, CURLOPT_HTTPHEADER, array( |
||
1332 | 'Content-Type: application/x-www-form-urlencoded', |
||
1333 | 'Content-Length: ' . strlen($data), |
||
1334 | 'x-api-key: ' . drush_get_option('newrelic-api-key'), |
||
1335 | ) |
||
1336 | ); |
||
1337 | |||
1338 | $curl_response = curl_exec($curl); |
||
1339 | if ($curl_response === FALSE) { |
||
1340 | drush_log(dt('Curl failed: !response', array('!response' => curl_error($curl))), 'error'); |
||
1341 | } |
||
1342 | elseif (!$curl_response) { |
||
1343 | drush_log(dt('New Relic notification failed for some reason.'), 'error'); |
||
1344 | } |
||
1345 | curl_close($curl); |
||
1346 | } |
||
1347 | else { |
||
1348 | drush_log(dt('No version deployed, not sending New Relic notification.'), 'warning'); |
||
1349 | } |
||
1350 | return TRUE; |
||
1351 | } |
||
1352 | } |
||
1353 | } |
||
1354 |