provision.inc 15.9 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 86 87 88 89 90 91 92 93
  $return = $extra;
  $return['site'] = $data;
  $return['error_status'] = provision_get_error(); // error code being returned
  $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 {
94 95 96 97 98 99 100
    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);
      }
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
    }
  }
  exit(provision_get_error());
}


/**
 * @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.
 */


118 119 120
if (PROVISION_SUCCESS != -1) {
  include_once('provision_errors.inc');
}
121 122 123 124 125 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 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

/**
 * 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) {
  _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) {
249
    if (preg_match("/^site_/", $key)) {
250 251 252
      $site_data[$key] = $value;
    }
  }
253 254 255
  $site_data['site_url'] = $url;
  $site_data['site_action_type'] = $args['commands'][1];
  $docroot = drush_get_option("r", $_SERVER['PWD']);
256
  $site_data['publish_path'] =  ($docroot) ? $docroot : $_SERVER['DOCUMENT_ROOT'];
257 258 259
  $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);
260 261
  if ($old_data = provision_load_site_data($url)) {
    # Merge previously saved data with the new data. This way, old parameters overwrite new ones.
262
    $site_data = array_merge($old_data, $site_data);    
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 305 306
  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");
307
    $action = array('id' => $data['action_id'], 
308
                    'timestamp' => mktime(), 
309
                    'action' => $data['site_action_type'], 
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
                    '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".
 */


/**
330
 * @defgroup provisionvalues Value replacement support for the provisioning framework
331 332 333 334
 * @{
 */


335

336
/**
337
 * List of values available for the config files
338 339
 *
 * @return
340
 *   A keyed array listing the substitution values.
341
 */
342 343 344 345 346 347 348 349 350 351 352
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;
353 354
}

355 356 357 358 359 360 361 362 363 364 365 366
/**
 * 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
}

367
/**
368
 * @} End of "defgroup provisionvalues".
369 370 371 372 373
 */

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

377 378
function provision_confirm_drush() {
  return true;
379 380
}

381
/**
382
 * Get the backup path of the Provision installation
383
 */
384 385 386 387 388 389
function _provision_backup_path() {
  $parts = explode("/", $_SERVER['DOCUMENT_ROOT']);
  array_pop($parts);
  return variable_get('provision_backup_path', implode("/" , $parts) . '/backups');
}

390

391
/**
392
 * Get the base configuration path for the system
393
 */
394 395 396 397
function _provision_config_path() {
  $path = ($_SERVER['PWD']) ? $_SERVER['PWD'] : $_SERVER['DOCUMENT_ROOT'];
  $parts = explode("/", $path);
  array_pop($parts);
Adrian Rossouw's avatar
Adrian Rossouw committed
398
  return variable_get('provision_config_path', implode("/" , $parts) . '/config') ;
399 400 401 402 403 404
}

/**
 * Get the vhost path of the Provision installation
 */
function _provision_vhost_path() {
405
 return _provision_config_path() . '/vhost.d';
406 407
}

408 409 410 411 412
/**
 * Get the drushrc path of the Provision installation
 */
function _provision_drushrc_path() {
 return _provision_config_path() . '/drush';
413 414
}

415

416 417 418 419 420 421 422 423 424 425 426 427 428
/**
 * 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);
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
}

/**
 * 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;
445 446
    $db_url = array();
    $db_url['default'] = $old_db_url;
447
  }
448

449
  if ($new_db_url) {
450 451
    $db_url[md5($new_db_url)] = $new_db_url;
    db_set_active(md5($new_db_url));
452 453
  }
  else {
454
    db_set_active('default');
455 456 457 458 459 460 461 462 463
  }
}

/**
 * Return the user who owns this script.
 *
 * Used to generate permissions. Can be overridden by variable_set().
 */
function provision_get_script_owner() {
464
  return variable_get('provision_script_user', get_current_user());   
465 466 467 468 469 470 471 472 473
}

/**
 * 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());
474 475
  print_r($info);
  return variable_get('provision_web_group', $info['name']); 
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
}

/**
 * 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;
495 496
    case "owner"    : $info = posix_getpwuid(fileowner($path));
                      $value = $info['name'];
497 498 499 500 501
                      break;        
    case "group"    : $value = filegroup($path); 
                      break;
    case "mkdir"    : $value = mkdir($path, 0770, true); 
                      break;
502 503 504
    case "chmod"    : chmod($path, $test);
                      $test = (int) sprintf('%o', $test); # needs to be reset to oct
                      $value =  (int) substr(sprintf('%o', fileperms($path)), -4);
505 506
                      break;
    case "chown"    : chown($path, $test);
507 508
                      $info = posix_getpwuid(fileowner($path));
                      $value = $info['name'];
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
                      break;
    case "chgrp"    : chgrp($path, $test);
                      $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;
  }
  $status = ($value == $test) ? true : false;
  if ($status) {
    if ($succeed_message) {
      provision_log("message", $succeed_message);      
    }
  }
  else {
    if ($error_codes) {
      provision_set_error($error_codes);
    }
    if ($fail_message) {
      provision_log("error", $fail_message);      
    }
  }
  return $status;
533
}