Skip to content

Commit

Permalink
feat: add AWS region option
Browse files Browse the repository at this point in the history
Specify an AWS region globally or for S3 object access if different than currently configured region
  • Loading branch information
PierreBeucher committed May 9, 2024
1 parent c929283 commit 074fcc0
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 16 deletions.
14 changes: 14 additions & 0 deletions docs/schema/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@
"string",
"null"
]
},
"region": {
"description": "AWS region to use. Default to currently configured region.",
"type": [
"string",
"null"
]
}
}
},
Expand Down Expand Up @@ -100,6 +107,13 @@
"key": {
"description": "S3 object key",
"type": "string"
},
"region": {
"description": "Optional bucket region name",
"type": [
"string",
"null"
]
}
}
},
Expand Down
10 changes: 10 additions & 0 deletions docs/src/config/aws.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
```
26 changes: 17 additions & 9 deletions src/modules/aws/client.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -21,7 +21,7 @@ pub trait AwsClient {

async fn assume_role(&self, role_arn: &str, session_name: &str) -> Result<AssumeRoleOutput, anyhow::Error>;

async fn get_s3_object(&self, bucket: &str, key: &str) -> Result<GetObjectOutput, anyhow::Error>;
async fn get_s3_object(&self, bucket: &str, key: &str, region: &Option<String>) -> Result<GetObjectOutput, anyhow::Error>;
}

pub async fn get_client(ctx: &NovopsContext) -> Box<dyn AwsClient + Send + Sync> {
Expand Down Expand Up @@ -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<GetObjectOutput, anyhow::Error> {
let client = get_s3_client(&self.config).await?;
async fn get_s3_object(&self, bucket: &str, key: &str, region: &Option<String>) -> Result<GetObjectOutput, anyhow::Error> {
let client = get_s3_client(&self.config, region).await?;
client.get_object()
.bucket(bucket)
.key(key)
Expand Down Expand Up @@ -135,7 +135,7 @@ impl AwsClient for DryRunAwsClient{
Ok(result)
}

async fn get_s3_object(&self, _: &str, _: &str) -> Result<GetObjectOutput, anyhow::Error> {
async fn get_s3_object(&self, _: &str, _: &str, _: &Option<String>) -> Result<GetObjectOutput, anyhow::Error> {
Ok(GetObjectOutput::builder()
.body(ByteStream::from_static(b"dummy"))
.build())
Expand Down Expand Up @@ -173,9 +173,12 @@ pub async fn get_sdk_config(client_conf: &AwsClientConfig) -> Result<aws_config:
shared_config = shared_config.profile_name(profile);
}

if let Some(region) = &client_conf.region {
shared_config = shared_config.region(Region::new(region.clone()));
}

Ok(shared_config.load().await)


}

pub async fn get_iam_client(novops_aws: &AwsClientConfig) -> Result<aws_sdk_iam::Client, anyhow::Error>{
Expand Down Expand Up @@ -206,12 +209,17 @@ pub async fn get_secretsmanager_client(novops_aws: &AwsClientConfig) -> Result<a
Ok(aws_sdk_secretsmanager::Client::new(&conf))
}

pub async fn get_s3_client(novops_aws: &AwsClientConfig) -> Result<aws_sdk_s3::Client, anyhow::Error> {
pub async fn get_s3_client(novops_aws: &AwsClientConfig, region: &Option<String>) -> Result<aws_sdk_s3::Client, anyhow::Error> {
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()))
}
13 changes: 8 additions & 5 deletions src/modules/aws/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ pub struct AwsInput {
#[derive(Default)]
pub struct AwsClientConfig {
pub profile: Option<String>,
pub endpoint: Option<String>
pub endpoint: Option<String>,
pub region: Option<String>,
}



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(),
}
}
}
Expand Down Expand Up @@ -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<String>
pub profile: Option<String>,

/// AWS region to use. Default to currently configured region.
pub region: Option<String>
}

10 changes: 9 additions & 1 deletion src/modules/aws/s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ pub struct AwsS3Object {

/// S3 object key
pub key: String,

/// Optional bucket region name
pub region: Option<String>
}

#[async_trait]
impl ResolveTo<String> for AwsS3ObjectInput {
async fn resolve(&self, ctx: &NovopsContext) -> Result<String, anyhow::Error> {
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);

Expand Down
3 changes: 3 additions & 0 deletions tests/.novops.aws_s3_object.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ environments:
aws_s3_object:
bucket: novops-test-bucket
key: path/to/var
region: eu-central-1

files:
- dest: /tmp/S3_OBJECT_AS_FILE
content:
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
2 changes: 1 addition & 1 deletion tests/test_aws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 074fcc0

Please sign in to comment.