provision.environment.inc 13.1 KB
Newer Older
1
<?php
Adrian Rossouw's avatar
Adrian Rossouw committed
2 3 4 5 6 7 8 9 10 11
/**
 * Provision environment API
 *
 *
 */

/**
 * Store and access environment objects by their alias name
 *
 */
drumm's avatar
drumm committed
12
function & d($name = NULL, $root_object = FALSE) {
13
  static $instances = null;
14
  static $default_instance = '@self';
15

16 17 18 19
  if ($name == 'all') {
    return $instances;
  }

drumm's avatar
drumm committed
20 21 22 23
  if (is_null($name)) {
    return $instances[$default_instance];
  }
  else {
24 25
    if ($root_object) {
      $default_instance = $name;
26 27 28 29 30 31 32 33
      if ($name != '@self') {
        // This is an unfortunate workaround to the drush sitealias functionality, which pollutes
        // the options namespace with the alias sourced settings.
        drush_set_option('root', drush_get_option('root', null, 'options'), 'process');
        drush_set_option('uri', drush_get_option('uri', null, 'options'), 'process');
        drush_unset_option('root', 'options');
        drush_unset_option('uri', 'options');
      }
34
    }
35

36 37 38
    if (isset($instances[$name])) {
      return $instances[$name];
    }
39
    else {
40
      $instances[$name] = provision_environment_factory($name);
41 42 43
      $instances[$name]->init();
      return $instances[$name];
    }
44 45 46
  }
}

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
/**
 * Simple access layer for drush_sitealias_get_record.
 *
 * Everytime sitealiases are fetched a lot of processing happens,
 * but if the file doesnt exist yet there's a whole lot of unnecesary
 * stuff happening. 
 *
 * We cache the result locally here.
 */
function provision_sitealias_get_record($name) {
  static $cache = array();

  if (!isset($cache[$name])) {
    $cache[$name] = drush_sitealias_get_record($name);
  }

  return $cache[$name];
}

Adrian Rossouw's avatar
Adrian Rossouw committed
66 67 68
/**
 * Create a new environment object and cache it in the d() static cache function.
 */
69
function provision_environment_factory($name) {
70
  $classes = array('server' => 'provisionServer', 'platform' => 'provisionPlatform', 'site' => 'provisionSite');
Adrian Rossouw's avatar
Adrian Rossouw committed
71

72 73
  $type = 'server';

74
  $record = provision_sitealias_get_record($name);
75
  $options = array_merge(drush_get_context('stdin'), drush_get_context('options'));
76 77 78 79 80 81 82

  if (isset($record['context_type'])) {
    $type = $record['context_type'];
  }
  elseif (isset($options['context_type'])) {
    $type = $options['context_type'];
  }
83
  $classname = $classes[$type];
84 85

  return new $classname($name);
Adrian Rossouw's avatar
Adrian Rossouw committed
86 87
}

88

Adrian Rossouw's avatar
Adrian Rossouw committed
89 90 91 92 93
/**
 * Base environment class.
 *
 * Contains magic getter/setter functions
 */
94
class provisionEnvironment {
95 96 97
  public $name = null;
  public $type = null;

98

99
  protected $properties = array();
100 101

  protected $service_subs = array();
102
  protected $parent_key = null;
103 104 105

