provision.drush.inc 12.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 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
<?php
/**
 * Drush commands
 *
 *
 * This module provides a framework for a Drupal site to manage and install new Drupal sites, using the command line
 * Drush utility. 
 *
 * It allows for pluggable 'provisioning modules' that can extend and modify the tasks that are taken during installation.
 *
 * Each site has the following commands that can be run on it.
 *
 * Implemented : 
 *   install - Install a new Drupal site. The install command uses 3 separate hooks to do it's job, 
 *              namely hook_provision_pre_install(), hook_provision_install() and hook_provision_post_install()
 *   verify  - Recreate all configuration files, to be in synch with changes in the front end. And test that they are correct.
 *   stats   - Return an associated array of site statistics. (implemented in provision_stats module, is thus optional)
 *   import  - Import the details of an already existing site into the provisioning framework.
 *             This command inspects the settings.php and generates the site.php file that the framework uses for configuration.
 *   backup  - Generates a tarball containing the sites directory, the site data configuration and the database dump.
 *             This allows the tarball to act as a 'site package', which can be redeployed on other installations,
 *             or used for an upgrade.
 *   disable - Disable an installed Drupal site. Changes the virtual host config file so that it redirects to provision_disabled_site_redirect_url
 *   enable  - Re-enable a site that has already been disabled. Recreates the virtual host file.
 *   delete -  Generates a back up of the site, and then removes all references to it.
 *   restore - Revert to a previous backup of the site.
 *   cron - Run cron process for a site
 *
 * Not implemented yet :
 *   upgrade - Accepts a site package (backup) as argument, and redeploys it, running the upgrade processes on it.
 *             Uses hook_provision_pre_upgrade(), hook_provision_upgrade() and hook_provision_post_upgrade() hooks,
 *             and allows clean roll back if any errors occur. Will include stringent checking of module versions,
 *             and allow unit tests to be run.
 *   rename  - Change the url of a site. This requires moving of files, and numerous other issues.
 */

/**
 * @defgroup provisiondrush Command line interface for Provision.
 * @{
 */
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
include_once('provision.inc');
include_once('provision.path.inc');
provision_init();
/** 
 * Implementation of hook_init
 *
 * Provide defines for all the major paths and settings.
 * These are settings that must not be modified during the running of the
 * program, but are configurable.
 */
function provision_init() {
  // Do not allow the program to be run as the root user. ever
  $name = posix_getpwuid(posix_geteuid());
  if ($name['name'] == 'root') {
    provision_log('error', 'You are running the provision script as the root user. Exiting');
    provision_set_error(PROVISION_FRAMEWORK_ERROR);
    provision_output($url);
  }
  // Load requested additional modules
  #provision_load_from_args();

  // Set up defines for platform
  $docroot = provision_get_option(array("r", "root"), $_SERVER['PWD']);
  $backend = provision_get_option(array('b', 'backend'), 0);
  define("PROVISION_DRUSH_BACKEND", $backend);
  
  define('PROVISION_DOMAIN', $_SERVER['HTTP_HOST']);
  // Paths
  $path =  ($docroot) ? $docroot : $_SERVER['DOCUMENT_ROOT'];
  define('PROVISION_DOCROOT_PATH', rtrim($path, '/'));
  define('PROVISION_SITES_PATH', rtrim($path, '/') .'/sites');
  define('PROVISION_DRUSH_PATH', './drush.php');
  $parts = explode("/", rtrim($path, '/'));
  array_pop($parts);
  define('PROVISION_PARENT_PATH', rtrim(implode("/" , $parts), '/'));
  define('PROVISION_BACKUP_PATH', 
    provision_get_option('backup_path', PROVISION_PARENT_PATH . '/backups'));
  define('PROVISION_CONFIG_PATH', 
    provision_get_option('config_path', PROVISION_PARENT_PATH .'/config'));
  define('PROVISION_VHOST_PATH', PROVISION_CONFIG_PATH .'/vhost.d');

  // Commands
  define('PROVISION_RESTART_CMD',
    provision_get_option('restart_cmd', _provision_default_restart_cmd()));

  // System account
  $info = posix_getgrgid(posix_getgid());
  define('PROVISION_WEB_GROUP', 
    provision_get_option('web_group', $info['name'] ));
  define('PROVISION_SCRIPT_USER', 
    provision_get_option('script_user', get_current_user() ));

  // Redirection urls
  define('PROVISION_MASTER_URL', 
    provision_get_option('master_url',  $GLOBALS['base_url']));
  define('PROVISION_WEB_DISABLE_URL', PROVISION_MASTER_URL .'/provision/disabled');
  define('PROVISION_WEB_MAINTENENCE_URL', PROVISION_MASTER_URL .'/provision/maintenance');

  // Database
  define('PROVISION_MASTER_DB', 
    provision_get_option('master_db', $GLOBALS['db_url']));
  $db = parse_url(PROVISION_MASTER_DB);
  define('PROVISION_DB_USER', $db['user']);
  define('PROVISION_DB_PASSWD', $db['pass']);
  define('PROVISION_DB_HOST', $db['host']);
 
  // Drupal does not support multiple types of connections in the same session
  preg_match("$^([a-z]*)://$", $GLOBALS['db_url'], $matches);
  define('PROVISION_DB_TYPE', $matches[1]);
}

112 113 114 115 116 117 118 119 120

/**
 * Implementation of hook_drush_command().
 */
function provision_drush_command() {
  
  $items['provision install'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('install'),
121 122
    'arguments' => array('domain.com' => dt('The domain of the site to install.')),
    'description' => dt('Provision a new site using the provided data.'),
123
    'bootstrap' => -1
124 125 126 127 128
  );

  $items['provision import'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('import'),
129 130
    'arguments' => array('domain.com' => dt('The domain of the site to import.')),
    'description' => dt('Turn an already running site into a provisioned site.'),
131
    'bootstrap' => -1
132 133 134 135 136
  );

  $items['provision update'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('update'),
137
    'arguments' => array('domain.com' => dt('The domain of the site to update.')),
138 139
    'description' => dt('Run any outstanding updates on the site.'),
    'bootstrap' => -1
140 141 142 143 144
  );
  
  $items['provision backup'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('backup'),
145 146
    'arguments' => array('domain.com' => dt('The domain of the site to back up.')),
    'optional arguments' => array('backup-file' => dt('The file to save the backup to. This will be a gzipped tarball.')),
147 148
    'description' => dt('Generate a back up for the site.'),
    'bootstrap' => -1
149 150 151 152 153
  );
  
  $items['provision enable'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('enable'), 
154
    'arguments' => array('domain.com' => dt('The domain of the site to enable (only if enabled).')),
155 156
    'description' => 'Enable a disabled site.',
    'bootstrap' => -1
157 158 159 160
  );
  $items['provision disable'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('disable'), 
161
    'arguments' => array('domain.com' => dt('The domain of the site to disable (only if disabled).')),    
162 163
    'description' => 'Disable a site.',
    'bootstrap' => -1
164 165 166 167 168
  );

  $items['provision verify'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('verify'),
169 170
    'arguments' => array('domain.com' => dt('The domain of the site to verify).')),    
    'description' => 'Verify that the provisioning framework is correctly installed.',
171
    'bootstrap' => -1
172 173 174 175 176 177
  );
 
  $items['provision restore'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('restore'),
    'description' => 'Restore the site to a previous backup. This will also generate a backup of the site as it was.',
178
    'arguments' => array('domain.com' => dt('The domain of the site to be restored'),
179 180
    'site_backup.tar.gz' => dt('The backup to restore the site to.')),
    'bootstrap' => -1
181 182 183 184 185
  );
 
  $items['provision delete'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('delete'),
186 187
    'description' => 'Delete a site.',
    'bootstrap' => -1
188 189 190 191 192 193
  );

  $items['provision cron'] = array(
    'callback' => 'provision_command',
    'callback arguments' => array('cron'),
    'description' => 'Run cron process for site.',
194 195
    'arguments' => array('domain.com' => dt('The domain of the site to be processed')),
    'bootstrap' => -1
196 197 198 199 200 201
  );
 
  if (!function_exists('hosting_setup')) {
    $items['provision setup'] = array(
      'callback' => '_provision_setup_cmd',
      'description' => 'Initialize this platform to be able to create hosted sites.',
202
      'bootstrap' => -1
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    );
  }

  return $items;
}


