provision.config.inc 12.7 KB
Newer Older
1 2 3 4 5 6 7 8 9
<?php

// $Id$

/**
 * Provision configuration generation classes.
 */

class provisionConfig {
drumm's avatar
drumm committed
10 11 12 13
  /**
   * Template file, a PHP file which will have access to $this and variables
   * as defined in $data.
   */
14
  public $template = null;
drumm's avatar
drumm committed
15 16 17 18

  /**
   * Associate array of variables to make available to the template.
   */
19
  public $data = array();
drumm's avatar
drumm committed
20 21 22 23 24 25

  /**
   * A provisionEnvironment object thie configuration relates to.
   *
   * @var provisionEnvironment
   */
26
  public $owner = null;
drumm's avatar
drumm committed
27 28 29 30

  /**
   * If set, replaces file name in log messages.
   */
31
  public $description = null;
drumm's avatar
drumm committed
32 33 34 35

  /**
   * Octal Unix mode for permissons of the created file.
   */
36
  protected $mode = NULL;
drumm's avatar
drumm committed
37 38 39 40

  /**
   * Unix group name for the created file.
   */
41
  protected $group = NULL;
42

Adrian Rossouw's avatar
Adrian Rossouw committed
43 44 45 46 47 48 49 50 51 52
  /**
   * An optional data store class to instantiate for this config.
   */
  protected $data_store_class = NULL;

  /**
   * The data store.
   */
  protected $store = NULL;

drumm's avatar
drumm committed
53 54 55 56
  /**
   * Forward $this->... to $this->owner->...
   * object.
   */
57
  function __get($name) {
58 59
    if (isset($this->owner)) {
      return $this->owner->$name;
60 61
    }
  }
drumm's avatar
drumm committed
62 63 64 65 66 67 68 69 70 71 72

  /**
   * Constructor, overriding not recommended.
   *
   * @param $owner
   *   An alias name for d(), the provisionEnvironment that this configuration
   *   is relevant to.
   * @param $data
   *   An associative array to potentiall manipulate in process() and make
   *   available as variables to the template.
   */
73
  function __construct($owner, $data = array()) {
74 75 76
    if (is_null($this->template)) {
      throw(exception);
    }
77

78 79
    // Accept both a reference and an alias name for the owner.
    $this->owner = is_object($owner) ? $owner : d($owner);
80

81
    if (sizeof($data)) {
82
      $this->data = $data;
83
    }
Adrian Rossouw's avatar
Adrian Rossouw committed
84 85 86 87 88 89 90 91 92
    //$this->parsed_data = $this->parse();
    //$this->data = array_merge($this->parsed_data, $this->data);

    
    if (!is_null($this->data_store_class) && class_exists($this->data_store_class)) {
      $class = $this->data_store_class;
      $this->store = new $class($owner, $data);
    }

drumm's avatar
drumm committed
93 94
  }

95 96 97 98 99 100 101
  /**
   * Parse the existing config to load data
   *
   * This is called in the constructor, and should take care of
   * opening the file and parsing it into the data array.
   */
  function parse() {
102
    return array();
103 104
  }

105
  /**
drumm's avatar
drumm committed
106
   * Process and add to $data before writing the configuration.
107
   *
drumm's avatar
drumm committed
108
   * This is a stub to be implemented by subclasses.
109
   */
110 111 112 113
  function process() {
    return true;
  }

114
  /**
drumm's avatar
drumm committed
115
   * The filename where the configuration is written.
116
   * 
drumm's avatar
drumm committed
117
   * This is a stub to be implemented by subclasses.
118
   */
119 120 121 122
  function filename() {
    return false;
  }

drumm's avatar
drumm committed
123 124 125
  /**
   * Load template from filename().
   */
126
  private function load_template() {
Adrian Rossouw's avatar
Adrian Rossouw committed
127 128
    $class_name = get_class($this);

129 130 131
    $reflect = new reflectionObject($this);

    if (isset($this->template)) {
Adrian Rossouw's avatar
Adrian Rossouw committed
132 133 134 135 136 137 138 139 140 141 142 143 144 145
      while ($class_name) {
        // Iterate through the config file's parent classes until we
        // find the template file to use.
        $reflect = new reflectionClass($class_name);
        $base_dir = dirname($reflect->getFilename());

        $file = $base_dir . '/' . $this->template;

        if (file_exists($file) && is_readable($file)) {
          drush_log("Template loaded: $file");
          return file_get_contents($file);
        }

        $class_name = get_parent_class($class_name);
146
      }
Adrian Rossouw's avatar
Adrian Rossouw committed
147 148 149

    } 

150 151 152
    return false;
  }

drumm's avatar
drumm committed
153 154 155 156
  /**
   * Render template, making variables available from $variables associative
   * array.
   */
157 158 159 160 161 162 163 164 165 166 167
  private function render_template($template, $variables) {
    drush_errors_off();
    extract($variables, EXTR_SKIP);  // Extract the variables to a local namespace
    ob_start();                      // Start output buffering
    eval('?>'. $template);                 // Generate content
    $contents = ob_get_contents();   // Get the contents of the buffer
    ob_end_clean();                  // End buffering and discard
    drush_errors_on();
    return $contents;                // Return the contents
  }

drumm's avatar
drumm committed
168 169 170 171 172 173 174 175 176 177
  /**
   * Write out this configuration.
   *
   * 1. Make sure parent directory exists and is writable.
   * 2. Load template with load_template().
   * 3. Process $data with process().
   * 4. Make existing file writable if necessary and possible.
   * 5. Render template with $this and $data and write out to filename().
   * 6. If $mode and/or $group are set, apply them for the new file.
   */
178 179
  function write() {
    $filename = $this->filename();
drumm's avatar
drumm committed
180
    // Make directory structure if it does not exist.
181 182
    if (!provision_file()->exists(dirname($filename))->status()) {
      provision_file()->mkdir(dirname($filename))
drumm's avatar
drumm committed
183 184 185 186
        ->succeed('Created directory @path.')
        ->fail('Could not create directory @path.');
    }

187 188 189 190 191
    if ($filename && is_writeable(dirname($filename))) {
      // manipulate data before passing to template.
      $this->process();

      if ($template = $this->load_template()) {
192
        // Make sure we can write to the file
193 194
        if (!is_null($this->mode) && !($this->mode & 0200) && provision_file()->exists($filename)->status()) {
          provision_file()->chmod($filename, $this->mode | 0200)
195 196 197 198
            ->succeed('Changed permissions of @path to @perm')
            ->fail('Could not change permissions of @path to @perm');
        }

Adrian Rossouw's avatar
Adrian Rossouw committed
199
        $this->file_put_contents($filename, $this->render_template($template, $this->data));
200 201 202

        // Change the permissions of the file if needed
        if (!is_null($this->mode)) {
203
          provision_file()->chmod($filename, $this->mode)
204 205 206 207
            ->succeed('Changed permissions of @path to @perm')
            ->fail('Could not change permissions of @path to @perm');
        }
        if (!is_null($this->group)) {
208
          provision_file()->chgrp($filename, $this->group)
209 210
            ->succeed('Change group ownership of @path to @gid')
            ->fail('Could not change group ownership of @path to @gid');
211 212 213 214 215
        }
      }
    }
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
216 217 218 219 220 221
  // allow overriding w.r.t locking
  function file_put_contents($filename, $text) {
    provision_file()->file_put_contents($filename, $text)
      ->succeed('Generated config ' . (empty($this->description) ? $filename : $this->description), 'success');
  }

drumm's avatar
drumm committed
222 223 224
  /**
   * Remove configuration file as specified by filename().
   */
225
  function unlink() {
226
    provision_file()->unlink($this->filename())->status();
227
  }
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304


  /**
   * add a line to a file if the file (or pattern) is not in the file
   * already
   *
   * This is bound to be called repeatedly so it needs to be fast.
   */
  function add_line_if_not_exists($line, $pattern = null) {
    $file = $this->filename();
    // XXX: we should do exponential backoff here to limit contention
    $fd = fopen($file, 'r+'); // read/write, beginning of file
    flock($fd, LOCK_EX);
    $line = trim($line);
    if (is_null($pattern)) {
      $pattern = '/' . $line . '/';
    }
    $found = FALSE;
    while ($l = fgets($fd)) {
      if (preg_match($pattern, $l)) {
        $found = TRUE;
        break;
      }
    }
    if (!$found) {
      fseek($fd, 0, SEEK_END);
      drush_log("pattern $pattern not found in file $file, adding at the end: $line");
      fwrite($fd, $line . "\n");
    }
    fclose($fd);
    return $found;
  }

  /**
   * add a line to a file if the file (or pattern) is not in the file
   * already
   *
   * This is bound to be called repeatedly so it needs to be fast.
   */
  function add_line($line) {
    $file = $this->filename();
    // XXX: we should do exponential backoff here to limit contention
    $fd = fopen($file, 'a'); // read/write, beginning of file
    flock($fd, LOCK_EX);
    $line = trim($line);
    fwrite($fd, $line . "\n");
    fclose($fd);
  }

  function replace_or_add_line($replacement, $pattern) {
    $file = $this->filename();
    if (!($fd = fopen($file, 'a+'))) {
      drush_log("warning: cannot open $file");
    }

    flock($fd, LOCK_EX);
    $body = fread($fd, filesize($file));
    $newbody = preg_replace($pattern, $replacement, $body);
    if ($body === $newbody) {
      drush_log("pattern $pattern not found in $file, adding at the end: $replacement");
      fseek($fd, 0, SEEK_END);
      fwrite($fd, $replacement . "\n");
    } else {
      drush_log("pattern $pattern found and replaced with $replacement");
      fseek($fd, 0);
      fwrite($fd, $newbody);
      ftruncate($fd, strlen($newbody));
    }
    fclose($fd);    
  }

  function delete_line($pattern) {
    $file = $this->filename();
    $fd = fopen($file, 'r+');
    flock($fd, LOCK_EX);
    $body = fread($fd, filesize($file));
    $newbody = preg_replace($pattern, '', $body);
anarcat's avatar
anarcat committed
305 306 307 308 309
    if ($newbody != $body) {
      drush_log("pattern $pattern found and replaced with $replacement");
    } else {
      drush_log("couldn't find line $pattern", 'warning');
    }
310 311 312 313 314 315
    fseek($fd, 0);
    fwrite($fd, $newbody);
    ftruncate($fd, strlen($newbody));
    fclose($fd);    
  }
  
316
}
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333

/**
 * Specialized class to handle the creation of drushrc.php files.
 *
 * This is based on the drush_save_config code, but has been abstracted
 * for our purposes.
 */ 
class provisionConfig_drushrc extends provisionConfig {
  public $template = 'provision_drushrc.tpl.php';
  public $description = 'Drush configuration file';
  protected $mode = 0400;
  protected $context_name = 'drush';

