cloud-8.x-2.0-beta1/modules/cloud_service_providers/aws_cloud/src/Service/S3/S3Service.php
modules/cloud_service_providers/aws_cloud/src/Service/S3/S3Service.php
<?php
namespace Drupal\aws_cloud\Service\S3;
use Aws\S3\S3Client;
use Aws\S3\Exception\S3Exception;
use Aws\MockHandler;
use Aws\Result;
use Aws\Sts\StsClient;
use Aws\Credentials\AssumeRoleCredentialProvider;
use Aws\Credentials\CredentialProvider;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\Messenger;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface;
/**
* S3Service service interacts with the AWS Cloud S3 API.
*/
class S3Service implements S3ServiceInterface {
use StringTranslationTrait;
/**
* The Messenger service.
*
* @var \Drupal\Core\Messenger\Messenger
*/
private $messenger;
/**
* 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;
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* TRUE to run the operation, FALSE to run the operation in validation mode.
*
* @var bool
*/
private $dryRun;
/**
* Constructs a new S3Service object.
*
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* A logger instance.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* A configuration factory.
* @param \Drupal\Core\Messenger\Messenger $messenger
* The messenger service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface $cloud_config_plugin_manager
* The cloud service provider plugin manager (CloudConfigPluginManager).
*/
public function __construct(
LoggerChannelFactoryInterface $logger_factory,
ConfigFactoryInterface $config_factory,
Messenger $messenger,
TranslationInterface $string_translation,
CloudConfigPluginManagerInterface $cloud_config_plugin_manager
) {
// Setup the logger.
$this->logger = $logger_factory->get('aws_s3_service');
// Setup the configuration factory.
$this->configFactory = $config_factory;
// Setup the dryRun flag.
$this->dryRun = (bool) $this->configFactory->get('aws_cloud.settings')->get('aws_cloud_test_mode');
// Setup the messenger.
$this->messenger = $messenger;
// Setup the $this->t()
$this->stringTranslation = $string_translation;
$this->cloudConfigPluginManager = $cloud_config_plugin_manager;
}
/**
* {@inheritdoc}
*/
public function setCloudContext($cloud_context) {
$this->cloudContext = $cloud_context;
$this->cloudConfigPluginManager->setCloudContext($cloud_context);
}
/**
* {@inheritdoc}
*/
public function getObject(array $params = []) {
$results = $this->execute('GetObject', $params);
return $results;
}
/**
* {@inheritdoc}
*/
public function putObject(array $params = []) {
$results = $this->execute('PutObject', $params);
return $results;
}
/**
* {@inheritdoc}
*/
public function listObjects(array $params = []) {
$results = $this->execute('ListObjects', $params);
return $results;
}
/**
* {@inheritdoc}
*/
public function deleteMatchingObjects(array $params = []) {
$results = $this->execute('DeleteMatchingObjects', $params);
return $results;
}
/**
* Execute the API of AWS Cloud S3 Service.
*
* @param string $operation
* The operation to perform.
* @param array $params
* An array of parameters.
*
* @return array
* Array of execution result or NULL if there is an error.
*
* @throws \Drupal\aws_cloud\Service\S3\S3ServiceException
* If the S3 Client is null.
*/
private function execute($operation, array $params = []) {
$results = NULL;
$s3_client = $this->getS3Client();
if ($s3_client == NULL) {
throw new S3ServiceException('No S3 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,
]);
if ($operation == 'DeleteMatchingObjects') {
$s3_client->deleteMatchingObjects($params['Bucket'], $params['Prefix']);
}
else {
$command = $s3_client->getCommand($operation, $params);
$results = $s3_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 (S3Exception $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->messenger->addError($e->getMessage());
}
return $results;
}
/**
* Load and return an S3Client.
*/
private function getS3Client() {
// Use the plugin manager to load the aws credentials.
$credentials = $this->cloudConfigPluginManager->loadCredentials();
try {
$s3_params = [
'region' => $credentials['region'],
'version' => $credentials['version'],
];
$provider = FALSE;
// Load credentials if needed.
if ($credentials['use_instance_credentials'] == FALSE) {
$provider = CredentialProvider::ini('default', $credentials['ini_file']);
$provider = CredentialProvider::memoize($provider);
}
if ($credentials['assume_role'] == TRUE) {
// 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' => 's3_client_assume_role',
],
]);
// Memoize takes care of re-authenticating when the tokens expire.
$assumeRoleCredentials = CredentialProvider::memoize($assumeRoleCredentials);
$s3_params = [
'region' => $credentials['region'],
'version' => $credentials['version'],
'credentials' => $assumeRoleCredentials,
];
// If switch role is enabled, execute one more assume role.
if ($credentials['switch_role'] == TRUE) {
$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' => 's3_client_switch_role',
],
]);
$switchRoleCredentials = CredentialProvider::memoize($switchRoleCredentials);
$s3_params['credentials'] = $switchRoleCredentials;
}
}
elseif ($provider != FALSE) {
$s3_params['credentials'] = $provider;
}
$s3_client = new S3Client($s3_params);
}
catch (\Exception $e) {
$s3_client = NULL;
$this->logger->error($e->getMessage());
}
$this->addMockHandler($s3_client);
return $s3_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\S3\S3Client $s3_client
* The S3 client.
*
* @see https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_handlers-and-middleware.html
*/
private function addMockHandler(S3Client $s3_client) {
$mock_data = $this->configFactory->get('aws_cloud.settings')->get('aws_cloud_mock_data');
if ($this->dryRun && $mock_data) {
$func = function ($command, $request) {
$mock_data = \Drupal::service('config.factory')
->get('aws_cloud.settings')
->get('aws_cloud_mock_data');
$mock_data = json_decode($mock_data, TRUE);
// If the mock data of a command is defined.
$command_name = $command->getName();
if (isset($mock_data[$command_name])) {
$result_data = $mock_data[$command_name];
return new Result($result_data);
}
else {
return new Result();
}
};
// Set mock handler.
$s3_client->getHandlerList()->setHandler(new MockHandler([$func, $func]));
}
}
}
