diff --git a/blt/01dev.blt.yml b/blt/01dev.blt.yml deleted file mode 100644 index eb58d055d1..0000000000 --- a/blt/01dev.blt.yml +++ /dev/null @@ -1,10 +0,0 @@ -## This config will import features on a code update -## and not care if there are config differences. -cm: - strategy: features - allow-overrides: true - features: - allow-overrides: true - bundle: - - default -cgov.drupal_users_file: '/home/ncigov/test-users.yml' diff --git a/blt/01live.blt.yml b/blt/01live.blt.yml deleted file mode 100644 index 3858e381f5..0000000000 --- a/blt/01live.blt.yml +++ /dev/null @@ -1,10 +0,0 @@ -## This config will import features on a code update -## and not care if there are config differences. -cm: - strategy: features - allow-overrides: true - features: - allow-overrides: true - bundle: - - default -cgov.drupal_users_file: '/home/ncigov/standard-users.yml' diff --git a/blt/01test.blt.yml b/blt/01test.blt.yml deleted file mode 100644 index eb58d055d1..0000000000 --- a/blt/01test.blt.yml +++ /dev/null @@ -1,10 +0,0 @@ -## This config will import features on a code update -## and not care if there are config differences. -cm: - strategy: features - allow-overrides: true - features: - allow-overrides: true - bundle: - - default -cgov.drupal_users_file: '/home/ncigov/test-users.yml' diff --git a/blt/blt.yml b/blt/blt.yml index a657fb8ab4..4e101f5c86 100644 --- a/blt/blt.yml +++ b/blt/blt.yml @@ -126,13 +126,12 @@ modules: akamai ] ## We have no prod in ACE. - 01dev: + meodev: enable: [ dblog, cgov_syslog, acquia_connector, - acsf, shield, cgov_https_config, cgov_caching_cdn, @@ -140,15 +139,15 @@ modules: ] uninstall: [ - cgov_caching_nocdn + acsf, + cgov_caching_nocdn, ] - 01test: + meostage: enable: [ dblog, cgov_syslog, acquia_connector, - acsf, shield, cgov_https_config, cgov_caching_cdn, @@ -156,14 +155,14 @@ modules: ] uninstall: [ - cgov_caching_nocdn + acsf, + cgov_caching_nocdn, ] - 01live: + meoprod: enable: [ cgov_syslog, acquia_connector, - acsf, shield, cgov_https_config, cgov_caching_cdn, @@ -171,8 +170,9 @@ modules: ] uninstall: [ + acsf, dblog, - cgov_caching_nocdn + cgov_caching_nocdn, ] ## If this is not set, blt drupal:install will iterate through the sites in the sites folder. ## For now we set it to default, so it does not try installing simpletest, or god forbid "g". @@ -224,11 +224,10 @@ command-hooks: command: null cgov: - ## Defaulting users to the repo file was a bad idea, and we will just - ## set this to the path ACE environments use so that we do not need - ## as many blt overrides. This will avoid any misconfigured environments - ## loading the defaults from the repo. - drupal_users_file: '/home/ncigovcd/cgov-drupal-users.yml' + ## All Acquia environment will use the drupal_users_file path below. + ## Only local will use its own path; CI does not load users at this + ## time. + drupal_users_file: '/mnt/files/${env.AH_SITE_GROUP}.${env.AH_SITE_ENVIRONMENT}/cgdp/cgov-drupal-users.yml' front_end_globals_file: '${repo.root}/FrontendGlobals.json' pdq_load_glossifier_file: '${repo.root}/glossifier_refresh.json' diff --git a/blt/dev.blt.yml b/blt/dev.blt.yml deleted file mode 100644 index 743f44513f..0000000000 --- a/blt/dev.blt.yml +++ /dev/null @@ -1 +0,0 @@ -project.human_name: 'CGDP - Dev' diff --git a/blt/src/Blt/Plugin/Commands/CgovAcsfCommands.php b/blt/src/Blt/Plugin/Commands/CgovMeoCommands.php similarity index 70% rename from blt/src/Blt/Plugin/Commands/CgovAcsfCommands.php rename to blt/src/Blt/Plugin/Commands/CgovMeoCommands.php index 9072f2a32f..9cf1038186 100644 --- a/blt/src/Blt/Plugin/Commands/CgovAcsfCommands.php +++ b/blt/src/Blt/Plugin/Commands/CgovMeoCommands.php @@ -5,18 +5,18 @@ use Acquia\Blt\Robo\BltTasks; /** - * Commands for use in the ACSF Environments. + * Commands for use in the MEO Environments. * - * Some of the Factory Hooks are a pain to run multiple commands. (I am + * Some of the MEO deployment tasks require specific handling. (I am * looking at you post-install) So for consistency, we will define all - * cgov factory hook methods here. + * cgov MEO hook methods here. */ -class CgovAcsfCommands extends BltTasks { +class CgovMeoCommands extends BltTasks { /** - * Post Install Support for ACSF. + * Post Install Support for MEO. * - * @command cgov:acsf:post-install + * @command cgov:meo:post-install * * @validateDrushConfig * @executeInVm @@ -31,9 +31,9 @@ public function postInstall() { } /** - * DB Update Support for ACSF. + * DB Update Support for MEO. * - * @command cgov:acsf:db-update + * @command cgov:meo:db-update * * @validateDrushConfig * @executeInVm @@ -47,9 +47,9 @@ public function dbUpdate() { } /** - * Post staging deploy support for ACSF. + * Post staging deploy support for MEO. * - * @command cgov:acsf:post-staging-deploy + * @command cgov:meo:post-staging-deploy * * @validateDrushConfig * @executeInVm @@ -63,7 +63,7 @@ public function postStagingDeploy() { /** * Blocks the admin user from being able to login. * - * @command cgov:acsf:block-admin + * @command cgov:meo:block-admin */ public function blockAdminUser() { $task = $this->taskDrush() diff --git a/blt/src/Blt/Plugin/Commands/UsersCommands.php b/blt/src/Blt/Plugin/Commands/UsersCommands.php index 0d3fda94b7..60aa9e9e0f 100644 --- a/blt/src/Blt/Plugin/Commands/UsersCommands.php +++ b/blt/src/Blt/Plugin/Commands/UsersCommands.php @@ -35,7 +35,7 @@ public function loadUsers() { $commands = []; $blocked_admin = [ - 'cgov:acsf:block-admin' => [], + 'cgov:meo:block-admin' => [], ]; if (array_key_exists('admin', $userConfig)) { if ($this->isAdminBlockValid($userConfig['admin'])) { diff --git a/blt/src/Blt/Plugin/Config/CustomConfigInitializer.php b/blt/src/Blt/Plugin/Config/CustomConfigInitializer.php new file mode 100644 index 0000000000..1be1f067f6 --- /dev/null +++ b/blt/src/Blt/Plugin/Config/CustomConfigInitializer.php @@ -0,0 +1,48 @@ + { /^https?\:\/\/([a-zA-Z0-9\-]+\.)+/i.test(el.href) && !/^https?\:\/\/([a-zA-Z0-9\-]+\.)+gov/i.test(el.href) && !/^https?\:\/\/([a-zA-Z0-9\-]+\.)+acquia-sites\.com/i.test(el.href) && - !/^https?\:\/\/([a-zA-Z0-9\-]+\.)+acsitefactory\.com/i.test(el.href) && el.href !== "" && el.href.indexOf(location.protocol + "//" + location.hostname) !== 0 && !$(el).hasClass("add_this_btn") && diff --git a/docroot/sites/default/acsf.settings.php b/docroot/sites/default/acsf.settings.php deleted file mode 100644 index 8fd5f33459..0000000000 --- a/docroot/sites/default/acsf.settings.php +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Service Temporarily Unavailable - - - -

Service Temporarily Unavailable

-

We're currently performing maintenance/updates. The site will be back shortly.

- - -HTML; - exit(); - } - - // Print a 404 response and a small HTML page. - header("HTTP/1.0 404 Not Found"); - header('Content-type: text/html; charset=utf-8'); - if (!empty($GLOBALS['gardens_site_settings']['page_ttl']) - && is_numeric($GLOBALS['gardens_site_settings']['page_ttl']) - && $GLOBALS['gardens_site_settings']['page_ttl'] > 0) { - // Set alternative Cache-Control header. The other header is required - // because Acquia's Varnish will not allow max-age < 900 without it. - header("Cache-Control: max-age={$GLOBALS['gardens_site_settings']['page_ttl']}, public"); - header('X-Acquia-No-301-404-Caching-Enforcement: 1'); - } - - print << - - - - 404 Page Not Found - - - -

The site you are looking for could not be found.

