config_preview_deploy-1.0.0-alpha3/config_preview_deploy.install
config_preview_deploy.install
<?php
/**
* @file
* Install, update and uninstall functions for the config_preview_deploy module.
*/
use Drupal\key\Entity\Key;
use Drupal\consumers\Entity\Consumer;
use Drupal\simple_oauth\Entity\Oauth2Scope;
// Include constants for requirements.
require_once DRUPAL_ROOT . '/core/includes/install.inc';
/**
* Implements hook_install().
*/
function config_preview_deploy_install() {
// Ensure required modules are installed first.
$module_installer = \Drupal::service('module_installer');
if (!\Drupal::moduleHandler()->moduleExists('simple_oauth')) {
$module_installer->install(['simple_oauth']);
}
if (!\Drupal::moduleHandler()->moduleExists('key')) {
$module_installer->install(['key']);
}
// Create OAuth scope and consumer for config deployment.
_config_preview_deploy_create_oauth_scope();
_config_preview_deploy_create_oauth_consumer();
}
/**
* Implements hook_uninstall().
*/
function config_preview_deploy_uninstall() {
// Remove OAuth consumer and scope.
_config_preview_deploy_remove_oauth_consumer();
_config_preview_deploy_remove_oauth_scope();
}
/**
* Creates OAuth2 scope entity for config deployment.
*/
function _config_preview_deploy_create_oauth_scope() {
try {
// Check if scope already exists.
$existing = \Drupal::entityTypeManager()
->getStorage('oauth2_scope')
->load('config_preview_deploy');
if ($existing) {
\Drupal::logger('config_preview_deploy')->info('OAuth2 scope already exists, skipping creation.');
return;
}
// Create OAuth2 scope entity.
$scope = Oauth2Scope::create([
'name' => 'config_preview_deploy',
'description' => 'Deploy configuration changes from preview environment to production',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Securely deploy your configuration changes to the production website',
],
],
'umbrella' => FALSE,
'granularity_id' => 'permission',
'granularity_configuration' => [
'permission' => 'accept config deployments',
],
]);
$scope->save();
\Drupal::logger('config_preview_deploy')->info('Created OAuth2 scope entity: @name', [
'@name' => $scope->getName(),
]);
}
catch (\Exception $e) {
\Drupal::logger('config_preview_deploy')->error('Failed to create OAuth2 scope: @message', [
'@message' => $e->getMessage(),
]);
}
}
/**
* Creates OAuth consumer for config deployment operations.
*/
function _config_preview_deploy_create_oauth_consumer() {
try {
// Check if consumer already exists.
$existing = \Drupal::entityTypeManager()
->getStorage('consumer')
->loadByProperties(['client_id' => 'config_preview_deploy']);
if (!empty($existing)) {
\Drupal::logger('config_preview_deploy')->info('OAuth consumer already exists, skipping creation.');
return;
}
// Generate secure client secret.
$client_secret = bin2hex(random_bytes(32));
// Create OAuth consumer with config deployment scope.
$consumer = Consumer::create([
'label' => 'Config Preview Deploy',
'client_id' => 'config_preview_deploy',
'secret' => $client_secret,
// This will be configured per environment.
'redirect' => '',
'scopes' => ['config_preview_deploy'],
'is_default' => FALSE,
'third_party' => FALSE,
'confidential' => TRUE,
// Always require user approval.
'automatic_authorization' => FALSE,
// Ask for approval every time.
'remember_approval' => FALSE,
'grant_types' => ['authorization_code'],
'description' => 'OAuth consumer for secure configuration deployment between preview and production environments.',
]);
$consumer->save();
\Drupal::logger('config_preview_deploy')->info('Created OAuth consumer for config deployment with client_id: @client_id', [
'@client_id' => 'config_preview_deploy',
]);
// Store client secret using Key module.
_config_preview_deploy_create_deployment_key($client_secret);
// Store other OAuth configuration.
$config = \Drupal::configFactory()->getEditable('config_preview_deploy.oauth');
$config->set('client_id', 'config_preview_deploy');
$config->set('scope', 'config_preview_deploy');
$config->save();
}
catch (\Exception $e) {
\Drupal::logger('config_preview_deploy')->error('Failed to create OAuth consumer: @message', [
'@message' => $e->getMessage(),
]);
}
}
/**
* Removes OAuth consumer for config deployment operations.
*/
function _config_preview_deploy_remove_oauth_consumer() {
try {
// Find and delete the consumer.
$consumers = \Drupal::entityTypeManager()
->getStorage('consumer')
->loadByProperties(['client_id' => 'config_preview_deploy']);
foreach ($consumers as $consumer) {
$consumer->delete();
\Drupal::logger('config_preview_deploy')->info('Removed OAuth consumer: @label', [
'@label' => $consumer->label(),
]);
}
// Remove OAuth configuration.
\Drupal::configFactory()->getEditable('config_preview_deploy.oauth')->delete();
}
catch (\Exception $e) {
\Drupal::logger('config_preview_deploy')->error('Failed to remove OAuth consumer: @message', [
'@message' => $e->getMessage(),
]);
}
}
/**
* Removes OAuth2 scope entity for config deployment.
*/
function _config_preview_deploy_remove_oauth_scope() {
try {
// Find and delete the scope.
$scope = \Drupal::entityTypeManager()
->getStorage('oauth2_scope')
->load('config_preview_deploy');
if ($scope) {
$scope->delete();
\Drupal::logger('config_preview_deploy')->info('Removed OAuth2 scope: @name', [
'@name' => $scope->getName(),
]);
}
}
catch (\Exception $e) {
\Drupal::logger('config_preview_deploy')->error('Failed to remove OAuth2 scope: @message', [
'@message' => $e->getMessage(),
]);
}
}
/**
* Creates deployment key using Key module.
*
* Only creates config-stored key if DRUPAL_CONFIG_DEPLOY_SECRET env var is not
* set.
*
* @param string $client_secret
* The generated client secret to store as fallback.
*/
function _config_preview_deploy_create_deployment_key($client_secret) {
// Check if environment variable is set.
$env_secret = getenv('DRUPAL_CONFIG_DEPLOY_SECRET');
if ($env_secret) {
// Environment variable is set, create key that uses env provider.
$key = Key::create([
'id' => 'config_deploy_secret',
'label' => 'Configuration Deployment Secret',
'description' => 'Secret for secure configuration deployment authentication',
'key_type' => 'authentication',
'key_provider' => 'env',
'key_provider_settings' => [
'env_variable' => 'DRUPAL_CONFIG_DEPLOY_SECRET',
],
]);
$key->save();
\Drupal::logger('config_preview_deploy')->info('Created deployment key using environment variable DRUPAL_CONFIG_DEPLOY_SECRET');
}
else {
// No environment variable, store in configuration.
$key = Key::create([
'id' => 'config_deploy_secret',
'label' => 'Configuration Deployment Secret',
'description' => 'Secret for secure configuration deployment authentication',
'key_type' => 'authentication',
'key_provider' => 'config',
'key_provider_settings' => [],
]);
$key->save();
// Set the key value using the Key module API.
$key->setKeyValue($client_secret);
$key->save();
\Drupal::logger('config_preview_deploy')->info('Created deployment key using configuration storage (set DRUPAL_CONFIG_DEPLOY_SECRET env var for better security)');
}
}
/**
* Switch from static to dynamic OAuth scopes and fix consumer configuration.
*/
function config_preview_deploy_update_10011() {
// Ensure required modules are installed first.
$module_installer = \Drupal::service('module_installer');
if (!\Drupal::moduleHandler()->moduleExists('simple_oauth')) {
$module_installer->install(['simple_oauth']);
}
// Note: We use dynamic scopes (config entities), not static scopes.
if (!\Drupal::moduleHandler()->moduleExists('key')) {
$module_installer->install(['key']);
}
// Create OAuth2 scope entity (dynamic scope).
_config_preview_deploy_create_oauth_scope();
// Disable static scope module if enabled.
if (\Drupal::moduleHandler()->moduleExists('simple_oauth_static_scope')) {
$module_installer->uninstall(['simple_oauth_static_scope']);
\Drupal::logger('config_preview_deploy')->info('Disabled simple_oauth_static_scope module in favor of dynamic scopes');
}
// Set scope provider to dynamic.
\Drupal::configFactory()->getEditable('simple_oauth.settings')
->set('scope_provider', 'dynamic')
->save();
// Find existing OAuth consumer and fix/create it.
$consumers = \Drupal::entityTypeManager()
->getStorage('consumer')
->loadByProperties(['client_id' => 'config_preview_deploy']);
if (!empty($consumers)) {
$consumer = reset($consumers);
// Get or generate client secret.
$key_storage = \Drupal::entityTypeManager()->getStorage('key');
$existing_key = $key_storage->load('config_deploy_secret');
if ($existing_key) {
$client_secret = $existing_key->getKeyValue();
}
else {
$client_secret = bin2hex(random_bytes(32));
_config_preview_deploy_create_deployment_key($client_secret);
}
// Preserve existing redirect URIs.
$existing_redirects = $consumer->get('redirect')->getValue();
// Fix the consumer configuration.
$consumer->set('third_party', FALSE);
$consumer->set('confidential', TRUE);
$consumer->set('grant_types', ['authorization_code']);
$consumer->set('secret', $client_secret);
// Only reset redirect if none exist, otherwise preserve existing ones.
if (empty($existing_redirects)) {
$consumer->set('redirect', '');
}
$consumer->set('scopes', ['config_preview_deploy']);
// Always require user approval.
$consumer->set('automatic_authorization', FALSE);
// Ask for approval every time.
$consumer->set('remember_approval', FALSE);
$consumer->save();
\Drupal::logger('config_preview_deploy')->info('Updated OAuth consumer: third_party=FALSE, confidential=TRUE, grant_types=authorization_code, secret=set');
}
else {
// Create new consumer with correct settings.
_config_preview_deploy_create_oauth_consumer();
}
// Clear all caches to pick up dynamic scope configuration.
\Drupal::cache('discovery')->deleteAll();
if (\Drupal::hasService('plugin.manager.oauth2_scope')) {
\Drupal::service('plugin.manager.oauth2_scope')->clearCachedDefinitions();
}
\Drupal::service('cache_tags.invalidator')->invalidateTags(['oauth2_scope_plugins', 'oauth2_scope']);
\Drupal::logger('config_preview_deploy')->info('Switched to dynamic OAuth scopes and updated consumer configuration.');
}
/**
* Implements hook_requirements().
*/
function config_preview_deploy_requirements($phase) {
$requirements = [];
if ($phase === 'runtime') {
/** @var \Drupal\config_preview_deploy\Service\PatchTool $patchTool */
$patchTool = \Drupal::service('config_preview_deploy.patch_tool');
$toolInfo = $patchTool->getToolInfo();
$requirements['config_preview_deploy_patch_tool'] = [
'title' => t('Config Preview Deploy - Patch Tool'),
'severity' => $toolInfo['available'] ? REQUIREMENT_OK : REQUIREMENT_ERROR,
'value' => $toolInfo['available'] ? t('Available (@version)', ['@version' => $toolInfo['version']]) : t('Not Available'),
'description' => $toolInfo['available']
? t('GNU patch tool is available and working correctly.')
: t('GNU patch tool is required for Config Preview Deploy. Install with: <code>apt install patch</code> (Debian/Ubuntu) or <code>yum install patch</code> (CentOS/RHEL) or <code>brew install gpatch</code> (macOS).'),
];
// Check if OAuth consumer exists (only if Simple OAuth module is enabled).
$entity_type_manager = \Drupal::entityTypeManager();
if ($entity_type_manager->hasDefinition('consumer')) {
$consumers = $entity_type_manager
->getStorage('consumer')
->loadByProperties(['client_id' => 'config_preview_deploy']);
if (empty($consumers)) {
$requirements['config_preview_deploy_oauth'] = [
'title' => t('Config Preview Deploy OAuth'),
'value' => t('OAuth consumer missing'),
'description' => t('OAuth consumer for config deployment is missing. Try reinstalling the module.'),
'severity' => REQUIREMENT_ERROR,
];
}
else {
$requirements['config_preview_deploy_oauth'] = [
'title' => t('Config Preview Deploy OAuth'),
'value' => t('OAuth consumer configured'),
'description' => t('OAuth authentication is properly configured for config deployment.'),
'severity' => REQUIREMENT_OK,
];
}
}
}
return $requirements;
}
