provision.config.inc 10.3 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

drumm's avatar
drumm committed
43 44 45 46
  /**
   * Forward $this->... to $this->owner->...
   * object.
   */
47
  function __get($name) {
48 49
    if (isset($this->owner)) {
      return $this->owner->$name;
50 51
    }
  }
drumm's avatar
drumm committed
52 53 54 55 56 57 58 59 60 61 62

  /**
   * 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.
   */
63
  function __construct($owner, $data = array()) {
64 65 66
    if (is_null($this->template)) {
      throw(exception);
    }
67

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

71
    if (sizeof($data)) {
72
      $this->data = $data;
73
    }
74 75
    $this->parsed_data = $this->parse();
    $this->data = array_merge($this->parsed_data, $this->data);
drumm's avatar
drumm committed
76 77
  }

78 79 80 81 82 83 84
  /**
   * 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() {
85
    return array();
86 87
  }

88
  /**
drumm's avatar
drumm committed
89
   * Process and add to $data before writing the configuration.
90
   *
drumm's avatar
drumm committed
91
   * This is a stub to be implemented by subclasses.
92
   */
93 94 95 96
  function process() {
    return true;
  }

97
  /**
drumm's avatar
drumm committed
98
   * The filename where the configuration is written.
99
   * 
drumm's avatar
drumm committed
100
   * This is a stub to be implemented by subclasses.
101
   */
102 103 104 105
  function filename() {
    return false;
  }

drumm's avatar
drumm committed
106 107 108
  /**
   * Load template from filename().
   */
109 110 111 112 113 114 115 116 117 118 119 120 121 122
  private function load_template() {
    $reflect = new reflectionObject($this);
    $base_dir = dirname($reflect->getFilename());

    if (isset($this->template)) {
      $file = $base_dir . '/' . $this->template;
      drush_log("Template loaded: $file");
      if (file_exists($file) && is_readable($file)) {
        return file_get_contents($file);
      }
    }
    return false;
  }

drumm's avatar
drumm committed
123 124 125 126
  /**
   * Render template, making variables available from $variables associative
   * array.
   */
127 128 129 130 131 132 133 134 135 136 137
  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
138 139 140 141 142 143 144 145 146 147
  /**
   * 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.
   */
148 149
  function write() {
    $filename = $this->filename();
drumm's avatar
drumm committed
150
    // Make directory structure if it does not exist.
151 152
    if (!provision_file()->exists(dirname($filename))->status()) {
      provision_file()->mkdir(dirname($filename))
drumm's avatar
drumm committed
153 154 155 156
        ->succeed('Created directory @path.')
        ->fail('Could not create directory @path.');
    }

157 158 159 160 161
    if ($filename && is_writeable(dirname($filename))) {
      // manipulate data before passing to template.
      $this->process();

      if ($template = $this->load_template()) {
162
        // Make sure we can write to the file
163 164
        if (!is_null($this->mode) && !($this->mode & 0200) && provision_file()->exists($filename)->status()) {
          provision_file()->chmod($filename, $this->mode | 0200)
165 166 167 168
            ->succeed('Changed permissions of @path to @perm')
            ->fail('Could not change permissions of @path to @perm');
        }

drumm's avatar
drumm committed
169
        provision_file()->file_put_contents($filename, $this->render_template($template, $this->data))
170 171 172 173
          ->succeed('Generated config ' . (empty($this->description) ? $filename : $this->description), 'success');

        // Change the permissions of the file if needed
        if (!is_null($this->mode)) {
174
          provision_file()->chmod($filename, $this->mode)
175 176 177 178
            ->succeed('Changed permissions of @path to @perm')
            ->fail('Could not change permissions of @path to @perm');
        }
        if (!is_null($this->group)) {
179
          provision_file()->chgrp($filename, $this->group)
180 181
            ->succeed('Change group ownership of @path to @gid')
            ->fail('Could not change group ownership of @path to @gid');
182 183 184 185 186
        }
      }
    }
  }

drumm's avatar
drumm committed
187 188 189
  /**
   * Remove configuration file as specified by filename().
   */
190
  function unlink() {
191
    provision_file()->unlink($this->filename())->status();
192
  }
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 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


  /**
   * 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
270 271 272 273 274
    if ($newbody != $body) {
      drush_log("pattern $pattern found and replaced with $replacement");
    } else {
      drush_log("couldn't find line $pattern", 'warning');
    }
275 276 277 278 279 280
    fseek($fd, 0);
    fwrite($fd, $newbody);
    ftruncate($fd, strlen($newbody));
    fclose($fd);    
  }
  
281
}
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298

/**
 * 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);
  }

299 300
  function __construct($owner, $data = array()) {
    parent::__construct($owner, $data);
301 302
    $this->load_data();
  }
303

304
  function load_data() {
305 306 307 308 309 310 311 312 313 314 315
    // 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);
  }
}

316 317 318
/**
 * Class to write an alias records.
 */
319
class provisionConfig_drushrc_alias extends provisionConfig_drushrc {
drumm's avatar
drumm committed
320 321
  public $template = 'provision_drushrc_alias.tpl.php';

322
  /**
drumm's avatar
drumm committed
323 324 325 326
   * @param $name
   *   String '\@name' for named context.
   * @param $options
   *   Array of string option names to save.
327
   */
328 329
  function __construct($owner, $data = array()) {
    parent::__construct($owner, $data);
drumm's avatar
drumm committed
330
    $this->data = array(
331
      'aliasname' => ltrim($owner, '@'),
332
      'options' => $data,
drumm's avatar
drumm committed
333
    );
334 335 336
  }

  function filename() {
drumm's avatar
drumm committed
337
    return drush_server_home() . '/.drush/' . $this->data['aliasname'] . '.alias.drushrc.php'; 
338 339 340
  }
}

341 342 343 344 345 346 347 348
/**
 * Server level config for drushrc.php files.
 */
class provisionConfig_drushrc_server extends provisionConfig_drushrc {
  protected $context_name = 'user';
  public $description = 'Server drush configuration';
}

349 350 351 352 353 354
/**
 * Class for writing $platform/drushrc.php files.
 */
class provisionConfig_drushrc_platform extends provisionConfig_drushrc {
  protected $context_name = 'drupal';
  public $description = 'Platform Drush configuration file';
355 356 357 358

  function filename() {
    return $this->root . '/drushrc.php';
  }
359 360 361 362 363 364 365 366 367
}

/**
 * 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';
368 369 370 371

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