/**
 * Initial setup of platform
 * 
 * Creates symlink to drush.php
 * Creates config path
 * Creates drushrc path
 *
 * This function is re-used by the hosting_setup command, as it is a superset of this functionality.
 */
function _provision_setup() {
  $success = TRUE;
221 222
  
  $drush_path = realpath($_SERVER['argv'][0]);
223 224

  $success &= provision_path('symlink', $drush_path, PROVISION_DOCROOT_PATH . '/drush.php', 
225 226
    dt('Created symlink for drush.php file'), 
    dt('Could not create symlink for drush.php'), 
227 228 229 230 231 232 233 234 235 236
    PROVISION_FRAMEWORK_ERROR);

  $success &= _provision_generate_config();
  return $success; 
}

/**
 * Drush command wrapper for the setup of the platform
 */
function _provision_setup_cmd() {
237
  _provision_setup();
238 239 240 241 242 243 244 245 246 247 248 249
 
  // @TODO use provision_output for this, but we need pretty print first.
  $logs = provision_get_log();
  foreach ($logs as $log) {
    print "$log[message]\n";
  }

  if (provision_get_error()) {
    print "\nThe command did not complete successfully, please fix the issues and re-run this script.";
  }
}

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 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

/**
 * Generate a provision.settings.php file to configure provision
 */
function _provision_generate_config() {
  provision_log('notice', dt("Generating drushrc.php file"));
  provision_path("chmod", PROVISION_DOCROOT_PATH . '/drushrc.php', 0600,
     dt('Changed permissions of drushrc.php to @confirm'),
      dt('Could not change permissions of drushrc.php to @confirm'));

  provision_save_platform_data();
  provision_path("chmod", PROVISION_DOCROOT_PATH . '/drushrc.php', 0400,
    dt('Changed permissions of drushrc.php to @confirm'),
    dt('Could not change permissions of drushrc.php to @confirm'));
  return TRUE;
}


/**
 * Load additional modules just for this process
 *
 * Load additional modules specified by --load_module_name
 */
function provision_load_from_args() {
  if (!function_exists('drush_verify_cli')) {
    return null;
  }
  global $args;
  $reset = FALSE;
  foreach ($args['options'] as $arg => $value) {
    if (preg_match("/^load-([a-z\-]*)$/", $arg, $matches)) {
      $module = str_replace("-", "_", $matches[1]);
      // find file : can only search under provision path
      $files = file_scan_directory(dirname(__FILE__), "$module.module", array('.', '..', 'CVS'), 0, TRUE, 'name');
      if (isset($files[$module])) {
        provision_log("notice", dt("Loading @module on request", array("@module" => $module)));
        $loaded[$module]['name'] = $module;
        $loaded[$module]['filename']  = $files[$module]->filename;
        include_once($loaded[$module]['filename']);
      }
      $reset = TRUE;
    }
  }
  $existing = array();
  $list = module_list(TRUE, FALSE);
  foreach ($list as $module) {
    $existing[$module]['name'] = $module;
    $existing[$module]['filename'] = drupal_get_filename('module', $module);
    drupal_load('module', $module);
  }
  $loaded = array_merge($existing, (array) $loaded);
  foreach ($loaded as $name => $info) {
    $files = file_scan_directory(dirname($info['filename']), ".drush.inc$");
    foreach ($files as $filename => $info2) {
      include_once($filename);
    }

  }
  module_list(TRUE, FALSE, TRUE, $loaded);
  // Force to regenerate the stored list of hook implementations.
  module_implements('', TRUE, TRUE);
}

function _provision_default_restart_cmd() {
  # try to detect the apache restart command
  $command = '/usr/sbin/apachectl'; # a proper default for most of the world
  foreach (array('/usr/local/sbin/apachectl', # freebsd
                 '/usr/sbin/apache2ctl', # debian + apache2
      $command) as $test) {
    if (is_executable($test)) {
      $command = $test;
    }
  }

  return "sudo $command graceful";
}