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'), ); } return $items; } /** * Implementation of hook_help() */ function provision_help($section) { switch ($section) { case 'admin/help#provision': $output .= t('

The Provision framework is a powerful set of modules that lets you to accomplish a lot of maintenance tasks through the unix command line such as installing new sites, backing them up, rolling back to previous backups and facilitating upgrades.

'); $output .= t('

Additionally, the Provision framework is one component of the distributed hosting infrastructure provided by the hostmaster install profile. The Hostmaster profile is capable of driving several provision backends, in a distributed manner, and provides an interface for the functionality of provision.

'); $output .= t('

It is not required to run the hosting front end to use the Provision framework, but the system does not provide much in the way of a web accessible front end, by design.

'); $output .= '

' . t('Requirements') . '

'; $output .= t('

For a more detailed breakdown of steps that need to be taken to configure Provisioning to run with your system, please read the in depth requirement documentation

', array('@url' => url('admin/help/provision/requirements'))); $output .= '

' . t('Commands') . '

'; $commands = module_invoke_all('drush_command'); $output .= "
"; foreach ($commands as $command => $info) { if (preg_match('/^provision/', $command)) { if (sizeof($info['arguments'])) { $command .= ' ' . implode(' ', (array) key($info['arguments'])); } if (sizeof($info['optional arguments'])) { $command .= ' [' . implode('] [', (array) key($info['optional arguments'])) . ']'; } $output .= '
' . "drush.php $command" . '
'; $output .= '
' . $info["description"] . '
'; } } $output .= "
"; $output .= '

' . t('Options') . '

'; $options = module_invoke_all('value_list'); $output .= "
"; foreach ($options as $option => $description) { $output .= '
' . "--$option" . '
'; $output .= '
' . $description . '
'; } $output .= "
"; return $output; case 'admin/help/provision#requirements' : $username = provision_get_script_owner(); $group = provision_get_group_name(); $backup_path = _provision_backup_path(); $mkdir_cmd['@backup_path'] = $backup_path; $mkdir_cmd['@provision_link'] = url('admin/settings/provision'); $mkdir_cmd['@mkdir_cmd'] = <<The user account running the script, and the group of the httpd daemon. The provision framework takes special care to make sure that the file permissions of the hosted sites are always as safe as can be, especially to make sure that the web server does not have the ability to modify the code of the site, therefor this information is required to assure that safety while keeping the sites accessible.

Based on your server configuration we have determined that you should set the username to "@username" and the group to "@group", but you can change these in the provisioning section.

', array("@username" => $username, "@group" => $group, "@provision_link" => url('admin/settings/provision'))) . ''; $output .= '
  • ' . t('

    Write access to a directory to store backups. The drush user needs to be able to maintain the backups repository to ensure that your site is backed up successfully. It is incredibly important that this path is not accessible via the web server, so that no undesirables can get their hands on your database. The recommended path is directly above your platform path, but it can be anywhere.

    Based on your server configuration we have determined that your path should be @backup_path, but you can change this in the provisioning section.

    To configure: Please enter the following commands :
    @mkdir_cmd
    ',$mkdir_cmd) . '
  • '; $output .= ""; return $output; } } /** * Page callback with in depth requirement documentation */ function provision_help_requirements() { $output .= t('

    Unfortunately, due to the requirements of some of the functionality, significantly more access than is usually allowed on a shared hosting solution is required, and as such, a virtual server or dedicated hosting system will be required to run this system.

    '); $output .= t('

    Some of the features of the system also require the ability to create symlinks, which means that it needs to run on a unix-like operating system. There are no plans currently to add windows support.

    '); $modules = module_implements('provision_service'); foreach ($modules as $module) { $service = module_invoke($module, 'provision_service'); $name = current($service); $help = module_invoke($module, 'help', 'admin/help/provision#requirements'); if ($name && $help) { $output .= '

    ' . t($name) . '

    '; $output .= $help; } } return $output; } function provision_disabled_site() { return "

    This site was disabled by the site administrators.

    "; } /** * 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 action. * * 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']); _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) { $backup_path = _provision_backup_path(); // This is the actual drupal provisioning requirements. if (!is_dir($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 = "$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 = "$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 = '') { $path = _provision_config_path(); $exists = provision_check_path($path, "exists", true , t("Provision configuration path exists."), t("Provision configuration path does not exist.")); if (!$exists) { $made = provision_check_path($path, "mkdir", true, t("Provision configuration path has been created."), t("Provision configuration could not be created."), PROVISION_PERM_ERROR | PROVISION_FRAMEWORK_ERROR); } provision_check_path($path, "writable", true , t("Provision configuration path is writable."), t("Provision configuration path is not writable."), PROVISION_PERM_ERROR | PROVISION_FRAMEWORK_ERROR); provision_check_path($path, "chown", provision_get_script_owner(), t("Changed ownership of %path", array("%path" => $path)), t("Could not change ownership %path", array("%path" => $path)), PROVISION_PERM_ERROR | PROVISION_FRAMEWORK_ERROR ); provision_check_path($path, "chmod", 0700, t("Changed permissions of %path to %perms", array("%path" => $path, '%perms' => sprintf('%o', 0700))), t("Could not change ownership %path to %perms", array("%path" => $path, '%perms' => sprintf('%o', 0700))), PROVISION_PERM_ERROR | PROVISION_FRAMEWORK_ERROR ); $path = _provision_backup_path(); $exists = provision_check_path($path, "exists", true , t("Provision backup path exists."), t("Provision backup path does not exist.")); if (!$exists) { $made = provision_check_path($path, "mkdir", true, t("Provision backup path has been created."), t("Provision backup could not be created."), PROVISION_PERM_ERROR | PROVISION_FRAMEWORK_ERROR); } provision_check_path($path, "writable", true , t("Provision backup path is writable."), t("Provision backup path is not writable."), PROVISION_PERM_ERROR | PROVISION_FRAMEWORK_ERROR); provision_check_path($path, "chown", provision_get_script_owner(), t("Changed ownership of %path", array("%path" => $path)), t("Could not change ownership %path", array("%path" => $path)), PROVISION_PERM_ERROR | PROVISION_FRAMEWORK_ERROR ); provision_check_path($path, "chmod", 0700, t("Changed permissions of %path to %perms", array("%path" => $path, '%perms' => sprintf('%o', 0700))), t("Could not change ownership %path to %perms", array("%path" => $path, '%perms' => sprintf('%o', 0700))), PROVISION_PERM_ERROR | PROVISION_FRAMEWORK_ERROR ); $data = array(); $rolled_back = provision_invoke("verify", $url, $data); if ($url) { provision_save_site_data($url, $data); } provision_output($url, $data); } function _provision_restore($site, $backup_file) { 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); } if (!is_file($backup_file)) { provision_log("File specified does not exist."); provision_set_error(PROVISION_PERM_ERROR); provision_output($url, $data); } $data = provision_get_site_data($url); _provision_backup_site($url, $data); # Backup site for posterity, before rolling back. provision_shell_exec("tar -zxf %s -C %s/%s.tmp", $backup_file, provision_backup_path(), $url); # check out old directory. $rolled_back = provision_invoke("restore", $url, $data); $data['site_installed'] = TRUE; provision_save_site_data($url, $data); } 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_installed'] = TRUE; $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_installed'] = TRUE; $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); }