acsf.drush.inc in Acquia Cloud Site Factory Connector 8
Provides drush commands for site related operations.
File
acsf.drush.incView source
<?php
/**
* @file
* Provides drush commands for site related operations.
*/
use Drupal\acsf\AcsfConfigDefault;
use Drupal\acsf\AcsfMessageFailedResponseException;
use Drupal\acsf\AcsfMessageRest;
use Drupal\acsf\AcsfSite;
use Drupal\acsf\Event\AcsfEvent;
use Drupal\Core\Extension\InfoParser;
/**
* Implements hook_drush_command().
*/
function acsf_drush_command() {
return [
'acsf-build-registry' => [
'description' => dt('Rebuilds the ACSF registry.'),
],
'acsf-site-scrub' => [
'description' => dt('Scrubs sensitive information regarding ACSF.'),
],
'acsf-site-sync' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE,
'description' => dt('Synchronize data with the Factory.'),
'options' => [
'data' => dt('A base64 encoded php array describing the site generated from the Factory.'),
],
],
'acsf-get-factory-creds' => [
'description' => dt('Print credentials retrieved from the factory.'),
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
],
'go-offline' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
'description' => dt('Set a site offline.'),
'aliases' => [
'go-off',
],
],
'go-online' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
'description' => dt('Set a site online.'),
'aliases' => [
'go-on',
],
],
'report-complete-async-process' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
'description' => dt('Send notice back to the Factory when a process completes.'),
'options' => [
'data' => dt('Serialized PHP data regarding the caller.'),
],
],
'acsf-version-get' => [
'description' => dt('Fetches the version of the acsf moduleset.'),
'arguments' => [
'path' => dt('The path to the acsf moduleset.'),
],
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
],
'acsf-install-task-get' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
'description' => dt('Returns the last installation task that was completed.'),
],
];
}
/**
* Command callback. Rebuilds the ACSF registry.
*/
function drush_acsf_build_registry() {
acsf_build_registry();
drush_print(dt('The ACSF registry was rebuilt.'));
}
/**
* Command callback. Scrubs the site database and/or other storage.
*
* Note that 'scrubbing' in our case doesn't mean just clearing configuration
* values but also initializing them for use in a new website.
*
* drush acsf-site-scrub is called by a 'db-copy' hosting task, which in turn
* seems to be called (only?) by the staging process.
*/
function drush_acsf_site_scrub() {
drush_print(dt('Flushing all caches ... '));
drupal_flush_all_caches();
drush_print(dt('Caches flushed.'));
$connection = \Drupal::database();
// Ensure that we are testing the scrub cleanly.
\Drupal::state()
->delete('acsf_site_scrubbed');
drush_print(dt('Preparing to scrub users ... '));
// Get a list of roles whose users should be preserved during staging
// scrubbing. Both lists are implemented as "alters" for consistency with
// hook_acsf_duplication_scrub_preserved_users_alter.
$preserved_roles = [];
\Drupal::moduleHandler()
->alter('acsf_staging_scrub_admin_roles', $preserved_roles);
if (!empty($preserved_roles)) {
drush_print(dt('Preserving roles: @rids', [
'@rids' => implode(', ', $preserved_roles),
]));
}
// Get a list of UIDs to preserve during staging scrubbing. UIDs are first
// obtained by the preserved roles, then can be altered to add or remove UIDs.
if (!empty($preserved_roles)) {
$preserved_users = \Drupal::entityQuery('user')
->condition('roles', $preserved_roles, 'IN')
->execute();
}
else {
$preserved_users = [];
}
\Drupal::moduleHandler()
->alter('acsf_staging_scrub_preserved_users', $preserved_users);
// Always preserve the anonymous user.
$preserved_users[] = 0;
$preserved_users = array_unique($preserved_users);
// The anonymous user makes the size of this array always at least 1.
drush_print(dt('Preserving users: @uids', [
'@uids' => implode(', ', $preserved_users),
]));
// Avoid collisions between the Factory and site users when scrubbing.
$connection
->update('users_field_data')
->expression('mail', "CONCAT('user', uid, '@', MD5(mail), '.example.com')")
->expression('init', "CONCAT('user', uid, '@', MD5(init), '.example.com')")
->condition('uid', $preserved_users, 'NOT IN')
->execute();
// Reset the cron key.
\Drupal::state()
->set('system.cron_key', md5(mt_rand()));
// Reset the drupal private key.
\Drupal::service('private_key')
->set('');
// Reset the local site data and run acsf-site-sync to fetch factory data
// about the site.
$site = AcsfSite::load();
$site
->clean();
drush_acsf_site_sync();
drush_log(dt('Executed acsf-site-sync to gather site data from factory and reset all acsf variables.'), 'ok');
// Trigger a rebuild of router paths (formerly 'menu paths').
\Drupal::service("router.builder")
->rebuild();
// Clear sessions and log tables that might have stale data, and whose
// implementing classes have no dedicated 'clear()' or equivalent mechanism.
$truncate_tables = [
'sessions',
'watchdog',
'acsf_theme_notifications',
];
foreach ($truncate_tables as $table) {
if ($connection
->schema()
->tableExists($table)) {
$connection
->truncate($table)
->execute();
}
}
// Clear caches and key-value store.
$bins = [
'bootstrap',
'config',
'data',
'default',
'discovery',
'dynamic_page_cache',
'entity',
'menu',
'migrate',
'render',
'rest',
'toolbar',
];
foreach ($bins as $bin) {
if (\Drupal::hasService("cache.{$bin}")) {
\Drupal::cache($bin)
->deleteAll();
}
}
$bins = [
'form',
'form_state',
];
foreach ($bins as $bin) {
\Drupal::keyValueExpirable($bin)
->deleteAll();
}
// Raise the database connection wait timeout (default 10 minutes) so mysql
// will not have "gone away" after the separate sql-sanitize process ends.
\Drupal::database()
->query('SET session wait_timeout=3600');
// Run the sql-sanitize which allows customers to use custom scrubbing. We
// will handle email addresses and passwords ourselves.
drush_invoke_process('@self', 'sql-sanitize', [], [
'sanitize-email' => 'no',
'sanitize-password' => 'no',
]);
// Mark this database as scrubbed.
\Drupal::state()
->set('acsf_site_scrubbed', 'scrubbed');
}
/**
* Command callback. Synchronizes data with the Factory.
*
* When executed without a --data option, this command will call the Factory to
* get data. When called with the --data option it will simply digest that data.
*/
function drush_acsf_site_sync() {
$site = AcsfSite::load();
$data = drush_get_option('data', NULL);
// Create an event to gather site stats to send to the Factory.
$context = [];
$event = AcsfEvent::create('acsf_stats', $context);
$event
->run();
$stats = $event->context;
if ($data) {
// If data was sent, we can consume it here. Ensure that we are always
// passing associative arrays here, not objects.
$site_info = json_decode(base64_decode($data), TRUE);
if (!empty($site_info) && is_array($site_info)) {
// Allow other modules to consume the data.
$context = $site_info;
$event = AcsfEvent::create('acsf_site_data_receive', $context);
$event
->run();
// For debugging purpose to be able to tell if the data has been pulled
// or pushed.
$site->last_sf_push = time();
$site
->saveSiteInfo($site_info['sf_site']);
}
}
else {
// If no data was sent, we'll request it.
$site
->refresh($stats);
}
}
/**
* Command callback. Prints factory information.
*/
function drush_acsf_get_factory_creds() {
if (!class_exists('\\Drupal\\acsf\\AcsfConfigDefault')) {
// Since there might not be a bootstrap, we need to find our config objects.
$include_path = realpath(dirname(__FILE__));
require_once $include_path . '/src/AcsfConfig.php';
require_once $include_path . '/src/AcsfConfigDefault.php';
require_once $include_path . '/src/AcsfConfigIncompleteException.php';
require_once $include_path . '/src/AcsfConfigMissingCredsException.php';
}
try {
$config = new AcsfConfigDefault();
} catch (\Exception $e) {
drush_set_error('Failed to get config: ' . $e
->getMessage());
exit(1);
}
$creds = [
'url' => $config
->getUrl(),
'username' => $config
->getUsername(),
'password' => $config
->getPassword(),
'url_suffix' => $config
->getUrlSuffix(),
];
$output = drush_format($creds, NULL, 'json');
if (drush_get_context('DRUSH_PIPE')) {
drush_print_pipe($output);
}
else {
drush_print($output);
}
}
/**
* Command callback. Puts a site into maintenance.
*/
function drush_acsf_go_offline() {
$lock = \Drupal::lock();
$acsf_settings = \Drupal::configFactory()
->getEditable('acsf.settings');
// Track if a site admin purposely put their site into maintenance.
$maintenance_mode = \Drupal::state()
->get('system.maintenance_mode');
if ($maintenance_mode) {
// Site is in maintenance mode already. We need to keep that in mind.
$acsf_settings
->set('site_owner_maintenance_mode', TRUE)
->save();
}
// For now hard-code a 10 minute expected offline time.
$expected = time() + 10 * 60;
\Drupal::state()
->set('system.maintenance_mode', TRUE);
$acsf_settings
->set('maintenance_time', $expected)
->save();
// Get the cron lock to prevent cron from running during an update.
// Use a large lock timeout because an update can take a long time.
// All cron processes are stopped before update begins, so the lock will
// be available.
$lock
->acquire('cron', 1200.0);
}
/**
* Runs after a go-offline command executes. Verifies maintenance mode.
*/
function drush_acsf_post_go_offline() {
$offline = \Drupal::state()
->get('system.maintenance_mode');
if ($offline) {
drush_log('Site has been placed offline.', 'success');
}
else {
drush_log('Site has not been placed offline.', 'error');
}
}
/**
* Command callback. Takes a site out of maintenance.
*/
function drush_acsf_go_online() {
$lock = \Drupal::lock();
// Determine whether the user intended the site to be in maintenance mode.
$content = \Drupal::config('acsf.settings')
->get('site_owner_maintenance_mode');
// Clearing maintenance mode.
\Drupal::state()
->set('system.maintenance_mode', FALSE);
\Drupal::configFactory()
->getEditable('acsf.settings')
->set('maintenance_time', 0)
->save();
if (!empty($content)) {
\Drupal::state()
->set('system.maintenance_mode', TRUE);
}
// Release cron lock.
$lock
->release('cron');
}
/**
* Runs after a go-online command executes. Verifies maintenance mode.
*/
function drush_acsf_post_go_online() {
$content = \Drupal::state()
->get('system.maintenance_mode');
if (empty($content)) {
drush_log('Site has been placed online.', 'success');
}
else {
$content = \Drupal::config('acsf.settings')
->get('site_owner_maintenance_mode');
if (empty($content)) {
drush_log('Site has not been placed online.', 'error');
}
else {
drush_log('Site has been left offline as set by the site owner.', 'success');
// Unset our maintenance mode setting.
\Drupal::configFactory()
->getEditable('acsf.settings')
->set('site_owner_maintenance_mode', FALSE)
->save();
}
}
}
/**
* Command callback. Reports process completion back to the factory.
*/
function drush_acsf_report_complete_async_process() {
$data = unserialize(drush_get_option('data', NULL));
if (empty($data->callback) || empty($data->object_id) || empty($data->acsf_path)) {
drush_set_error('Data error', dt('Requires serialized object in --data argument with $data->callback and $data->object_id populated.'));
exit(1);
}
// Since this does not bootstrap drupal fully, we need to manually require the
// classes necessary to send a message to the Factory.
require_once $data->acsf_path . '/src/AcsfConfig.php';
require_once $data->acsf_path . '/src/AcsfConfigDefault.php';
require_once $data->acsf_path . '/src/AcsfConfigIncompleteException.php';
require_once $data->acsf_path . '/src/AcsfConfigMissingCredsException.php';
require_once $data->acsf_path . '/src/AcsfMessage.php';
require_once $data->acsf_path . '/src/AcsfMessageEmptyResponseException.php';
require_once $data->acsf_path . '/src/AcsfMessageFailedResponseException.php';
require_once $data->acsf_path . '/src/AcsfMessageFailureException.php';
require_once $data->acsf_path . '/src/AcsfMessageMalformedResponseException.php';
require_once $data->acsf_path . '/src/AcsfMessageRest.php';
require_once $data->acsf_path . '/src/AcsfMessageResponse.php';
require_once $data->acsf_path . '/src/AcsfMessageResponseRest.php';
$arguments = [
'wid' => $data->object_id,
'signal' => 1,
'state' => isset($data->state) ? $data->state : NULL,
'data' => $data,
];
try {
// We do not have a Drupal bootstrap at this point, so we need to use
// AcsfConfigDefault to obtain the shared credentials.
$config = new AcsfConfigDefault();
$message = new AcsfMessageRest('POST', $data->callback, $arguments, $config);
$message
->send();
} catch (AcsfMessageFailedResponseException $e) {
syslog(LOG_ERR, 'Unable to contact the factory via AcsfMessage.');
}
}
/**
* Command callback. Fetches the version of the acsf moduleset.
*/
function drush_acsf_version_get($path = NULL) {
if (empty($path)) {
$path = __DIR__;
}
$version = '0.0';
$acsf_file_path = rtrim($path, '/') . '/acsf.info.yml';
if (file_exists($acsf_file_path)) {
$info_parser = new InfoParser();
$info = $info_parser
->parse($acsf_file_path);
$version = isset($info['acsf_version']) ? $info['acsf_version'] : '0.1';
}
drush_print($version);
}
/**
* Command callback. Fetches the last installation task.
*/
function drush_acsf_install_task_get() {
try {
$task = \Drupal::state()
->get('install_task');
} catch (\Exception $e) {
// Do not trigger an error if the database query fails, since the database
// might not be set up yet.
}
if (isset($task)) {
return json_encode($task);
}
}
Functions
Name | Description |
---|---|
acsf_drush_command | Implements hook_drush_command(). |
drush_acsf_build_registry | Command callback. Rebuilds the ACSF registry. |
drush_acsf_get_factory_creds | Command callback. Prints factory information. |
drush_acsf_go_offline | Command callback. Puts a site into maintenance. |
drush_acsf_go_online | Command callback. Takes a site out of maintenance. |
drush_acsf_install_task_get | Command callback. Fetches the last installation task. |
drush_acsf_post_go_offline | Runs after a go-offline command executes. Verifies maintenance mode. |
drush_acsf_post_go_online | Runs after a go-online command executes. Verifies maintenance mode. |
drush_acsf_report_complete_async_process | Command callback. Reports process completion back to the factory. |
drush_acsf_site_scrub | Command callback. Scrubs the site database and/or other storage. |
drush_acsf_site_sync | Command callback. Synchronizes data with the Factory. |
drush_acsf_version_get | Command callback. Fetches the version of the acsf moduleset. |