diff --git a/docs/schema/config-schema.json b/docs/schema/config-schema.json index 1eff4f7..be4f357 100644 --- a/docs/schema/config-schema.json +++ b/docs/schema/config-schema.json @@ -71,6 +71,13 @@ "string", "null" ] + }, + "region": { + "description": "AWS region to use. Default to currently configured region.", + "type": [ + "string", + "null" + ] } } }, @@ -100,6 +107,13 @@ "key": { "description": "S3 object key", "type": "string" + }, + "region": { + "description": "Optional bucket region name", + "type": [ + "string", + "null" + ] } } }, diff --git a/docs/src/config/aws.md b/docs/src/config/aws.md index 33ccae5..886c5d7 100644 --- a/docs/src/config/aws.md +++ b/docs/src/config/aws.md @@ -22,6 +22,7 @@ You can also use `config` root element override certains configs (such as AWS en config: aws: endpoint: "http://localhost:4566/" # Use LocalStack endpoint + region: eu-central-1 # Set AWS region name ``` ## STS Assume Role @@ -105,3 +106,12 @@ environments: bucket: some-bucket key: path/to/object.json ``` + +It's also possible to specify the region in which Bucket is located if different than configured region: + +```yml +aws_s3_object: + bucket: some-bucket + key: path/to/object + region: eu-central-1 +``` diff --git a/src/modules/aws/client.rs b/src/modules/aws/client.rs index 44a5a22..c064df3 100644 --- a/src/modules/aws/client.rs +++ b/src/modules/aws/client.rs @@ -1,6 +1,6 @@ use crate::core::NovopsContext; use super::config::AwsClientConfig; -use aws_config::BehaviorVersion; +use aws_config::{BehaviorVersion, Region}; use aws_sdk_secretsmanager::operation::get_secret_value::GetSecretValueOutput; use aws_sdk_sts::{operation::assume_role::AssumeRoleOutput, types::builders::CredentialsBuilder}; use aws_sdk_ssm::{operation::get_parameter::GetParameterOutput, types::builders::ParameterBuilder}; @@ -21,7 +21,7 @@ pub trait AwsClient { async fn assume_role(&self, role_arn: &str, session_name: &str) -> Result; - async fn get_s3_object(&self, bucket: &str, key: &str) -> Result; + async fn get_s3_object(&self, bucket: &str, key: &str, region: &Option) -> Result; } pub async fn get_client(ctx: &NovopsContext) -> Box { @@ -87,8 +87,8 @@ impl AwsClient for DefaultAwsClient { .with_context(|| format!("Couldn't impersonate role {:} (session name: {:?})", role_arn, session_name)) } - async fn get_s3_object(&self, bucket: &str, key: &str) -> Result { - let client = get_s3_client(&self.config).await?; + async fn get_s3_object(&self, bucket: &str, key: &str, region: &Option) -> Result { + let client = get_s3_client(&self.config, region).await?; client.get_object() .bucket(bucket) .key(key) @@ -135,7 +135,7 @@ impl AwsClient for DryRunAwsClient{ Ok(result) } - async fn get_s3_object(&self, _: &str, _: &str) -> Result { + async fn get_s3_object(&self, _: &str, _: &str, _: &Option) -> Result { Ok(GetObjectOutput::builder() .body(ByteStream::from_static(b"dummy")) .build()) @@ -173,9 +173,12 @@ pub async fn get_sdk_config(client_conf: &AwsClientConfig) -> Result Result{ @@ -206,12 +209,17 @@ pub async fn get_secretsmanager_client(novops_aws: &AwsClientConfig) -> Result Result { +pub async fn get_s3_client(novops_aws: &AwsClientConfig, region: &Option) -> Result { let conf = get_sdk_config(novops_aws).await?; debug!("Creating AWS S3 client with config {:?}", conf); - let s3_conf = aws_sdk_s3::config::Builder::from(&conf).force_path_style(true).build(); + let mut s3_conf = aws_sdk_s3::config::Builder::from(&conf) + .force_path_style(true); + + if let Some(r) = region.clone() { + s3_conf = s3_conf.region(Region::new(r)); + }; - Ok(aws_sdk_s3::Client::from_conf(s3_conf)) + Ok(aws_sdk_s3::Client::from_conf(s3_conf.build())) } diff --git a/src/modules/aws/config.rs b/src/modules/aws/config.rs index f938aae..e4a54b8 100644 --- a/src/modules/aws/config.rs +++ b/src/modules/aws/config.rs @@ -16,16 +16,16 @@ pub struct AwsInput { #[derive(Default)] pub struct AwsClientConfig { pub profile: Option, - pub endpoint: Option + pub endpoint: Option, + pub region: Option, } - - impl From<&AwsConfig> for AwsClientConfig { fn from(cf: &AwsConfig) -> AwsClientConfig{ AwsClientConfig { profile: cf.profile.clone(), - endpoint: cf.endpoint.clone() + endpoint: cf.endpoint.clone(), + region: cf.region.clone(), } } } @@ -56,6 +56,9 @@ pub struct AwsConfig { /// /// It's advised not to use this directly as profile name configuration is higly dependent /// on local configuration. Prefer using AWS_PROFILE environment variable where needed. - pub profile: Option + pub profile: Option, + + /// AWS region to use. Default to currently configured region. + pub region: Option } diff --git a/src/modules/aws/s3.rs b/src/modules/aws/s3.rs index b81e187..619e167 100644 --- a/src/modules/aws/s3.rs +++ b/src/modules/aws/s3.rs @@ -19,13 +19,21 @@ pub struct AwsS3Object { /// S3 object key pub key: String, + + /// Optional bucket region name + pub region: Option } #[async_trait] impl ResolveTo for AwsS3ObjectInput { async fn resolve(&self, ctx: &NovopsContext) -> Result { let client = get_client(ctx).await; - let result = client.get_s3_object(&self.aws_s3_object.bucket, &self.aws_s3_object.key).await?; + + let result = client.get_s3_object( + &self.aws_s3_object.bucket, + &self.aws_s3_object.key, + &self.aws_s3_object.region + ).await?; debug!("Got file {:} from S3 bucket {:}", &self.aws_s3_object.key, &self.aws_s3_object.bucket); diff --git a/tests/.novops.aws_s3_object.yml b/tests/.novops.aws_s3_object.yml index 7870232..da6a995 100644 --- a/tests/.novops.aws_s3_object.yml +++ b/tests/.novops.aws_s3_object.yml @@ -6,6 +6,7 @@ environments: aws_s3_object: bucket: novops-test-bucket key: path/to/var + region: eu-central-1 files: - dest: /tmp/S3_OBJECT_AS_FILE @@ -13,9 +14,11 @@ environments: aws_s3_object: bucket: novops-test-bucket key: path/to/file + region: eu-central-1 config: default: environment: dev aws: endpoint: "http://localhost:4566/" # LocalStack + region: eu-central-1 diff --git a/tests/test_aws.rs b/tests/test_aws.rs index aa63452..dfebcd5 100644 --- a/tests/test_aws.rs +++ b/tests/test_aws.rs @@ -139,7 +139,7 @@ async fn test_s3_object() -> Result<(), anyhow::Error> { async fn ensure_test_s3_object_exists(bucket_name: &str, object_key: &str, content: &[u8]) -> Result<(), anyhow::Error> { - let client = get_s3_client(&aws_test_config()).await?; + let client = get_s3_client(&aws_test_config(), &None).await?; let bucket_exists = match client.head_bucket().bucket(bucket_name).send().await { Ok(_) => true,