Commit 2841de35 authored by Adrian Rossouw's avatar Adrian Rossouw Committed by adrian

Initial import of provisioning framework into drupal contrib. This set of...

Initial import of provisioning framework into drupal contrib. This set of modules extends Drush to provide the ability to install and manage Drupal sites. It is the back end component of the hostmaster system.
parents
This diff is collapsed.
name = Provision
description = Allows for the automated provisioning of hosted Drupal sites, via the command line
package = Provision
dependencies = drush provision_drupal provision_mysql provision_apache token
\ No newline at end of file
This diff is collapsed.
name = Provision: Apache
description = Provides provisioning requirements for the Apache web server
package = Provision
dependencies = provision
<?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.");
}
}
\ No newline at end of file
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
\ No newline at end of file
<?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();
}
}
name = Provision: Mysql
description = Provides provisioning requirements for the Mysql database
package = Provision
dependencies = provision
<?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.
}
name = Provision: Statistics
description = Generate statistics from a running Drupal site.
package = Provision
dependencies = drush
\ No newline at end of file
<?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));
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment