use anyhow::{anyhow, Result};
use log::{error, info};
use rusoto_core::RusotoError;
use rusoto_ssm::GetParameterRequest;
use rusoto_ssm::PutParameterRequest;
use rusoto_ssm::{Ssm, SsmClient};
#[derive(Debug, Clone)]
pub struct Parameter {
pub name: String,
pub value: String,
pub description: String,
pub is_secure: bool,
}
impl Parameter {
pub fn new<S>(name: S, value: S, description: S, is_secure: bool) -> Parameter
where
S: Into<String>,
{
Parameter {
name: name.into(),
value: value.into(),
description: description.into(),
is_secure,
}
}
pub async fn update(&self, client: &SsmClient) -> Result<String> {
if self.needs_updating(client).await? {
info!("Parameter {} needs updating", self.name);
let parameter_request = self.to_put_parameter_request();
match client.put_parameter(parameter_request).await {
Ok(_parameter_result) => info!("Parameter {} successfully updated", self.name),
Err(error) => error!("Parameter {} failed to update: {}", self.name, error),
}
} else {
info!("Parameter {} does not need updating", self.name);
}
Ok(self.name.clone())
}
async fn needs_updating(&self, client: &SsmClient) -> Result<bool> {
match client.get_parameter(self.to_get_parameter_request()).await {
Ok(parameter_result) => match parameter_result.parameter {
Some(parameter) => match parameter.value {
Some(parameter_value) => {
info!(
"Found parameter {} with existing value: {}",
self.name, ¶meter_value
);
Ok(self.value != parameter_value)
}
None => Err(anyhow!("No value was found")),
},
None => Err(anyhow!("No parameter was returned")),
},
Err(error) => {
match error {
RusotoError::Credentials(credential_error) => error!(
"Error with credentials {}: {:?}",
self.name, credential_error.message
),
_ => error!("Could not retrieve parameter {}: {:?}", self.name, error),
};
Err(anyhow!("Failed while fetching parameter"))
}
}
}
fn to_get_parameter_request(&self) -> GetParameterRequest {
GetParameterRequest {
name: self.name.clone(),
with_decryption: Some(true), }
}
fn to_put_parameter_request(&self) -> PutParameterRequest {
PutParameterRequest {
name: self.name.clone(),
value: self.value.clone(),
description: Some(self.description.clone()),
type_: if self.is_secure {
Some("SecureString".into())
} else {
Some("String".into())
},
overwrite: Some(true), allowed_pattern: None,
key_id: None,
policies: None,
tags: None,
tier: None,
data_type: None,
}
}
}
#[cfg(test)]
mod tests {
use crate::parameter::Parameter;
#[test]
fn get_parameter_request_sets_with_decryption_true() {
let secure_parameter = Parameter::new("test_name", "test_value", "test_description", true);
let request = secure_parameter.to_get_parameter_request();
assert!(request.with_decryption.unwrap());
}
#[test]
fn get_parameter_request_sets_name() {
let secure_parameter = Parameter::new("test_name", "test_value", "test_description", true);
let request = secure_parameter.to_get_parameter_request();
assert_eq!(request.name, "test_name".to_string());
}
#[test]
fn put_parameter_request_sets_overwrite_true() {
let secure_parameter = Parameter::new("test_name", "test_value", "test_description", true);
let request = secure_parameter.to_put_parameter_request();
assert!(request.overwrite.unwrap());
}
#[test]
fn put_parameter_request_is_secure_sets_type_secure_string() {
let secure_parameter = Parameter::new("test_name", "test_value", "test_description", true);
let request = secure_parameter.to_put_parameter_request();
assert_eq!(request.type_, Some("SecureString".into()));
}
#[test]
fn put_parameter_request_is_not_secure_sets_type_string() {
let secure_parameter = Parameter::new("test_name", "test_value", "test_description", false);
let request = secure_parameter.to_put_parameter_request();
assert_eq!(request.type_, Some("String".into()));
}
}