provision.inc 16 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
<?php
/**
 * @file
 * The provisioning framework API.
 *
 * API functions that are used by the provisioning framework to provide structure to the provisioning modules.
 *
 * @see errorhandling
 * @see logging
 * @see sitedata
11
 * @see provisionvalues
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
 */

/**
 * Invoke provision api calls. 
 * 
 * Call the correct hook for all the modules that implement it. We can not use Drupal's default module_invoke, because we
 * can not pass references through it.
 * Additionally, the ability to rollback when an error has been encountered is also provided.
 * If at any point during execution, the provision_get_error() function returns anything but 0, provision_invoke will
 * trigger $hook_rollback for each of the hooks that implement it, in reverse order from how they were executed.
 *
 * @param hook
 *   The hook name to be executed for all the modules.
 * @param url
 *   The url of the site being invoked.
 * @param data
 *   A reference to the associated array containing the data for the site. This needs to be a reference, 
 *   because the modules might provide additional information about the site.
 * @param rollback
 *   A boolean specifying whether or not the entire action needs to be rolled back. 
 *   This is used specifically in commands which implement multiple hooks, such as 'install',
 *   which implements 'pre_install', 'install' and 'post_install' hooks.
 * @return
 *   A boolean specifying whether or not any rollback has been performed. 
 * 
 */
function provision_invoke($hook, $url, &$data, $rollback = false) {
  
  if (!$rollback) {
    foreach (module_implements("provision_$hook") as $name) {
      $completed[] = $name;
      $func = $name . "_provision_" . $hook;
      $func($url, $data);
      if (provision_get_error()) {
        # As soon as an error occurs, roll back
        $rollback = TRUE;
        break;
      }
    }
  }
  else {
    $completed = module_implements("provision_$hook");
  }
  
  if ($rollback) {
    foreach (array_reverse($completed) as $name) {
      $func = $name . "_" . $hook . '_rollback';
      if (function_exists($func)) {
        $func($url, $data);
        provision_set_log("Rollback", "Changes for $name module have been rolled back.");
      }
    }
64
    return TRUE;
65 66
  }
  else {
67
    return FALSE;
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
  }
}

/**
 * Return output to the command line.
 *
 * Provides support for the -b/--backend flag to drush, which returns a serialized data structure.
 * This feature is used for communication with the front end.
 *
 * @param url
 *   The url of the site being invoked.
 * @param data
 *    The complete associative array containing all the aggregated site settings.
 * @param extra
 *   An associative array containing additional data to be returned from the command. @see provision_stats_stats()
 */
84
function provision_output($url = null, $data = array(), $extra = null) {
85
  $return = $extra;
Adrian Rossouw's avatar
Adrian Rossouw committed
86

87
  $return['site'] = $data;
Adrian Rossouw's avatar
Adrian Rossouw committed
88 89 90 91 92
  $error = provision_get_error();
  if (!$error) {
    $error = PROVISION_SUCCESS; // return 1 on success.
  }
  $return['error_status'] = $error; // error code being returned
93 94 95 96 97 98
  $return['log'] = provision_get_log(); // Append logging information
  $return['messages'] = drupal_get_messages();
  if (drush_get_option(array('b', 'backend'), FALSE)) {
    print serialize($return);
  }
  else {
99 100 101 102 103 104 105
    if ($return) {
      if ($output = theme("provision_" . $data['action_type'] . "_output", $url, $return)) {
        return $output;
      } else {
        /** TODO : return a cleanly formatted display of all the necessary information */ 
        print_r($return);
      }
106 107
    }
  }
Adrian Rossouw's avatar
Adrian Rossouw committed
108
  exit($error);
109 110 111 112 113 114 115 116 117 118 119 120 121 122
}


/**
 * @defgroup errorhandling Managing errors that occur in the provisioning framework.
 * @{
 * Functions that manage the current error status of the provisioning framework.
 *
 * These functions operate by maintaining a static variable that is a bitmask of all the errors that have occurred.
 * This bitmask value is returned at the end of program execution, and provide the hosting front end more information
 * on how to diagnose any problems that may have occurred.
 */


123 124 125
if (PROVISION_SUCCESS != -1) {
  include_once('provision_errors.inc');
}
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206

