Skip to content
Snippets Groups Projects

Issue #3284774 by Xiaohua Guan, yas: Deploy Cloud Orchestrator by Cloud Cluster (by CFn) (Deploy)

Merged Issue #3284774 by Xiaohua Guan, yas: Deploy Cloud Orchestrator by Cloud Cluster (by CFn) (Deploy)
All threads resolved!
Merged xiaohua guan requested to merge issue/cloud-3284774:3284774-deploy-cloud-orchestrator into 5.x
All threads resolved!
Files
12
<?php
namespace Drupal\aws_cloud\Service\CloudFormation;
use Aws\Credentials\AssumeRoleCredentialProvider;
use Aws\Credentials\CredentialProvider;
use Aws\MockHandler;
use Aws\Result;
use Aws\ResultInterface;
use Aws\CloudFormation\Exception\CloudFormationException;
use Aws\CloudFormation\CloudFormationClient;
use Aws\Sts\StsClient;
use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface;
use Drupal\cloud\Service\CloudServiceBase;
use Drupal\Core\Config\ConfigFactoryInterface;
/**
* Interacts with the AWS Cloud CloudFormation API.
*/
class CloudFormationService extends CloudServiceBase implements CloudFormationServiceInterface {
/**
* Cloud context string.
*
* @var string
*/
private $cloudContext;
/**
* The config factory.
*
* Subclasses should use the self::config() method, which may be overridden to
* address specific needs when loading config, rather than this property
* directly. See \Drupal\Core\Form\ConfigFormBase::config() for an example of
* this.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The cloud service provider plugin manager (CloudConfigPluginManager).
*
* @var \Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface
*/
private $cloudConfigPluginManager;
/**
* TRUE or FALSE whether to be in test mode.
*
* @var bool
*/
private $testMode;
/**
* Constructs a new CloudFormationService object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* A configuration factory.
* @param \Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface $cloud_config_plugin_manager
* The cloud service provider plugin manager (CloudConfigPluginManager).
*/
public function __construct(ConfigFactoryInterface $config_factory,
CloudConfigPluginManagerInterface $cloud_config_plugin_manager) {
// The parent constructor takes care of $this->messenger object.
parent::__construct();
// Setup the configuration factory.
$this->configFactory = $config_factory;
// Setup the testMode flag.
$this->testMode = (bool) $this->configFactory->get('aws_cloud.settings')->get('aws_cloud_test_mode');
$this->cloudConfigPluginManager = $cloud_config_plugin_manager;
}
/**
* {@inheritdoc}
*/
public function setCloudContext($cloud_context): void {
$this->cloudContext = $cloud_context;
$this->cloudConfigPluginManager->setCloudContext($cloud_context);
}
/**
* {@inheritdoc}
*/
public function createStack(array $params = []): ?ResultInterface {
$results = $this->execute('CreateStack', $params);
return $results;
}
/**
* Execute the API of AWS Cloud CloudFormation service.
*
* @param string $operation
* The operation to perform.
* @param array $params
* An array of parameters.
*
* @return \Aws\ResultInterface
* Result object or NULL if there is an error.
*
* @throws \Drupal\aws_cloud\Service\CloudFormation\CloudFormationServiceException
* If the $cloud_formation_client (CloudFormationClient) is NULL.
*/
private function execute(string $operation, array $params = []): ?ResultInterface {
$results = NULL;
$cloud_formation_client = $this->getCloudFormationClient();
if ($cloud_formation_client === NULL) {
throw new CloudFormationServiceException('No CloudFormation Client found. Cannot perform API operations');
}
try {
// Let other modules alter the parameters
// before they are sent through the API.
\Drupal::moduleHandler()->invokeAll('aws_cloud_pre_execute_alter', [
&$params,
$operation,
$this->cloudContext,
]);
$command = $cloud_formation_client->getCommand($operation, $params);
$results = $cloud_formation_client->execute($command);
// Let other modules alter the results before the module processes it.
\Drupal::moduleHandler()->invokeAll('aws_cloud_post_execute_alter', [
&$results,
$operation,
$this->cloudContext,
]);
}
catch (CloudFormationException $e) {
// IAM permission validation needs AwsException to be thrown as it is
// determined by the AwsErrorCode of the exception, 'DryRunOperation'.
if (!empty($params['DryRun'])) {
throw $e;
}
$this->messenger->addError($this->t('Error: The operation "@operation" could not be performed.', [
'@operation' => $operation,
]));
$this->messenger->addError($this->t('Error Info: @error_info', [
'@error_info' => $e->getAwsErrorCode(),
]));
$this->messenger->addError($this->t('Error from: @error_type-side', [
'@error_type' => $e->getAwsErrorType(),
]));
$this->messenger->addError($this->t('Status Code: @status_code', [
'@status_code' => $e->getStatusCode(),
]));
$this->messenger->addError($this->t('Message: @msg', ['@msg' => $e->getAwsErrorMessage()]));
throw $e;
}
catch (\Exception $e) {
$this->handleException($e);
throw $e;
}
return $results;
}
/**
* Load and return a CloudFormationClient.
*/
private function getCloudFormationClient(): ?CloudFormationClient {
// Use the plugin manager to load the aws credentials.
$credentials = $this->cloudConfigPluginManager->loadCredentials();
try {
$cloud_formation_params = [
'region' => $credentials['region'],
'version' => $credentials['version'],
'http' => [
'connect_timeout' => $this->connectTimeout,
],
];
$provider = FALSE;
// Load credentials if needed.
if (empty($credentials['use_instance_profile'])) {
$provider = CredentialProvider::ini('default', $credentials['ini_file']);
$provider = CredentialProvider::memoize($provider);
}
if (!empty($credentials['use_assume_role'])) {
// Assume role.
$sts_params = [
'region' => $credentials['region'],
'version' => $credentials['version'],
];
if ($provider !== FALSE) {
$sts_params['credentials'] = $provider;
}
$assumeRoleCredentials = new AssumeRoleCredentialProvider([
'client' => new StsClient($sts_params),
'assume_role_params' => [
'RoleArn' => $credentials['role_arn'],
'RoleSessionName' => 'cloud_formation_client_assume_role',
],
]);
// Memoize takes care of re-authenticating when the tokens expire.
$assumeRoleCredentials = CredentialProvider::memoize($assumeRoleCredentials);
$cloud_formation_params = [
'region' => $credentials['region'],
'version' => $credentials['version'],
'credentials' => $assumeRoleCredentials,
];
// If switch role is enabled, execute one more assume role.
if (!empty($credentials['use_switch_role'])) {
$switch_sts_params = [
'region' => $credentials['region'],
'version' => $credentials['version'],
'credentials' => $assumeRoleCredentials,
];
$switchRoleCredentials = new AssumeRoleCredentialProvider([
'client' => new StsClient($switch_sts_params),
'assume_role_params' => [
'RoleArn' => $credentials['switch_role_arn'],
'RoleSessionName' => 'cloud_formation_client_switch_role',
],
]);
$switchRoleCredentials = CredentialProvider::memoize($switchRoleCredentials);
$cloud_formation_params['credentials'] = $switchRoleCredentials;
}
}
elseif ($provider !== FALSE) {
$cloud_formation_params['credentials'] = $provider;
}
$cloud_formation_client = new CloudFormationClient($cloud_formation_params);
}
catch (\Exception $e) {
$cloud_formation_client = NULL;
$this->logger('cloud_formation_service')->error($e->getMessage());
}
if ($this->testMode) {
$this->addMockHandler($cloud_formation_client);
}
return $cloud_formation_client;
}
/**
* Add a mock handler of aws sdk for testing.
*
* The mock data of aws response is saved
* in configuration "aws_cloud_mock_data".
*
* @param \Aws\CloudFormation\CloudFormationClient $cloud_formation_client
* The CloudFormation client.
*
* @see https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_handlers-and-middleware.html
*/
private function addMockHandler(CloudFormationClient $cloud_formation_client): void {
$mock_data = $this->configFactory->get('aws_cloud.settings')->get('aws_cloud_mock_data');
if (empty($this->testMode) || empty($mock_data)) {
return;
}
$mock_data = json_decode($mock_data, TRUE);
$result = static function ($command, $request) use ($mock_data) {
$command_name = $command->getName();
$response_data = $mock_data[$command_name] ?? [];
// ErrorCode field is proprietary defined to mock AwsErrorCode.
// Checking the value of DryRun parameter as some test cases expect a
// different result based on the DryRun parameter.
if (!empty($response_data['ErrorCode'])
&& $command->hasParam('DryRun')
&& ($command->toArray())['DryRun']) {
return new CloudFormationException('CloudFormationException by CloudFormationService::addMockHandler()',
$command, ['code' => $response_data['ErrorCode']]);
}
return new Result($response_data);
};
// Set a mock handler with the number of mocked results in the queue.
// The mock queue count should be the number of API calls and is similar
// to the prepared mocked results. TO be safe, an additional queue is set
// for an async call or a repeated call.
$results_on_queue = array_pad([], count($mock_data) + 1, $result);
$cloud_formation_client
->getHandlerList()
->setHandler(new MockHandler($results_on_queue));
}
}
Loading