t("Basic configuration")); } /** * Implementation of hook_perm(). */ function provision_perm() { return array("administer provisioning"); } /** * @defgroup provisionui Configure provisioning framework. * @{ */ /** * Implementation of hook_menu(). */ function provision_menu($may_cache = true) { if ($may_cache) { $items[] = array( 'path' => 'admin/settings/provision', 'title' => t('Provisioning'), 'description' => t("Configure how new Drupal sites will be provisioned."), 'callback' => 'drupal_get_form', 'callback arguments' => array('provision_configure'), 'access' => user_access('administer provisioning'), ); $items[] = array( 'path' => 'admin/help/provision/requirements', 'title' => t('Provisioning requirements'), 'description' => t("Information of how to configure the provisioning system."), 'callback' => 'provision_help_requirements', 'type' => MENU_CALLBACK ); } else { $items[] = array( 'path' => 'provision/disabled', 'title' => t('Site disabled'), 'description' => t('Page showed when a site is disabled.'), 'callback' => 'provision_disabled_site', 'type' => MENU_CALLBACK, 'access' => user_access('access content'), ); $items[] = array( 'path' => 'provision/maintenance', 'title' => t('Site is undergoing maintenance.'), 'description' => t('Page shown when a site is being restored or moved etc.'), 'callback' => 'provision_site_maintenance', 'type' => MENU_CALLBACK, 'access' => user_access('access content'), ); } return $items; } function provision_disabled_site() { return "

This site was disabled by the site administrators.

"; } function provision_site_maintenance() { return "

This site is being worked on presently. Check back later.

"; } /** * Menu callback. * * Configuration screen for the provisioning framework. */ function provision_configure() { $form['services'] = array('#type' => 'value'); foreach (module_implements('provision_configure') as $module) { $data = module_invoke($module, 'provision_service'); $service = key($data); $title = current($data); $form['services']['#value'][] = $service; $form[$service] = array( '#type' => 'fieldset', '#title' => $title, '#collapsible' => TRUE, '#collapsed' => FALSE, '#access' => user_access("administer $service provisioning"), '#tree' => true ); $form[$service] = array_merge($form[$service], module_invoke($module, "provision_configure")); } $form['submit'] = array( '#type' => 'submit', '#value' => t('Save settings'), ); return $form; } function provision_configure_submit($form_id, $values) { foreach ($values['services'] as $service) { foreach ($values[$service] as $key => $value) { variable_set('provision_' . $key, $value); } } } /** * @} End of "defgroup provisionui" */ /** * @defgroup provisiondrush Command line interface for Provision. * @{ */ /** * Implementation of hook_drush_command(). */ function provision_drush_command() { $items['provision install'] = array( 'callback' => 'provision_install_cmd', 'arguments' => array('domain.com' => t('The domain of the site to install.')), 'description' => t('Provision a new site using the provided data.') ); $items['provision synch'] = array( 'callback' => '_provision_synch', 'arguments' => array('domain.com' => t('The domain of the site to synch.')), 'description' => t('Regenerate all the config files for a site.') ); $items['provision import'] = array( 'callback' => '_provision_import', 'arguments' => array('domain.com' => t('The domain of the site to import.')), 'description' => t('Turn an already running site into a provisioned site.') ); $items['provision backup'] = array( 'callback' => '_provision_backup', 'arguments' => array('domain.com' => t('The domain of the site to back up.')), 'optional arguments' => array('backup-file' => t('The file to save the backup to. This will be a gzipped tarball.')), 'description' => t('Generate a back up for the site.') ); $items['provision enable'] = array( 'callback' => '_provision_enable', 'arguments' => array('domain.com' => t('The domain of the site to enable (only if enabled).')), 'description' => 'Enable a disabled site.' ); $items['provision disable'] = array( 'callback' => '_provision_disable', 'arguments' => array('domain.com' => t('The domain of the site to disable (only if disabled).')), 'description' => 'Disable a site.' ); $items['provision verify'] = array( 'callback' => '_provision_verify', # 'arguments' => array('domain.com' => t('The domain of the site to disable (only if disabled).')), 'description' => 'Verify that the provisioning framework is correctly installed.' ); $items['provision restore'] = array( 'callback' => '_provision_restore', 'description' => 'Restore the site to a previous backup. This will also generate a backup of the site as it was.', 'arguments' => array('domain.com' => t('The domain of the site to be restored'), 'site_backup.tar.gz' => t('The backup to restore the site to.')) ); $items['provision delete'] = array( 'callback' => '_provision_delete', 'description' => 'Delete a site.' ); /* // Not implemented yet. $items['provision deploy'] = array( 'callback' => '_provision_deploy', 'description' => 'Deploy a backup made on another provisioning platform on this one.' ); $items['provision rename'] = array( 'callback' => '_provision_rename', 'description' => 'Change the url of an existing site.' ); */ return $items; } /** * Drush callback function * * Installs a new site at $url. * It does so by calling hook_provision_pre_install(), hook_provision_install() and hook_provision_post_install(). * * @param url * The url of the site being installed. * @return * Returns provision_output on success or error. * Will exit with a PROVISION_SITE_INSTALLED error if the site already exists. * Will exit with a PROVISION_FRAMEWORK_ERROR if the command is incorrectly used. */ function provision_install_cmd($url) { global $args; $data = provision_get_site_data($url); if (!$args['commands'][2]) { print "Usage: drush.php provision install DOMAIN [OPTIONS]\n"; print "Install a new site for the domain DOMAIN.\n"; print "Example: drush.php provision install mydomain.com --site-db-host localhost\n"; provision_log("error", "Incorrect usage of the provisioning framework"); provision_set_error(PROVISION_FRAMEWORK_ERROR); provision_output($url, $data); } $ops = array('pre_install', 'install', 'post_install'); # these are the provision hooks that will get triggered. if (_provision_drupal_site_installed($url)) { provision_set_error(PROVISION_SITE_INSTALLED); provision_log("error", t("Site has already been installed. Exiting.")); provision_output($url, $data); } foreach ($ops as $op ) { $func = "_provision_$op"; $func($url, $data); } provision_save_site_data($url, $data); provision_output($url, $data); } /** * Drush task. * * Calls hook_provision_pre_install(). * Also responsible for calling creating site directory layout, and the drupal settings file. * * @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. * @return * Boolean denoting whether the provision_invoke rolled back changes made. */ function _provision_pre_install($url, &$data) { $rolled_back = provision_invoke("pre_install", $url, $data); if (!provision_get_error()) { // This is the actual drupal provisioning requirements. _provision_drupal_create_directories($url, $data['profile']); // Requires at least the database settings to complete. _provision_drupal_create_settings_file($url, $data); } return $rolled_back; } /** * Install drupal site * * The magic here is that we need to drive the install api through this code. * At this point, we no longer have access to the central database, and we need to be able * to drive this code blind, without causing bad stuff to happen. * * Install profile gets triggered at the end of this code. * * @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. * @return * Boolean denoting whether the provision_invoke rolled back changes made. */ function _provision_install($url, &$data) { $rolled_back = provision_invoke("install", $url, $data); if (!$rolled_back) { _provision_drupal_switch_active_site($url); # Change headers and db info, also backs up _provision_drupal_force_load_modules($url); _provision_drupal_install_schema($data['site_profile'], $data['site_language']); _provision_drupal_force_load_modules(); _provision_drupal_switch_active_site(); # This *should* bring the site back to where we were before installing } return $rolled_back; } /** * Clean up after installation. * * Most notably give the web server the opportunity to recheck it's configuration files. * * @param url * The url of the site being installed. * @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. * @return * Boolean denoting whether the provision_invoke rolled back changes made. */ function _provision_post_install($url, &$data) { $rolled_back = provision_invoke("post_install", $url, $data); if (!$rolled_back) { $data['site_installed'] = true; } return $rolled_back; } /** * Regenerate the config files of an already running site. * * @param url * The url of the site being synched. * @return * Output of provision_output() function. * Will exit with a PROVISION_SITE_NOT_FOUND error if the site does not exist. */ function _provision_synch($url) { if (!_provision_drupal_site_installed($url)) { provision_log("error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output($url, $data); } $data = provision_get_site_data($url); // This is the actual drupal provisioning requirements. _provision_drupal_create_directories($url, $data['profile']); $rolled_back = provision_invoke("synch", $url, $data); // Requires at least the database settings to complete. _provision_drupal_create_settings_file($url, $data); $data['site_installed'] = TRUE; provision_save_site_data($url, $data); provision_output($url, $data); } /** * Generate a backup of the site using a site package. * * @param url * The url of the site being backed up. * @return * Output of provision_output() function. * Will exit with a PROVISION_SITE_NOT_FOUND error if the site does not exist. */ function _provision_backup($url) { if (!_provision_drupal_site_installed($url)) { provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output($url, $data); } $data = provision_get_site_data($url); $args = func_get_args(); array_shift($args); $file = array_shift($args); _provision_backup_site($url, $data, $file); $data['site_installed'] = TRUE; provision_save_site_data($url, $data); provision_output($url, $data); } /** * Generate a backup tarbal for a site. */ function _provision_backup_site($url, &$data, $file = null) { // This is the actual drupal provisioning requirements. if (!is_dir(PROVISION_BACKUP_PATH)) { provision_log("Backup directory does not exist."); provision_set_error(PROVISION_PERM_ERROR); provision_output($url, $data); } if (is_file($file)) { provision_log("File specified already exists."); provision_set_error(PROVISION_PERM_ERROR); provision_output($url, $data); } $suggested = PROVISION_BACKUP_PATH . "/$url-" . date("Y-m-d",mktime()) . ".tar"; // Use format of mysite.com-2008-01-02, if already existing, add number. while (is_file($suggested . '.gz')) { $count++; $suggested = PROVISION_BACKUP_PATH . "/$url-" . date("Y-m-d", mktime()) . "_$count.tar"; } $data['backup_file'] = (!empty($file)) ? ereg_replace('.gz$', '', $file) : $suggested; $rolled_back = provision_invoke("backup", $url, $data); provision_shell_exec("gzip %s", $data['backup_file']); $data['backup_file'] = $data['backup_file'] . '.gz'; } /** * Import a running Drupal site into a provisioned site. * * This is accomplished by inspecting the settings.php file and generating a site.php file. * * @param url * The url of the site being synched. * @return * Output of provision_output() function. * Will exit with a PROVISION_SITE_NOT_FOUND error if the site does not exist. */ function _provision_import($url = null) { $rolled_back = provision_invoke("import", $url, $data); provision_output($url, $data); } /** * Import a running Drupal site into a provisioned site. * * This is accomplished by inspecting the settings.php file and generating a site.php file. * * @param url * The url of the site being synched. * @return * Output of provision_output() function. * Will exit with a PROVISION_SITE_NOT_FOUND error if the site does not exist. */ function _provision_verify($url = '') { _provision_create_dir(PROVISION_CONFIG_PATH, t('Provision configuration'), 0700); _provision_create_dir(PROVISION_BACKUP_PATH, t('Web server configuration'), 0700); $data = array(); $rolled_back = provision_invoke("verify", $url, $data); if ($url) { provision_save_site_data($url, $data); } provision_output($url, $data); } /** * Restore command implementation * * This command when called will * 1. Make a backup of the current site, before modifications are made. * 2. Temporarily disable the site by causing apache to redirect to a help page. Restarting apache is required. * 3. Extract the backup that is being restored to to a temporary folder in the sites directory. * 4. Create a new database, belonging to the site's user, and switch it around with the current site's database. * 5. Import the old database and site.php details. * 6. Switch around the sites directory of the current site and the backup being restored. * 7. Regenerate configuration files. * 8. TODO: diagnostic to test that everything is ok? * 9. Remove the temporary redirect and restart apache so the previous site is available again. * 10. Remove the extranuous db and duplicate site directory. * * If at any time an error occurs, before step 9. It should reverse all the changes it has made, * and leave the current site directory and database in the right place, and remove all cruft that * was created by this process. */ function _provision_restore($url, $restore_file) { if (!($exists = _provision_drupal_site_installed($url))) { // this can probably be done more consistently with another // provision_path like function. provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); } $exists &= provision_path("exists", $restore_file, true, t("Restoring site from @path"), t("Could not find backup file @path"), PROVISION_FRAMEWORK_ERROR); if ($exists) { $data = provision_get_site_data($url); $data['restore_file'] = $restore_file; $phases = array("pre_restore", "restore", "post_restore"); $completed = array(); $rolled_back = false; // initializes to false. foreach ($phases as $phase) { $rolled_back = provision_invoke($phase, $url, $data, $rolled_back); if (!$rolled_back) { $completed[] = $phase; } else { break; // exit out of the loop, to allow any changes to be reversed next. } } if ($rolled_back) { // An error has occurred, and we must undo all the changes we made, in reverse // This works by triggering the _rollback functions for each of the hooks. foreach (array_reverse($completed) as $phase) { provision_invoke($phase, $url, $data, $rolled_back); } } else { provision_save_site_data($url, $data); } } provision_output($url, $data); } function provision_provision_pre_restore($url, &$data) { _provision_backup_site($url, $data); # Backup site for posterity, before rolling back. provision_path("extract", $data['restore_file'], PROVISION_SITES_PATH . "/$url.restore", t('Successfully extracted the contents of @path to @confirm'), t('Failed to extract the contents of @path to @confirm'), PROVISION_PERM_ERROR, PROVISION_FRAMEWORK_ERROR); } function provision_provision_pre_restore_rollback($url, $data) { _provision_recursive_delete(PROVISION_SITES_PATH . "/$url.restore"); } function provision_provision_post_restore($url, $data) { _provision_recursive_delete(PROVISION_SITES_PATH . "/$url.restore"); } function _provision_disable($url) { if (!_provision_drupal_site_installed($url)) { provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output($url, $data); } $data = provision_get_site_data($url); _provision_backup_site($url, $data); # Backup site for posterity, before disabling $rolled_back = provision_invoke("disable", $url, $data); $data['site_enabled'] = FALSE; provision_save_site_data($url, $data); provision_output($url, $data); } function _provision_enable($url) { if (!_provision_drupal_site_installed($url)) { provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output($url, $data); } $data = provision_get_site_data($url); $rolled_back = provision_invoke("enable", $url, $data); $data['site_enabled'] = TRUE; provision_save_site_data($url, $data); provision_output($url, $data); } function _provision_delete($url) { $data = provision_get_site_data($url); $args = func_get_args(); array_shift($args); $file = array_shift($args); _provision_backup_site($url, $data, $file); $rolled_back = provision_invoke("delete", $url, $data); provision_output($url, $data); }