/**
 * Set an error code for the error handling system.
 *
 * @param error_code
 *   Any of the defined error status definitions. A numerical bitmask value.
 * @return
 *   The current aggregate error status
 */
function provision_set_error($error_code = 0) {
  static $error = 0;

  if ($error_code) {
    $error = $error | (int) $error_code;    
  }

  return $error;
}

/**
 * Return the current error handling status
 *
 * @return
 *   The current aggregate error status
 */
function provision_get_error() {
  return provision_set_error();
}

/**
 * Check if a specific error status has been set.
 *
 * @param error
 *   Any of the defined error status definitions. A numerical bitmask value. 
 * @return
 *   TRUE if the specified error has been set, FALSE if not 
 */
function provision_cmp_error($error) {
  return provision_get_error() ^ $error;
}

/**
 * @} End of "defgroup errorhandling".
 */

/**
 * @defgroup logging Logging information to be provided as output.
 * @{
 * Functions that allow the provisioning framework to log messages to be provided to the front end.
 *
 * These functions are primarily for diagnostic purposes, but also provide an overview of actions that were taken
 * by the framework during creation of a site.
 */

/**
 * Maintain a static array containing all the log messages
 *
 * @param entry
 *   Associative array containing the log message.
 * @return
 *   Entire log history, only if $entry is null
 */
function _provision_set_log($entry = null) {
  static $log = array();
  if ($entry == null) {
    return $log;
  }
  else {
    $log[] = $entry;
  }
}

/**
 * Add a log message to the log history.
 *
 * @param type
 *   The type of message to be logged. Common types are 'warning', 'error' and 'notice'.
 * @param message
 *   String containing the message to be logged.
 */
function provision_log($type, $message) {
207
  if (DRUSH_VERBOSE) print $message;
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
  _provision_set_log(array(
     'type' => $type, 
     'message' => $message, 
     'timestamp' => time()
    ));
}

/**
 * Retrieve the log messages from the log history
 *
 * @return
 *   Entire log history
 */
function provision_get_log() {
  return _provision_set_log();
}

/**
 * @} End of "defgroup errorhandling".
 */

/**
 * @defgroup sitedata Site data management utility functions.
 * @{
 * The provision framework maintains a site.php file in the sites directory, to maintain additional
 * information from the front end, as well as providing a change history of setting changes. 
 *
 * These functions load, save and manage changes made to the site data. This data has diagnostic and infrastructure
 * values, that allow sites to be more easily moved between different provisioned platforms.
 */

/**
 * Returns the aggregated site data from both the pre-existing site.php file, and the options passed to Drush
 *
 * This function merges the data from the command line parser, and the information already saved by previous invokations
 * of the api. This provides a single view of all data relating to the site.
 * This function also provides sensible defaults for some of the settings.
 *
 * @param url
 *   The url of the site being invoked.
 * @return
 *   An associated array containing the relevant settings for the site.
 */
