Skip to content
Snippets Groups Projects
Commit a8e0e612 authored by baldwinlouie's avatar baldwinlouie Committed by Yas Naoi
Browse files

Issue #3404490 by baldwinlouie, yas: Add Route53 service and implement ListResourceRecordsets

parent 65b9582f
No related branches found
No related tags found
No related merge requests found
......@@ -27,6 +27,10 @@ services:
class: Drupal\aws_cloud\Service\CloudFormation\CloudFormationService
arguments: ['@entity_type.manager', '@config.factory', '@lock', '@plugin.manager.cloud_config_plugin', '@cloud', '@module_handler']
aws_cloud.route53:
class: Drupal\aws_cloud\Service\Route53\Route53Service
arguments: [ '@config.factory', '@plugin.manager.cloud_config_plugin', '@module_handler' ]
aws_cloud.instance_type_price_data_provider:
class: Drupal\aws_cloud\Service\Pricing\InstanceTypePriceDataProvider
arguments: ['@aws_cloud.pricing']
......
<?php
namespace Drupal\aws_cloud\Service\Route53;
use Aws\Credentials\AssumeRoleCredentialProvider;
use Aws\Credentials\CredentialProvider;
use Aws\MockHandler;
use Aws\Result;
use Aws\ResultInterface;
use Aws\Route53\Exception\Route53Exception;
use Aws\Route53\Route53Client;
use Aws\Sts\StsClient;
use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface;
use Drupal\cloud\Service\CloudServiceBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Route53 service class that interacts with AWS Cloud Route53 API.
*/
class Route53Service extends CloudServiceBase implements Route53ServiceInterface {
/**
* 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;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Logger channel string.
*
* @var string
*/
private $loggerChannel = 'route_53';
/**
* Constructs a new Route53 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).
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
*/
public function __construct(ConfigFactoryInterface $config_factory,
CloudConfigPluginManagerInterface $cloud_config_plugin_manager,
ModuleHandlerInterface $module_handler) {
// The parent constructor takes care of $this->messenger object.
parent::__construct();
// Set up the configuration factory.
$this->configFactory = $config_factory;
// Set up the testMode flag.
$this->testMode = (bool) $this->configFactory->get('aws_cloud.settings')->get('aws_cloud_test_mode');
$this->cloudConfigPluginManager = $cloud_config_plugin_manager;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function setCloudContext($cloud_context): void {
$this->cloudContext = $cloud_context;
$this->cloudConfigPluginManager->setCloudContext($cloud_context);
}
/**
* Execute the API of AWS Cloud Route53 service.
*
* @param string $operation
* The operation to perform.
* @param array $params
* An array of parameters.
* @param array $credentials
* The array of credentials.
*
* @return \Aws\ResultInterface|null
* Result object or NULL if there is an error.
*
* @throws \Drupal\aws_cloud\Service\Route53\Route53ServiceException
* If the $route53_client (Route53) is NULL.
*/
private function execute(string $operation, array $params = [], array $credentials = []): ?ResultInterface {
$results = NULL;
$route53_client = $this->getRoute53Client($credentials);
if ($route53_client === NULL) {
throw new Route53ServiceException('No Route53 Client found. Cannot perform API operations');
}
try {
// Let other modules alter the parameters
// before they are sent through the API.
$this->moduleHandler->invokeAll('aws_cloud_pre_execute_alter', [
&$params,
$operation,
$this->cloudContext,
]);
$command = $route53_client->getCommand($operation, $params);
$results = $route53_client->execute($command);
// Let other modules alter the results before the module processes it.
$this->moduleHandler->invokeAll('aws_cloud_post_execute_alter', [
&$results,
$operation,
$this->cloudContext,
]);
}
catch (Route53Exception $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()]));
}
catch (\Exception $e) {
$this->handleException($e);
}
return $results;
}
/**
* Load and return a Route53Client.
*
* @param array $credentials
* The array of credentials.
*
* @return \Aws\Route53\Route53Client|null
* If the $params is empty or $route53_client is NULL.
*/
protected function getRoute53Client(array $credentials = []): ?Route53Client {
if (empty($credentials)) {
$credentials = $this->cloudConfigPluginManager->loadCredentials();
}
try {
$route53_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' => 'route53_client_assume_role',
],
]);
// Memoize takes care of re-authenticating when the tokens expire.
$assumeRoleCredentials = CredentialProvider::memoize($assumeRoleCredentials);
$route53_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' => 'route53_client_switch_role',
],
]);
$switchRoleCredentials = CredentialProvider::memoize($switchRoleCredentials);
$route53_params['credentials'] = $switchRoleCredentials;
}
}
elseif ($provider !== FALSE) {
$route53_params['credentials'] = $provider;
}
$route53_client = new Route53Client($route53_params);
}
catch (\Exception $e) {
$route53_client = NULL;
$this->logger($this->loggerChannel)->error($e->getMessage());
}
if ($this->testMode) {
$this->addMockHandler($route53_client);
}
return $route53_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\Route53\Route53Client $route53_client
* The Route53 client.
*
* @see https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_handlers-and-middleware.html
*/
private function addMockHandler(Route53Client $route53_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 Route53Exception('Route53Exception by Route53::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);
$route53_client
->getHandlerList()
->setHandler(new MockHandler($results_on_queue));
}
/**
* {@inheritdoc}
*/
public function listResourceRecordSets(array $params = []): ?ResultInterface {
return $this->execute('ListResourceRecordSets', $params);
}
/**
* {@inheritdoc}
*/
public function listHostedZonesByName(array $params = []): ?ResultInterface {
return $this->execute('ListHostedZonesByName', $params);
}
}
<?php
namespace Drupal\aws_cloud\Service\Route53;
/**
* AWS Cloud Route53 service exception.
*/
class Route53ServiceException extends \Exception {
}
<?php
namespace Drupal\aws_cloud\Service\Route53;
use Aws\ResultInterface;
/**
* Route53 service interface to interact with AWS Cloud Route53 API.
*/
interface Route53ServiceInterface {
/**
* Set the cloud context.
*
* @param string $cloud_context
* Cloud context string.
*/
public function setCloudContext(string $cloud_context): void;
/**
* Calls Amazon Route53 API and list resource records.
*
* @param array $params
* Parameters array to send to API.
*
* @return \Aws\ResultInterface|null
* An array of results or NULL if there is an error.
*
* @throws \Drupal\aws_cloud\Service\Route53\Route53ServiceException
* Exception if there is an error.
*/
public function listResourceRecordSets(array $params = []): ?ResultInterface;
/**
* Calls Amazon Route53 API and list hosted zones.
*
* @param array $params
* Parameters array to send to API.
*
* @return \Aws\ResultInterface|null
* An array of results or NULL if there is an error.
*
* @throws \Drupal\aws_cloud\Service\Route53\Route53ServiceException
* Exception if there is an error.
*/
public function listHostedZonesByName(array $params = []): ?ResultInterface;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment