provision.environment.inc 11.6 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

drumm's avatar
drumm committed
16 17 18 19
  if (is_null($name)) {
    return $instances[$default_instance];
  }
  else {
20 21 22
    if ($root_object) {
      $default_instance = $name;
    }
23

24 25 26
    if (isset($instances[$name])) {
      return $instances[$name];
    }
27
    else {
28
      $instances[$name] = provision_environment_factory($name);
29 30 31
      $instances[$name]->init();
      return $instances[$name];
    }
32 33 34
  }
}

Adrian Rossouw's avatar
Adrian Rossouw committed
35 36 37
/**
 * Create a new environment object and cache it in the d() static cache function.
 */
38
function provision_environment_factory($name) {
39
  $classes = array('server' => 'provisionServer', 'platform' => 'provisionPlatform', 'site' => 'provisionSite');
Adrian Rossouw's avatar
Adrian Rossouw committed
40

41 42 43
  $type = 'server';

  $record = drush_sitealias_get_record($name);
44
  $options = array_merge(drush_get_context('stdin'), drush_get_context('options'));
45 46 47 48 49 50 51

  if (isset($record['context_type'])) {
    $type = $record['context_type'];
  }
  elseif (isset($options['context_type'])) {
    $type = $options['context_type'];
  }
52
  $classname = $classes[$type];
53 54

  return new $classname($name);
Adrian Rossouw's avatar
Adrian Rossouw committed
55 56
}

57

Adrian Rossouw's avatar
Adrian Rossouw committed
58 59 60 61 62
/**
 * Base environment class.
 *
 * Contains magic getter/setter functions
 */
63
class provisionEnvironment {
64 65 66
  public $name = null;
  public $type = null;

67

68
  protected $properties = array();
69 70

  protected $service_subs = array();
71
  protected $parent_key = null;
72 73 74

  protected $oid_map = array();

Adrian Rossouw's avatar
Adrian Rossouw committed
75 76
  /**
   * Retrieve value from $properties array if property does not exist in class proper.
77 78 79 80
   *
   * 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
81
   */
82
  function __get($name) {
83
    if ($name == 'options') {
84
      return array_merge(drush_sitealias_get_record($this->name), array_filter(drush_get_context('stdin')), array_filter(drush_get_context('options')));
85
    }
86
    if (array_key_exists($name, $this->properties)) {
87
      if (isset($this->oid_map[$name]) && !empty($this->properties[$name])) {
88 89 90 91 92
        return d($this->properties[$name]);
      }
      else {
        return $this->properties[$name];
      }
93 94 95
    }
  }

96 97 98
  /**
   * Specify that a property contains a named context.
   */
99 100 101 102
  function is_oid($name) {
    $this->oid_map[$name] = TRUE;
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
103 104 105
  /**
   * Store value in properties array if the property does not exist in class proper.
   */
106 107 108 109 110 111 112 113 114
  function __set($name, $value) {
    if (!property_exists($this, $name)) {
      $this->properties[$name] = $value;
    }
    else {
      $this->$name = $value;
    }
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
115 116 117
  /**
   * Check the properties array if the property does not exist in the class proper.
   */
118 119 120 121 122 123 124
  function __isset($name) {
    if (property_exists($this->properties, $name) || property_exists($this, $name)) {
      return TRUE;
    }
    return FALSE;
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
125 126 127
  /**
   * Remove the value from the properties array if the property does not exist in the class proper.
   */
128 129 130 131 132 133 134 135 136 137
  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
138 139 140
  /**
   * Constructor for the environment.
   */
141
  function __construct($name) {
142
    $this->name = $name;
143 144
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
145 146 147
  /**
   * Init stub function/
   */
148
  function init() {
149
    $this->setProperty('context_type', 'server');
150 151 152
    return true;
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
153 154 155
  /**
   * Check the $options property for a field, saving to the properties array.
   */
156 157 158 159 160 161 162 163 164
  function setProperty($field, $default = null) {
    if (isset($this->options[$field])) {
      $this->$field = $this->options[$field];
    }
    else {
      $this->$field = $default;
    }
  }

drumm's avatar
drumm committed
165 166 167 168
  /**
   * Write out this named context to an alias file.
   */
  function write_alias() {
169
    $config = new provisionConfig_drushrc_alias($this->name, $this->properties);
drumm's avatar
drumm committed
170 171
    $config->write();
  }
172

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
  /**
   * 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.
   */
190
  function service($service, $name = null) {
191 192 193 194
    if (isset($this->service_subs[$service])) {
      return d($this->service_subs[$service])->service($service, ($name) ? $name : $this->name);
    }
    elseif (!is_null($this->parent_key)) {
195
      return $this->{$this->parent_key}->service($service, ($name) ? $name : $this->name);
196 197 198 199
    }
    else {
      return new provisionService_null($this->name);
    }
200
  }
201 202
}

Adrian Rossouw's avatar
Adrian Rossouw committed
203 204 205 206 207 208
/**
 * Server environment class.
 *
 * This class bootstraps the Service API by generating server
 * objects for each of the available service types.
 */
209 210
class provisionServer extends provisionEnvironment {

211
  protected $services = array();
drumm's avatar
drumm committed
212 213 214 215 216 217 218 219 220 221 222 223 224
  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();
225
      $options['--' . $service . '_service_type'] = 'placeholder';
drumm's avatar
drumm committed
226 227 228 229 230 231 232
      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')));
        }
      }
233
      $options['--' . $service . '_service_type'] = 'server: ' . implode(', ', $types) . ', or null; default ' . (empty($default) ? 'null' : $default);
drumm's avatar
drumm committed
234 235 236 237
    }
    return $options;
  }

238 239 240
  function init() {
    parent::init();

241 242 243 244
    $this->type = 'server';
    $this->setProperty('remote_host', 'localhost');
    $this->setProperty('script_user', get_current_user());
    $this->setProperty('aegir_root', '/var/aegir');
245
    $this->setProperty('backup_path', $this->aegir_root . '/backups');
246
    $this->setProperty('config_path', $this->aegir_root . '/config');
247
    $this->setProperty('master_url');
248 249 250
    $this->load_services();
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
251 252 253
  /**
   * Iterate through the available service types and spawn a handler for each type.
   */
254 255 256 257 258 259 260
  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
261 262 263
  /**
   * Spawn an instance for a specific service type and associate it to the owner.
   */
264 265 266 267
  function spawn_service($service, $default = null) {
    $reflect = new reflectionClass('provisionService_' . $service);
    $base_dir = dirname($reflect->getFilename());

268
    $type_option = "{$service}_service_type";
269 270
    
    $type = isset($this->options[$type_option]) ? $this->options[$type_option] : $default;
271 272 273
    if ($service === 'file') {
      // Force provision-save local
      $command = drush_get_command();
274
      if (preg_match("/^provision-save\b/", $command['command'])) {
275 276 277
        $type = 'local';
      }
    }
278 279 280 281 282 283
    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);
284 285
        $object = new $className($this->name);
        $object->init();
286
        $this->services[$service] = $object;
287
        $this->setProperty($type_option, $type);
288 289 290
      }
    }
    else {
drumm's avatar
drumm committed
291
      $this->services[$service] = new provisionService_null($this->name);
292
    }
293
  }
294

Adrian Rossouw's avatar
Adrian Rossouw committed
295 296 297
  /**
   * Retrieve a service of a specific type from the environment.
   */
298 299
  function service($service, $name = null) {
    $this->services[$service]->context = ($name) ? $name : $this->name;
300 301
    return $this->services[$service];
  }
drumm's avatar
drumm committed
302

drumm's avatar
drumm committed
303 304 305 306 307 308 309 310
  /**
   * 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
311
  function services_invoke($callback) {
312 313
    foreach (array_keys($this->services) as $service) {
      $this->service($service)->$callback();
drumm's avatar
drumm committed
314
    }
315 316 317 318
  }

  function verify() {
    $this->services_invoke('verify');
drumm's avatar
drumm committed
319
  }
320 321
}

Adrian Rossouw's avatar
Adrian Rossouw committed
322 323 324
/**
 * Class for the platform environment.
 */
325
class provisionPlatform extends provisionEnvironment {
drumm's avatar
drumm committed
326 327
  static function option_documentation() {
    return array(
drumm's avatar
drumm committed
328
      '--root' => 'platform: path to a Drupal installation',
drumm's avatar
drumm committed
329 330
      '--server' => 'drush backend server; default @server_localhost',
      '--web_server' => 'web server hosting the platform; default @server_localhost',
drumm's avatar
drumm committed
331 332
    );
  }
333

334
  function init() {
335 336
    $this->parent_key = 'server';

337
    parent::init();
338
    $this->type = 'platform';
339
    $this->setProperty('root');
340

341 342
    $this->service_subscribe("http", $this->web_server);

343
    $this->setProperty('server', '@server_localhost');
344 345
    $this->is_oid('server');

346
    $this->setProperty('web_server', '@server_localhost');
347
    $this->is_oid('web_server');
348 349
  }

350
  function verify() {
351
    $this->service('http')->verify();
352 353 354 355
  }
}

class provisionSite extends provisionEnvironment {
drumm's avatar
drumm committed
356 357
  static function option_documentation() {
    return array(
drumm's avatar
drumm committed
358 359 360 361 362 363 364 365
      '--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
366 367
    );
  }
368

369
  function init() {
370 371
    $this->parent_key = 'platform';

372
    parent::init();
373
    $this->type = 'site';
374 375 376 377 378 379 380
    
    $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;

381
    $this->setProperty('uri');
382 383
    $this->setProperty('language', 'en');
    $this->setProperty('aliases', array());
384 385 386
    $this->setProperty('site_port', 80);

    $this->setProperty('client_email');
387

388
    $this->service_subscribe("db", $this->db_server);
389 390 391
    $this->setProperty('db_server');
    $this->is_oid('db_server');

392 393 394 395 396 397 398 399
    // todo - turn into a re-usable mechanism for comma separated values
    if ($this->options['aliases'] && !is_array($site_data['aliases'])) {
      $this->aliases = explode(",", $site_data['aliases']);
    }
    else {
      $this->aliases = array();
    }

drumm's avatar
drumm committed
400
    // this can potentially be handled by a Drupal sub class
401
    $this->setProperty('profile', 'default');
drumm's avatar
drumm committed
402
  }
403 404 405 406 407

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