- - -HTML; - - exit(); -} diff --git a/docroot/sites/default/settings.php b/docroot/sites/default/settings.php index 987f9df2d7..a292ad533b 100644 --- a/docroot/sites/default/settings.php +++ b/docroot/sites/default/settings.php @@ -2,241 +2,12 @@ // @codingStandardsIgnoreFile -/** - * Include our ACSF settings if we are on an ACSF environment. - */ -if (!empty($_ENV['AH_SITE_ENVIRONMENT']) && - function_exists('gardens_site_data_get_filepath')) { - // ===== Added by acsf-init, please do not delete. Section start. ===== -$_acsf_infrastructure = include dirname(__FILE__) . '/acsf.settings.php'; -if ($_acsf_infrastructure === 'acsf-infrastructure') { - return; -} -// ===== Added by acsf-init, please do not delete. Section end. ===== -} - /** * @file - * Drupal site-specific configuration file. - * - * IMPORTANT NOTE: - * This file may have been set to read-only by the Drupal installation program. - * If you make changes to this file, be sure to protect it again after making - * your modifications. Failure to remove write permissions to this file is a - * security risk. - * - * In order to use the selection rules below the multisite aliasing file named - * sites/sites.php must be present. Its optional settings will be loaded, and - * the aliases in the array $sites will override the default directory rules - * below. See sites/example.sites.php for more information about aliases. - * - * The configuration directory will be discovered by stripping the website's - * hostname from left to right and pathname from right to left. The first - * configuration file found will be used and any others will be ignored. If no - * other configuration file is found then the default configuration file at - * 'sites/default' will be used. - * - * For example, for a fictitious site installed at - * https://www.drupal.org:8080/mysite/test/, the 'settings.php' file is searched - * for in the following directories: - * - * - sites/8080.www.drupal.org.mysite.test - * - sites/www.drupal.org.mysite.test - * - sites/drupal.org.mysite.test - * - sites/org.mysite.test - * - * - sites/8080.www.drupal.org.mysite - * - sites/www.drupal.org.mysite - * - sites/drupal.org.mysite - * - sites/org.mysite - * - * - sites/8080.www.drupal.org - * - sites/www.drupal.org - * - sites/drupal.org - * - sites/org - * - * - sites/default - * - * Note that if you are installing on a non-standard port number, prefix the - * hostname with that number. For example, - * https://www.drupal.org:8080/mysite/test/ could be loaded from - * sites/8080.www.drupal.org.mysite.test/. - * - * @see example.sites.php - * @see \Drupal\Core\DrupalKernel::getSitePath() - * - * In addition to customizing application settings through variables in - * settings.php, you can create a services.yml file in the same directory to - * register custom, site-specific service definitions and/or swap out default - * implementations with custom ones. - */ - -/** - * Database settings: - * - * The $databases array specifies the database connection or - * connections that Drupal may use. Drupal is able to connect - * to multiple databases, including multiple types of databases, - * during the same request. - * - * One example of the simplest connection array is shown below. To use the - * sample settings, copy and uncomment the code below between the @code and - * @endcode lines and paste it after the $databases declaration. You will need - * to replace the database username and password and possibly the host and port - * with the appropriate credentials for your database system. - * - * The next section describes how to customize the $databases array for more - * specific needs. - * - * @code - * $databases['default']['default'] = array ( - * 'database' => 'databasename', - * 'username' => 'sqlusername', - * 'password' => 'sqlpassword', - * 'host' => 'localhost', - * 'port' => '3306', - * 'driver' => 'mysql', - * 'prefix' => '', - * 'collation' => 'utf8mb4_general_ci', - * ); - * @endcode - */ -$databases = []; - -/** - * Customizing database settings. - * - * Many of the values of the $databases array can be customized for your - * particular database system. Refer to the sample in the section above as a - * starting point. - * - * The "driver" property indicates what Drupal database driver the - * connection should use. This is usually the same as the name of the - * database type, such as mysql or sqlite, but not always. The other - * properties will vary depending on the driver. For SQLite, you must - * specify a database file name in a directory that is writable by the - * webserver. For most other drivers, you must specify a - * username, password, host, and database name. - * - * Transaction support is enabled by default for all drivers that support it, - * including MySQL. To explicitly disable it, set the 'transactions' key to - * FALSE. - * Note that some configurations of MySQL, such as the MyISAM engine, don't - * support it and will proceed silently even if enabled. If you experience - * transaction related crashes with such configuration, set the 'transactions' - * key to FALSE. - * - * For each database, you may optionally specify multiple "target" databases. - * A target database allows Drupal to try to send certain queries to a - * different database if it can but fall back to the default connection if not. - * That is useful for primary/replica replication, as Drupal may try to connect - * to a replica server when appropriate and if one is not available will simply - * fall back to the single primary server (The terms primary/replica are - * traditionally referred to as master/slave in database server documentation). - * - * The general format for the $databases array is as follows: - * @code - * $databases['default']['default'] = $info_array; - * $databases['default']['replica'][] = $info_array; - * $databases['default']['replica'][] = $info_array; - * $databases['extra']['default'] = $info_array; - * @endcode - * - * In the above example, $info_array is an array of settings described above. - * The first line sets a "default" database that has one primary database - * (the second level default). The second and third lines create an array - * of potential replica databases. Drupal will select one at random for a given - * request as needed. The fourth line creates a new database with a name of - * "extra". - * - * You can optionally set prefixes for some or all database table names - * by using the 'prefix' setting. If a prefix is specified, the table - * name will be prepended with its value. Be sure to use valid database - * characters only, usually alphanumeric and underscore. If no prefixes - * are desired, leave it as an empty string ''. - * - * To have all database names prefixed, set 'prefix' as a string: - * @code - * 'prefix' => 'main_', - * @endcode - * - * Per-table prefixes are deprecated as of Drupal 8.2, and will be removed in - * Drupal 9.0. After that, only a single prefix for all tables will be - * supported. - * - * To provide prefixes for specific tables, set 'prefix' as an array. - * The array's keys are the table names and the values are the prefixes. - * The 'default' element is mandatory and holds the prefix for any tables - * not specified elsewhere in the array. Example: - * @code - * 'prefix' => array( - * 'default' => 'main_', - * 'users' => 'shared_', - * 'sessions' => 'shared_', - * 'role' => 'shared_', - * 'authmap' => 'shared_', - * ), - * @endcode - * You can also use a reference to a schema/database as a prefix. This may be - * useful if your Drupal installation exists in a schema that is not the default - * or you want to access several databases from the same code base at the same - * time. - * Example: - * @code - * 'prefix' => array( - * 'default' => 'main.', - * 'users' => 'shared.', - * 'sessions' => 'shared.', - * 'role' => 'shared.', - * 'authmap' => 'shared.', - * ); - * @endcode - * NOTE: MySQL and SQLite's definition of a schema is a database. - * - * Advanced users can add or override initial commands to execute when - * connecting to the database server, as well as PDO connection settings. For - * example, to enable MySQL SELECT queries to exceed the max_join_size system - * variable, and to reduce the database connection timeout to 5 seconds: - * @code - * $databases['default']['default'] = array( - * 'init_commands' => array( - * 'big_selects' => 'SET SQL_BIG_SELECTS=1', - * ), - * 'pdo' => array( - * PDO::ATTR_TIMEOUT => 5, - * ), - * ); - * @endcode - * - * WARNING: The above defaults are designed for database portability. Changing - * them may cause unexpected behavior, including potential data loss. See - * https://www.drupal.org/developing/api/database/configuration for more - * information on these defaults and the potential issues. - * - * More details can be found in the constructor methods for each driver: - * - \Drupal\Core\Database\Driver\mysql\Connection::__construct() - * - \Drupal\Core\Database\Driver\pgsql\Connection::__construct() - * - \Drupal\Core\Database\Driver\sqlite\Connection::__construct() - * - * Sample Database configuration format for PostgreSQL (pgsql): - * @code - * $databases['default']['default'] = array( - * 'driver' => 'pgsql', - * 'database' => 'databasename', - * 'username' => 'sqlusername', - * 'password' => 'sqlpassword', - * 'host' => 'localhost', - * 'prefix' => '', - * ); - * @endcode + * CGDP Drupal configuration file. * - * Sample Database configuration format for SQLite (sqlite): - * @code - * $databases['default']['default'] = array( - * 'driver' => 'sqlite', - * 'database' => '/path/to/databasefilename', - * ); - * @endcode + * All commented out settings have been removed. See default.settings.php + * for a list of available settings. */ /** @@ -266,16 +37,6 @@ function_exists('gardens_site_data_get_filepath')) { */ $config_directories = []; -/** - * Settings: - * - * $settings contains environment-specific configuration, such as the files - * directory and reverse proxy address, and temporary configuration, such as - * security overrides. - * - * @see \Drupal\Core\Site\Settings::get() - */ - /** * Salt for one-time login links, cancel links, form tokens, etc. * @@ -295,16 +56,6 @@ function_exists('gardens_site_data_get_filepath')) { */ $settings['hash_salt'] = ''; -/** - * Deployment identifier. - * - * Drupal's dependency injection container will be automatically invalidated and - * rebuilt when the Drupal core version changes. When updating contributed or - * custom code that changes the container, changing this identifier will also - * allow the container to be invalidated as soon as code is deployed. - */ -# $settings['deployment_identifier'] = \Drupal::VERSION; - /** * Access control for update.php script. * @@ -318,430 +69,11 @@ function_exists('gardens_site_data_get_filepath')) { */ $settings['update_free_access'] = FALSE; -/** - * External access proxy settings: - * - * If your site must access the Internet via a web proxy then you can enter the - * proxy settings here. Set the full URL of the proxy, including the port, in - * variables: - * - $settings['http_client_config']['proxy']['http']: The proxy URL for HTTP - * requests. - * - $settings['http_client_config']['proxy']['https']: The proxy URL for HTTPS - * requests. - * You can pass in the user name and password for basic authentication in the - * URLs in these settings. - * - * You can also define an array of host names that can be accessed directly, - * bypassing the proxy, in $settings['http_client_config']['proxy']['no']. - */ -# $settings['http_client_config']['proxy']['http'] = 'http://proxy_user:proxy_pass@example.com:8080'; -# $settings['http_client_config']['proxy']['https'] = 'http://proxy_user:proxy_pass@example.com:8080'; -# $settings['http_client_config']['proxy']['no'] = ['127.0.0.1', 'localhost']; - -/** - * Reverse Proxy Configuration: - * - * Reverse proxy servers are often used to enhance the performance - * of heavily visited sites and may also provide other site caching, - * security, or encryption benefits. In an environment where Drupal - * is behind a reverse proxy, the real IP address of the client should - * be determined such that the correct client IP address is available - * to Drupal's logging, statistics, and access management systems. In - * the most simple scenario, the proxy server will add an - * X-Forwarded-For header to the request that contains the client IP - * address. However, HTTP headers are vulnerable to spoofing, where a - * malicious client could bypass restrictions by setting the - * X-Forwarded-For header directly. Therefore, Drupal's proxy - * configuration requires the IP addresses of all remote proxies to be - * specified in $settings['reverse_proxy_addresses'] to work correctly. - * - * Enable this setting to get Drupal to determine the client IP from - * the X-Forwarded-For header (or $settings['reverse_proxy_header'] if set). - * If you are unsure about this setting, do not have a reverse proxy, - * or Drupal operates in a shared hosting environment, this setting - * should remain commented out. - * - * In order for this setting to be used you must specify every possible - * reverse proxy IP address in $settings['reverse_proxy_addresses']. - * If a complete list of reverse proxies is not available in your - * environment (for example, if you use a CDN) you may set the - * $_SERVER['REMOTE_ADDR'] variable directly in settings.php. - * Be aware, however, that it is likely that this would allow IP - * address spoofing unless more advanced precautions are taken. - */ -# $settings['reverse_proxy'] = TRUE; - -/** - * Specify every reverse proxy IP address in your environment. - * This setting is required if $settings['reverse_proxy'] is TRUE. - */ -# $settings['reverse_proxy_addresses'] = ['a.b.c.d', ...]; - -/** - * Set this value if your proxy server sends the client IP in a header - * other than X-Forwarded-For. - */ -# $settings['reverse_proxy_header'] = 'X_CLUSTER_CLIENT_IP'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Proto. - */ -# $settings['reverse_proxy_proto_header'] = 'X_FORWARDED_PROTO'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Host. - */ -# $settings['reverse_proxy_host_header'] = 'X_FORWARDED_HOST'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Port. - */ -# $settings['reverse_proxy_port_header'] = 'X_FORWARDED_PORT'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than Forwarded. - */ -# $settings['reverse_proxy_forwarded_header'] = 'FORWARDED'; - -/** - * Page caching: - * - * By default, Drupal sends a "Vary: Cookie" HTTP header for anonymous page - * views. This tells a HTTP proxy that it may return a page from its local - * cache without contacting the web server, if the user sends the same Cookie - * header as the user who originally requested the cached page. Without "Vary: - * Cookie", authenticated users would also be served the anonymous page from - * the cache. If the site has mostly anonymous users except a few known - * editors/administrators, the Vary header can be omitted. This allows for - * better caching in HTTP proxies (including reverse proxies), i.e. even if - * clients send different cookies, they still get content served from the cache. - * However, authenticated users should access the site directly (i.e. not use an - * HTTP proxy, and bypass the reverse proxy if one is used) in order to avoid - * getting cached pages from the proxy. - */ -# $settings['omit_vary_cookie'] = TRUE; - - -/** - * Cache TTL for client error (4xx) responses. - * - * Items cached per-URL tend to result in a large number of cache items, and - * this can be problematic on 404 pages which by their nature are unbounded. A - * fixed TTL can be set for these items, defaulting to one hour, so that cache - * backends which do not support LRU can purge older entries. To disable caching - * of client error responses set the value to 0. Currently applies only to - * page_cache module. - */ -# $settings['cache_ttl_4xx'] = 3600; - -/** - * Expiration of cached forms. - * - * Drupal's Form API stores details of forms in a cache and these entries are - * kept for at least 6 hours by default. Expired entries are cleared by cron. - * - * @see \Drupal\Core\Form\FormCache::setCache() - */ -# $settings['form_cache_expiration'] = 21600; - -/** - * Class Loader. - * - * If the APC extension is detected, the Symfony APC class loader is used for - * performance reasons. Detection can be prevented by setting - * class_loader_auto_detect to false, as in the example below. - */ -# $settings['class_loader_auto_detect'] = FALSE; - -/* - * If the APC extension is not detected, either because APC is missing or - * because auto-detection has been disabled, auto-loading falls back to - * Composer's ClassLoader, which is good for development as it does not break - * when code is moved in the file system. You can also decorate the base class - * loader with another cached solution than the Symfony APC class loader, as - * all production sites should have a cached class loader of some sort enabled. - * - * To do so, you may decorate and replace the local $class_loader variable. For - * example, to use Symfony's APC class loader without automatic detection, - * uncomment the code below. - */ -/* -if ($settings['hash_salt']) { - $prefix = 'drupal.' . hash('sha256', 'drupal.' . $settings['hash_salt']); - $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $class_loader); - unset($prefix); - $class_loader->unregister(); - $apc_loader->register(); - $class_loader = $apc_loader; -} -*/ - -/** - * Authorized file system operations: - * - * The Update Manager module included with Drupal provides a mechanism for - * site administrators to securely install missing updates for the site - * directly through the web user interface. On securely-configured servers, - * the Update manager will require the administrator to provide SSH or FTP - * credentials before allowing the installation to proceed; this allows the - * site to update the new files as the user who owns all the Drupal files, - * instead of as the user the webserver is running as. On servers where the - * webserver user is itself the owner of the Drupal files, the administrator - * will not be prompted for SSH or FTP credentials (note that these server - * setups are common on shared hosting, but are inherently insecure). - * - * Some sites might wish to disable the above functionality, and only update - * the code directly via SSH or FTP themselves. This setting completely - * disables all functionality related to these authorized file operations. - * - * @see https://www.drupal.org/node/244924 - * - * Remove the leading hash signs to disable. - */ -# $settings['allow_authorize_operations'] = FALSE; - -/** - * Default mode for directories and files written by Drupal. - * - * Value should be in PHP Octal Notation, with leading zero. - */ -# $settings['file_chmod_directory'] = 0775; -# $settings['file_chmod_file'] = 0664; - -/** - * Public file base URL: - * - * An alternative base URL to be used for serving public files. This must - * include any leading directory path. - * - * A different value from the domain used by Drupal to be used for accessing - * public files. This can be used for a simple CDN integration, or to improve - * security by serving user-uploaded files from a different domain or subdomain - * pointing to the same server. Do not include a trailing slash. - */ -# $settings['file_public_base_url'] = 'http://downloads.example.com/files'; - -/** - * Public file path: - * - * A local file system path where public files will be stored. This directory - * must exist and be writable by Drupal. This directory must be relative to - * the Drupal installation directory and be accessible over the web. - */ -# $settings['file_public_path'] = 'sites/default/files'; - -/** - * Private file path: - * - * A local file system path where private files will be stored. This directory - * must be absolute, outside of the Drupal installation directory and not - * accessible over the web. - * - * Note: Caches need to be cleared when this value is changed to make the - * private:// stream wrapper available to the system. - * - * See https://www.drupal.org/documentation/modules/file for more information - * about securing private files. - */ -# $settings['file_private_path'] = ''; - -/** - * Session write interval: - * - * Set the minimum interval between each session write to database. - * For performance reasons it defaults to 180. - */ -# $settings['session_write_interval'] = 180; - -/** - * String overrides: - * - * To override specific strings on your site with or without enabling the Locale - * module, add an entry to this list. This functionality allows you to change - * a small number of your site's default English language interface strings. - * - * Remove the leading hash signs to enable. - * - * The "en" part of the variable name, is dynamic and can be any langcode of - * any added language. (eg locale_custom_strings_de for german). - */ -# $settings['locale_custom_strings_en'][''] = [ -# 'forum' => 'Discussion board', -# '@count min' => '@count minutes', -# ]; - -/** - * A custom theme for the offline page: - * - * This applies when the site is explicitly set to maintenance mode through the - * administration page or when the database is inactive due to an error. - * The template file should also be copied into the theme. It is located inside - * 'core/modules/system/templates/maintenance-page.html.twig'. - * - * Note: This setting does not apply to installation and update pages. - */ -# $settings['maintenance_theme'] = 'bartik'; - -/** - * PHP settings: - * - * To see what PHP settings are possible, including whether they can be set at - * runtime (by using ini_set()), read the PHP documentation: - * http://php.net/manual/ini.list.php - * See \Drupal\Core\DrupalKernel::bootEnvironment() for required runtime - * settings and the .htaccess file for non-runtime settings. - * Settings defined there should not be duplicated here so as to avoid conflict - * issues. - */ - -/** - * If you encounter a situation where users post a large amount of text, and - * the result is stripped out upon viewing but can still be edited, Drupal's - * output filter may not have sufficient memory to process it. If you - * experience this issue, you may wish to uncomment the following two lines - * and increase the limits of these variables. For more information, see - * http://php.net/manual/pcre.configuration.php. - */ -# ini_set('pcre.backtrack_limit', 200000); -# ini_set('pcre.recursion_limit', 200000); - -/** - * Active configuration settings. - * - * By default, the active configuration is stored in the database in the - * {config} table. To use a different storage mechanism for the active - * configuration, do the following prior to installing: - * - Create an "active" directory and declare its path in $config_directories - * as explained under the 'Location of the site configuration files' section - * above in this file. To enhance security, you can declare a path that is - * outside your document root. - * - Override the 'bootstrap_config_storage' setting here. It must be set to a - * callable that returns an object that implements - * \Drupal\Core\Config\StorageInterface. - * - Override the service definition 'config.storage.active'. Put this - * override in a services.yml file in the same directory as settings.php - * (definitions in this file will override service definition defaults). - */ -# $settings['bootstrap_config_storage'] = ['Drupal\Core\Config\BootstrapConfigStorageFactory', 'getFileStorage']; - -/** - * Configuration overrides. - * - * To globally override specific configuration values for this site, - * set them here. You usually don't need to use this feature. This is - * useful in a configuration file for a vhost or directory, rather than - * the default settings.php. - * - * Note that any values you provide in these variable overrides will not be - * viewable from the Drupal administration interface. The administration - * interface displays the values stored in configuration so that you can stage - * changes to other environments that don't have the overrides. - * - * There are particular configuration values that are risky to override. For - * example, overriding the list of installed modules in 'core.extension' is not - * supported as module install or uninstall has not occurred. Other examples - * include field storage configuration, because it has effects on database - * structure, and 'core.menu.static_menu_link_overrides' since this is cached in - * a way that is not config override aware. Also, note that changing - * configuration values in settings.php will not fire any of the configuration - * change events. - */ -# $config['system.file']['path']['temporary'] = '/tmp'; -# $config['system.site']['name'] = 'My Drupal site'; -# $config['system.theme']['default'] = 'stark'; -# $config['user.settings']['anonymous'] = 'Visitor'; - -/** - * Fast 404 pages: - * - * Drupal can generate fully themed 404 pages. However, some of these responses - * are for images or other resource files that are not displayed to the user. - * This can waste bandwidth, and also generate server load. - * - * The options below return a simple, fast 404 page for URLs matching a - * specific pattern: - * - $config['system.performance']['fast_404']['exclude_paths']: A regular - * expression to match paths to exclude, such as images generated by image - * styles, or dynamically-resized images. The default pattern provided below - * also excludes the private file system. If you need to add more paths, you - * can add '|path' to the expression. - * - $config['system.performance']['fast_404']['paths']: A regular expression to - * match paths that should return a simple 404 page, rather than the fully - * themed 404 page. If you don't have any aliases ending in htm or html you - * can add '|s?html?' to the expression. - * - $config['system.performance']['fast_404']['html']: The html to return for - * simple 404 pages. - * - * Remove the leading hash signs if you would like to alter this functionality. - */ -# $config['system.performance']['fast_404']['exclude_paths'] = '/\/(?:styles)|(?:system\/files)\//'; -# $config['system.performance']['fast_404']['paths'] = '/\.(?:txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'; -# $config['system.performance']['fast_404']['html'] = '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'; - /** * Load services definition file. */ $settings['container_yamls'][] = $app_root . '/' . $site_path . '/services.yml'; -/** - * Override the default service container class. - * - * This is useful for example to trace the service container for performance - * tracking purposes, for testing a service container with an error condition or - * to test a service container that throws an exception. - */ -# $settings['container_base_class'] = '\Drupal\Core\DependencyInjection\Container'; - -/** - * Override the default yaml parser class. - * - * Provide a fully qualified class name here if you would like to provide an - * alternate implementation YAML parser. The class must implement the - * \Drupal\Component\Serialization\SerializationInterface interface. - */ -# $settings['yaml_parser_class'] = NULL; - -/** - * Trusted host configuration. - * - * Drupal core can use the Symfony trusted host mechanism to prevent HTTP Host - * header spoofing. - * - * To enable the trusted host mechanism, you enable your allowable hosts - * in $settings['trusted_host_patterns']. This should be an array of regular - * expression patterns, without delimiters, representing the hosts you would - * like to allow. - * - * For example: - * @code - * $settings['trusted_host_patterns'] = array( - * '^www\.example\.com$', - * ); - * @endcode - * will allow the site to only run from www.example.com. - * - * If you are running multisite, or if you are running your site from - * different domain names (eg, you don't redirect http://www.example.com to - * http://example.com), you should specify all of the host patterns that are - * allowed by your site. - * - * For example: - * @code - * $settings['trusted_host_patterns'] = array( - * '^example\.com$', - * '^.+\.example\.com$', - * '^example\.org$', - * '^.+\.example\.org$', - * ); - * @endcode - * will allow the site to run off of all variants of example.com and - * example.org, with all subdomains included. - */ - /** * The default list of directories that will be ignored by Drupal's file API. * @@ -767,24 +99,15 @@ function_exists('gardens_site_data_get_filepath')) { */ $settings['entity_update_batch_size'] = 50; -/** - * Load local development override configuration, if available. - * - * Use settings.local.php to override variables on secondary (staging, - * development, etc) installations of this site. Typically used to disable - * caching, JavaScript/CSS compression, re-routing of outgoing emails, and - * other things that should not happen on development and testing sites. - * - * Keep this code block at the end of this file to take full effect. - */ -# -# if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) { -# include $app_root . '/' . $site_path . '/settings.local.php'; -# } -$config_directories['sync'] = '../config/sync'; - require DRUPAL_ROOT . "/../vendor/acquia/drupal-recommended-settings/settings/acquia-recommended.settings.php"; +// Set config directories to default location. This is required for MEO, but it should also +// work fine in non-MEO environments. +// Setting it here ensures that nothing can override it. +$config_directories['vcs'] = '../config/default'; +$config_directories['sync'] = '../config/default'; +$settings['config_sync_directory'] = '../config/default'; + /** * IMPORTANT. * diff --git a/docroot/sites/default/settings/default.local.settings.php b/docroot/sites/default/settings/default.local.settings.php index 3c2cf9dedb..31891bbedc 100644 --- a/docroot/sites/default/settings/default.local.settings.php +++ b/docroot/sites/default/settings/default.local.settings.php @@ -8,9 +8,6 @@ use Drupal\Component\Assertion\Handle; $db_name = '${drupal.db.database}'; -if (isset($_acsf_site_name)) { - $db_name .= '_' . $_acsf_site_name; -} /** * Database configuration. @@ -162,10 +159,6 @@ * Private file path. */ $settings['file_private_path'] = $dir . '/files-private'; -if (isset($_acsf_site_name)) { - $settings['file_public_path'] = "sites/default/files/$_acsf_site_name"; - $settings['file_private_path'] = "$repo_root/files-private/$_acsf_site_name"; -} /** * Trusted host configuration. diff --git a/docroot/sites/g/.gitignore b/docroot/sites/g/.gitignore deleted file mode 100644 index a6748ababa..0000000000 --- a/docroot/sites/g/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# We assume a docroot/.gitignore could be added to the Drupal docroot which -# excludes files like sites/*/settings*.php. (This is not standard in D8; it was -# in D7.) The ACSF files must be included in the repository, so we explicitly -# make them visible here. In fact we will list all ACSF files here, just to make -# sure. -!.gitignore -!apc_rebuild.php -!services.yml -!settings.php -!SimpleRest.php -!sites.inc diff --git a/docroot/sites/g/SimpleRest.php b/docroot/sites/g/SimpleRest.php deleted file mode 100644 index 85ee82e2aa..0000000000 --- a/docroot/sites/g/SimpleRest.php +++ /dev/null @@ -1,223 +0,0 @@ -name = $name; - $this->password = $password; - $this->url = $url; - } - -} - -/** - * Class SimpleRestMessage. - * - * A simple class used to send REST requests to the Site Factory. - */ -class SimpleRestMessage { - - /** - * Maximum amount of retries before giving up sending a message. - */ - private int $retryMax = 3; - - /** - * Number of seconds to wait before trying again after sending failed. - */ - private int $retryWait = 5; - - /** - * The hosting sitegroup name. - */ - private string $site; - - /** - * The hosting environment name. - */ - private string $env; - - /** - * Creates a new instance of SimpleRestMessage. - * - * @param string $site - * The hosting sitegroup name. - * @param string $env - * The hosting environment name. - */ - public function __construct($site, $env) { - $this->site = $site; - $this->env = $env; - } - - /** - * Sends a request. - * - * @param string $method - * The request method. Either 'POST' or 'GET'. - * @param string $endpoint - * The request endpoint. - * @param array $parameters - * Any required parameters for the request. Note: parameters are currently - * only implemented for POST requests. To add support for GET parameters - * would require changes in this method. - * @param SimpleRestCreds $creds - * The credentials to use for the Site Factory request. - * - * @throws Exception - * If the request fails. - * - * @return \SimpleRestResponse - * The response. - */ - public function send($method, $endpoint, array $parameters, SimpleRestCreds $creds) { - $error = ''; - $user_agent = sprintf('%s.%s %s', $this->site, $this->env, gethostname()); - $curl = curl_init(); - - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_USERAGENT, $user_agent); - curl_setopt($curl, CURLOPT_HEADER, 0); - curl_setopt($curl, CURLOPT_USERPWD, $creds->name . ":" . $creds->password); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); - - // If it is not a GET request, set the method here. - if ($method != 'GET') { - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); - } - - // If we are sending parameters, set the query string or POST fields here. - $query_string = ''; - if ($method != 'GET' && !empty($parameters)) { - $data_string = json_encode($parameters, JSON_THROW_ON_ERROR); - curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string); - curl_setopt($curl, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json', - 'Content-Length: ' . strlen($data_string), - ]); - } - - $full_url = sprintf('%s/%s%s', $creds->url, $endpoint, $query_string); - curl_setopt($curl, CURLOPT_URL, $full_url); - - $attempts = 0; - $response = FALSE; - - while (!$response && ++$attempts <= $this->retryMax) { - $response = curl_exec($curl); - if (!$response) { - $error = curl_error($curl); - sleep($this->retryWait); - } - } - - if (!$response) { - throw new Exception(sprintf('Error reaching url "%s" with method "%s." Returned error "%s."', $full_url, $method, $error)); - } - - $response_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - $response_body = json_decode($response, TRUE, 512, JSON_THROW_ON_ERROR); - - if (!is_array($response_body)) { - $response_body = []; - } - - curl_close($curl); - - return new SimpleRestResponse($endpoint, $response_code, $response_body); - } - -} - -/** - * Class SimpleRestResponse. - * - * Holds the response. - */ -class SimpleRestResponse { - /** - * The request endpoint. - * - * @var string - */ - public $endpoint; - - /** - * The response code. - * - * @var string - */ - public $code; - - /** - * The response body. - * - * @var array - */ - public $body; - - /** - * Constructs a new instance of SimpleRestResponse. - * - * @param string $endpoint - * The request endpoint. - * @param string $response_code - * The response code. - * @param array $response_body - * The response body. - */ - public function __construct($endpoint, $response_code, array $response_body) { - $this->endpoint = $endpoint; - $this->code = $response_code; - $this->body = $response_body; - } - -} diff --git a/docroot/sites/g/apc_rebuild.php b/docroot/sites/g/apc_rebuild.php deleted file mode 100644 index 9a0c8ba3e7..0000000000 --- a/docroot/sites/g/apc_rebuild.php +++ /dev/null @@ -1,41 +0,0 @@ -404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'; - -// phpcs:enable - -/** - * Acquia Cloud Site Factory specific settings. - */ -if (file_exists('/var/www/site-php')) { - // This global variable is set during the 'configuration' (sites.php) - // bootstrap phase. Notes: - // - The 'env' value contains the 'canonical environment' which (unlike - // $_ENV['AH_SITE_ENVIRONMENT']) stays the same during code deployments - // where sites are moved between two environments. - // - post-settings-php hooks should use $GLOBALS['gardens_site_settings'] - // rather than the below 'local' variables which are in principle not - // guaranteed to stay defined. (We still define $env / $role for some - // existing customers' post-settings-php hooks that already use them.) - $site_settings = !empty($GLOBALS['gardens_site_settings']) - ? $GLOBALS['gardens_site_settings'] - : ['site' => '', 'env' => '', 'conf' => ['acsf_db_name' => '']]; - $env = $site_settings['env']; - $role = $site_settings['conf']['acsf_db_name']; - - $drupal_version = 8; - if (version_compare(\Drupal::VERSION, '9', '>=') && version_compare(\Drupal::VERSION, '10', '<')) { - $drupal_version = 9; - } - elseif (version_compare(\Drupal::VERSION, '10', '>=') && version_compare(\Drupal::VERSION, '11', '<')) { - $drupal_version = 10; - } - elseif (version_compare(\Drupal::VERSION, '11', '>=')) { - $drupal_version = 11; - } - $_acsf_include_file = "/var/www/site-php/{$site_settings['site']}.{$site_settings['env']}/D{$drupal_version}-{$site_settings['env']}-{$site_settings['conf']['acsf_db_name']}-settings.inc"; - if (file_exists($_acsf_include_file)) { - // Acquia rules disallow 'include/require' with dynamic arguments. - // phpcs:disable - include $_acsf_include_file; - // phpcs:enable - // Overwrite trusted_host_patterns setting, remove unnecessary hosts. - // Allowed hosts for D8: https://www.drupal.org/node/2410395. - // The overwrite doesn't cause any security problem because the valid hosts - // were checked before in our sites.json registry. - $settings['trusted_host_patterns'] = [ - '^' . str_replace('*', '.+', - str_replace('.', '\.', $_SERVER['HTTP_HOST']) - ) . '$', - ]; - } - elseif (PHP_SAPI === 'cli') { - throw new Exception("No database connection file was found for DB {$site_settings['conf']['acsf_db_name']}."); - } - else { - syslog(LOG_ERR, "GardensError: AN-22471 - No database connection file was found for DB {$site_settings['conf']['acsf_db_name']}."); - header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service unavailable'); - print 'The website encountered an unexpected error. Please try again later.'; - exit; - } - // todo: this part needs to be rewritten, we might consider removing it - // entirely for the time being. - if (!class_exists('DrupalFakeCache')) { - $config['cache_backends'][] = 'includes/cache-install.inc'; - } - // Rely on the external Varnish cache for page caching. - $config['cache_class_cache_page'] = 'DrupalFakeCache'; - $config['cache'] = 1; - $config['page_cache_maximum_age'] = 300; - // We can't use an external cache if we are trying to invoke these hooks. - $config['page_cache_invoke_hooks'] = FALSE; - - // This section has been ported from D7 to D8 by mistake; the 'memcache_*' - // settings are not supposed to be set and the $config changes aren't generic. - // todo: reevaluate and possibly remove this. - if (!empty($site_settings['flags']['memcache_enabled']) && !empty($site_settings['memcache_inc'])) { - $config['cache_backends'][] = $site_settings['memcache_inc']; - $config['cache_default_class'] = 'MemCacheDrupal'; - $config['cache_class_cache_form'] = 'DrupalDatabaseCache'; - // The oembed cache in many cases should not evict data (given that data - // is obtained from costly API calls and is not expected to change when - // refreshed), so is more suited to the database than to memcache. - $config['cache_class_cache_oembed'] = 'DrupalDatabaseCache'; - } - - // Until the site installation finishes, noone should be able to visit the - // site, unless the site is being installed via install.php and the user has - // the correct token to access it. - if (PHP_SAPI !== 'cli' && !empty($site_settings['flags']['access_restricted']['enabled'])) { - $_tmp = !empty($site_settings['flags']['access_restricted']['token']) && !empty($_GET['site_install_token']) && $_GET['site_install_token'] === $site_settings['flags']['access_restricted']['token']; - if (!$_tmp || $_SERVER['SCRIPT_NAME'] !== $GLOBALS['base_path'] . 'install.php') { - header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service unavailable'); - if (!empty($site_settings['flags']['access_restricted']['reason'])) { - print $site_settings['flags']['access_restricted']['reason']; - } - exit; - } - } - if (isset($_ENV['AH_SITE_ENVIRONMENT'])) { - // DG-10819: Enable Migrate background operations by default on all Acquia - // hosting environments. See https://drupal.org/node/1958170. The path here - // should be valid on all Acquia hosting servers, and will not take effect - // on non-Acquia environments since AH_SITE_ENVIRONMENT won't be set in that - // case. - $config['migrate_drush_path'] = '/usr/local/bin/drush'; - } - - // Do not override the private path if the customer has defined its value - // in a pre-settings-php hook. - if (empty($settings['file_private_path']) && !empty($site_settings['file_private_path'])) { - $settings['file_private_path'] = $site_settings['file_private_path']; - } - - // Do not override the public path if the customer has defined its value - // in a pre-settings-php hook. - if (empty($settings['file_public_path']) && !empty($site_settings['file_public_path'])) { - $settings['file_public_path'] = $site_settings['file_public_path']; - } - - // Propagate all configuration values in ACSF per-site storage into Drupal's - // configuration. - if (!empty($site_settings['conf']) && is_array($site_settings['conf'])) { - $config = $site_settings['conf'] + $config; - } -} - -/** - * Location of the site configuration files. - * - * The $config_directories array specifies the location of file system - * directories used for configuration data. On install, "active" and "sync" - * directories are created for configuration. The sync directory is used for - * configuration imports; the active directory is not used by default, since the - * default storage for active configuration is the database rather than the file - * system (this can be changed; see "Active configuration settings" below). - * - * The default location for the active and sync directories is inside a - * randomly-named directory in the public files path; this setting allows you to - * override these locations. If you use files for the active configuration, you - * can enhance security by putting the active configuration outside your - * document root. - * - * Example: - * @code - * $config_directories = array( - * CONFIG_SYNC_DIRECTORY => '/another/directory/outside/webroot', - * ); - * @endcode - */ -if (isset($config_directories['vcs'])) { - // The hosting settings include file adds a VCS config directory, but this can - // only work with livedeev enabled. Livedev is not supported on ACSF, and the - // addition of this directory breaks site installs, so the VCS config - // directory is removed for now. - // @see https://backlog.acquia.com/browse/CL-11815 - // @see https://github.com/drush-ops/drush/pull/1711 - if (class_exists('\Drush\Drush') && Drush::hasContainer()) { - try { - $_tmp = Drush::input()->getArgument('command'); - } - catch (InvalidArgumentException $e) { - $_tmp = FALSE; - } - - if ($_tmp === 'site-install') { - unset($config_directories['vcs']); - } - } -} - -// Include custom settings.php code from factory-hooks/post-settings-php. -if (function_exists('acsf_hooks_includes')) { - foreach (acsf_hooks_includes('post-settings-php') as $_acsf_include_file) { - // Acquia rules disallow 'include/require' with dynamic arguments. - // phpcs:disable - include $_acsf_include_file; - // phpcs:enable - } -} diff --git a/docroot/sites/g/sites.inc b/docroot/sites/g/sites.inc deleted file mode 100644 index 2c1f6b4c64..0000000000 --- a/docroot/sites/g/sites.inc +++ /dev/null @@ -1,868 +0,0 @@ - -// acsf.settings.php -> "Site not found" page), but that's better than having -// sites be unreachable for too long. -define('GARDENS_SITE_DATA_READ_FAILURE_TTL', 60); -// Note: a nonexistent sites.json leads to no caching at all. -// The path (template) to the lock file that should be checked when sites.json -// is unreadable. Sitegroup + env need to be filled. -define('GARDENS_SITE_JSON_ALERT_LOCK_TEMPLATE', '/mnt/tmp/%s.%s/.sites-json-alert'); - -// The PHP configuration is used to switch between the new and the legacy -// site.json usage. The new approach uses the interface provided by the -// Acquia Cloud layer, the legacy approach uses the file deployed to gluster. -define('GARDENS_SITE_JSON_LEGACY', empty(get_cfg_var('acsf.sites_json_on_ephemeral'))); - -// This fallback to populate $_ENV should not be necessary on Acquia Hosting -// anymore. Also, ah_site_info() is not a public API and not guaranteed to -// keep existing. Still, we keep it for exotic environments which may be using -// it to insert the environment values, e.g. local development environments. -if (!isset($_ENV['AH_SITE_NAME']) || !isset($_ENV['AH_SITE_GROUP']) || !isset($_ENV['AH_SITE_ENVIRONMENT'])) { - if (!function_exists('ah_site_info') && file_exists('/var/www/site-scripts/site-info.php')) { - require_once '/var/www/site-scripts/site-info.php'; - } - if (function_exists('ah_site_info')) { - list($name, $group, $stage, $secret) = ah_site_info(); - if (!isset($_ENV['AH_SITE_NAME'])) { - $_ENV['AH_SITE_NAME'] = $name; - } - if (!isset($_ENV['AH_SITE_GROUP'])) { - $_ENV['AH_SITE_GROUP'] = $group; - } - if (!isset($_ENV['AH_SITE_ENVIRONMENT'])) { - $_ENV['AH_SITE_ENVIRONMENT'] = $stage; - } - } -} - -/** - * Returns the sites data structure. - * - * @return bool|mixed - * An array of sites data on success, or FALSE on failure to load or parse the - * file. - */ -function gardens_site_data_load_file() { - if (GARDENS_SITE_JSON_LEGACY) { - // Retrieve sites.json data from a gluster file. - $json = @file_get_contents(gardens_site_data_get_filepath()); - return $json ? json_decode($json, TRUE) : FALSE; - } - else { - // Retrieve sites data using the interface provided by the Acquia Cloud - // layer. - $config = Config::getInstance(); - return empty($config) ? FALSE : $config->all(); - } -} - -/** - * Checks for a registered ACSF site based on Apache server variables. - * - * Prerequisite: $_SERVER and $_ENV are both populated as per Acquia practices. - * - * @return array|int|null - * 0 if no site was found for the given domain; NULL if a sites.json read - * failure was encountered; otherwise, an array of site data as constructed - * by gardens_site_data_build_data() - with an added key 'dir_key' containing - * the key where sites.php would expect to set this site's directory in its - * '$sites' variable. - * - * @see gardens_site_data_build_data() - */ -function gardens_site_data_get_site_from_server_info() { - // First derive the 'site uri' (the base domain with possibly a sub path). - if (PHP_SAPI === 'cli' && class_exists('\Drush\Drush') && Drush::hasContainer()) { - $acsf_drush_boot_manager = Drush::bootstrapManager(); - if ($acsf_drush_boot_manager->getUri()) { - $acsf_uri = $acsf_drush_boot_manager->getUri(); - } - // Drush site-install gets confused about the uri when we specify the - // --sites-subdir option. By specifying the --acsf-install-uri option with - // the value of the standard domain, we can catch that here and correct the - // uri argument for drush site installs. - $acsf_drush_input = Drush::input(); - try { - $acsf_install_uri = $acsf_drush_input->getOption('acsf-install-uri'); - if ($acsf_install_uri) { - $acsf_uri = $acsf_install_uri; - } - } - catch (InvalidArgumentException $e) { - } - if (!preg_match('|https?://|', $acsf_uri)) { - $acsf_uri = 'http://' . $acsf_uri; - } - $host = $_SERVER['HTTP_HOST'] = parse_url($acsf_uri, PHP_URL_HOST); - $path = parse_url($acsf_uri, PHP_URL_PATH); - } - else { - $host = rtrim($_SERVER['HTTP_HOST'], '.'); - $path = $_SERVER['SCRIPT_NAME'] ? $_SERVER['SCRIPT_NAME'] : $_SERVER['SCRIPT_FILENAME']; - // Path based domains are implemented by symlinking a subdirectory back to - // the docroot. To support these, we need to know which part of the script - // path is the domain sub path, and which is a URL path inside the website. - // (Note our only concern in 'supporting path based domains' is locating - // the right site. How the request would derive the right URL inside that - // site is not up to us, and the only thing that is actually known to work - // with path based domains -through HTTP requests or Drush- is the standard - // index.php.) - if (substr($path, -10) === '/index.php') { - // Assume the requested index.php is in the root, meaning that the full - // script path leading up to it is the domain sub path. In other words: - // we do not support index.php being anywhere else except the docroot. - // This goes for every customer, not just those using path based domains. - $path = substr($path, 0, strlen($path) - 10); - } - else { - // For any non-index.php path, assume the sub path is empty. In other - // words: simply do not support path based domains for these. - $path = ''; - } - } - - // Convert host/path to lower case because our registry stores data this way; - // upper cased paths may still not be recognized (by Drush commands, unless - // there is a symlink matching the specific case) though. - $host = strtolower($host); - - if (empty($path)) { - $path = ''; - } - - // The path may have a trailing slash (because PHP_URL_PATH can contain one, - // and because a script may have set SCRIPT_NAME to '//index.php'). Unify, so - // the result is either empty or a path with a leading slash. - $path = strtolower(rtrim($path, '/')); - - // Get data for the 'site uri' from APC. - if (GARDENS_SITE_DATA_USE_APC && GARDENS_SITE_JSON_LEGACY) { - // Check for data in APC: FALSE means no; 0 means "not found" cached in - // APC; NULL means "sites.json read failure" cached in APC. - $data = gardens_site_data_cache_get($host . $path); - if ($data === FALSE) { - // There's no guarantee about how many times this will be called, because - // Core doesn't do any caching around the 'conf path'. For instance Drush - // 7 would will call this file and logic around 19 times during each - // command; Drush 8 only once and Drush 9.6 increases this to around 4. To - // minimise gluster access, cache data to a local static cache. We assume - // that *if* we have data for a given domain at any point during a request - // on the command line then that data remains the same for the duration of - // the command execution. - // For some drush commands (that have bootstrap levels lower than - // 'configuration', only when run with drush6/drush7), we don't have - // drupal_static() available. We'll skip caching for those few commands, - // so sites.json will be read always (which used to be the case for all - // commands before we implemented this cache). - if (function_exists('drupal_static')) { - $static_cache = &drupal_static('acsf_sites_php_site_data'); - } - else { - $static_cache = []; - } - if (isset($static_cache[$host . $path])) { - $data = $static_cache[$host . $path]; - } - else { - $data = gardens_site_data_refresh_one($host . $path); - if ($data) { - // We only cache truthy data ourselves, because we don't want to - // assume that if a domain is not found (or there's a gluster error), - // that will stay the same during command execution. Note we also set - // the static cache if APC is active (which isn't necessary) because - // it doesn't really matter and because otherwise, we would have to - // - either replicate the check for APC which is now abstracted away - // into gardens_site_data_cache_get() - // - or move $static_cache into gardens_site_data_cache_get() & - // gardens_site_data_cache_set(), which isn't wrong but we prefer - // not caching every domain in memory when the full file gets read. - $static_cache[$host . $path] = $data; - } - } - } - } - // Get domain data. - else { - $data = gardens_site_data_refresh_one($host . $path); - } - - if (is_array($data)) { - // Generate the expected drupal sites.php key for this domain. - $data['dir_key'] = str_replace('/', '.', $host . $path); - } - - return $data; -} - -/** - * Returns the location of the sites data json file. - * - * We rely on the existence of the files-private directory that's created in - * /mnt/files next to the public files directory. - * - * @return string - * The json file location. - */ -function gardens_site_data_get_filepath() { - // Use the "real" path here rather than the canonical hosting path - // to minimize symlink traversal. - return "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/files-private/sites.json"; -} - -/** - * Returns the location of the private files directory. - * - * The private files directory is supposed to be kept outside of the docroot to - * make sure that its contents are not directly accessible. This directory - * should not have a symbolic link in the site's directory. - * - * @param string $db_role - * The site's db role. - * - * @return string - * The private files directory location. - */ -function gardens_site_data_get_private_files_directory($db_role) { - return "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/sites/g/files-private/{$db_role}"; -} - -/** - * Returns the location of the public files directory. - * - * @param string $db_role - * The site's db role. - * - * @return string - * The public files directory location. - */ -function gardens_site_data_get_public_files_directory($db_role) { - return "sites/g/files/{$db_role}/files"; -} - -/** - * Fully refreshes the APC cached site/domain data, rewriting every key. - */ -function gardens_site_data_refresh_all() { - if ($map = gardens_site_data_load_file()) { - foreach ($map['sites'] as $domain => $site) { - $data = gardens_site_data_build_data($site, $map); - gardens_site_data_cache_set($domain, $data); - } - } -} - -/** - * Returns the data structure for a single site. - * - * @param array $site - * An array of information about a specific site, containing keys including - * 'conf', 'flags', 'name' etc. - * @param array $map - * An array containing global information that applies to all sites (site, - * env, memcache_inc). - * - * @return array - * A data structure containing information about a single site. - */ -function gardens_site_data_build_data(array $site, array $map) { - $db_name = $site['conf']['acsf_db_name']; - $private_files_directory = gardens_site_data_get_private_files_directory($db_name); - $public_files_directory = gardens_site_data_get_public_files_directory($db_name); - - return [ - 'dir' => "g/files/{$site['name']}", - // Put some settings into a global used in settings.php. - 'gardens_site_settings' => [ - 'site' => $map['cloud']['site'], - 'env' => $map['cloud']['env'], - 'memcache_inc' => !empty($map["memcache_inc"]) ? $map["memcache_inc"] : '', - 'flags' => !empty($site['flags']) ? $site['flags'] : [], - 'conf' => !empty($site['conf']) ? $site['conf'] : [], - 'file_private_path' => file_exists($private_files_directory) ? $private_files_directory : NULL, - 'file_public_path' => file_exists($public_files_directory) ? $public_files_directory : NULL, - ], - ]; -} - -/** - * Parses the entire JSON sites file and returns a result for a single domain. - * - * Use gardens_site_data_refresh_one() for a faster near-equivalent. - * - * @param string $domain - * A domain name to search for in the JSON. - * - * @return array - * A gardens site data structure, or zero if the domain was not found. - */ -function gardens_site_data_get_site_from_file($domain) { - $result = 0; - // This function does not seem to be used. Issues with the sites.json in here - // is not handled. - if ($map = gardens_site_data_load_file()) { - if (!empty($map['sites'][$domain])) { - $result = gardens_site_data_build_data($map['sites'][$domain], $map); - } - } - return $result; -} - -/** - * Returns data for a single domain. - * - * Optionally also stores the data in APC. - * - * @param string $domain - * The domain name to look up. - * - * @return array|int|null - * An array of site data, 0 if no site was found for the given domain, or NULL - * if a sites.json read failure was encountered. - */ -function gardens_site_data_refresh_one($domain) { - if (GARDENS_SITE_JSON_LEGACY) { - // Using the legacy sites.json file from the gluster, and optionally also - // stores the data in APC. - $data = gardens_site_data_refresh_domains([$domain]); - } - else { - // Using interface provided by the Acquia Cloud layer, no APC cache handling - // in this case. - $data = gardens_site_data_from_multi_site_config([$domain]); - } - return isset($data[$domain]) ? $data[$domain] : NULL; -} - -/** - * Returns data for the specified domains directly from the JSON file. - * - * Optionally also stores the data in APC. - * - * @param array $domains - * The domain names to look up in the JSON file. - * - * @return array - * An array keyed by the specified domains, whose values are site data arrays - * or 0 if no site was found for the given domain. If a domain is not present - * in the array keys, this indicates a sites.json read failure. - */ -function gardens_site_data_refresh_domains(array $domains) { - $location = gardens_site_data_get_filepath(); - $data = []; - foreach ($domains as $domain) { - $domain = trim($domain); - // Below code expects the JSON file to contain newlines such that - // - all data except the 'sites' data and the closing brace are on the first - // line; - // - all data for a key/value pair representing one single site, on a single - // line. (See below for example.) - // This way we can isolate data for one site by performing a grep command, - // which is much quicker than reading all data into one JSON object. The - // code is built to keep working if the formatting changes by accident; it - // will just be much slower. Also, our grep command does not assume that the - // key for a site is included in double quotes (apparently for fear of - // having a file in illegal JSON format, which does not double-quote its - // object keys...) so we may hit false positives. - // Acquia rules disallow exec() with dynamic arguments. - // phpcs:disable - exec(sprintf("grep %s %s --no-filename --color=never --context=0", escapeshellarg($domain), escapeshellarg($location)), $output_array, $exit_code); - // phpcs:enable - $result = trim(implode("\n", $output_array)); - - if (empty($result)) { - // Log an explicit fail in APC if we cannot find the domain, so that we - // can take advantage of APC caching the "fail" also. Differentiate - // between values for "site not found" and "read failure" so future - // requests can emit different responses for them. (From the docs about - // Gnu grep: exit status is 0 if a line is selected -which should never - // happen here-, 1 if no line is selected, 2 if error encountered.) - if ($exit_code === 1) { - $data[$domain] = 0; - } - } - else { - // $result is in the form of - // "example.com": {"name": "g123", "flags": {}}, - // (with or without the trailing comma). Since we didn't include quotes, - // we may have more than 1 line returned from the grep command, typically - // if the searched-for site domain is a substring of another site domain. - // The "m" (multiline) modifier is used in the regular expression so that - // the begin and end anchors can match the beginning and end of any one of - // those lines, rather than having to match the entire string from - // beginning to end (which fails if there is more than 1 line of results). - $matches = []; - $pattern = '@^\s*"' . preg_quote($domain, '@') . '": ({.+}),?$@m'; - if (preg_match($pattern, $result, $matches)) { - $found_site = json_decode($matches[1], TRUE); - } - - // Retrieve the first line of the JSON file, which contains the global - // site settings data. - $f = fopen($location, 'r'); - $json = fgets($f); - fclose($f); - $json = rtrim($json, ",\n"); - $json .= "}"; - $global_map_data = json_decode($json, TRUE); - - if (empty($found_site) || empty($global_map_data)) { - // This will happen if the domain appears in the JSON file, but the - // format of the file has changed such that the grep-based single-line - // parsing no longer works. - if (class_exists('Drupal') && \Drupal::hasService('logger.factory')) { - \Drupal::logger('acsf')->alert('Unable to extract site data for site @site from sites.json line "@line".', ['@site' => $domain, '@line' => $result]); - } - elseif (function_exists('syslog')) { - syslog(LOG_ERR, sprintf('Unable to extract site data for site %s from sites.json line "%s".', $domain, $result)); - } - if ($map = gardens_site_data_load_file()) { - if (!empty($map['sites'][$domain])) { - $data[$domain] = gardens_site_data_build_data($map['sites'][$domain], $map); - } - else { - // The domain isn't actually present; apparently $domain is a - // substring of the domain(s) matched by grep. (Or, who knows: the - // string might appear somewhere else on the line than the 'key'.) - $data[$domain] = 0; - } - } - // If $data[$domain] was not set here, the file is readable (or there's - // a race condition and the error just appeared) because we did get a - // line of data returned earlier. So the JSON is invalid. - } - else { - $data[$domain] = gardens_site_data_build_data($found_site, $global_map_data); - } - } - - if (isset($data[$domain])) { - // Update the current record in place *if* we are using APC. - if (GARDENS_SITE_DATA_USE_APC) { - gardens_site_data_cache_set($domain, $data[$domain]); - } - } - else { - // Report the read failure, only if Drupal is bootstrapped. - if (function_exists('drupal_register_shutdown_function')) { - // Since reporting involves contacting the Site Factory it should be - // done in a way that does not affect pageload. - drupal_register_shutdown_function('gardens_site_data_json_alert_flag_set'); - } - // Stop processing further domains. - break; - } - } - - if (count($data) == count($domains)) { - // No read failure encountered; all domains were accounted for / cached. - if (gardens_site_data_json_alert_flag_check() && function_exists('drupal_register_shutdown_function')) { - // Clear the flag. Since it involves contacting the Site Factory it should - // be done in a way that does not affect pageload. - drupal_register_shutdown_function('gardens_site_data_json_alert_flag_clear'); - } - } - else { - // If we were checking several domains and any check reported a read failure - // then don't try reading the file again for other domains; cache the failed - // domain plus any that were not processed yet, as "read failure". (It's - // unlikely that we gathered data for some domains before encountering a - // read failure for another one, but account for it.) - if (GARDENS_SITE_DATA_USE_APC) { - foreach ($domains as $domain) { - if (!isset($data[$domain])) { - gardens_site_data_cache_set($domain, NULL); - } - } - } - } - - return $data; -} - -/** - * Returns data for the specified domains using hosting's php interface. - * - * @param array $domains - * The domain names to look up in the domain registry. - * - * @return array - * An array keyed by the specified domains, whose values are site data arrays - * or 0 if no site was found for the given domain. - */ -function gardens_site_data_from_multi_site_config(array $domains) { - $data = []; - $config = Config::getInstance(); - foreach ($domains as $domain) { - $domain = trim($domain); - if ($config) { - // Site config returns an associative array representation of one - // site object, selected by domain. - $found_site = $config->siteConfig($domain); - // Shared config returns an associative array with every key except sites. - $global_map_data = $config->sharedConfig(); - } - // In case of missing data, set the results to zero and report the failure. - if (empty($found_site) || empty($global_map_data)) { - $data[$domain] = 0; - if (class_exists('Drupal') && \Drupal::hasService('logger.factory')) { - \Drupal::logger('acsf')->alert('Unable to extract site data for site @site.', ['@site' => $domain]); - } - elseif (function_exists('syslog')) { - syslog(LOG_ERR, sprintf('Unable to extract data for site %s.', $domain)); - } - } - else { - $data[$domain] = gardens_site_data_build_data($found_site, $global_map_data); - } - } - return $data; -} - -/** - * Stores site info for a given domain in APC. - * - * @param string $domain - * The domain name used in the cache key to store. - * @param mixed $data - * An array of data about the site/domain containing keys 'dir' and - * 'gardens_site_settings'. If the domain was not found in the sites.json then - * a scalar 0; if sites.json could not be read, then NULL. - */ -function gardens_site_data_cache_set($domain, $data) { - if (extension_loaded('apcu') && ini_get('apc.enabled') && function_exists('apcu_store')) { - if ($data === NULL) { - $ttl = GARDENS_SITE_DATA_READ_FAILURE_TTL; - } - elseif ($data === 0) { - $ttl = GARDENS_SITE_DATA_NOT_FOUND_TTL; - } - else { - $ttl = GARDENS_SITE_DATA_TTL; - } - if ($ttl) { - $domain_key = "gardens_domain:$domain"; - apcu_store($domain_key, $data, $ttl); - } - } -} - -/** - * Retrieves cached site info from APC for a given domain. - * - * @param string $domain - * The domain associated with the cached data. - * - * @return mixed - * An object containing information about the site on success, or FALSE if no - * cached data was found for the domain. - */ -function gardens_site_data_cache_get($domain) { - $result = FALSE; - if (extension_loaded('apcu') && ini_get('apc.enabled') && function_exists('apcu_fetch')) { - $domain_key = "gardens_domain:$domain"; - $result = apcu_fetch($domain_key); - } - return $result; -} - -/** - * Re-checks for a fatal issue with the sites.json file. - * - * This function is not the only location where issues are determined; it's used - * to doublecheck the exact type of issue / doublecheck for a race condition, - * after an issue was initially detected outside this function. - * - * @return string - * Type of issue encountered. Empty string means the sites.json file is OK - * (or is missing, which is also OK). - */ -function gardens_site_data_sites_json_issue_type_get() { - $issue_type = ''; - $sites_json_path = gardens_site_data_get_filepath(); - // If sites.json is missing completely then this script is being executed - // outside of an ACSF infrastructure in which case no alert is needed. - if (file_exists($sites_json_path)) { - // Check if sites.json is readable. - if (!is_readable($sites_json_path)) { - $issue_type = 'file_unreadable'; - } - // Check if the file's contents are inaccessible. - if (!$issue_type) { - // Try to read sites.json and see if it succeeds and what kind of error - // we get back if it fails. There is a fail which we need to ignore: when - // the sites.json is being rewritten for a short period of time the - // following error will be returned: - // - // head: cannot open `/mnt/files/balazs.01live/files-private/sites.json' - // for reading: Structure needs cleaning - // - // To make sure we are only triggering an alert in case of a Gluster split - // brain, redirect the stderr to stdout and look for the indicator - // message: 'Input/output error'. - // - // Acquia rules disallow exec() with dynamic arguments. - // phpcs:disable - exec(sprintf('head -n1 %s 2>&1', escapeshellarg($sites_json_path)), $output_array, $exit_code); - // phpcs:enable - $output = implode('', $output_array); - if ($exit_code !== 0 && strpos($output, 'Input/output error') !== FALSE) { - $issue_type = 'gluster_split_brain'; - } - } - // Check for invalid JSON data. We treat empty file as invalid too here, - // because it doesn't matter much. It's not consistent with the initial - // check, though. (An empty file does not cause - // gardens_site_data_json_alert_flag_set() to be called.) - if (!$issue_type) { - $map = gardens_site_data_load_file(); - if (!$map) { - $issue_type = 'invalid_json_data'; - } - } - } - - return $issue_type; -} - -/** - * Tries to set a flag, marking that an issue with sites.json exists. - * - * This function is not supposed to be used for checking that there is an issue - * with sites.json; it should only be called if an issue exists. - * - * As we do not have a DB connection, and we assume gluster is the primary - * suspect for issues, the lock will live on the ephemeral disk. If something - * strange happens while setting the flag (like the file cannot be opened or - * written to), the function will always return empty string, and no logging/ - * alerting is done at all. We basically have a choice between this and flooding - * watchdog/syslog/the factory with alerts. - */ -function gardens_site_data_json_alert_flag_set() { - $lock_file = sprintf(GARDENS_SITE_JSON_ALERT_LOCK_TEMPLATE, $_ENV['AH_SITE_GROUP'], $_ENV['AH_SITE_ENVIRONMENT']); - - if (!file_exists($lock_file)) { - // Create/open file, do not generate an error in race conditions (two - // processes opening the file at the same time). - $fh = fopen($lock_file, 'c'); - if ($fh) { - // Get (exclusive, non-blocking) lock. Note we assume we can actually rely - // on flock; see multithreading notes in the php.net docs. - if (flock($fh, LOCK_EX | LOCK_NB)) { - // Something more evasive than the 'fopen()' race condition: what - // happens just around the time a sites.json issue stops existing? Could - // one slow process that still thinks there is an issue, be delayed and - // execute this code just _after_ another process removed the flag? That - // would result in a superfluous alert being sent out at that time. To - // prevent this, we repeat the check. (We often would need to do this - // check anyway, somewhere, if we did not know the issue type yet.) - $issue_type = gardens_site_data_sites_json_issue_type_get(); - - // Send the alert to the Site Factory. - $alert_sent = FALSE; - if ($issue_type) { - $response = gardens_site_data_alert_send('sites_json', $issue_type); - if ($response->code == 200 && !empty($response->body['received'])) { - $alert_sent = TRUE; - } - } - - // Remove the lock file if issue has gone away or the alert was not - // processed by the Site Factory. - if (!$alert_sent) { - // Remove the lock file (name; the file/handle itself is still - // locked/open, which is fine). - unlink($lock_file); - } - - // Release the lock. - flock($fh, LOCK_UN); - } - fclose($fh); - } - } -} - -/** - * Checks if a 'sites.json alert' flag exists. - * - * @return bool - * TRUE on if the flag exists. - */ -function gardens_site_data_json_alert_flag_check() { - $lock_file = sprintf(GARDENS_SITE_JSON_ALERT_LOCK_TEMPLATE, $_ENV['AH_SITE_GROUP'], $_ENV['AH_SITE_ENVIRONMENT']); - return file_exists($lock_file); -} - -/** - * Clears a 'sites.json alert' flag. - */ -function gardens_site_data_json_alert_flag_clear() { - $lock_file = sprintf(GARDENS_SITE_JSON_ALERT_LOCK_TEMPLATE, $_ENV['AH_SITE_GROUP'], $_ENV['AH_SITE_ENVIRONMENT']); - - if (file_exists($lock_file)) { - // To prevent a situation where a slow process would remove the file just - // after it was created (i.e. the reverse of what is documented in - // gardens_site_data_json_alert_flag_set()), we lock the file and check - // again, and only remove the file if no issues were encountered. This isn't - // exactly symmetric in the sense that we have no domain name, so: if domain - // specific information was just lost somehow, then a slow process has a - // higher chance of clearing the flag when it shouldn't. The effect: two - // alerts would be sent out in sequence (because the flag is set, cleared - // here, and then set again). That is less problematic than the reverse, - // which would send out an alert at the moment the domain specific error was - // just solved. - $fh = @fopen($lock_file, 'r+'); - if ($fh) { - if (flock($fh, LOCK_EX | LOCK_NB)) { - // Make sure that the issue is gone. - $issue_type = gardens_site_data_sites_json_issue_type_get(); - - // Send an all fine message to the Site Factory if the issue is gone. - $alert_sent = FALSE; - if (!$issue_type) { - $response = gardens_site_data_alert_send('sites_json', 'all_fine'); - if ($response->code == 200 && !empty($response->body['received'])) { - $alert_sent = TRUE; - } - } - - // Clear the flag if the all fine message was sent and processed. - if ($alert_sent) { - // There is no problem with unlinking a locked file; the file name - // gets freed up (while the 'orphaned' file itself is still locked). - // Removing a (locked) file like this does not introduce a race - // condition, if all processes try to lock the file in an exclusive - // and non-blocking manner. So unlinking the file should never fail. - // If it does, we could try to log to watchdog/syslog but that would - // completely flood the logs. - @unlink($lock_file); - } - // We could release the lock here but if the file is already unlinked - // that won't do much useful - and if it's not, we may be better off - // keeping it locked. - } - fclose($fh); - } - } -} - -/** - * Returns the shared credentials. - * - * @param string $site - * The hosting sitegroup name. - * @param string $env - * The hosting environment name. - * - * @return Acquia\SimpleRest\SimpleRestCreds - * The credentials. - * - * @throws Exception - * If the credentials cannot be read for any reason. - */ -function gardens_site_data_shared_creds_get($site, $env) { - $ini_file = sprintf('/mnt/files/%s.%s/nobackup/sf_shared_creds.ini', $site, $env); - if (file_exists($ini_file)) { - $data = parse_ini_file($ini_file, TRUE); - if (!empty($data) && !empty($data['gardener'])) { - return new SimpleRestCreds($data['gardener']['username'], - $data['gardener']['password'], - $data['gardener']['url']); - } - } - throw new Exception(sprintf('Unable to read credentials from %s.', $ini_file)); -} - -/** - * Alerts the Site Factory on possible sites.json issues. - * - * @param string $scope - * The scope type. (Currently only 'sites_json' scope type is accepted by the - * Site Factory.) - * @param string $issue_type - * The issue type. - * - * @return Acquia\SimpleRest\SimpleRestResponse - * The response. - */ -function gardens_site_data_alert_send($scope, $issue_type) { - // The SF REST API endpoint. - $endpoint = 'site-api/v1/sf-alert'; - // The hosting site group name. - $site = $_ENV['AH_SITE_GROUP']; - // The hosting environment name. - $env = $_ENV['AH_SITE_ENVIRONMENT']; - // The fully qualified webnode name. - $webnode = gethostname(); - - try { - $parameters = [ - 'scope' => $scope, - 'data' => [ - 'issue_type' => $issue_type, - 'site_group' => $site, - 'site_env' => $env, - 'server' => $webnode, - // Instead of \Drupal::time()->getRequestTime() we're going to use - // time() here because there is no definite usage of \Drupal at this - // point in the bootstrap. - // Rest of the code above also does a class_exists() check against - // 'Drupal', probably due to the same reason. - 'timestamp' => time(), - ], - ]; - $creds = gardens_site_data_shared_creds_get($site, $env); - $message = new SimpleRestMessage($site, $env); - $response = $message->send('POST', $endpoint, $parameters, $creds); - } - catch (Exception $e) { - $error_message = sprintf('Sending alert to Site Factory failed: %s', $e->getMessage()); - syslog(LOG_ERR, $error_message); - $response = new SimpleRestResponse($endpoint, 500, ['message' => $error_message]); - } - - return $response; -} diff --git a/docroot/sites/settings/cgov_caching.settings.php b/docroot/sites/settings/cgov_caching.settings.php index 140db5f7fd..5b8033a447 100644 --- a/docroot/sites/settings/cgov_caching.settings.php +++ b/docroot/sites/settings/cgov_caching.settings.php @@ -2,7 +2,7 @@ /** * @file - * Implementation of cache settings for ACE + ACSF. + * Implementation of cache settings for ACE + MEO. * * @see https://docs.acquia.com/site-factory/tiers/paas/workflow/hooks */ @@ -23,24 +23,14 @@ $config['system.performance']['js']['preprocess'] = TRUE; $config['system.performance']['js']['gzip'] = TRUE; - switch ($env) { - case 'dev': - case '01dev': - case 'int': - case '01test': - case 'test': - case '01live': - case 'ode': - // Cache settings. - $config['system.performance']['cache']['page']['max_age'] = 16588800; - break; - } + // All Acquia environments should have the same max cache age of 192 days. + $config['system.performance']['cache']['page']['max_age'] = 16588800; // ODEs are not behind Akamai, but everything else conceivably could be. if ($env != 'ode') { - // Setup proper .edgerc path for Akamai module + // Setup proper .edgerc path for Akamai module. $ah_group = isset($_ENV['AH_SITE_GROUP']) ? $_ENV['AH_SITE_GROUP'] : NULL; - $config['akamai.settings']['edgerc_path'] = "/mnt/gfs/home/$ah_group/common/.edgerc"; + $config['akamai.settings']['edgerc_path'] = "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/cgdp/edgerc"; $config['akamai.settings']['basepath'] = 'https://' . $domain; } } diff --git a/docroot/sites/settings/cgov_core.settings.php b/docroot/sites/settings/cgov_core.settings.php index a7955475ac..11f172bdd1 100644 --- a/docroot/sites/settings/cgov_core.settings.php +++ b/docroot/sites/settings/cgov_core.settings.php @@ -13,15 +13,15 @@ $domain = CGovSettingsUtil::getDomain(); /* -* Set the translation path to allow for easy management of third-party -* translation files. The installer ignores the path for the initial install, -* but it will honor the use source. (It is here as configs will not be set -* before the installation starts.) -* -* There is a known issue where the installer will not honor the override for the -* path because it is hardcoded in the installer code. -* https://www.drupal.org/project/drupal/issues/2689305 -*/ + * Set the translation path to allow for easy management of third-party + * translation files. The installer ignores the path for the initial install, + * but it will honor the use source. (It is here as configs will not be set + * before the installation starts.) + * + * There is a known issue where the installer will not honor the override for the + * path because it is hardcoded in the installer code. + * https://www.drupal.org/project/drupal/issues/2689305 + */ $config['locale.settings']['translation']['use_source'] = 'local'; $config['locale.settings']['translation']['path'] = DRUPAL_ROOT . '/translations'; @@ -30,29 +30,23 @@ $config['simple_sitemap.settings']['base_url'] = 'https://' . $domain; // Settings for metatag attribute cgdp.domain. -if(CGovSettingsUtil::isACSF()) { - // For ACSF env. - $acsf_domains = array_keys(gardens_data_get_sites_from_file($GLOBALS['gardens_site_settings'])); - foreach ($acsf_domains as $domain_value) { - if(preg_match('/acsitefactory.com/', $domain_value)){ - $temp = explode('.', $domain_value); - if($temp[1] == $_ENV['AH_SITE_GROUP']){ - $domain_env = $temp[0]; - }else { - $acsf_env = str_replace('-' . $_ENV['AH_SITE_GROUP'], '', $temp[1]); - $domain_env = $temp[0] . '-' . $acsf_env; - } - } +if (CGovSettingsUtil::isMeo()) { + // For MEO environments, use the site name and environment directly. + if (isset($_ENV['AH_DRUPAL_SITE_NAME']) && isset($_ENV['AH_SITE_ENVIRONMENT'])) { + $settings['cgdp_domain'] = $_ENV['AH_DRUPAL_SITE_NAME'] . '-' . $_ENV['AH_SITE_ENVIRONMENT']; + } + else { + error_log("MEO environment variables AH_DRUPAL_SITE_NAME or AH_SITE_ENVIRONMENT not set"); + $settings['cgdp_domain'] = $env; } - $settings['cgdp_domain'] = $domain_env; } else { $settings['cgdp_domain'] = $env; } -// on non-prod environments, route emails to logger -if ($env !== "01live") { - // route e-mails to the logger. +// On non-prod environments, route emails to logger. +if ($env !== "meoprod") { + // Route e-mails to the logger. $config['system.mail']['interface']['default'] = 'cgov_mail_logger'; } @@ -67,19 +61,17 @@ 'user.reset.form', ]; -// Get the license dir from our current environment -$licenseDir = '/var/licenses'; +// Get the license dir from our current environment. +$licenseFilename = '/var/licenses/licenses.php'; if (CGovSettingsUtil::isAcquia()) { - $licenseDir = "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/licenses"; + $licenseFilename = "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/cgdp/licenses.php"; } - -$licenseFilename = sprintf('%s/licenses.php', $licenseDir); if (file_exists($licenseFilename)) { // Load the license keys file. - require($licenseFilename); + require $licenseFilename; - // Setup CKEditor LTS keys + // Setup CKEditor LTS keys. if (array_key_exists('ckeditor_lts', $cgdp_license_keys)) { // Set the default key if a specific environment key does not exist. diff --git a/docroot/sites/settings/cgov_saml_auth_config.settings.php b/docroot/sites/settings/cgov_saml_auth_config.settings.php index 29949cdd7c..c302d7cd8a 100644 --- a/docroot/sites/settings/cgov_saml_auth_config.settings.php +++ b/docroot/sites/settings/cgov_saml_auth_config.settings.php @@ -11,7 +11,7 @@ // Get our current environment if (CGovSettingsUtil::isAcquia()) { - $samlDir = "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/saml"; + $samlDir = "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/cgdp/saml"; } elseif (file_exists('/var/saml')) { $samlDir = "/var/saml"; } diff --git a/docroot/sites/settings/cgov_settings_utilities.inc b/docroot/sites/settings/cgov_settings_utilities.inc index b239eca941..2d3d4a4bfc 100644 --- a/docroot/sites/settings/cgov_settings_utilities.inc +++ b/docroot/sites/settings/cgov_settings_utilities.inc @@ -18,22 +18,20 @@ class CGovSettingsUtil { * Determines whether we are on an Acquia environment. * * @return bool - * True if this is an Acquia environment (ACSF or AC) + * True if this is an Acquia environment (MEO or ACE) */ public static function isAcquia() { return (file_exists('/var/www/site-php') && isset($_ENV['AH_SITE_ENVIRONMENT'])); } /** - * Determines whether we are on an ACSF environment. + * Determines whether we are on an Acquia MEO environment. * * @return bool - * True if this is an ACSF environment + * True if this is an Acquia MEO environment */ - public static function isACSF() { - $is_acsf_env = EnvironmentDetector::isAcsfEnv(); - $acsf_db_name = EnvironmentDetector::getAcsfDbName(); - return ($is_acsf_env && $acsf_db_name); + public static function isMeo() { + return (self::isAcquia() && $_ENV['AH_ENVIRONMENT_TYPE'] === 'meo'); } /** @@ -48,7 +46,13 @@ class CGovSettingsUtil { if (preg_match('/^ode\d*$/', $env)) { $env = 'ode'; } - } else { + if (self::isMeo()) { + // Prefixing MEO environments to avoid conflicts with ACE and matching + // BLT env names. + $env = 'meo' . $env; + } + } + else { $env = 'local'; } return $env; @@ -63,27 +67,38 @@ class CGovSettingsUtil { public static function getDomain() { // Default our domain to PROD. $domain = 'www.cancer.gov'; - // Check if this is an ACSF site first. - if (self::isACSF()) { - $domains = gardens_data_get_sites_from_file($GLOBALS['gardens_site_settings']['conf']['acsf_db_name']); - // You should always add the public domain first domain when creating a site, - // but just in case we will set to the default first. - $domain = array_keys($domains)[0]; - foreach ($domains as $site_name => $site_info) { - if (!empty($site_info['flags']['preferred_domain'])) { - $domain = $site_name; + + // Check if this is an MEO site first. + if (self::isMeo()) { + // Load the CDP mapping file to determine which MEO site we are on. + $cdp_mapping_path = "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/cgdp/sites.php"; + if (!is_file($cdp_mapping_path)) { + error_log("MEO CDP mapping file not found: $cdp_mapping_path."); + } + else { + require $cdp_mapping_path; + + // Check if the site name exists in the mapping file. + if (!isset($cgdpUrls) || !isset($_ENV['AH_DRUPAL_SITE_NAME']) || !isset($cgdpUrls[$_ENV['AH_DRUPAL_SITE_NAME']])) { + error_log("Site name '{$_ENV['AH_DRUPAL_SITE_NAME']}' not found in CDP mapping file."); + } + else { + $domain = $cgdpUrls[$_ENV['AH_DRUPAL_SITE_NAME']]; } } - // For non-ACSF sites, the domain depends on the environment name. - } else { + } + else { + // For non-MEO sites, the domain depends on the environment name. $env = self::getEnvironmentName(); switch ($env) { case 'local': $domain = 'www.devbox'; break; + case 'ode': $domain = 'ncigovcd' . $_ENV['AH_SITE_ENVIRONMENT'] . '.prod.acquia-sites.com'; break; + // Other AC environments have a standard domain format. default: $domain = 'www-' . $env . '-ac.cancer.gov'; @@ -92,6 +107,5 @@ class CGovSettingsUtil { } return $domain; } -} - +} diff --git a/docroot/sites/sites.php b/docroot/sites/sites.php index d324436b98..de13c41ee9 100644 --- a/docroot/sites/sites.php +++ b/docroot/sites/sites.php @@ -1,148 +1,71 @@ ..' => 'directory'. As an + * example, to map https://www.drupal.org:8080/my-site/test to the configuration + * directory sites/example.com, the array should be defined as: + * @code + * $sites = [ + * '8080.www.drupal.org.my-site.test' => 'example.com', + * ]; + * @endcode + * The URL, https://www.drupal.org:8080/my-site/test/, could be a symbolic link + * or an Apache Alias directive that points to the Drupal root containing + * index.php. An alias could also be created for a subdomain. See the + * @link https://www.drupal.org/documentation/install online Drupal installation guide @endlink + * for more information on setting up domains, subdomains, and subdirectories. * - * In short: this file is supposed to populate the $sites variable for use by - * the caller, with a mapping from domain names to site specific directories. - * If the requested domain has a site specific directory defined, the caller - * (likely) includes the settings.php in that specific directory rather than - * sites/default/settings.php. + * The following examples look for a site configuration in sites/example.com: + * @code + * URL: http://dev.drupal.org + * $sites['dev.drupal.org'] = 'example.com'; * - * Acquia Cloud Site Factory has its own storage of per-domain data, which is - * read by this file to get the needed info. In addition, the global variable - * $gardens_site_settings is populated with various per-site information, for - * use by the site specific settings.php file. (See: sites/g/settings.php). + * URL: http://localhost/example + * $sites['localhost.example'] = 'example.com'; * - * If an error occurs or the domain is not found, then we quit processing - * without setting $sites. The caller then (likely) includes - * sites/default/acsf.settings.php from sites/default/settings.php, which will - * emit a "Site not found" response. That response can be customized by - * modifying sites/default/settings.php. + * URL: http://localhost:8080/example + * $sites['8080.localhost.example'] = 'example.com'; * - * (At least Acquia's standard) Drush aliases are *not supported* for usage - * with Acquia Cloud Site Factory. Drush9 commands simply don't work with them. + * URL: https://www.drupal.org:8080/my-site/test/ + * $sites['8080.www.drupal.org.my-site.test'] = 'example.com'; + * @endcode + * + * @see default.settings.php + * @see \Drupal\Core\DrupalKernel::getSitePath() + * @see https://www.drupal.org/docs/getting-started/multisite-drupal */ -// Protect against sites.php being included from the wrong docroot, on Acquia -// servers containing multiple (staging) environments for a customer. This is -// a known issue only in previous versions of the module which worked with -// Drush7/8 + Acquia's aliases, but it seems prudent to do this check anyway. -// Notes: -// - The environment values are always defined on Acquia hardware. If they are -// not defined, we cannot assume anything about the file system structure so -// we skip this check. -// - realpath() includes AH_SITE_NAME; we instead want to match -// AH_SITE_GROUP.AH_SITE_ENVIRONMENT (which is always a symlink) -// - to accommodate for possible future changes or botched provisioning; -// - because Site Factory doesn't use AH_SITE_NAME anywhere else; -// - because AH_SITE_NAME may not be correct in some cases, e.g. when our -// post-db-copy/000-acquia_required_scrub.php hook executes acsf-site-scrub. -if (isset($_ENV['AH_SITE_NAME']) && isset($_ENV['AH_SITE_ENVIRONMENT']) - && strpos(__FILE__, (string) realpath("/var/www/html/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/docroot")) !== 0 - && strpos(__FILE__, (string) realpath("/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/livedev/docroot")) !== 0 -) { - return; -} - -if (!function_exists('acsf_hooks_includes')) { - - /** - * Scans a factory-hooks sub-directory and returns PHP files to be included. - * - * @param string $hook_name - * The name of the hook whose files should be returned. - * - * @return string[] - * A list of customer-defined hook files to include sorted alphabetically - * ascending. - */ - function acsf_hooks_includes($hook_name) { - // Only include hooks if we are properly booting Drupal. - if (!defined('DRUPAL_ROOT')) { - return []; - } - $hook_pattern = sprintf('%s/../factory-hooks/%s/*.php', DRUPAL_ROOT, $hook_name); - return glob($hook_pattern); +if (isset($_ENV['AH_ENVIRONMENT_TYPE']) && $_ENV['AH_ENVIRONMENT_TYPE'] === 'meo') { + // Include Acquia sites.inc + if (file_exists('/var/www/site-php')) { + // phpcs:ignore + require '/var/www/site-php/' . $_ENV['AH_SITE_GROUP'] . '/' . $_ENV['AH_SITE_GROUP'] . '-sites.inc'; } -} - -// Include custom sites.php code from factory-hooks/pre-sites-php. -foreach (acsf_hooks_includes('pre-sites-php') as $_acsf_include_file) { - // This should not use include_once / require_once. Some Drush versions do - // Drupal bootstrap multiple times, and include_once / require_once would - // make the hook modifications not be included on the second bootstrap. - // Acquia rules disallow 'include/require' with dynamic arguments. - // phpcs:disable - include $_acsf_include_file; - // phpcs:enable -} - -if (!function_exists('is_acquia_host')) { - - /** - * Checks whether the site is on Acquia Hosting. - * - * @return bool - * TRUE if the site is on Acquia Hosting, otherwise FALSE. - */ - function is_acquia_host() { - return file_exists('/var/acquia'); + $cdp_mapping_path = '/mnt/files/' . $_ENV['AH_SITE_GROUP'] . '.' . $_ENV['AH_SITE_ENVIRONMENT'] . '/cgdp/sites.php'; + if (file_exists($cdp_mapping_path)) { + // phpcs:ignore + require $cdp_mapping_path; } - -} - -// Check that we are on an Acquia server so we do not run this code for local -// development. -if (!is_acquia_host()) { - return; -} - -// This safeguard should not be necessary since we stopped executing sites.php -// on unrelated environments (above). We keep it only in case removing it would -// have an effect in exotic unknown cases. -if (!function_exists('gardens_site_data_load_file')) { - require_once __DIR__ . '/g/sites.inc'; -} - -// Prevents to run further if the sites.json file doesn't exists. -// This step also tries to prevent errors on a none acsf environment. -if (empty($_ENV['AH_SITE_GROUP']) || empty($_ENV['AH_SITE_ENVIRONMENT']) || !function_exists('gardens_site_data_get_filepath') || !file_exists(gardens_site_data_get_filepath())) { - return; -} - -$_tmp = gardens_site_data_get_site_from_server_info(); - -// If either "not found" or "read failure" (from either the cache or the -// sites.json file): don't set $sites and fall through (to, probably, reading -// sites/default/settings.php for settings). -if (empty($_tmp)) { - if ($_tmp === NULL) { - // If we encountered a read error, indicate that we want the same (short) - // cache time for the page, as we have for the data in APC. - $GLOBALS['gardens_site_settings']['page_ttl'] = GARDENS_SITE_DATA_READ_FAILURE_TTL; - } - return; -} - -// We found a site, so add the corresponding 'configuration directory' to -// $sites, as per the regular sites.php spec. (For most Drupal sites this is -// a single-layer directory equal to a domain name; for us, it is typically -// g/files/SITE-ID.) -$sites[$_tmp['dir_key']] = $_tmp['dir']; -// Also set 'gardens_site_settings' for other code further on. (Mainly -// settings.php.) -$GLOBALS['gardens_site_settings'] = $_tmp['gardens_site_settings']; - -// Include custom sites.php code from factory-hooks/post-sites-php, only when -// a domain was found. -foreach (acsf_hooks_includes('post-sites-php') as $_acsf_include_file) { - // Acquia rules disallow 'include/require' with dynamic arguments. - // phpcs:disable - include $_acsf_include_file; - // phpcs:enable } diff --git a/factory-hooks/db-update/db-update.sh b/factory-hooks/db-update/db-update.sh deleted file mode 100755 index bda1ffe9c6..0000000000 --- a/factory-hooks/db-update/db-update.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# -# Factory Hook: db-update -# -# The existence of one or more executable files in the -# /factory-hooks/db-update directory will prompt them to be run *instead of* the -# regular database update (drush updatedb) command. So that update command will -# normally be part of the commands executed below. -# -# Usage: db-update.sh sitegroup env db-role domain custom-arg - -# Exit immediately on error and enable verbose log output. -set -ev - -# Map the script inputs to convenient names: -# Acquia Hosting sitegroup (application) and environment. -sitegroup="$1" -env="$2" -# Database role. This is a truly unique identifier for an ACSF site and is e.g. -# part of the public files path. -db_role="$3" -# The public domain name of the website. If the site uses a path based domain, -# the path is appended (without trailing slash), e.g. "domain.com/subpath". -domain="$4" - -# BLT wants the name of the website, which we can derive from its internal -# domain name. Use the uri.php file provided by the acsf module to get the -# internal domain name based on the site, environment and db role arguments. -uri=`/usr/bin/env php /mnt/www/html/$sitegroup.$env/hooks/acquia/uri.php $sitegroup $env $db_role` -# To get only the site name in ${name[0]}: -IFS='.' read -a name <<< "${uri}" - -# BLT executable: -blt="/mnt/www/html/$sitegroup.$env/vendor/acquia/blt/bin/blt" - -echo "Running BLT deploy tasks on $uri domain in $env environment on the $sitegroup subscription." - -# Run blt drupal:update tasks. The trailing slash behind the domain works -# around a bug in Drush < 9.6 for path based domains: "domain.com/subpath/" is -# considered a valid URI but "domain.com/subpath" is not. -$blt drupal:update --environment=$env --site=${name[0]} --define drush.uri=$domain/ --verbose --no-interaction - -# Clean up the drush cache directory. -echo "Removing temporary drush cache files." - -set +v - -# @todo Exit with the status of the BLT commmand. If the exit status is non-zero, -# Site Factory will send a notification of a failed 'blt drupal:update', -# interrupting the execution of additional db-update scripts. diff --git a/factory-hooks/db-update/z-cgov-post-update.sh b/factory-hooks/db-update/z-cgov-post-update.sh deleted file mode 100755 index b4db67834c..0000000000 --- a/factory-hooks/db-update/z-cgov-post-update.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# -# Factory Hook: db-update -# -# The existence of one or more executable files in the -# /factory-hooks/db-update directory will prompt them to be run *instead of* the -# regular database update (drush updatedb) command. So that update command will -# normally be part of the commands executed below. -# -# Usage: db-update.sh sitegroup env db-role domain custom-arg - -# Exit immediately on error and enable verbose log output. -set -ev - -# Map the script inputs to convenient names: -# Acquia Hosting sitegroup (application) and environment. -sitegroup="$1" -env="$2" -# Database role. This is a truly unique identifier for an ACSF site and is e.g. -# part of the public files path. -db_role="$3" -# The public domain name of the website. If the site uses a path based domain, -# the path is appended (without trailing slash), e.g. "domain.com/subpath". -domain="$4" - -# BLT wants the name of the website, which we can derive from its internal -# domain name. Use the uri.php file provided by the acsf module to get the -# internal domain name based on the site, environment and db role arguments. -uri=`/usr/bin/env php /mnt/www/html/$sitegroup.$env/hooks/acquia/uri.php $sitegroup $env $db_role` -# To get only the site name in ${name[0]}: -IFS='.' read -a name <<< "${uri}" - -# BLT executable: -blt="/mnt/www/html/$sitegroup.$env/vendor/acquia/blt/bin/blt" - -echo "Running BLT deploy tasks on $uri domain in $env environment on the $sitegroup subscription." - -# Run blt drupal:update tasks. The trailing slash behind the domain works -# around a bug in Drush < 9.6 for path based domains: "domain.com/subpath/" is -# considered a valid URI but "domain.com/subpath" is not. -$blt drupal:update --environment=$env --site=${name[0]} --define drush.uri=$domain/ --verbose --no-interaction - -set +v - - -########################################################## -### ----------- Cgov Specific Tasks Here ------------- ### -### Differences from db-update start here. ### -########################################################## -$blt cgov:acsf:db-update --environment=$env --site=${name[0]} --define drush.uri=$domain/ --verbose --no-interaction -D drush.ansi=false - -set +v diff --git a/factory-hooks/post-settings-php/includes.php b/factory-hooks/post-settings-php/includes.php deleted file mode 100644 index 14ada63ac7..0000000000 --- a/factory-hooks/post-settings-php/includes.php +++ /dev/null @@ -1,15 +0,0 @@ -deleteAll();' diff --git a/factory-hooks/pre-settings-php/includes.php b/factory-hooks/pre-settings-php/includes.php deleted file mode 100644 index 924658b104..0000000000 --- a/factory-hooks/pre-settings-php/includes.php +++ /dev/null @@ -1,14 +0,0 @@ - '', - 'pass' => '', - ]; - if ($info['path'][0] === '/') { - $info['path'] = substr($info['path'], 1); - } - - $database = [ - 'driver' => $info['scheme'], - 'username' => $info['user'], - 'password' => $info['pass'], - 'host' => $info['host'], - 'database' => $info['path'], - ]; - if (isset($info['port'])) { - $database['port'] = $info['port']; - } - return $database; -} - -/** - * Helper function for mysqli query execute. - * - * @param mysqli $con - * A link identifier returned by mysqli_connect() or mysqli_init(). - * @param string $query - * An SQL query. - * - * @return array|bool - * If query was successful, retrieve all the rows into an array, - * otherwise return FALSE. - */ -function execute_query(mysqli $con, $query) { - // Acquia rules disallow mysqli_query() with dynamic arguments. - // phpcs:disable - $result = mysqli_query($con, $query); - // phpcs:enable - // If query failed, return FALSE. - if ($result === FALSE) { - return FALSE; - } - $rows = []; - while ($row = mysqli_fetch_assoc($result)) { - $rows[] = $row; - } - return $rows; -} diff --git a/hooks/acquia/uri.php b/hooks/acquia/uri.php deleted file mode 100755 index 189f18851c..0000000000 --- a/hooks/acquia/uri.php +++ /dev/null @@ -1,25 +0,0 @@ -&1', - escapeshellarg(drush_cache_path($site, $env, $domain)), - escapeshellarg($env), - escapeshellarg($docroot), - escapeshellarg('https://' . $domain), - $cmd - ); - - fwrite(STDERR, "Executing: $drush_cmd;\n"); - $result = 0; - $output = []; - // Acquia rules disallow exec() with dynamic arguments. - // phpcs:disable - exec($drush_cmd, $output, $result); - // phpcs:enable - print implode("\n", $output); - - fwrite(STDERR, "Command execution returned status code: $result\n"); - - // In case the returned status code ($result) is not 0, we'll remove the drush - // cache directory and halt the execution of the whole script. - if ($result) { - rmdir_r(drush_cache_path($site, $env, $domain)); - exit($result); - } - - return $output; -} - -if (empty($argv[3])) { - echo "Error: Not enough arguments.\n"; - exit(1); -} - -// AH site group. -$site = $argv[1]; -// AH site env. -$env = $argv[2]; -// Database name. -$db_role = $argv[3]; - -$docroot = sprintf('/var/www/html/%s.%s/docroot', $site, $env); - -include_once $docroot . '/sites/g/sites.inc'; -$sites_json = gardens_site_data_load_file(); -if (!$sites_json) { - // If the file exists, and cannot be loaded, exit with an error. - if (file_exists(gardens_site_data_get_filepath())) { - fwrite(STDERR, "The site registry could not be loaded from the server.\n"); - exit(1); - } - // Exit gracefully if the sites.json is not available. That usually - // indicates that the code is running on a non-acsf environment. - fwrite(STDERR, "The site registry does not exist; this doesn't look like an ACSF environment.\n"); - exit(0); -} - -fwrite(STDERR, sprintf("Scrubbing site database: site: %s; env: %s; db_role: %s;\n", $site, $env, $db_role)); - -$new_domain = FALSE; -foreach ($sites_json['sites'] as $site_domain => $site_info) { - if ($site_info['conf']['acsf_db_name'] === $db_role && !empty($site_info['flags']['preferred_domain'])) { - $new_domain = $site_domain; - fwrite(STDERR, "Site domain: $new_domain;\n"); - - // When the site being staged has different a code than its source, the - // original code will be deployed on the update environment to ensure that - // the scrubbing process will not fail due to code / data structure - // differences. - if (!empty($site_info['flags']['staging_exec_on'])) { - $env = $site_info['flags']['staging_exec_on']; - $docroot = sprintf('/var/www/html/%s.%s/docroot', $site, $env); - } - break; - } -} - -if (!$new_domain) { - error('Could not find the domain that belongs to the site.'); -} - -// Make directory drush will use for temporary cache. -mkdir_p(drush_cache_path($site, $env, $new_domain)); - -// Check for the availability of the 'cache_container' table. -fwrite(STDERR, "Checking the availability of 'cache_container' table.\n"); -$cache_table_check = drush_exec($site, $env, $new_domain, 'sqlq "SHOW TABLES LIKE \'cache_container\';"'); -$cache_table_check = array_shift($cache_table_check); -// An empty value/string means that no 'cache_container' table was found. -if (!empty($cache_table_check)) { - // Make absolutely sure Drupal will not pick up cached component locations - // on boostrap when called by Drush cache-rebuild. - // The following Drush cache-rebuild will repopulate this table. - fwrite(STDERR, "Table was found. Deleting all data from the 'cache_container' table.\n"); - drush_exec($site, $env, $new_domain, 'sqlq "TRUNCATE TABLE cache_container;"'); -} - -// Explicitly run a cache-rebuild before anything else. -drush_exec($site, $env, $new_domain, 'cache-rebuild'); - -// Execute the scrub. If we execute code on the update environment (as per -// above), we must change AH_SITE_ENVIRONMENT to match the docroot during -// execution; see sites.php. -drush_exec($site, $env, $new_domain, 'acsf-site-scrub'); - -// Clean up the drush cache directory. -rmdir_r(drush_cache_path($site, $env, $new_domain)); diff --git a/hooks/common/post-db-copy/db-scrub.sh b/hooks/common/post-db-copy/db-scrub.sh deleted file mode 100755 index c7c8a03f61..0000000000 --- a/hooks/common/post-db-copy/db-scrub.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -# -# db-copy Cloud hook: db-scrub -# -# Scrub important information from a Drupal database. -# -# Usage: db-scrub.sh site target-env db-name source-env - -set -ev - -site="$1" -target_env="$2" -db_name="$3" -source_env="$4" - -# Prep for BLT commands. -repo_root="/var/www/html/$site.$target_env" -export PATH=$repo_root/vendor/bin:$PATH -cd $repo_root - -blt artifact:ac-hooks:db-scrub $site $target_env $db_name $source_env -D drush.ansi=false - -set +v diff --git a/hooks/common/pre-web-activate/000-acquia-deployment.php b/hooks/common/pre-web-activate/000-acquia-deployment.php deleted file mode 100755 index 00cb28ad18..0000000000 --- a/hooks/common/pre-web-activate/000-acquia-deployment.php +++ /dev/null @@ -1,598 +0,0 @@ -#!/usr/bin/env php -code == 200) { - $task_id = $response->body['task_id']; - if ($task_id == 'NA') { - printf("VCS theming is not configured for %s.%s\n", $site, $env); - exit(0); - } - else { - $lock->write($task_id . "\n"); - } - - // Wait here until the themes are deployed. - do { - sleep(10); - $task_info = get_wip_task_status($site, $env, $task_id); - $task = $task_info->body['wip_task']; - if ($verbose) { - printf("Wip task status: %s\n", print_r($task, TRUE)); - } - } while ($task['status'] < WIP_STATUS_COMPLETED); - - // Note: STATUS_WARNING is 144, which has the 16 (STATUS_COMPLETED) bit - // set, so checking against 16 will be true for both completed, and - // warning. - if ($task['status'] & WIP_STATUS_COMPLETED) { - $success = TRUE; - break; - } - } - sleep(10 * ($max_attempts - $attempts)); - } while ($attempts-- > 0); - - if (!$success) { - // Failed to deploy the theme files. - printf("Failed to deploy theme files to %s for %s.%s\n", $webnode, $site, $env); - exit(1); - } - } - else { - printf("INFO: Nothing to do - themes appear deployed and no force flag was set. No lockfile was found from another process. No error status is returned.\n"); - } -} - -/** - * Class FileLock - * - * Acquires a file-based lock and automatically clears it. - */ -class FileLock { - /** - * The lock filename. - * - * @var string - */ - var $lockFile = ''; - - /** - * The lock file pointer. - * - * @var false|resource - */ - var $fp; - - /** - * FileLock constructor. - * - * @param string $lock_filename - * A filename to use for the lock. - */ - public function __construct($lock_filename) { - $this->lockFile = $lock_filename; - $this->fp = fopen($lock_filename, 'w'); - if (!flock($this->fp, LOCK_EX | LOCK_NB)) { - throw new \Exception('Unable to acquire lock for theme distribution.'); - } - } - - /** - * Write data into the lockfile. - * - * Can be used to add debugging data into the lockfile. - * - * @param string $contents - */ - public function write($contents) { - fwrite($this->fp, $contents); - fflush($this->fp); - } - - /** - * Removes the lockfile when this object goes out of scope. - */ - public function __destruct() { - flock($this->fp, LOCK_UN); - fclose($this->fp); - if (file_exists($this->lockFile)) { - unlink($this->lockFile); - } - } -} - -/** - * Indicates whether theme files have been deployed. - * - * @param string $site - * The sitegroup name. - * @param string $env - * The environment name. - * - * @return bool - * TRUE if this webnode has theme files; FALSE otherwise. - */ -function has_theme_files($site, $env) { - $result = FALSE; - $path = get_theme_directory($site, $env); - if (file_exists($path) && is_dir($path)) { - $result = TRUE; - } - return $result; -} - -/** - * Returns the shared credentials. - * - * @param string $site - * The hosting sitegroup name. - * @param string $env - * The hosting environment name. - * - * @return SimpleRestCreds - * The credentials. - * - * @throws Exception - * If the credentials cannot be read for any reason. - */ -function get_shared_creds($site, $env) { - $path = sprintf('/mnt/files/%s.%s/nobackup', $site, $env); - $shared = sprintf('%s/sf_shared_creds.ini', $path); - if (file_exists($shared)) { - $data = parse_ini_file($shared, TRUE); - if (!empty($data) && !empty($data['gardener'])) { - return new SimpleRestCreds($data['gardener']['username'], - $data['gardener']['password'], - $data['gardener']['url']); - } - } - throw new Exception(sprintf('Unable to read credentials from %s', $shared)); -} - -/** - * Returns the path to the site registry file. - * - * @param string $site - * The sitegroup name. - * @param string $env - * The environment name. - * - * @return string - * The site registry path. - */ -function get_registry_file($site, $env) { - return sprintf('/var/www/site-php/%s.%s/multisite-config.json', $site, $env); -} - -/** - * Get the lockfile path (should only be the live env for both live and update) - * - * @param string $site - * The sitegroup (application) of this script. - * @param string $env - * The current environment of this script. - * - * @return string - * The name of the live environment. - */ -function get_lockfile($site, $env) { - $live_env = get_live_env(get_registry_file($site, $env)); - // Note: lockfile is per server - // Note: some risk of this lockfile getting cleaned out by hosting automated - // ephemeral wipe, but that would be unlucky. In order to prevent that, we'd - // need a protected file location in ephemeral. - printf("INFO: live env%s\n", $live_env); - return sprintf('/mnt/tmp/%s.%s/%s-%s-themedistribute.lock', $site, $live_env, $site, $live_env); -} - -/** - * Gets the live environment corresponding to the current environment. - * - * In effect, if this hook runs from the live environment (eg. 01live), it will - * return that same environment. If run from update (eg. 01update), it will - * return the corresponding live environment. - * - * @param string $registry_path - * The filename of the sites.json site registry. - * - * @return string - * An environment name. - */ -function get_live_env($registry_path) { - $data = json_decode(file_get_contents($registry_path), null, 512, JSON_THROW_ON_ERROR); - if (empty($data->cloud->env)) { - throw new \Exception('Unable to locate live environment for registry path ' . $registry_path); - } - return $data->cloud->env; -} - -/** - * Returns the path to the theme repository. - * - * @param string $site - * The sitegroup name. - * @param string $env - * The environment name. - * - * @return string - * The theme repo path. - */ -function get_theme_directory($site, $env) { - return sprintf('/mnt/tmp/%s.%s/theme_repo/live/', $site, $env); -} - -/** - * Sends the request to deploy themes on the specified webnode. - * - * @param string $site - * The hosting site group name. - * @param string $env - * The hosting environment name. - * @param string $webnode - * The fully qualified webnode name. - * - * @return SimpleRestResponse - * The response. - */ -function request_theme_files($site, $env, $webnode) { - $endpoint = 'site-api/v1/theme/deploy'; - try { - $parameters = [ - 'sitegroup' => $site, - 'webnode' => $webnode, - 'environment' => $env, - ]; - $creds = get_shared_creds($site, $env); - $message = new SimpleRestMessage($site, $env); - $response = $message->send('POST', $endpoint, $parameters, $creds); - } - catch (Exception $e) { - $error_message = sprintf('Theme deploy failed with error: %s', $e->getMessage()); - syslog(LOG_ERR, $error_message); - $response = new SimpleRestResponse($endpoint, 500, ['message' => $error_message]); - } - return $response; -} - -/** - * Requests status of a particular wip task. - * - * @param string $site - * The hosting site group name. - * @param string $env - * The hosting environment name. - * @param int $task_id - * The Wip task id. - * - * @return SimpleRestResponse - * The response. - */ -function get_wip_task_status($site, $env, $task_id) { - $endpoint = sprintf('site-api/v1/wip/task/%s/status', $task_id); - try { - $parameters = []; - $creds = get_shared_creds($site, $env); - $message = new SimpleRestMessage($site, $env); - $response = $message->send('GET', $endpoint, $parameters, $creds); - } - catch (Exception $e) { - $error_message = sprintf('Wip task status failed with error: %s', $e->getMessage()); - $file = __FILE__; - syslog(LOG_ERR, "Error in cloud hook pre-web-activate/$file: $error_message"); - $response = new SimpleRestResponse($endpoint, 500, ['message' => $error_message]); - } - return $response; -} - -/** - * Class SimpleRestCreds. - * - * Contains the REST credentials that will be used when making Site Factory - * requests. - */ -// Class name doesn't match filename. -// phpcs:disable -class SimpleRestCreds { -// phpcs:enable - - /** - * The username to be used to contact Site Factory. - * - * @var string - */ - public $name; - - /** - * The password to be used to contact Site Factory. - * - * @var string - */ - public $password; - - /** - * The URL of the Site Factory. - * - * @var string - */ - public $url; - - /** - * Creates a new instance of SimpleRestCreds. - * - * @param string $name - * The username to be used to contact Site Factory. - * @param string $password - * The password to be used to contact Site Factory. - * @param string $url - * The url of the Site Factory. - */ - public function __construct($name, $password, $url) { - $this->name = $name; - $this->password = $password; - $this->url = $url; - } - -} - -/** - * Class SimpleRestMessage. - * - * A simple class used to send REST requests to the Site Factory. - */ -class SimpleRestMessage { - - /** - * Maximum amount of retries before giving up sending a message. - */ - private int $retryMax = 3; - - /** - * Number of seconds to wait before trying again after sending failed. - */ - private int $retryWait = 5; - - /** - * The hosting sitegroup name. - */ - private string $site; - - /** - * The hosting environment name. - */ - private string $env; - - /** - * Creates a new instance of SimpleRestMessage. - * - * @param string $site - * The hosting site name. - * @param string $env - * The hosting environment name. - */ - public function __construct($site, $env) { - $this->site = $site; - $this->env = $env; - } - - /** - * Sends a request. - * - * @param string $method - * The request method. Either 'POST' or 'GET'. - * @param string $endpoint - * The request endpoint. - * @param array $parameters - * Any required parameters for the request. Note: parameters are currently - * only implemented for POST requests. To add support for GET parameters - * would require changes in this method. - * @param SimpleRestCreds $creds - * The credentials to use for the Site Factory request. - * - * @throws Exception - * If the request fails. - * - * @return \SimpleRestResponse - * The response. - */ - public function send($method, $endpoint, array $parameters, SimpleRestCreds $creds) { - $error = ''; - $user_agent = sprintf('%s.%s %s', $this->site, $this->env, gethostname()); - $curl = curl_init(); - - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_USERAGENT, $user_agent); - curl_setopt($curl, CURLOPT_HEADER, 0); - curl_setopt($curl, CURLOPT_USERPWD, $creds->name . ":" . $creds->password); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); - - // If it is not a GET request, set the method here. - if ($method != 'GET') { - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); - } - - // If we are sending parameters, set the query string or POST fields here. - $query_string = ''; - if ($method != 'GET' && !empty($parameters)) { - $data_string = json_encode($parameters, JSON_THROW_ON_ERROR); - curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string); - curl_setopt($curl, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json', - 'Content-Length: ' . strlen($data_string), - ]); - } - - $full_url = sprintf('%s/%s%s', $creds->url, $endpoint, $query_string); - curl_setopt($curl, CURLOPT_URL, $full_url); - - $attempts = 0; - $response = FALSE; - - while (!$response && ++$attempts <= $this->retryMax) { - $response = curl_exec($curl); - if (!$response) { - $error = curl_error($curl); - sleep($this->retryWait); - } - } - - if (!$response) { - throw new Exception(sprintf('Error reaching url "%s" with method "%s." Returned error "%s."', $full_url, $method, $error)); - } - - $response_body = json_decode($response, TRUE, 512, JSON_THROW_ON_ERROR); - $response_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - - if (!is_array($response_body)) { - $response_body = []; - } - - curl_close($curl); - - return new SimpleRestResponse($endpoint, $response_code, $response_body); - } - -} - -/** - * Class SimpleRestResponse. - * - * Holds the response. - */ -class SimpleRestResponse { - /** - * The request endpoint. - * - * @var string - */ - public $endpoint; - - /** - * The response code. - * - * @var string - */ - public $code; - - /** - * The response body. - * - * @var array - */ - public $body; - - /** - * Constructs a new instance of SimpleRestResponse. - * - * @param string $endpoint - * The request endpoint. - * @param string $response_code - * The response code. - * @param array $response_body - * The response body. - */ - public function __construct($endpoint, $response_code, array $response_body) { - $this->endpoint = $endpoint; - $this->code = $response_code; - $this->body = $response_body; - } - -} diff --git a/patches/drupal-scaffold/htaccess.acsf.patch b/patches/drupal-scaffold/htaccess.acsf.patch deleted file mode 100644 index f20eacbca0..0000000000 --- a/patches/drupal-scaffold/htaccess.acsf.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git docroot/.htaccess docroot/.htaccess -index 044ff1f3..971f754b 100644 ---- docroot/.htaccess -+++ docroot/.htaccess -@@ -157,6 +157,8 @@ AddEncoding gzip svgz - # Copy and adapt this rule to directly execute PHP files in contributed or - # custom modules or to run another PHP application in the same directory. - RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics\.php$ -+ # ACSF requirement: allow access to apc_rebuild.php. -+ RewriteCond %{REQUEST_URI} !/sites/g/apc_rebuild.php$ - # Deny access to any other PHP files that do not match the rules above. - # Specifically, disallow autoload.php from being served directly. - RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F] diff --git a/patches/drupal-scaffold/htaccess.https.patch b/patches/drupal-scaffold/htaccess.https.patch index 32218219a5..f06aa3a8f5 100644 --- a/patches/drupal-scaffold/htaccess.https.patch +++ b/patches/drupal-scaffold/htaccess.https.patch @@ -2,7 +2,7 @@ diff --git docroot/.htaccess docroot/.htaccess index b2a1a29..0247fb0 100644 --- docroot/.htaccess +++ docroot/.htaccess -@@ -122,6 +122,18 @@ AddEncoding gzip svgz +@@ -122,6 +122,19 @@ AddEncoding gzip svgz # uncomment the following line: # RewriteBase / @@ -11,11 +11,12 @@ index b2a1a29..0247fb0 100644 + RewriteCond %{HTTP:X-Forwarded-Proto} !https + RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + -+ # Return Status 403 (Forbidden) on non-ODE server access in the ++ # Return Status 403 (Forbidden) on non-ODE/MEO server access in the + # Acquia cloud. + RewriteCond %{ENV:AH_SITE_ENVIRONMENT} ^(.)+$ + RewriteCond %{HTTP_HOST} ^(.)+\.acquia-sites\.com$ [NC] + RewriteCond %{ENV:AH_SITE_ENVIRONMENT} !^ode[0-9]+$ [NC] ++ RewriteCond %{ENV:AH_ENVIRONMENT_TYPE} !^meo$ [NC] + RewriteRule .* - [R=403] + # Redirect common PHP files to their new locations. diff --git a/factory-hooks/post-site-install/post-site-install.sh b/scripts/automation-support/post-site-install.sh similarity index 65% rename from factory-hooks/post-site-install/post-site-install.sh rename to scripts/automation-support/post-site-install.sh index 9a551d3ce5..64aff9f5ed 100755 --- a/factory-hooks/post-site-install/post-site-install.sh +++ b/scripts/automation-support/post-site-install.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Factory Hook: post-site-install +# Hook: post-site-install # # This is necessary so that blt drupal:install tasks are invoked automatically # when a site is created on ACSF. @@ -8,7 +8,7 @@ # Usage: post-site-install.sh sitegroup env db-role domain # Exit immediately on error and enable verbose log output. -set -ev +set -e # Map the script inputs to convenient names: # Acquia Hosting sitegroup (application) and environment. @@ -26,17 +26,20 @@ internal_domain="$4" # To get only the site name in ${name[0]}: IFS='.' read -a name <<< $internal_domain -# BLT executable: -blt="/mnt/www/html/$sitegroup.$env/vendor/acquia/blt/bin/blt" +# Source BLT environment helper +source "/mnt/www/html/$sitegroup.$env/scripts/blt/blt-env-helper.sh" + +# Set up BLT executable and environment mapping +setup_blt_environment "$sitegroup" "$env" # Execute the updates. -$blt drupal:update --environment=$env --site=${name[0]} --define drush.uri=$internal_domain --verbose --no-interaction -result=$? +$blt_executable drupal:update --environment=$blt_environment --site=${name[0]} --define drush.uri=$internal_domain --verbose --no-interaction -D drush.ansi=false +$blt_executable cgov:meo:post-install --environment=$blt_environment --site=${name[0]} --define drush.uri=$internal_domain --verbose --no-interaction -D drush.ansi=false -set +v +result=$? # Exit with the status of the BLT commmand. If the exit status is non-zero, -# Site Factory will send a notification of a partiolly failed install and will +# MEO will send a notification of a partially failed install and will # stop executing any further post-site-install hook scripts that would be in # this directory (and get executed in alphabetical order). exit $result diff --git a/scripts/blt/README.md b/scripts/blt/README.md new file mode 100644 index 0000000000..0bda9eb91d --- /dev/null +++ b/scripts/blt/README.md @@ -0,0 +1,58 @@ +# BLT Environment Helper + +This helper script provides reusable functions for setting up BLT executable paths and environment mapping across ACE and MEO platforms. + +## Usage + +### Basic Setup + +```bash +# Source the helper +source "/mnt/www/html/$sitegroup.$env/scripts/blt/blt-env-helper.sh" + +# Set up BLT environment +setup_blt_environment "$sitegroup" "$env" + +# Use the configured variables +$blt_executable drupal:update --environment=$blt_environment --site=$sitename +``` + +### Convenience Function + +```bash +# Source the helper +source "/mnt/www/html/$sitegroup.$env/scripts/blt/blt-env-helper.sh" + +# Run BLT command with automatic environment mapping +run_blt_command "$sitegroup" "$env" "drupal:update" "--environment=$env" "--site=$sitename" +``` + +## Environment Mapping + +The helper automatically handles platform differences: + +| Platform | Input Env | BLT Executable | Mapped Env | +|----------|-----------|----------------|------------| +| ACE | dev | vendor/acquia/blt/bin/blt | dev | +| ACE | stage | vendor/acquia/blt/bin/blt | stage | +| ACE | prod | vendor/acquia/blt/bin/blt | prod | +| MEO | dev | scripts/blt/cgov-blt | meodev | +| MEO | stage | scripts/blt/cgov-blt | meostage | +| MEO | prod | scripts/blt/cgov-blt | meoprod | + +## Detection + +MEO environments are detected using the `AH_ENVIRONMENT_TYPE=meo` environment variable. + +## Variables Set + +After calling `setup_blt_environment`: + +- `$blt_executable` - Path to the appropriate BLT executable +- `$blt_environment` - Mapped environment name for BLT commands + +## Scripts Using This Helper + +- `scripts/post-site-install.sh` +- `scripts/site-post-code-deploy.sh` +- `scripts/z-cgov-post-update.sh` \ No newline at end of file diff --git a/scripts/blt/blt-env-helper.sh b/scripts/blt/blt-env-helper.sh new file mode 100644 index 0000000000..a267ae6a84 --- /dev/null +++ b/scripts/blt/blt-env-helper.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# +# BLT Environment Helper +# +# This script provides a reusable function to set up BLT executable and environment +# mapping for both ACE and MEO platforms. +# +# Usage: +# source /path/to/blt-env-helper.sh +# setup_blt_environment "$sitegroup" "$env" +# # Then use $blt_executable and $blt_environment in your commands +# + +## +# Sets up BLT executable path and environment name for ACE/MEO platforms +# +# Arguments: +# $1 - sitegroup (e.g., "ncigovmeo") +# $2 - environment (e.g., "dev", "stage", "ode", "prod") +# +# Sets global variables: +# blt_executable - Path to the appropriate BLT executable +# blt_environment - Mapped environment name for BLT commands +# +setup_blt_environment() { + local sitegroup="$1" + local env="$2" + + # Default to ACE setup + blt_executable="/mnt/www/html/$sitegroup.$env/vendor/acquia/blt/bin/blt" + blt_environment="$env" + + # Override for MEO environments + if [ "$AH_ENVIRONMENT_TYPE" = "meo" ]; then + blt_executable="/mnt/www/html/$sitegroup.$env/scripts/blt/cgov-blt" + + # Map environment names for MEO + case "$env" in + dev) blt_environment="meodev" ;; + stage) blt_environment="meostage" ;; + prod) blt_environment="meoprod" ;; + *) blt_environment="$env" ;; + esac + fi +} + +## +# Convenience function that runs a BLT command with proper environment setup +# +# Arguments: +# $1 - sitegroup +# $2 - environment +# $3+ - BLT command and arguments +# +# Example: +# run_blt_command "$sitegroup" "$env" "drupal:update" "--site=${sitename}" "--define" "drush.uri=$domain/" +# +run_blt_command() { + local sitegroup="$1" + local env="$2" + shift 2 + + setup_blt_environment "$sitegroup" "$env" + + # Replace --environment=* with correct mapped environment + local args=() + for arg in "$@"; do + if [[ "$arg" == --environment=* ]]; then + args+=("--environment=$blt_environment") + else + args+=("$arg") + fi + done + + echo "Running: $blt_executable ${args[*]}" + "$blt_executable" "${args[@]}" +} diff --git a/scripts/blt/cgov-blt b/scripts/blt/cgov-blt new file mode 100755 index 0000000000..141d278199 --- /dev/null +++ b/scripts/blt/cgov-blt @@ -0,0 +1,55 @@ +#!/usr/bin/env php +isVerbose()) { + $output->writeln("BLT version " . Blt::getVersion() . ""); + $output->writeln("Using CGOV ConfigInitializer for MEO environment handling"); +} + +// Initialize configuration with our custom initializer. +$config_initializer = new CustomConfigInitializer($repo_root, $input); +$config = $config_initializer->initialize(); + +// Execute command. +$blt = new Blt($config, $input, $output, $classLoader); +$status_code = (int) $blt->run($input, $output); + +if (!$input->getFirstArgument() || $input->getFirstArgument() == 'list') { + $output->writeln("To create custom BLT commands, see https://docs.acquia.com/blt/extending-blt/#adding-a-custom-robo-hook-or-command."); + $output->writeln("To add BLT commands via community plugins, see https://support.acquia.com/hc/en-us/articles/360046918614-Acquia-BLT-Plugins"); +} + +exit($status_code); \ No newline at end of file diff --git a/scripts/meo-hook-scripts/site-post-code-deploy.sh b/scripts/meo-hook-scripts/site-post-code-deploy.sh new file mode 100755 index 0000000000..42a73a276e --- /dev/null +++ b/scripts/meo-hook-scripts/site-post-code-deploy.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# +# This file is aimed to be invoked by Acquia Hosting's post-code-deploy hook. +# +# Usage: site-post-code-deploy.sh sitegroup env sitename domain + +# Exit immediately on error and enable verbose log output. +set -e + +# Map the script inputs to convenient names: +# Acquia Hosting sitegroup (application) and environment. +sitegroup="$1" +env="$2" +# The public domain name of the website. If the site uses a path based domain, +# the path is appended (without trailing slash), e.g. "domain.com/subpath". +domain="$4" + +# BLT wants the name of the website for the blt drupal:update command. +# Sitename. +sitename="$3" +# Source BLT environment helper +source "/mnt/www/html/$sitegroup.$env/scripts/blt/blt-env-helper.sh" + +# Set up BLT executable and environment mapping +setup_blt_environment "$sitegroup" "$env" + +echo "Running BLT deploy tasks on $sitename site in $env environment on the $sitegroup subscription." + +# Run blt drupal:update tasks. The trailing slash behind the domain works +# around a bug in Drush < 9.6 for path based domains: "domain.com/subpath/" is +# considered a valid URI but "domain.com/subpath" is not. +$blt_executable drupal:update --environment=$blt_environment --site=$sitename --define drush.uri=$domain/ --verbose --no-interaction -D drush.ansi=false +$blt_executable cgov:meo:db-update --environment=$blt_environment --site=${sitename} --define drush.uri=$domain/ --verbose --no-interaction -D drush.ansi=false + +# Clean up the drush cache directory. +echo "Removing temporary drush cache files." + +# @todo Exit with the status of the BLT commmand. If the exit status is non-zero, +# Site Factory will send a notification of a failed 'blt drupal:update', +# interrupting the execution of additional db-update scripts. diff --git a/scripts/utility/saml_convert.php b/scripts/utility/saml_convert.php index 742765baa8..f02a636565 100644 --- a/scripts/utility/saml_convert.php +++ b/scripts/utility/saml_convert.php @@ -7,7 +7,7 @@ // Get our current environment. if (file_exists('/var/www/site-php') && isset($_ENV['AH_SITE_ENVIRONMENT'])) { - $samlDir = "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/saml"; + $samlDir = "/mnt/files/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/cgdp/saml"; } elseif (file_exists('/var/saml')) { $samlDir = "/var/saml";