function provision_get_site_data($url) {
  global $args;
  #TODO: Accept serialized string via unix pipe.
  foreach ($args['options'] as $key => $value) {
255
    if (preg_match("/^site_/", $key)) {
256 257 258
      $site_data[$key] = $value;
    }
  }
259 260
  $site_data['site_url'] = $url;
  $site_data['site_action_type'] = $args['commands'][1];
Adrian Rossouw's avatar
Adrian Rossouw committed
261
  $docroot = drush_get_option(array("r", "root"), $_SERVER['PWD']);
262
  $site_data['publish_path'] =  ($docroot) ? $docroot : $_SERVER['DOCUMENT_ROOT'];
263 264 265
  $site_data['site_profile'] = ($site_data['site_profile']) ? $site_data['site_profile'] : variable_get('provision_default_profile', 'default');
  $site_data['site_ip'] =  variable_get('provision_apache_server_ip', '127.0.0.1');
  $site_data['site_port'] =  variable_get('provision_apache_server_ip', 80);
266 267
  if ($old_data = provision_load_site_data($url)) {
    # Merge previously saved data with the new data. This way, old parameters overwrite new ones.
268
    $site_data = array_merge($old_data, $site_data);    
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 305 306 307 308 309 310 311 312
  return $site_data;
}

/**
 * Load site data stored in the site.php file for the specified site.
 *
 * @param url
 *   The url of the site being invoked
 * @return
 *   If the file was found, an associative array of the data that was loaded. Otherwise returns FALSE.
 */
function provision_load_site_data($url) {
  # Load the configuration data.
  $conf_file = "sites/$url/site.php";
  if (file_exists($conf_file) && is_readable($conf_file)) {
    require_once($conf_file);
    return (array) $data;
  }
  return false;
}

/**
 * Save modified options to the site.php file
 *
 * @param url
 *   The url of the site being invoked
 * @param data
 *   The complete data structure that has been created. Only settings that have been changed will be recorded.
 */
function provision_save_site_data($url, $data) {
  global $args;
    
  $old_data = provision_load_site_data($url);
  
  $conf_file = "sites/$url/site.php";
  $fp = fopen($conf_file, "w+"); # Append to the end of the config file.
  if (!$fp) {
    provision_log("error", "Site config file could not be written");
    provision_set_error(PROVISION_PERM_ERROR);
  }
  else {
    fwrite($fp, "<?php\n");
313
    $action = array('id' => $data['action_id'], 
314
                    'timestamp' => mktime(), 
315
                    'action' => $data['site_action_type'], 
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
                    'success' => $data['success']);
    $line = "\n\n\$actions[] = " . var_export($action, TRUE) . ';';
    fwrite($fp, $line);

    foreach ($data as $key => $value) {
      if ($data[$key] != $old_data[$key]) {
        $line = "\n\$data['$key'] = " . var_export($value, true) . ';';
        fwrite($fp, $line);  
      }
    }
    fclose($fp);
  }
}

/**
 * @} End of "defgroup sitedata".
 */


/**
336
 * @defgroup provisionvalues Value replacement support for the provisioning framework
337 338 339 340
 * @{
 */


341

342
/**
343
 * List of values available for the config files
344 345
 *
 * @return
346
 *   A keyed array listing the substitution values.
347
 */
348 349 350 351 352 353 354 355 356 357 358
function provision_value_list() {
    /** TODO: Complete the value list to allow the front end to more easily edit the settings. */
  $values['site_url'] = t("The domain name used to access the site. This is defaulted to the value used on the command line.");
  $values['site_db_type'] = t("The type of database server used");
  $values['site_db_username'] = t("Username to access database for site");
  $values['site_db_password'] = t("Password to access database for site");
  $values['site_db_name'] = t("Database name for the site");
  $values['site_profile'] = t("Install profile of site");
  $values['site_action_type'] = t("What type of action has been used. Only used in conjuction with hosting front end");

  return $values;
359 360
}

361 362 363 364 365 366 367 368 369 370 371 372
/**
 * Generate the text for a config file using php
 */
function provision_render_config($template, $variables) {
  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
  return $contents;                // Return the contents
}

373
/**
374
 * @} End of "defgroup provisionvalues".
375 376 377 378 379
 */

/**
 * Confirm that provision is running through Drush.
 *
380
 * A safety mechanism to ensure that the drush commands are not run through the web front end.
381 382
 */

383 384
function provision_confirm_drush() {
  return true;
385 386
}

387
/**
388 389
 * Provide defines for all the major paths.
 * Avoids duplication and possible errors in duplication
390
 */
391
function provision_init_paths() {
Adrian Rossouw's avatar
Adrian Rossouw committed
392 393
  $path = ($_SERVER['PWD']) ? $_SERVER['PWD'] : $_SERVER['DOCUMENT_ROOT'];
  $parts = explode("/", rtrim($path, '/'));
394
  array_pop($parts);
395

396 397 398 399
  define('PROVISION_BACKUP_PATH', rtrim(variable_get('provision_backup_path', implode("/" , $parts) . '/backups'), '/'));
  define('PROVISION_CONFIG_PATH', rtrim(variable_get('provision_config_path', implode("/" , $parts) . '/config'), '/')) ;
  define('PROVISION_VHOST_PATH', PROVISION_CONFIG_PATH . '/vhost.d');
  define('PROVISION_DRUSHRC_PATH', PROVISION_CONFIG_PATH . '/drushrc.d');
400 401
}

402

403 404 405 406 407 408 409 410 411 412 413 414 415
/**
 * Wrapper around drush_shell_exec to provide sprintf functionality with some more safety.
 */
function provision_shell_exec() {
  $args = func_get_args();
  
  #do not change the command itself, just the parameters.
  for ($x = 1; $x < sizeof($args); $x++) {
    $args[$x] = escapeshellcmd($args[$x]);
  }
  $command = call_user_func_array("sprintf", $args);
 
  return drush_shell_exec($command);
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
}

/**
 * Set the active database.
 *
 * Wrapper around db_set_active, which provides switching out of db_url.
 * @param new_db_url
 *    The database url to set the connection to. If not provided, will switch back to Drupal default.
 */
function provision_set_active_db($new_db_url = null) {
  static $old_db_url = null;
  global $db_url;
  
  #initialize static
  if (!$old_db_url) {
    $old_db_url = $db_url;
432 433
    $db_url = array();
    $db_url['default'] = $old_db_url;
434
  }
435

436
  if ($new_db_url) {
437 438
    $db_url[md5($new_db_url)] = $new_db_url;
    db_set_active(md5($new_db_url));
439 440
  }
  else {
441
    db_set_active('default');
442 443 444 445 446 447 448 449 450
  }
}

/**
 * Return the user who owns this script.
 *
 * Used to generate permissions. Can be overridden by variable_set().
 */
function provision_get_script_owner() {
451
  return variable_get('provision_script_user', get_current_user());   
452 453 454 455 456 457 458 459 460
}

/**
 * Return the group who runs the httpd daemin.
 *
 * Used to generate permissions. Can be overridden by variable_set().
 */
function provision_get_group_name() {
  $info = posix_getgrgid(posix_getgid());
461
  return variable_get('provision_web_group', $info['name']); 
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
}

/**
 * Perform tests on directory.
 *
 * Perform test on path, and log error messages / codes on failure.
 */
function provision_check_path($path, $type, $test = true, $succeed_message = '', $fail_message = '', $error_codes = null) {
  # The code style is a bit weird here, but it's a bit easier to read this way.
  switch ($type) {
    case "writeable" : 
    case "writable" : $value = is_writable($path); 
                      break;
    case "exists"   : $value = file_exists($path); 
                      break;
    case "readable" : $value = is_readable($path); 
                      break;
    case "is_dir"   : $value = is_dir($path); 
                      break;
481 482
    case "owner"    : $info = posix_getpwuid(fileowner($path));
                      $value = $info['name'];
483 484 485 486 487
                      break;        
    case "group"    : $value = filegroup($path); 
                      break;
    case "mkdir"    : $value = mkdir($path, 0770, true); 
                      break;
488 489 490
    case "chmod"    : chmod($path, $test);
                      $test = (int) sprintf('%o', $test); # needs to be reset to oct
                      $value =  (int) substr(sprintf('%o', fileperms($path)), -4);
491 492
                      break;
    case "chown"    : chown($path, $test);
493 494 495 496 497
                      if (!is_numeric($test)) {
		        $info = posix_getpwnam($test);
			$test = $info['uid'];
		      }
                      $value = fileowner($path);
498 499
                      break;
    case "chgrp"    : chgrp($path, $test);
500 501 502 503
                      if (!is_numeric($test)) {
		        $info = posix_getgrnam($test);
			$test = $info['gid'];
		      }
504 505 506 507 508 509 510
                      $value = filegroup($path);
                      break;
    case "unlink"   : $value = (file_exists($path)) ? unlink($path) : false; 
                      break;
    case "rmdir"    : $value = (file_exists($path) && is_dir($path)) ? rmdir($path) : false;
                      break;
  }
anarcat's avatar
anarcat committed
511
  $status = ($value == $test);
512 513 514 515 516 517 518 519 520 521
  if ($status) {
    if ($succeed_message) {
      provision_log("message", $succeed_message);      
    }
  }
  else {
    if ($error_codes) {
      provision_set_error($error_codes);
    }
    if ($fail_message) {
Adrian Rossouw's avatar
Adrian Rossouw committed
522
      provision_log("error", $fail_message . " ($value != $test)");
523 524 525
    }
  }
  return $status;
526
}