  protected $oid_map = array();

Adrian Rossouw's avatar
Adrian Rossouw committed
106 107
  /**
   * Retrieve value from $properties array if property does not exist in class proper.
108 109 110 111
   *
   * TODO: consider returning a reference to the value, so we can do things like:
   *       `$this->options['option'] = 'value'` and it will correctly set it in the
   *       drush context cache.
Adrian Rossouw's avatar
Adrian Rossouw committed
112
   */
113
  function __get($name) {
114
    if ($name == 'options') {
115
      return array_merge(provision_sitealias_get_record($this->name), array_filter(drush_get_context('stdin')), array_filter(drush_get_context('options')));
116
    }
117
    if (array_key_exists($name, $this->properties)) {
118
      if (isset($this->oid_map[$name]) && !empty($this->properties[$name])) {
119 120 121 122 123
        return d($this->properties[$name]);
      }
      else {
        return $this->properties[$name];
      }
124 125 126
    }
  }

127 128 129
  /**
   * Specify that a property contains a named context.
   */
130 131 132 133
  function is_oid($name) {
    $this->oid_map[$name] = TRUE;
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
134 135 136
  /**
   * Store value in properties array if the property does not exist in class proper.
   */
137 138 139 140 141 142 143 144 145
  function __set($name, $value) {
    if (!property_exists($this, $name)) {
      $this->properties[$name] = $value;
    }
    else {
      $this->$name = $value;
    }
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
146 147 148
  /**
   * Check the properties array if the property does not exist in the class proper.
   */
149 150 151 152 153 154 155
  function __isset($name) {
    if (property_exists($this->properties, $name) || property_exists($this, $name)) {
      return TRUE;
    }
    return FALSE;
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
156 157 158
  /**
   * Remove the value from the properties array if the property does not exist in the class proper.
   */
159 160 161 162 163 164 165 166 167 168
  function __unset($name) {
    if (property_exists($this->properties, $name)) {
      unset($this->properties[$name]);
    }
    elseif (property_exists($this, $name)) {
      unset($this->$name);
    }

  }

Adrian Rossouw's avatar
Adrian Rossouw committed
169 170 171
  /**
   * Constructor for the environment.
   */
172
  function __construct($name) {
173
    $this->name = $name;
174 175
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
176 177 178
  /**
   * Init stub function/
   */
179
  function init() {
180
    $this->setProperty('context_type', 'server');
181 182 183
    return true;
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
184 185 186
  /**
   * Check the $options property for a field, saving to the properties array.
   */
drumm's avatar
drumm committed
187
  function setProperty($field, $default = NULL, $array = FALSE) {
188
    if (isset($this->options[$field])) {
drumm's avatar
drumm committed
189 190 191 192 193 194
      if ($array && !is_array($this->options[$field])) {
        $this->$field = explode(',', $this->options[$field]);
      }
      else {
        $this->$field = $this->options[$field];
      }
195 196 197 198 199 200
    }
    else {
      $this->$field = $default;
    }
  }

drumm's avatar
drumm committed
201 202 203 204
  /**
   * Write out this named context to an alias file.
   */
  function write_alias() {
205
    $config = new provisionConfig_drushrc_alias($this->name, $this->properties);
drumm's avatar
drumm committed
206 207
    $config->write();
  }
208

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
  /**
   * Subscribe a service handler.
   *
   * All future calls to $this->service($service) will be redirected
   * to the context object of #name you specify.
   */
  function service_subscribe($service, $name) {
    $this->service_subs[$service] = $name;
  }

  /**
   * Return a service object for the specific service type.
   *
   * This will return a specifically subscribed service object when 
   * one has been registered with service_subscribe, otherwise it will
   * return the value specified by the property specified by $this->parent_key.
   */
226
  function service($service, $name = null) {
227 228 229 230
    if (isset($this->service_subs[$service])) {
      return d($this->service_subs[$service])->service($service, ($name) ? $name : $this->name);
    }
    elseif (!is_null($this->parent_key)) {
231
      return $this->{$this->parent_key}->service($service, ($name) ? $name : $this->name);
232 233 234 235
    }
    else {
      return new provisionService_null($this->name);
    }
236
  }
237 238
}

Adrian Rossouw's avatar
Adrian Rossouw committed
239 240 241 242 243 244
/**
 * Server environment class.
 *
 * This class bootstraps the Service API by generating server
 * objects for each of the available service types.
 */
245 246
class provisionServer extends provisionEnvironment {

247
  protected $services = array();
drumm's avatar
drumm committed
248 249 250 251 252 253 254 255 256 257 258 259 260
  static function option_documentation() {
    $options = array(
      '--remote_host' => 'server: host name; default localhost',
      '--script_user' => 'server: OS user name; default current user',
      '--aegir_root' => 'server: Aegir root; default /var/aegir',
      '--backup_path' => 'server: Aegir backup path; default {aegir_root}/backup',
      '--config_path' => 'server: Aegir configuration path; default {aegir_root}/config',
      '--master_url' => 'server: Hostmaster URL',
    );
    foreach (drush_command_invoke_all('provision_services') as $service => $default) {
      $reflect = new reflectionClass('provisionService_' . $service);
      $base_dir = dirname($reflect->getFilename());
      $types = array();
261
      $options['--' . $service . '_service_type'] = 'placeholder';
drumm's avatar
drumm committed
262 263 264 265 266 267 268
      foreach (array_keys(drush_scan_directory($base_dir, '%.*_service\.inc%')) as $service_file) {
        if (preg_match('%^' . $base_dir . '/([a-z]+)/(?:\1)_service.inc$%', $service_file, $match)) {
          $types[] = $match[1];
          include_once($service_file);
          $options = array_merge($options, call_user_func(array(sprintf('provisionService_%s_%s', $service, $match[1]), 'option_documentation')));
        }
      }
269
      $options['--' . $service . '_service_type'] = 'server: ' . implode(', ', $types) . ', or null; default ' . (empty($default) ? 'null' : $default);
drumm's avatar
drumm committed
270 271 272 273
    }
    return $options;
  }

274 275 276
  function init() {
    parent::init();

277 278 279 280
    $this->type = 'server';
    $this->setProperty('remote_host', 'localhost');
    $this->setProperty('script_user', get_current_user());
    $this->setProperty('aegir_root', '/var/aegir');
281
    $this->setProperty('backup_path', $this->aegir_root . '/backups');
282
    $this->setProperty('config_path', $this->aegir_root . '/config');
283
    $this->setProperty('master_url');
284 285 286
    $this->load_services();
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
287 288 289
  /**
   * Iterate through the available service types and spawn a handler for each type.
   */
290 291 292 293 294 295 296
  function load_services() {
    $service_list = drush_command_invoke_all('provision_services');
    foreach ($service_list as $service => $default) {
      $this->spawn_service($service, $default);
    }
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
297 298 299
  /**
   * Spawn an instance for a specific service type and associate it to the owner.
   */
300 301 302 303
  function spawn_service($service, $default = null) {
    $reflect = new reflectionClass('provisionService_' . $service);
    $base_dir = dirname($reflect->getFilename());

304
    $type_option = "{$service}_service_type";
305 306
    
    $type = isset($this->options[$type_option]) ? $this->options[$type_option] : $default;
307 308 309
    if ($service === 'file') {
      // Force provision-save local
      $command = drush_get_command();
310
      if (preg_match("/^provision-save\b/", $command['command'])) {
311 312 313
        $type = 'local';
      }
    }
314 315 316 317 318 319
    if ($type) {
      $file = sprintf("%s/%s/%s_service.inc", $base_dir, $type, $type);
      $className = sprintf("provisionService_%s_%s", $service, $type);
      if (file_exists($file)) {
        drush_log("Loading $type driver for the $service service");
        include_once($file);
320 321
        $object = new $className($this->name);
        $object->init();
322
        $this->services[$service] = $object;
323
        $this->setProperty($type_option, $type);
324 325 326
      }
    }
    else {
drumm's avatar
drumm committed
327
      $this->services[$service] = new provisionService_null($this->name);
328
    }
329
  }
330

Adrian Rossouw's avatar
Adrian Rossouw committed
331 332 333
  /**
   * Retrieve a service of a specific type from the environment.
   */
334 335
  function service($service, $name = null) {
    $this->services[$service]->context = ($name) ? $name : $this->name;
336 337
    return $this->services[$service];
  }
drumm's avatar
drumm committed
338

drumm's avatar
drumm committed
339 340 341 342 343 344 345 346
  /**
   * Call method $callback on each of the environment's service objects.
   *
   * @param $callback
   *   A provisionService method.
   * @return
   *   An array of return values from method implementations.
   */
drumm's avatar
drumm committed
347
  function services_invoke($callback) {
348 349
    foreach (array_keys($this->services) as $service) {
      $this->service($service)->$callback();
drumm's avatar
drumm committed
350
    }
351 352 353 354
  }

  function verify() {
    $this->services_invoke('verify');
drumm's avatar
drumm committed
355
  }
drumm's avatar
drumm committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374

  /**
   * Execute $command on this server, using SSH if necessary.
   *
   * @param $command
   *   Shell command to execute.
   *
   * @return
   *   Same as drush_shell_exec(). Use drush_shell_exec_output() for standard
   *   out and error.
   */
  function shell_exec($command) {
    if (drush_is_local_host($this->remote_host)) {
      return drush_shell_exec(escapeshellcmd($command));
    }
    else {
      return drush_shell_exec('ssh ' . drush_get_option('ssh-options', '-o PasswordAuthentication=no') . ' %s %s', $this->script_user . '@' . $this->remote_host, escapeshellcmd($command));
    }
  }
375 376
}

Adrian Rossouw's avatar
Adrian Rossouw committed
377 378 379
/**
 * Class for the platform environment.
 */
380
class provisionPlatform extends provisionEnvironment {
drumm's avatar
drumm committed
381 382
  static function option_documentation() {
    return array(
drumm's avatar
drumm committed
383
      '--root' => 'platform: path to a Drupal installation',
drumm's avatar
drumm committed
384 385
      '--server' => 'drush backend server; default @server_localhost',
      '--web_server' => 'web server hosting the platform; default @server_localhost',
drumm's avatar
drumm committed
386 387
    );
  }
388

389
  function init() {
390 391
    $this->parent_key = 'server';

392
    parent::init();
393
    $this->type = 'platform';
394
    $this->setProperty('root');
395

396
    $this->setProperty('server', '@server_localhost');
397 398
    $this->is_oid('server');

399
    $this->setProperty('web_server', '@server_localhost');
400
    $this->is_oid('web_server');
drumm's avatar
drumm committed
401
    $this->service_subscribe('http', $this->web_server->name);
402 403
  }

404
  function verify() {
405
    $this->service('http')->verify();
406 407 408 409
  }
}

class provisionSite extends provisionEnvironment {
drumm's avatar
drumm committed
410 411
  static function option_documentation() {
    return array(
drumm's avatar
drumm committed
412 413 414 415 416 417 418 419
      '--platform' => 'site: the platform the site is run on',
      '--db_server' => 'site: the db server the site is run on',
      '--uri' => 'site: example.com URI, no http:// or trailing /',
      '--site_port' => 'site: port the site is hosted on; default 80',
      '--language' => 'site: site language; default en',
      '--aliases' => 'site: comma-separated URIs',
      '--client_email' => 'site: email for the site owner',
      '--profile' => 'site: Drupal profile to use; default default',
drumm's avatar
drumm committed
420 421
    );
  }
422

423
  function init() {
424 425
    $this->parent_key = 'platform';

426
    parent::init();
427
    $this->type = 'site';
428 429 430 431 432 433 434
    
    $this->setProperty('platform');
    $this->is_oid('platform');

    // we need to set the alias root to the platform root, otherwise drush will cause problems.
    $this->root = $this->platform->root;

435
    $this->setProperty('uri');
436
    $this->setProperty('language', 'en');
437 438
    $this->setProperty('site_port', 80);
    $this->setProperty('client_email');
drumm's avatar
drumm committed
439
    $this->setProperty('aliases', array(), TRUE);
440

441 442
    $this->setProperty('db_server');
    $this->is_oid('db_server');
drumm's avatar
drumm committed
443
    $this->service_subscribe('db', $this->db_server->name);
444

drumm's avatar
drumm committed
445
    // this can potentially be handled by a Drupal sub class
446
    $this->setProperty('profile', 'default');
drumm's avatar
drumm committed
447
  }
448 449 450 451 452

  function verify() {
    $this->service('db')->verify();
    $this->service('http')->verify();
  }
453
}