@@ -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.");
}
}
@@ -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
@@ -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();
}
}

@@ -0,0 +1,4 @@
name = Provision: Mysql
description = Provides provisioning requirements for the Mysql database
package = Provision
dependencies = provision
@@ -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.
}

@@ -0,0 +1,4 @@
name = Provision: Statistics
description = Generate statistics from a running Drupal site.
package = Provision
dependencies = drush
@@ -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));
}