silicon-iam-cli 1.1.0

Command-line client for Silicon IAM, built on the silicon-iam-client crate.
//! Testing environments.

use silicon_iam_client::models;

use crate::{
    cli::EnvCommand,
    context::Context,
    error::Result,
    output::{Format, Table, json, or_dash, timestamp, timestamp_or_dash},
};

/// Runs a testing environment command.
///
/// # Errors
///
/// Returns whatever the service reports.
#[allow(
    clippy::too_many_lines,
    reason = "one arm per verb; splitting the match would only move it elsewhere"
)]
pub async fn run(context: &Context, command: EnvCommand) -> Result<()> {
    // The two key-authorized commands work without a signed-in session, which
    // is the point of an environment key.
    if let EnvCommand::Current = command {
        context.require_test()?;
        let described = context.anonymous().environments().current().await?;
        return match context.format {
            Format::Json => json(&described),
            Format::Text => {
                let mut table = Table::new(["field", "value"]);
                table.row(["id", &described.id.to_string()]);
                table.row(["name", &described.name]);
                table.row(["description", &or_dash(described.description.as_deref())]);
                table.row(["key_generation", &described.key_generation.to_string()]);
                table.row(["created", &timestamp(described.created_at)]);
                table.print();
                Ok(())
            }
        };
    }

    if let EnvCommand::Clean {
        environment_id: None,
    } = command
    {
        context.require_test()?;
        let cleaned = context
            .anonymous()
            .environments()
            .clean_current(&context.mutation())
            .await?;
        return report_cleaning(context, &cleaned);
    }

    let client = context.authenticated().await?;
    let org = context.organization()?;
    match command {
        EnvCommand::Current
        | EnvCommand::Clean {
            environment_id: None,
        } => unreachable!(),
        EnvCommand::List { status, page } => {
            let listed = client
                .environments()
                .list(org, status.as_deref(), &page.paging())
                .await?;
            match context.format {
                Format::Json => json(&listed),
                Format::Text => {
                    let mut table =
                        Table::new(["id", "name", "status", "key gen", "last activity"]);
                    for entry in &listed.items {
                        table.row([
                            entry.id.to_string(),
                            entry.name.clone(),
                            format!("{:?}", entry.status).to_lowercase(),
                            entry.key_generation.to_string(),
                            timestamp(entry.last_activity_at),
                        ]);
                    }
                    table.print();
                    Ok(())
                }
            }
        }
        EnvCommand::Create { name, description } => {
            let created = client
                .environments()
                .create(
                    org,
                    &models::TestingEnvironmentCreate { name, description },
                    &context.mutation(),
                )
                .await?;
            context.remember_testing_environment(created.id, created.key.clone())?;
            match context.format {
                Format::Json => json(&created),
                Format::Text => {
                    println!("Created {} ({}).", created.name, created.id);
                    println!("Key: {}", created.key);
                    println!(
                        "This device stored the key. Enter it with: iam --test {} <command>",
                        created.id
                    );
                    Ok(())
                }
            }
        }
        EnvCommand::Show { environment_id } => {
            let environment = client.environments().get(org, environment_id).await?;
            report(context, &environment)
        }
        EnvCommand::Update {
            environment_id,
            name,
            description,
        } => {
            let current = client.environments().get(org, environment_id).await?;
            let updated = client
                .environments()
                .update(
                    org,
                    environment_id,
                    current.version,
                    &models::TestingEnvironmentPatch { name, description },
                    &context.mutation(),
                )
                .await?;
            report(context, &updated)
        }
        EnvCommand::Delete { environment_id } => {
            let retired = client
                .environments()
                .delete(org, environment_id, &context.mutation())
                .await?;
            match context.format {
                Format::Json => json(&retired),
                Format::Text => {
                    println!("Retired {}.", retired.name);
                    if let Some(purge_after) = retired.purge_after {
                        println!(
                            "Recoverable with `iam env restore {}` until {}.",
                            retired.id,
                            timestamp(purge_after)
                        );
                    }
                    Ok(())
                }
            }
        }
        EnvCommand::Restore { environment_id } => {
            let restored = client
                .environments()
                .restore(org, environment_id, &context.mutation())
                .await?;
            report(context, &restored)
        }
        EnvCommand::Key { environment_id } => {
            let key = client.environments().key(org, environment_id).await?;
            context.remember_testing_environment(environment_id, key.key.clone())?;
            match context.format {
                Format::Json => json(&key),
                Format::Text => {
                    println!("{}", key.key);
                    Ok(())
                }
            }
        }
        EnvCommand::RotateKey { environment_id } => {
            let rotated = client
                .environments()
                .rotate_key(org, environment_id, &context.mutation())
                .await?;
            context.remember_testing_environment(environment_id, rotated.key.clone())?;
            match context.format {
                Format::Json => json(&rotated),
                Format::Text => {
                    println!("Key: {}", rotated.key);
                    println!("The previous key stopped working.");
                    Ok(())
                }
            }
        }
        EnvCommand::Clean {
            environment_id: Some(environment_id),
        } => {
            let cleaned = client
                .environments()
                .clean(org, environment_id, &context.mutation())
                .await?;
            report_cleaning(context, &cleaned)
        }
    }
}

fn report(context: &Context, environment: &models::TestingEnvironment) -> Result<()> {
    match context.format {
        Format::Json => json(environment),
        Format::Text => {
            let mut table = Table::new(["field", "value"]);
            table.row(["id", &environment.id.to_string()]);
            table.row(["name", &environment.name]);
            table.row(["description", &or_dash(environment.description.as_deref())]);
            table.row([
                "status",
                &format!("{:?}", environment.status).to_lowercase(),
            ]);
            table.row(["key_generation", &environment.key_generation.to_string()]);
            table.row(["last_activity", &timestamp(environment.last_activity_at)]);
            table.row(["purge_after", &timestamp_or_dash(environment.purge_after)]);
            table.row(["version", &environment.version.to_string()]);
            table.print();
            Ok(())
        }
    }
}

fn report_cleaning(context: &Context, cleaning: &models::TestingEnvironmentCleaning) -> Result<()> {
    match context.format {
        Format::Json => json(cleaning),
        Format::Text => {
            println!("Erased {} rows.", cleaning.erased_rows);
            Ok(())
        }
    }
}