| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| <?php | ||
| /** | ||
| * @file | ||
| * Apache provisioning module | ||
| * This module simply serves to generate the virtual host entry, and make sure apache gets reloaded properly. | ||
| * Because Drupal is running via the command line for the entirety of this process, it is only necessary to make | ||
| * it available online once everything has been completed. | ||
| * | ||
| * This module still requires configuration and sanity checks. Need to figure out a way to inspect the apache configuration, | ||
| * to ensure that the sites are getting loaded up correctly. | ||
| */ | ||
|
|
||
| function provision_apache_provision_service() { | ||
| return t("Apache webserver"); | ||
| } | ||
|
|
||
| /** | ||
| * Hook into central configuration form for provisioning framework. | ||
| */ | ||
| function provision_apache_provision_configure() { | ||
|
|
||
| $form['provision_apache_vhost_template'] = array( | ||
| '#type' => 'textarea', | ||
| '#title' => t('Virtual Host configuration template'), | ||
| '#description' => t('The text to use when generating a virtual host configuration file for apache'), | ||
| '#default_value' => variable_get('provision_apache_vhost_template', _provision_apache_default_template()), | ||
| '#cols' => 60, | ||
| '#rows' => 5, | ||
| ); | ||
|
|
||
| $default_path = variable_get('provision_root', ereg_replace("/webroot$", "", $_SERVER['DOCUMENT_ROOT'])) . '/vhost.d'; | ||
| $form['provision_apache_vhost_path'] = array( | ||
| '#type' => 'textfield', | ||
| '#title' => t('Path to the directory to store apache configuration files for hosted sites'), | ||
| '#size' => 40, | ||
| '#value' => variable_get('provision_apache_vhost_path', $default_path), | ||
| '#maxlength' => 255, | ||
| ); | ||
| $form['provision_apache_restart_cmd'] = array( | ||
| '#type' => 'textfield', | ||
| '#title' => t('Apache restart command'), | ||
| '#description' => t('The command to run to restart apache for new changes to take effect. This is required for the new site to become live'), | ||
| '#size' => 40, | ||
| '#maxlength' => 255, | ||
| '#default_value' => variable_get('provision_apache_restart_cmd', 'sudo apachectl graceful') | ||
| ); | ||
|
|
||
| return $form; | ||
| } | ||
| function _provision_apache_default_template() { | ||
| return <<<EOF | ||
| <VirtualHost *:80> | ||
| ServerAdmin [site-email] | ||
| DocumentRoot [site-document-root] | ||
| ServerName [site-url] | ||
| ServerAlias [site-temporary-url] | ||
| ServerAlias www.[site-url] | ||
|
|
||
| # Error handler for Drupal > 4.6.7 | ||
| <Directory "[site-document-root]/sites/[site-url]/files"> | ||
| SetHandler This_is_a_Drupal_security_line_do_not_remove | ||
| </Directory> | ||
|
|
||
| </VirtualHost> | ||
| EOF; | ||
| } | ||
| function provision_apache_provision_pre_install($url, &$data) { | ||
| return _provision_apache_create_vhost_config($url, $data); | ||
| } | ||
| function provision_apache_provision_post_install($url, &$data) { | ||
| return _provision_apache_restart_apache(); | ||
| } | ||
|
|
||
| function provision_apache_provision_enable($url, &$data) { | ||
| _provision_apache_create_vhost_config($url, $data); | ||
| _provision_apache_restart_apache(); | ||
| } | ||
|
|
||
| function provision_apache_provision_regenerate($url, &$data) { | ||
| _provision_apache_create_vhost_config($url, $data); | ||
| _provision_apache_restart_apache(); | ||
| } | ||
|
|
||
| function _provision_apache_delete_vhost_config($url, $data) { | ||
| $vhost_path = variable_get('provision_apache_vhost_path', 'vhost.d'); | ||
| if (file_exists()) { | ||
| unlink($vhost_path . '/' . $url); | ||
| } | ||
| } | ||
|
|
||
| function _provision_apache_create_vhost_config($url, $data) { | ||
| $vhost_path = variable_get('provision_apache_vhost_path', 'vhost.d'); | ||
| $file = fopen($vhost_path . '/' . $url, "w"); | ||
| if (!$file) { | ||
| provision_log("error", "Could not create apache configuration file."); | ||
| provision_set_error(PROVISION_WEB_ERROR | PROVISION_PERM_ERROR); | ||
| return false; | ||
| } | ||
| $text = token_replace(variable_get('provision_apache_vhost_template', _provision_apache_default_template()) , 'site', $data); | ||
|
|
||
| fwrite($file, $text); | ||
| fclose($file); | ||
| } | ||
|
|
||
| function _provision_apache_restart_apache() { | ||
| # This is required to be configurable, due to the fact that different hosts might need to do this differently. | ||
| # TODO : add configuration / test for this | ||
| $apache_restart_cmd = escapeshellcmd(variable_get('provision_apache_restart_cmd', 'sudo apachectl graceful')); | ||
| $code = drush_shell_exec($apache_restart_cmd); | ||
| if ($code) { | ||
| provision_set_error(PROVISION_WEB_ERROR); | ||
| provision_log("error", "Web server could not be restarted. Changes might not be available until this has been done."); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| name = Provision: Drupal | ||
| description = Allows for Drupal sites to be provisioned using the provisioning framework. | ||
| package = Provision | ||
| dependencies = drush provision_drupal provision_mysql provision_apache |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,288 @@ | ||
| <?php | ||
| /** | ||
| * @file | ||
| * Drupal specific functions for the provisioning framework. | ||
| * | ||
| * This module is responsible for the creation and maintenance of the drupal settings.php file, the sites directory structure | ||
| * and all the install api code. | ||
| */ | ||
|
|
||
| /** | ||
| * @ingroup provisionui | ||
| * @{ | ||
| */ | ||
|
|
||
| /** | ||
| * Implementation of hook_provision_service() | ||
| */ | ||
| function provision_drupal_provision_service() { | ||
| return t("Drupal sites"); | ||
| } | ||
|
|
||
| /** | ||
| * Implentation of hook_provision_configure() | ||
| */ | ||
| function provision_drupal_provision_configure() { | ||
| $template = _provision_default_template(); | ||
| $profiles = file_scan_directory('./profiles', '\.profile$', array('.', '..', 'CVS'), 0, TRUE, 'name', 0); | ||
| // Don't need to choose profile if only one available. | ||
| if (sizeof($profiles) == 1) { | ||
| $profile = array_pop($profiles); | ||
| $form['provision_default_profile'] = array('#type' => 'value', '#value' => $profile->name); | ||
| } | ||
| elseif (sizeof($profiles) > 1) { | ||
| foreach ($profiles as $profile) { | ||
| if ($_POST['profile'] == $profile->name) { | ||
| $options[$profile->name] = $profile->name; | ||
| } | ||
| } | ||
| $form['provision_default_profile'] = array( | ||
| '#type' => 'radios', | ||
| '#title' => t('Default install profile'), | ||
| '#description' => t('New sites will be created with the following install profile'), | ||
| '#options' => $options, | ||
| '#default_value' => variable_get('provision_default_profile', 'default'), | ||
| ); | ||
|
|
||
| } | ||
|
|
||
| $form['provision_settings_template'] = array( | ||
| '#type' => 'textarea', | ||
| '#title' => t('Drupal settings template'), | ||
| '#description' => t('The template for the generated settings.php file.'), | ||
| '#default_value' => variable_get('provision_settings_template', $template), | ||
| '#cols' => 60, | ||
| '#rows' => 5, | ||
| ); | ||
|
|
||
| return $form; | ||
| } | ||
|
|
||
| /** | ||
| * @} End "ingroup provisionui" | ||
| */ | ||
|
|
||
| /** | ||
| * Test to see if the site settings.php exists | ||
| * | ||
| * @param url | ||
| * The url of the site to check | ||
| * @return | ||
| * If the file exists, return TRUE, else return FALSE. | ||
| */ | ||
| function _provision_drupal_site_exists($url) { | ||
| return file_exists("sites/$url/settings.php"); | ||
| } | ||
|
|
||
| /** | ||
| * The default template to use while generating config files. | ||
| * | ||
| * @return | ||
| * The default template for the config file | ||
| */ | ||
| function _provision_drupal_default_template() { | ||
| return <<<END | ||
| <?php | ||
| \$db_url = "[site-db-type]://[site-db-username]:[site-db-passwd]@[site-db-host]/[site-db-name]"; | ||
| \$profile = "[site-profile]"; | ||
|
|
||
| # Additional host wide configuration settings. Useful for safely specifying configuration settings. | ||
| if (file_exists('includes/global.inc')) { | ||
| include_once('includes/global.inc'); | ||
| } | ||
| ?> | ||
| END; | ||
| } | ||
|
|
||
| /** | ||
| * Generate a settings file for the site. | ||
| * | ||
| * @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. | ||
| */ | ||
| function _provision_drupal_create_settings_file($url, &$data) { | ||
| $fp = fopen("sites/$url/settings.php", "w"); | ||
| $text = variable_get('provision_settings_template', _provision_default_template()); | ||
| fwrite($fp, token_replace($text, 'site', $data)); | ||
| fclose($fp); | ||
|
|
||
| # Change the ownership of the file | ||
| #chown($path, variable_get('provision_user', 'hosting'), variable_get('provision_group', 'apache')); | ||
|
|
||
| # Change the permissions of the file | ||
| # system("chmod 0550 sites/$url/settings.php"); | ||
| # TODO: add md5 of the file created to $data | ||
| } | ||
|
|
||
| /** | ||
| * Create the directories needed to host a drupal site | ||
| * | ||
| * Also maintains permissions on existing directories. | ||
| */ | ||
| function _provision_drupal_create_directories($url, $profile = null) { | ||
| $paths = array( | ||
| "sites/$url" => '0750', | ||
| "sites/$url/files" => '2750', | ||
| "sites/$url/files/tmp" => '2770', | ||
| "sites/$url/files/images" => '2770', | ||
| "sites/$url/files/pictures" => '2770', | ||
| "sites/$url/themes" => '2750', | ||
| "sites/$url/modules" => '2750', | ||
| ); | ||
|
|
||
| foreach ($paths as $path => $perm) { | ||
| if (!is_dir($path)) { | ||
| mkdir($path); | ||
| } | ||
| # Change the ownership of the files so that they are owned by the user the script is running as, and the | ||
| # web server has access to them. | ||
| # chown($path, variable_get('provision_user', 'hosting'), variable_get('provision_group', 'apache')); | ||
|
|
||
| # Change the permissions to the optimal settings that were specified. | ||
| # system("chmod $perm $path"); | ||
|
|
||
| } | ||
|
|
||
| } | ||
|
|
||
| /** | ||
| * Switch the active database to the newly created database | ||
| * | ||
| * This function tricks Drupal into thinking that it's running on an uninstalled site, | ||
| * so that it can cleanly install the database schema. It also handles switching back to the | ||
| * main provisioning site. | ||
| */ | ||
| function _provision_drupal_switch_active_site($url = null) { | ||
| static $backups; | ||
| if ($url) { | ||
| /* Pretend to be the site being installed */ | ||
|
|
||
| // Fake the necessary HTTP headers that Drupal needs: | ||
| $drupal_base_url = parse_url($url); | ||
| $_SERVER['HTTP_HOST'] = $drupal_base_url['host']; | ||
| $_SERVER['PHP_SELF'] = $drupal_base_url['path'].'/index.php'; | ||
| $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; | ||
| $_SERVER['REMOTE_ADDR'] = NULL; | ||
| $_SERVER['REQUEST_METHOD'] = NULL; | ||
|
|
||
|
|
||
| /** | ||
| * This code is sourced from bootstrap.inc. I am trying to avoid patching core, but it might | ||
| * be smarter to make a small patch to allow core to re-initialize itself more easily | ||
| */ | ||
|
|
||
| // Export the following settings.php variables to the global namespace | ||
| global $base_url, $base_path, $base_root; | ||
| global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $active_db, $profile; | ||
|
|
||
| # This is just for backup, to be able to restore to the old DRUSH system. | ||
| $backups = compact("active_db", "base_url", "base_path", "db_url", "db_prefix", "cookie_domain", "conf", "installed_profile", "profile"); | ||
|
|
||
| $conf = array(); | ||
| include_once $_SERVER['DOCUMENT_ROOT'] .'sites/' . $url . '/settings.php'; | ||
|
|
||
| // Create base URL | ||
| $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'; | ||
| $base_url = $base_root .= '://'. preg_replace('/[^a-z0-9-:._]/i', '', $_SERVER['HTTP_HOST']); | ||
| if ($dir = trim(dirname($_SERVER['SCRIPT_NAME']), '\,/')) { | ||
| $base_path = "/$dir"; | ||
| $base_url .= $base_path; | ||
| $base_path .= '/'; | ||
| } | ||
| else { | ||
| $base_path = '/'; | ||
| } | ||
|
|
||
| unset($active_db); | ||
| $db_url['new'] = $db_url; | ||
| db_set_active('new'); | ||
|
|
||
| } | ||
| else { | ||
| /** | ||
| * Restore everything to the way it was before we switched sites. | ||
| */ | ||
| // Fake the necessary HTTP headers that Drupal needs: | ||
| $drupal_base_url = parse_url(DRUSH_URI); | ||
| $_SERVER['HTTP_HOST'] = $drupal_base_url['host']; | ||
| $_SERVER['PHP_SELF'] = $drupal_base_url['path'].'/index.php'; | ||
| $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; | ||
| $_SERVER['REMOTE_ADDR'] = NULL; | ||
| $_SERVER['REQUEST_METHOD'] = NULL; | ||
|
|
||
| global $base_url, $base_path, $base_root; | ||
| // Export the following settings.php variables to the global namespace | ||
| global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $profile; | ||
|
|
||
| # This is just for backup, to be able to restore to the old DRUSH system. | ||
| extract($backups, EXTR_OVERWRITE); | ||
|
|
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Force drupal to load the modules it expects to find on an uninstalled site | ||
| */ | ||
| function _provision_drupal_force_load_modules($url = null) { | ||
| static $backup_list; | ||
| if ($url) { | ||
| $backup_list = module_list(); | ||
| require_once './modules/system/system.install'; | ||
| require_once './includes/file.inc'; | ||
| require_once './includes/install.inc'; | ||
| // Load module basics (needed for hook invokes). | ||
| include_once './includes/module.inc'; | ||
| $module_list['system']['filename'] = 'modules/system/system.module'; | ||
| $module_list['filter']['filename'] = 'modules/filter/filter.module'; | ||
| module_list(TRUE, FALSE, FALSE, $module_list); | ||
| drupal_load('module', 'system'); | ||
| drupal_load('module', 'filter'); | ||
| #should i load all the other modules? i don't know .=\ | ||
| } | ||
| else { | ||
| module_list(TRUE, FALSE, FALSE, $backup_list); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Install the drupal schema and install profile | ||
| */ | ||
| function _provision_install_schema($profile) { | ||
| // Load the profile. | ||
| require_once "./profiles/$profile/$profile.profile"; | ||
|
|
||
| $requirements = drupal_check_profile($profile); | ||
| $severity = drupal_requirements_severity($requirements); | ||
|
|
||
| // If there are issues, report them. | ||
| if ($severity == REQUIREMENT_ERROR) { | ||
| foreach ($requirements as $requirement) { | ||
| if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) { | ||
| drupal_set_message($requirement['description'] .' ('. st('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) .')', 'error'); | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| // Verify existence of all required modules. | ||
| $modules = drupal_verify_profile($profile, null); | ||
|
|
||
| if (!$modules) { | ||
| return false; | ||
| } | ||
|
|
||
| // Perform actual installation defined in the profile. | ||
| drupal_install_profile($profile, $modules); | ||
|
|
||
| // Show profile finalization info. | ||
| $function = $profile .'_profile_final'; | ||
| if (function_exists($function)) { | ||
| // More steps required | ||
| $profile_message = $function(); | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| name = Provision: Mysql | ||
| description = Provides provisioning requirements for the Mysql database | ||
| package = Provision | ||
| dependencies = provision |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| <?php | ||
| /** | ||
| * @file | ||
| * Mysql provisioning module. | ||
| * | ||
| * The goal of this module is to create mysql databases and user accounts, for sites that are about to be created. | ||
| * It uses the provision API to tie into the right places in the site creation work flow. | ||
| */ | ||
|
|
||
| /** | ||
| * @ingroup provisionui | ||
| * @{ | ||
| */ | ||
|
|
||
| /** | ||
| * Implementation of provision_service() | ||
| */ | ||
| function provision_mysql_provision_service() { | ||
| return t("Mysql database server"); | ||
| } | ||
|
|
||
| /** | ||
| * Implementation of provision_configure | ||
| */ | ||
| function provision_mysql_provision_configure() { | ||
| $form['provision_mysql_user'] = array( | ||
| '#type' => 'textfield', | ||
| '#required' => TRUE, | ||
| '#title' => t('Mysql user account'), | ||
| '#description' => t('The user that will be used to create users and databases for new sites.'), | ||
| '#size' => 40, | ||
| '#default_value' => variable_get('provision_mysql_user', 'root'), | ||
| '#maxlength' => 255, | ||
| ); | ||
| $form['provision_mysql_password'] = array( | ||
| '#type' => 'password', | ||
| '#required' => TRUE, | ||
| '#title' => t('Mysql user password'), | ||
| '#description' => t('The user account that will be used to create new mysql users and databases for new sites'), | ||
| '#size' => 30, | ||
| '#maxlength' => 64, | ||
| ); | ||
|
|
||
| $form['provision_mysql_host'] = array( | ||
| '#type' => 'textfield', | ||
| '#title' => t('Mysql server hostname'), | ||
| '#description' => t('The mysql server to connect to.'), | ||
| '#size' => 30, | ||
| '#default_value' => variable_get('provision_mysql_host', 'localhost'), | ||
| '#maxlength' => 64, | ||
| ); | ||
| return $form; | ||
| } | ||
| /** | ||
| * @} end "ingroup provisionui" | ||
| */ | ||
|
|
||
|
|
||
| function provision_mysql_provision_pre_install($url, &$data) { | ||
| $data['site-db-type'] = 'mysql'; # only support innodb. for now. | ||
| $data['site-db-host'] = ($data['site-db-host']) ? $data['site-db-host'] : variable_get('provision_mysql_host', 'localhost'); | ||
| $data['site-db-passwd'] = user_password(); # generate a random password for use | ||
| if ($data['site_id']) { | ||
| $data['site-db-name'] = 'site_' . $data['site_id']; | ||
| $data['site-db-username'] = $data['site-db-name']; // mysql has some really really stupid rules about who db / usernames, so site id is the safest. | ||
| } | ||
| else { | ||
| $data['site-db-name'] = substr(ereg_replace("^www\.", "", str_replace(".", "", $url)), 0, 10); | ||
| $data['site-db-username'] = $data['site-db-name']; | ||
| // TODO : A reasonable fallback if the site id isn't available. This is going to make it a bit harder to test at first, but that's ok. | ||
| } | ||
|
|
||
| # For this to work, the user account the provisioning site has been set up with, requires CREATE database permissions. | ||
| # TODO : Add additional configuration for a database account to use for these , but this is the quickest way to get the code up and running. | ||
| $db_url = sprintf("mysqli://%s:%s@%s/mysql", variable_get('provision_mysql_user', 'root'), variable_get('provision_mysql_password', 'root'), $data['site-db-host'] ); | ||
|
|
||
| if ( db_result(db_query("SHOW DATABASES LIKE '%s'", $data['site-db-name'])) ) { | ||
| db_query("DROP DATABASE %s", $data['site-db-name']); | ||
| } | ||
|
|
||
| db_query("CREATE DATABASE %s", $data['site-db-name']); | ||
|
|
||
| if ( !db_result(db_query("SHOW DATABASES LIKE '%s'", $data['site-db-name'])) ) { | ||
| provision_set_error(PROVISION_DB_ERROR); | ||
| provision_log("error", "Database could not be created."); | ||
|
|
||
| return FALSE; | ||
| } | ||
|
|
||
| db_query("GRANT ALL PRIVILEGES ON %s.* TO %s@`%%` IDENTIFIED BY '%s'", $data['site-db-name'], $data['site-db-username'], $data['mysql_passwd']); | ||
| db_query("GRANT ALL PRIVILEGES ON %s.* TO %s@%s IDENTIFIED BY '%s'",$data['site-db-name'], $data['site-db-username'], $data['site-db-host'], $data['mysql_passwd']); | ||
|
|
||
|
|
||
| if ($data['site-mysql-old-passwords']) { | ||
| db_query("SET PASSWORD FOR '%s'@'%%' = OLD_PASSWORD('%s')", $data['site-db-username'], $data['site-db-passwd']); | ||
| db_query("SET PASSWORD FOR %s@%s = OLD_PASSWORD('%s')", $data['site-db-username'], $data['site-db-host'], $data['site-db-passwd']); | ||
| } | ||
| db_query("FLUSH PRIVILEGES"); | ||
| #TODO : Test to confirm that the database is actually writeable. Taking this on faith for now. | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| name = Provision: Statistics | ||
| description = Generate statistics from a running Drupal site. | ||
| package = Provision | ||
| dependencies = drush |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| <?php | ||
|
|
||
| function provision_stats_drush_command() { | ||
| $items['provision stats'] = array( | ||
| 'callback' => '_provision_stats', | ||
| 'description' => 'Return statistics from a running site.' | ||
| ); | ||
| return $items; | ||
| } | ||
|
|
||
| function _provision_stats($url) { | ||
| $data = provision_get_site_data($url); | ||
| if (!$data['site-installed']) { | ||
| print t('The site %site has not been installed yet.', array('%site' => $url)); | ||
| exit(PROVISION_FRAMEWORK_ERROR); #exit with error, so front end can catch it | ||
| } | ||
| #needs to be done on active database | ||
| $modules = module_implements('provision_stats'); | ||
| drupal_get_messages(); # clear the messages being saved so far. | ||
|
|
||
| # Change headers and db info, also backs up to restore later | ||
| _provision_drupal_switch_active_site($url); | ||
|
|
||
| #TODO: add some required modules here, possibly update_status if not enabled. | ||
| #Load the modules from the hosted site | ||
| module_list(TRUE); | ||
|
|
||
| $stats['node_count'] = db_result(db_query("select max(nid) from {node}")); | ||
| $stats['user_count'] = db_result(db_query("select max(uid) from {users}")); | ||
|
|
||
| foreach ($modules as $name) { | ||
| $func = $name . "_provision_stats"; | ||
| $stats = array_merge($stats, $func($url, $data)); | ||
| } | ||
|
|
||
| _provision_switch_active_site(); | ||
| module_list(TRUE); | ||
|
|
||
| return provision_output($url, $data, array('stats' => $stats)); | ||
| } |