Skip to content

Commit

Permalink
Merge pull request #56 from hiro-o918/feature/default-auth-configs
Browse files Browse the repository at this point in the history
✨ implement `__default` option to auth commands
  • Loading branch information
hiro-o918 authored Jul 30, 2022
2 parents 25b724e + 23f8347 commit b0fd5b5
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 53 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ config = {version = "0.13.1", features = ["ini", "yaml"]}
dirs = "4.0.0"
handlebars = "4.3.3"
log = "0.4.17"
maplit = "1.0.2"
once_cell = "1.13.0"
regex = "1.6.0"
rust-ini = "0.18.0"
Expand All @@ -28,6 +29,5 @@ skim = "0.9.4"
thiserror = "1.0.31"

[dev-dependencies]
maplit = "1.0.2"
rstest = "0.15.0"
tempfile = "3.3.0"
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ auth_commands:
bar: |
# In this case, name of one-login configuration is same as `profile`
onelogin-aws-login -C {{profile}} --profile {{profile}} -u [email protected]
# default configuration for profiles without auth configuration
__default: |
aws configure --profile {{profile}}
```
### Configure Completion
Expand Down
17 changes: 12 additions & 5 deletions src/aws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,20 @@ impl<P: AsRef<Path>> AWS<'_, P> {

impl<P: AsRef<Path>> ctx::CTX for AWS<'_, P> {
fn auth(&self, profile: &str) -> Result<ctx::Context, ctx::CTXError> {
let script_template = self.configs.auth_commands.get(profile).ok_or_else(|| {
ctx::CTXError::NoAuthConfiguration {
let script_template = self
.configs
.auth_commands
.get(profile)
// fallback to default configuration if a commend for the profile is not found
.or_else(|| {
self.configs
.auth_commands
.get(Configs::DEFAULT_AUTH_COMMAND_KEY)
})
.ok_or_else(|| ctx::CTXError::NoAuthConfiguration {
profile: profile.to_string(),
source: None,
}
})?;

})?;
let script = self
.reg
.render_template(script_template, &json!({ "profile": profile }))
Expand Down
85 changes: 52 additions & 33 deletions src/configs.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use dirs::home_dir;
use maplit::hashmap;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::{collections::HashMap, path::Path};

use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use config::{Config, File, FileFormat};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
Expand All @@ -19,7 +20,27 @@ pub static CONFIGS_PATH: Lazy<PathBuf> = Lazy::new(|| {
path.push(".awsctx/configs.yaml");
path
});
const CONFIGS_DESCRIPTIONS: &str = r#"# # Configurations for awsctx

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Configs {
pub auth_commands: HashMap<ProfileName, AuthScript>,
}

impl Default for Configs {
fn default() -> Self {
Self {
auth_commands: hashmap! {
Self::DEFAULT_AUTH_COMMAND_KEY.to_string() => r#"echo "This is default configuration for auth commands."
echo "You can edit this configuration on ~/.awsctx/configs.yaml according to your needs."
aws configure --profile {{profile}}
"#.to_string(),
},
}
}
}

impl Configs {
const CONFIGS_DESCRIPTIONS: &'static str = r#"# # Configurations for awsctx
# # You can manually edit configurations according to the following usage
# # To use subcommand `auth` or `refresh`, fill the below configs for each profile.
Expand All @@ -33,14 +54,13 @@ const CONFIGS_DESCRIPTIONS: &str = r#"# # Configurations for awsctx
# bar: |
# # In this case, name of one-login configuration is same as `profile`
# onelogin-aws-login -C {{profile}} --profile {{profile}} -u [email protected]
# # default configuration for profiles without auth configuration
# __default: |
# aws configure --profile {{profile}}
"#;

#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct Configs {
pub auth_commands: HashMap<ProfileName, AuthScript>,
}
pub const DEFAULT_AUTH_COMMAND_KEY: &'static str = "__default";

impl Configs {
pub fn load_configs<P: AsRef<Path>>(path: Option<P>) -> Result<Self, ctx::CTXError> {
let path = path
.map(|p| p.as_ref().to_path_buf())
Expand Down Expand Up @@ -80,22 +100,29 @@ impl Configs {
return Self::load_configs(Some(path));
}
// if the config directory does not exist, create the directory recursively
match path.parent() {
Some(parent) => fs::create_dir_all(parent)
.context("failed to create a directory of a configuration file")
.map_err(|e| ctx::CTXError::UnexpectedError { source: Some(e) })?,
None => (),
}
path.parent()
.map_or_else(
|| {
Err(anyhow!(
"no parent directory found for config path: {}",
path.to_str().unwrap()
))
},
|parent| fs::create_dir_all(parent).context("failed to create config directory"),
)
.map_err(|e| ctx::CTXError::UnexpectedError { source: Some(e) })?;

let c = Configs::default();
let mut file = fs::File::create(&path)
.context("failed to create a configuration file")
.map_err(|e| ctx::CTXError::UnexpectedError { source: Some(e) })?;
file.write_all(CONFIGS_DESCRIPTIONS.as_bytes())
file.write_all(Self::CONFIGS_DESCRIPTIONS.as_bytes())
.context("failed to write a configuration file")
.map_err(|e| ctx::CTXError::UnexpectedError { source: Some(e) })?;

let mut ser = serde_yaml::Serializer::new(&mut file);
c.serialize(&mut ser)
.context("failed to write a configuration file")
.context("failed to serialize configuration")
.map_err(|e| ctx::CTXError::UnexpectedError { source: Some(e) })?;
file.flush()
.context("failed to flush a configuration file")
Expand All @@ -116,25 +143,10 @@ mod tests {

#[fixture]
pub fn configs_text() -> String {
r#"# # Configurations for awsctx
# # You can manually edit configurations according to the following usage
# # To use subcommand `auth` or `refresh`, fill the below configs for each profile.
# auth_commands:
# # configuration for `foo` profile with aws configure
# foo: |
# # you can use pre-defined parameter `{{profile}}` which is replaced by key of this block
# # In this case, `{{profile}}` is replaced by `foo`
# aws configure --profile {{profile}}
# # configuration for `bar` profile with [onelogin-aws-cli](https://github.com/physera/onelogin-aws-cli)
# bar: |
# # In this case, name of one-login configuration is same as `profile`
# onelogin-aws-login -C {{profile}} --profile {{profile}} -u [email protected]
---
auth_commands:
r#"auth_commands:
foo: |
echo 1"#
.to_string()
.to_string()
}

#[fixture]
Expand Down Expand Up @@ -217,7 +229,14 @@ auth_commands:
# bar: |
# # In this case, name of one-login configuration is same as `profile`
# onelogin-aws-login -C {{profile}} --profile {{profile}} -u [email protected]
auth_commands: {}
# # default configuration for profiles without auth configuration
# __default: |
# aws configure --profile {{profile}}
auth_commands:
__default: |
echo "This is default configuration for auth commands."
echo "You can edit this configuration on ~/.awsctx/configs.yaml according to your needs."
aws configure --profile {{profile}}
"#;
let actual = fs::read_to_string(tmpfile).unwrap();
assert_eq!(expect, actual);
Expand Down
25 changes: 19 additions & 6 deletions tests/aws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,31 @@ use tempfile::NamedTempFile;
mod common;
use common::*;

#[rstest(input, expect)]
#[rstest(configs, input, expect)]
#[case(
configs(),
"foo",
Ok(ctx::Context {name: "foo".to_string(), active: true}),
)]
#[case(
configs(),
"bar",
Err(ctx::CTXError::InvalidConfigurations {
message: "failed to execute an auth script of profile (bar), check configurations".to_string(),
source: None
}),
)]
// baz is not defined in configs.auth_commands and default is set
#[case(
"unknown",
Err(ctx::CTXError::NoAuthConfiguration{ profile: "unknown".to_string(), source: None }),
configs(),
"baz",
Ok(ctx::Context {name: "baz".to_string(), active: true}),
)]
// baz is not defined in configs.auth_commands and default is not set
#[case(
configs_without_default(),
"baz",
Err(ctx::CTXError::NoAuthConfiguration{ profile: "baz".to_string(), source: None }),
)]
fn test_aws_auth(
configs: Rc<Configs>,
Expand All @@ -31,10 +41,10 @@ fn test_aws_auth(
) {
let aws: &dyn ctx::CTX = &AWS::new(configs, aws_credentials.path()).unwrap();
let actual = aws.auth(input);
match (expect, actual) {
match (&expect, &actual) {
(Ok(expect), Ok(actual)) => {
assert_eq!(expect, actual);
assert_eq!(expect, aws.get_active_context().unwrap())
assert_eq!(expect, &aws.get_active_context().unwrap())
}
(Err(expect), Err(actual)) => match (&expect, &actual) {
(
Expand Down Expand Up @@ -63,7 +73,10 @@ fn test_aws_auth(
}
_ => panic!("unexpected error: {}", actual),
},
_ => panic!("expect and actual are not match"),
_ => panic!(
"expect and actual are not match: expect: {:?}, actual: {:?}",
&expect, &actual
),
}
}

Expand Down
34 changes: 26 additions & 8 deletions tests/common.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use std::{
collections::HashMap,
io::{Seek, SeekFrom, Write},
rc::Rc,
};

use maplit::hashmap;
use rstest::*;
use tempfile::NamedTempFile;

use awsctx::{configs::Configs, creds::Credentials, ctx};

#[fixture]
pub fn aws_credentials_text() -> String {
r#"[bar]
r#"[baz]
aws_access_key_id=ZZZZZZZZZZZ
aws_secret_access_key=ZZZZZZZZZZZ
aws_session_token=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
[bar]
aws_access_key_id=YYYYYYYYYYY
aws_secret_access_key=YYYYYYYYYYY
aws_session_token=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
Expand Down Expand Up @@ -70,6 +75,10 @@ pub fn contexts() -> Vec<ctx::Context> {
name: "bar".to_string(),
active: false,
},
ctx::Context {
name: "baz".to_string(),
active: false,
},
ctx::Context {
name: "foo".to_string(),
active: true,
Expand All @@ -94,11 +103,20 @@ pub fn contexts_without_default() -> Vec<ctx::Context> {
#[fixture]
pub fn configs() -> Rc<Configs> {
Rc::new(Configs {
auth_commands: vec![
("foo".to_string(), "echo auth".to_string()),
("bar".to_string(), "exit 1".to_string()),
]
.into_iter()
.collect::<HashMap<String, String>>(),
auth_commands: hashmap! {
"foo".to_string() => "echo auth".to_string(),
"bar".to_string() => "exit 1".to_string(),
Configs::DEFAULT_AUTH_COMMAND_KEY.to_string() => "echo default auth".to_string(),
},
})
}

#[fixture]
pub fn configs_without_default() -> Rc<Configs> {
Rc::new(Configs {
auth_commands: hashmap! {
"foo".to_string() => "echo auth".to_string(),
"bar".to_string() => "exit 1".to_string(),
},
})
}

0 comments on commit b0fd5b5

Please sign in to comment.