lambda-runtime-types 0.6.13

Common structures for lambda architecture
Documentation
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
struct Secret {
    host: String,
    port: u16,
    database: String,
    user: String,
    password: String,
}

struct Runner;

#[async_trait::async_trait]
impl<'a> lambda_runtime_types::rotate::RotateRunner<'a, (), Secret> for Runner {
    async fn setup(_region: &'a str) -> anyhow::Result<()> {
        simple_logger::SimpleLogger::new()
            .with_level(log::LevelFilter::Info)
            .init()
            .expect("Unable to setup logging");
        Ok(())
    }

    async fn create(
        _shared: &'a (),
        mut secret_cur: lambda_runtime_types::rotate::SecretContainer<Secret>,
        smc: &lambda_runtime_types::rotate::Smc,
    ) -> anyhow::Result<lambda_runtime_types::rotate::SecretContainer<Secret>> {
        let password = smc.generate_new_password(false, None).await?;
        secret_cur.password = password;
        Ok(secret_cur)
    }

    async fn set(
        _shared: &'a (),
        secret_cur: lambda_runtime_types::rotate::SecretContainer<Secret>,
        secret_new: lambda_runtime_types::rotate::SecretContainer<Secret>,
    ) -> anyhow::Result<()> {
        PgDatabase::new(&secret_cur)
            .await?
            .change_password(&secret_new)
            .await
    }

    async fn test(
        _shared: &'a (),
        secret_new: lambda_runtime_types::rotate::SecretContainer<Secret>,
    ) -> anyhow::Result<()> {
        PgDatabase::new(&secret_new).await?.test_connection().await
    }
}

pub fn main() -> anyhow::Result<()> {
    lambda_runtime_types::exec_tokio::<_, _, Runner, _>()
}

pub struct PgDatabase {
    client: tokio_postgres::Client,
}

impl PgDatabase {
    pub(crate) async fn new(secret: &Secret) -> anyhow::Result<Self> {
        use anyhow::Context;

        let connector = native_tls::TlsConnector::new()
            .context("Unable to prepare TLS Connection for Database")?;
        let connector = postgres_native_tls::MakeTlsConnector::new(connector);

        let (client, connection) = tokio_postgres::connect(
            &format!(
                "host={host} port={port} user={user} password={password} dbname={dbname} sslmode=require",
                host = secret.host,
                port = secret.port,
                user = secret.user,
                password = secret.password,
                dbname = secret.database,
            ),
            connector,
        )
        .await?;

        tokio::spawn(async move {
            if let Err(e) = connection.await {
                eprintln!("Unable to connecto to postgres database: {}", e);
            }
        });

        Ok(Self { client })
    }

    pub(crate) async fn change_password(&self, secret: &Secret) -> anyhow::Result<()> {
        use anyhow::Context;

        let query: &str = &format!(
            "ALTER USER {} WITH PASSWORD '{}'",
            secret.user, secret.password
        );
        self.client
            .execute(query, &[])
            .await
            .context("Unable to change user password")?;
        Ok(())
    }

    pub(crate) async fn test_connection(&self) -> anyhow::Result<()> {
        use anyhow::Context;

        self.client
            .execute("SELECT 1;", &[])
            .await
            .context("Connection to database failed")?;
        Ok(())
    }
}