diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 676aa3df..296925ee 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1290,7 +1290,7 @@ dependencies = [ [[package]] name = "nitor-vault" -version = "1.3.1" +version = "1.4.0" dependencies = [ "aes-gcm", "anyhow", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 85d3b639..d6696a47 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nitor-vault" -version = "1.3.1" +version = "1.4.0" edition = "2021" description = "Encrypted AWS key-value storage utility." license = "Apache-2.0" diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 3e5e710e..eb459c02 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -6,7 +6,7 @@ use aws_sdk_cloudformation::types::StackStatus; use colored::Colorize; use tokio::time::Duration; -use nitor_vault::{cloudformation, CreateStackResult, Value, Vault}; +use nitor_vault::{cloudformation, CreateStackResult, UpdateStackResult, Value, Vault}; static WAIT_ANIMATION_DURATION: Duration = Duration::from_millis(500); static CLEAR_LINE: &str = "\x1b[2K"; @@ -44,12 +44,30 @@ pub async fn init_vault_stack( /// Update existing Cloudformation vault stack and wait for update to finish. pub async fn update_vault_stack(vault: &Vault) -> Result<()> { - vault + match vault .update_stack() .await - .with_context(|| "Failed to update vault stack".red())?; - - wait_for_stack_update_to_finish(vault).await + .with_context(|| "Failed to update vault stack".red())? + { + UpdateStackResult::UpToDate { data } => { + println!("{}", "Vault stack is up to date:".bold()); + println!("{data}"); + Ok(()) + } + UpdateStackResult::Update { + stack_id, + previous_version: current_version, + new_version, + } => { + println!( + "{}", + format!("Updating vault stack from version {current_version} to {new_version}") + .bold() + ); + println!("{stack_id}"); + wait_for_stack_update_to_finish(vault).await + } + } } /// Store a key-value pair. @@ -225,7 +243,7 @@ async fn wait_for_stack_update_to_finish(vault: &Vault) -> Result<()> { } StackStatus::UpdateFailed | StackStatus::RollbackFailed => { println!("{CLEAR_LINE}{stack_data}"); - anyhow::bail!("Stack update failed"); + anyhow::bail!("Stack update failed".red()); } _ => { // Print status if it has changed diff --git a/rust/src/cloudformation.rs b/rust/src/cloudformation.rs index 64edd4e7..25ad1edf 100644 --- a/rust/src/cloudformation.rs +++ b/rust/src/cloudformation.rs @@ -93,7 +93,7 @@ impl fmt::Display for CloudFormationStackData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "status: {}\nbucket: {}\nkey ARN: {}\nversion: {}{}", + "status: {}\nbucket: {}\nkey: {}\nversion: {}{}", self.status .as_ref() .map_or("None".to_string(), std::string::ToString::to_string), diff --git a/rust/src/lib.rs b/rust/src/lib.rs index d9aa2ad6..77f9e172 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -31,6 +31,19 @@ pub enum CreateStackResult { }, } +#[derive(Debug, Clone)] +/// Result data for updating the vault stack. +pub enum UpdateStackResult { + /// Vault stack is up to date. + UpToDate { data: CloudFormationStackData }, + /// Vault stack was updated. + Update { + stack_id: String, + previous_version: u32, + new_version: u32, + }, +} + #[derive(Debug, Clone)] pub(crate) struct EncryptObject { data_key: Vec, diff --git a/rust/src/vault.rs b/rust/src/vault.rs index 84ecb3d1..276707be 100644 --- a/rust/src/vault.rs +++ b/rust/src/vault.rs @@ -23,7 +23,7 @@ use crate::cloudformation::{CloudFormationParams, CloudFormationStackData}; use crate::errors::VaultError; use crate::template::{template, VAULT_STACK_VERSION}; use crate::value::Value; -use crate::{CreateStackResult, EncryptObject, Meta, S3DataKeys}; +use crate::{CreateStackResult, EncryptObject, Meta, S3DataKeys, UpdateStackResult}; #[derive(Debug)] pub struct Vault { @@ -99,6 +99,9 @@ impl Vault { /// Initialize new Vault stack. /// This will create all required resources in AWS, /// after which the Vault can be used to store and lookup values. + /// + /// Returns a `CreateStackResult` with relevant data whether a new vault stack was initialized, + /// or it already exists. pub async fn init( vault_stack: Option, region: Option, @@ -176,10 +179,13 @@ impl Vault { }) } - pub async fn update_stack(&self) -> Result<(), VaultError> { + /// Update the vault Cloudformation stack with the current template. + /// + /// Returns an `UpdateStackResult` enum that indicates if the vault was updated, + /// or is already up to date. + pub async fn update_stack(&self) -> Result { let stack_name = &self.cloudformation_params.stack_name; let stack_data = cloudformation::get_stack_data(&self.cf, stack_name).await?; - println!("{stack_data}"); let deployed_version = stack_data .version .map_or_else(|| Err(VaultError::StackVersionNotFoundError), Ok)?; @@ -201,16 +207,15 @@ impl Vault { .send() .await?; - if let Some(stack_id) = response.stack_id { - println!("{stack_id}"); - } - - println!("Updated vault stack '{stack_name}' version from {deployed_version} to {VAULT_STACK_VERSION}"); + let stack_id = response.stack_id.ok_or(VaultError::MissingStackIdError)?; + Ok(UpdateStackResult::Update { + stack_id, + previous_version: deployed_version, + new_version: VAULT_STACK_VERSION, + }) } else { - println!("Current stack version {deployed_version} does not need to be updated to version {VAULT_STACK_VERSION}"); + Ok(UpdateStackResult::UpToDate { data: stack_data }) } - - Ok(()) } /// Get Cloudformation stack status. @@ -342,6 +347,7 @@ impl Vault { } } + /// Return AWS SDK config with optional region name to use. pub async fn get_aws_config(region: Option) -> SdkConfig { aws_config::from_env() .region(get_region_provider(region))