type . "_" . str_replace(" ", "_", $op); if (function_exists($func)) { $func($node, $op, $teaser); } } /** * Page callback * Configure the templates used to generate config files for the framework. */ function hosting_config_templates($node) { $result = db_query("SELECT * FROM {config_template} WHERE vid=%d AND type='drupal_settings' limit 1", $node->vid); if (db_num_rows($result)) { $drupal_settings = db_fetch_array($result); } else { $drupal_settings = array( 'template' => variable_get('provision_drupal_settings_template', _provision_drupal_default_template()), 'status' => 0 ); } $form['#tree'] = true; $form['drupal_settings'] = array( '#type' => 'fieldset', '#title' => t('Drupal configuration files'), '#description' => t('These settings control the generation of the Drupal settings.php file for this host.'), '#collapsible' => FALSE, '#collapsed' => FALSE, ); $form['drupal_settings']['template'] = array( '#type' => 'textarea', '#title' => t('Drupal settings file template'), '#description' => t('The text you enter here will be used by the Provisioning framework to generate your Drupal settings.php file. This field contains php, and should start with <?php.'), '#default_value' => $drupal_settings['template'], '#cols' => 60, '#rows' => 5, ); $form['drupal_settings']['status'] = array( '#type' => 'checkbox', '#default_value' => $drupal_settings['status'], '#title' => t('Use this configuration file for this platform.'), ); $result = db_query("SELECT * FROM {config_template} WHERE vid=%d AND type='apache_vhost' limit 1", $node->vid); if (db_num_rows($result)) { $apache_vhost = db_fetch_array($result); } else { $apache_vhost = array( 'template' => variable_get('provision_apache_vhost_template', _provision_apache_default_template()), 'status' => 0 ); } $form['#tree'] = true; $form['apache_vhost'] = array( '#type' => 'fieldset', '#title' => t('Apache Virtual Host configuration files'), '#description' => t('These settings control the generation of the Virtual Host directives for this platform.'), '#collapsible' => FALSE, '#collapsed' => FALSE, ); $form['apache_vhost']['template'] = array( '#type' => 'textarea', '#title' => t('Drupal settings file template'), '#description' => t('The text you enter here will be used by the Provisioning framework to generate your Apache Virtual Host file. This field contains php, and should start with <?php.'), '#default_value' => $apache_vhost['template'], '#cols' => 60, '#rows' => 5, ); $form['apache_vhost']['status'] = array( '#type' => 'checkbox', '#default_value' => $apache_vhost['status'], '#title' => t('Use this configuration file for this platform.'), ); return $form; } function hosting_drush_command() { $items['hosting dispatch'] = array( 'callback' => 'hosting_dispatch', 'description' => 'Centralized command for dispatching the various queue processors (hosting, cron, backup etc.)' ); $items['hosting actions'] = array( 'callback' => 'hosting_queue_actions', 'description' => 'Process the queue of outstanding hosting actions.' ); $items['hosting backups'] = array( 'callback' => 'hosting_queue_backups', 'description' => 'Process the site backup queue.' ); $items['hosting import platform'] = array( 'callback' => 'hosting_import_platform', 'description' => 'Generate a platform node for an already installed platform.' ); $items['hosting import site'] = array( 'callback' => 'hosting_import_site', 'description' => 'Generate a site node for an already installed site.' ); return $items; } /** * Main queue processing command for hostmaster. * * This is a single command, which will (based on configuration) run all the other * queue commands (cron, backup, hosting queue, stats). This is done so that there * is only one cron job to configure, and allow the frequency of calls to be configured * from the interface. */ function hosting_dispatch() { // @todo All of it. =) } /** * Retrieve a list of outstanding actions. * * @param limit * The amount of items to return. * @return * An associative array containing action nodes, indexed by node id. */ function _hosting_get_new_actions($limit = 5) { $return = array(); $result = db_query("SELECT nid FROM {hosting_action_queue} WHERE status=%d ORDER BY timestamp ASC LIMIT %d", PROVISION_QUEUED, $limit); while ($node = db_fetch_object($result)) { $return[$node->nid] = node_load($node->nid); } return $return; } /** * Process the hosting action queue. * * Iterates through the list of outstanding actions, and execute the commands on the back end. */ function hosting_queue_actions() { global $provision_errors; variable_set("hosting_queue_last_run", mktime()); $count = drush_get_option(array('i', 'items'), 5); # process a default of 5 items at a time. $actions = _hosting_get_new_actions($count); foreach ($actions as $action) { // create a new revision $action->changed = mktime(); $action->executed = mktime(); $action->revision = true; node_save($action); hosting_action_log($action->vid, "queue", t("Action starts processing")); // execute the hosting_pre_${action_type} hook. module_invoke_all("hosting_pre_" . $action->action_type, $action); // @todo Allow multiple commands to be run. // For now actions just map to one backend command, but that might need to change with additional complexity. $cmd = hosting_provision_cmd($action->action_type, $action); exec(escapeshellcmd($cmd), $return, $code); $return = join("\n", $return); $data = unserialize($return); hosting_action_log($action->vid, 'command', "Executed: " . escapeshellcmd("$cmd")); if (!is_array($data)) { hosting_action_log($action->vid, 'error', t("The command could not be executed succesfully (return: %return, data: %data, code: %code)", array("%return" => $return, "%data" => $data, "%code" => $code))); $code = PROVISION_FRAMEWORK_ERROR; } foreach ((array) $data['log'] as $log) { hosting_action_log($action->vid, $log['type'], $log['message'], $log['severity'], $log['timestamp']); } # Drupal message errors. foreach ((array) $data['messages']['error'] as $error) { if (preg_match("/^warning:/", $error)) { hosting_action_log($action->vid, "warning", ereg_replace("/^warning: /", '', $error), 0, $log['timestamp']); } elseif (preg_match("/^user warning:/", $error)) { hosting_action_log($action->vid, "warning", ereg_replace("/^user warning: /", '', $error), 0, $log['timestamp']); } } // record status $action->action_status = $code; // New revision is created at the beginning of function. $action->revision = false; node_save($action); if ($code == PROVISION_SUCCESS) { # The action has been successful. Run the post hook. module_invoke_all("hosting_post_" . $action->action_type, $action, $data); // remove from queue hosting_action_log($action->vid, "queue", t("Removing action from hosting queue"), 0); db_query("UPDATE {hosting_action_queue} SET status=%d WHERE nid=%d", $code, $action->nid); } else { # Stop the queue from continuing. break; } } } /** * Generate a command line call to Drush, based on the available data for the action. * * This acts as the default command for all back end calls. Actions can provide their own * calls using hook_provision_${action_type}_cmd. * @todo get rid of the special handling for site-* arguments. */ function hosting_provision_cmd($cmd_type, $node) { // retrieve data map from action. $data = hosting_map_values($node); $func = "hosting_provision_". $cmd_type . "_cmd"; # Only one implementation of the cmd_type is possible. This might lead to conflicts with additional complexity. if (function_exists($func)) { $command = $func($data); } else { $option_str = _hosting_option_map($data); $command = sprintf("%s/drush.php provision %s %s --root=%s --uri=%s -b %s", $data['publish_path'] . '/' . drupal_get_path("module", "drush"), $cmd_type, $data['site_url'], $data['publish_path'], $data['web_host'], $option_str); } return $command; } function hosting_provision_restore_cmd($data) { $backup = hosting_site_get_backup($data['action_args']['bid']); $option_str = _hosting_option_map($data); $command = sprintf("%s/drush.php provision restore %s %s --root=%s --uri=%s -b %s", $data['publish_path'] . '/' . drupal_get_path("module", "drush"), $data['site_url'], $backup['filename'], $data['publish_path'], $data['web_host'], $option_str); return $command; } /** * Maps associative array to option string passed to command line script. */ function _hosting_option_map($data) { foreach ($data as $key => $value) { if (preg_match("/^site_/", $key) && $key != 'site_url') { $option_str .= " --$key=$value"; } } return $option_str; } /** * Implementation of hook_form_alter */ function hosting_form_alter($form_id, &$form) { // Replace configuration form submit handler if ($form_id == 'provision_configure') { $form['#submit'] = array('hosting_configure_provision' => array()); } // Add redirection to next step in wizard if ($step = hosting_wizard_current_step()) { if ($step['form_id'] == $form_id) { $form['#submit']['hosting_wizard_continue_submit'] = array(); } } } /** * Progress to the next part of the installation wizard */ function hosting_wizard_continue_submit() { if ($step = hosting_wizard_current_step(TRUE)) { return $step['path']; } else { return variable_get('site_frontpage', 'node'); } } /** * Return information about the current step of the installation wizard */ function hosting_wizard_current_step($next = false) { if (variable_get('hosting_wizard_completed', false)) { return false; } $steps = hosting_wizard_steps(); // set step, initializaing to first element of #steps $step = variable_get('hosting_wizard_current_step', $steps['#steps'][0]); if ($step == $steps['#steps'][sizeof($steps['#steps']) - 1] && $next) { variable_set('hosting_wizard_completed', true); variable_del('hosting_wizard_current_step'); return false; } if ($next) { $key = array_search($step, $steps['#steps']); variable_set('hosting_wizard_current_step', $steps['#steps'][$key + 1]); } return $steps[$step]; } /** * Implementation of hook_init * * Used by hosting to drive the installation wizard, by redirecting to the right page * in the wizard. * @todo Allow the user to go to help paths. */ function hosting_init() { // Definitions for the default platforms, clients etc. // Done to avoid using 'magic numbers' define('HOSTING_DEFAULT_CLIENT', variable_get('hosting_default_client', 1)); define('HOSTING_DEFAULT_DB_SERVER', variable_get('hosting_default_db_server', 2)); define('HOSTING_DEFAULT_WEB_SERVER', variable_get('hosting_default_web_server', 3)); define('HOSTING_DEFAULT_PLATFORM', variable_get('hosting_default_platform', 6)); define('HOSTING_OWN_DB_SERVER', variable_get('hosting_own_db_server', 2)); define('HOSTING_OWN_WEB_SERVER', variable_get('hosting_own_web_server', 3)); define('HOSTING_OWN_PLATFORM', variable_get('hosting_own_platform', 6)); if ($step = hosting_wizard_current_step()) { if ($_GET['skip_wizard']) { variable_set('hosting_wizard_completed', TRUE); } elseif ($step['path'] != $_GET['q']) { #todo: check for the help paths drupal_goto($step['path']); } } } /** * Control structure for stepping through the installation wizard. */ function hosting_wizard_steps() { return array( '#steps' => array('user_edit', 'provision_configure'), 'user_edit' => array("path" => "user/1/edit", 'title' => t("Change your account password."), "form_id" => "user_edit", 'message' => t('An administrator account has been registered for you. Please change your password to continue with the configuration process.')), 'provision_configure' => array("path" => "admin/settings/provision", 'title' => t("Configure the provisioning framework."), "form_id" => "provision_configure", 'message' => t('To be able to create sites using Hostmaster, we need some information about your server. Please complete this configuration form.')), ); } /** * Replacement submit handler for the admin/settings/provision page * * This submit handler saves the settings to the current provision environments' * individual nodes. These nodes intelligently save the settings provision uses * in it's environment. */ function hosting_configure_provision($form_id, &$values) { $platform = node_load(HOSTING_OWN_PLATFORM); $platform->revision = true; $platform->publish_path = $_SERVER['DOCUMENT_ROOT']; node_save($platform); $db_server = node_load(HOSTING_OWN_DB_SERVER); $db_server->revision = true; $db_server->title = $values['db_server']['db_host']; $db_server->db_user = $values['db_server']['db_user']; if ($values['db_server']['db_passwd']) { $db_server->db_passwd = $values['db_server']['db_passwd']; } node_save($db_server); $web_server = node_load($platform->web_server); $web_server->revision = true; $web_server->config_path = $values['web_server']['config_path']; $web_server->backup_path = $values['web_server']['backup_path']; $web_server->script_user = $values['web_server']['script_user']; $web_server->restart_cmd = $values['web_server']['restart_cmd']; $web_server->web_group = $values['web_server']['web_group']; node_save($web_server); } /** * Maps the properties of a node object to an associative array, based on node type, incrementally. * * The back end provisioning system accepts communication via a set of pre-defined arguments. * This function will generate whatever information can be extracted from a specific action, * regardless what node type it is related to, and use this for generating the command to be executed. * * This functionality is similar in scope to the token module, but the token module contains much additional * complexity that was not necessary for us, and provided a lot of information that we did not need. * * @param node * A node id, or node of any type defined by the hosting system. * Will call hook_hosting_map_values_$node->type to provide actual mappings. * @return * An associative array of terms to be used on the command line, or in templates. */ function hosting_map_values($node) { if (is_numeric($node)) { $node = node_load($node); } $values = array(); $func = 'hosting_map_values_' . $node->type; if (function_exists($func)) { $values = $func($node); } return $values; } function _hosting_node_link($nid) { $node = node_load($nid); if ($node->nid) { return l($node->title, "node/" . $node->nid); } } /** * Creates a new block. */ function _hosting_add_block($module, $delta, $theme, $status, $weight, $region, $visibility = 0, $pages = '', $custom = 0, $throttle = 0, $title = '') { db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, visibility, pages, custom, throttle, title) VALUES ('%s', '%s', '%s', %d, %d, '%s', %d, '%s', %d, %d, '%s')", $module, $delta, $theme, $status, $weight, $region, $visibility, $pages, $custom, $throttle, $title); if ($module == 'block') { $box = db_fetch_object(db_query('SELECT * FROM {boxes} WHERE bid=%d', $delta)); db_query("INSERT INTO {boxes} (bid, body, info, format) VALUES (%d, '%s', '%s', '%s')", $box->bid, $box->body, $box->info, $box->format); } } /** * Check if a hostname provided is an ip address */ function _hosting_valid_ip($hostname) { //TODO : provide IPv6 support $parts = explode('.', $hostname); if (sizeof($parts) != 4) { return false; } foreach ($parts as $part) { if (((int) $part < 0) || ((int) $part > 255)) { return false; } } return true; }