  function filename() {
    return _drush_config_file($this->context_name);
  }

334 335
  function __construct($owner, $data = array()) {
    parent::__construct($owner, $data);
336 337
    $this->load_data();
  }
338

339
  function load_data() {
340 341 342 343 344 345 346 347 348 349 350
    // we fetch the context to pass into the template based on the context name
    $this->data = array_merge(drush_get_context($this->context_name), $this->data);
  }

  function process() {
    unset($this->data['context-path']);
    unset($this->data['config-file']);
    $this->data['option_keys'] = array_keys($this->data);
  }
}

351 352 353
/**
 * Class to write an alias records.
 */
354
class provisionConfig_drushrc_alias extends provisionConfig_drushrc {
drumm's avatar
drumm committed
355 356
  public $template = 'provision_drushrc_alias.tpl.php';

357
  /**
drumm's avatar
drumm committed
358 359 360 361
   * @param $name
   *   String '\@name' for named context.
   * @param $options
   *   Array of string option names to save.
362
   */
363 364
  function __construct($owner, $data = array()) {
    parent::__construct($owner, $data);
drumm's avatar
drumm committed
365
    $this->data = array(
366
      'aliasname' => ltrim($owner, '@'),
367
      'options' => $data,
drumm's avatar
drumm committed
368
    );
369 370 371
  }

  function filename() {
drumm's avatar
drumm committed
372
    return drush_server_home() . '/.drush/' . $this->data['aliasname'] . '.alias.drushrc.php'; 
373 374 375
  }
}

376 377 378 379 380 381 382 383
/**
 * Server level config for drushrc.php files.
 */
class provisionConfig_drushrc_server extends provisionConfig_drushrc {
  protected $context_name = 'user';
  public $description = 'Server drush configuration';
}

384 385 386 387 388 389
/**
 * Class for writing $platform/drushrc.php files.
 */
class provisionConfig_drushrc_platform extends provisionConfig_drushrc {
  protected $context_name = 'drupal';
  public $description = 'Platform Drush configuration file';
390 391 392 393

  function filename() {
    return $this->root . '/drushrc.php';
  }
394 395 396 397 398 399 400 401 402
}

/**
 * Class for writing $platform/sites/$url/drushrc.php files.
 */
class provisionConfig_drushrc_site extends provisionConfig_drushrc {
  protected $context_name = 'site';
  public $template = 'provision_drushrc_site.tpl.php';
  public $description = 'Site Drush configuration file';
403 404 405 406

  function filename() {
    return $this->site_path . '/drushrc.php';
  }
407 408
}

Adrian Rossouw's avatar
Adrian Rossouw committed
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486

class provisionConfig_data_store extends provisionConfig {
  public $template = 'data_store.tpl.php';
  public $key = 'record';

  private $locked = FALSE;
  protected $fp = null;

  public $records = array();
  protected $loaded_records = array();

  protected $mode = 0400;


  function __construct($owner, $data = array()) {
    parent::__construct($owner, $data);

    $this->init_records = $data;
    $this->load_data();
  }

  function __destruct() {
    // release the file lock if we have it.
    $this->close();
  }

  function open() {
    if (!$this->fp) {
      $this->fp = fopen($this->filename(), "w");
    }
  }

  function lock() {
    if (!$this->locked) {
      $this->open();
      flock($this->fp, LOCK_EX);

      // Do one last load before setting our locked status.
      $this->load_data();
      $this->locked = TRUE;
    }
  }

  function file_put_contents($filename, $text) {
    $this->lock();
    fwrite($this->fp, $text);
    fflush($this->fp);
    $this->close();
  }

  function unlock() {
    if ($this->locked && $this->fp) {
      flock($this->fp, LOCK_UN);
      $this->locked = FALSE;
    }
  }

  function close() {
    if ($this->fp) {
      fclose($this->fp);
    }
  }

  function load_data() {
    if (!$this->locked) {
      // Once we have the lock we dont need to worry about it changing
      // from under us.
      if (file_exists($this->filename()) && is_readable($this->filename())) {
        include($this->filename());
        $this->loaded_records = ${$this->key};
      }
    }
  }

  function process() {
    $this->data['records'] = array_merge($this->loaded_records, $this->records);
  }
}