cloud_terrastodon_azure 0.35.1

Helpers for interacting with Azure for the Cloud Terrastodon project
use crate::fetch_all_policy_assignments;
use crate::fetch_all_policy_set_definitions;
use cloud_terrastodon_azure_types::AzureTenantId;
use cloud_terrastodon_azure_types::DistinctByScope;
use cloud_terrastodon_azure_types::PolicyAssignment;
use cloud_terrastodon_azure_types::PolicyDefinitionIdReference;
use cloud_terrastodon_azure_types::Scope;
use cloud_terrastodon_azure_types::ScopeImpl;
use cloud_terrastodon_command::CommandBuilder;
use cloud_terrastodon_command::CommandKind;
use cloud_terrastodon_user_input::Choice;
use cloud_terrastodon_user_input::PickerTui;
use eyre::Result;
use eyre::bail;
use eyre::eyre;
use itertools::Itertools;
use rand::RngCore;
use tracing::info;

pub async fn remediate_policy_assignment(tenant_id: AzureTenantId) -> Result<()> {
    info!("Fetching policy assignments");
    let policy_assignments = fetch_all_policy_assignments(tenant_id).await?;

    info!("Building choices of policies to remediate");
    let choices = policy_assignments
        .into_iter()
        .distinct_by_scope()
        .map(|ass| Choice::<PolicyAssignment> {
            key: format!("{} {:?}", ass.name, ass.properties.display_name),
            value: ass,
        });

    info!("Prompting for user choice");
    let policy_assignment: PolicyAssignment = PickerTui::new()
        .set_header("Choose policy to remediate")
        .pick_one(choices)?;

    info!("Finding policy definition for chosen");
    match policy_assignment.properties.policy_definition_id {
        PolicyDefinitionIdReference::PolicySetDefinitionId(policy_set_definition_id) => {
            info!("Remediating a policy set - must prompt for inner choice");
            let Some(policy_set_definition) = fetch_all_policy_set_definitions(tenant_id)
                .await?
                .into_iter()
                .find(|def| def.id == policy_set_definition_id)
            else {
                bail!("Could not find policy set definition with id {policy_set_definition_id:?}");
            };

            info!("Found policy set definition - prompting for inner definitions to remediate");
            let reference_ids = policy_set_definition
                .policy_definitions
                .ok_or(eyre!(
                    "Expected {policy_set_definition_id:?} to have inner policy definitions"
                ))?
                .into_iter()
                .map(|x| Choice {
                    key: x.policy_definition_reference_id.to_owned(),
                    value: x,
                })
                .collect_vec();
            let chosen = PickerTui::new()
                .set_header("Pick the inner definitions to remediate")
                .pick_many(reference_ids)?;

            info!("Launching remediation tasks");
            for choice in chosen {
                let mut cmd = CommandBuilder::new(CommandKind::AzureCLI);
                cmd.args(["policy", "remediation", "create"]);
                cmd.args([
                    "--name",
                    format!("myRemediation{:x}", rand::rng().next_u32()).as_ref(),
                ]);
                cmd.args(["--policy-assignment", &policy_assignment.id.expanded_form()]);
                cmd.args([
                    "--definition-reference-id",
                    choice.policy_definition_reference_id.as_ref(),
                ]);
                let scope = &policy_assignment.properties.scope;
                match scope {
                    ScopeImpl::ManagementGroup(management_group_id) => {
                        cmd.args(["--management-group", &management_group_id.short_form()]);
                    }
                    ScopeImpl::ResourceGroup(resource_group_id) => {
                        cmd.args(["--resource-group", &resource_group_id.short_form()]);
                    }
                    x => {
                        bail!(
                            "Scope is of unsupported kind, expected management group or resource group, got {x:?}"
                        );
                    }
                }
                cmd.should_announce(true);
                cmd.run_raw().await?;
            }
        }
        PolicyDefinitionIdReference::PolicyDefinitionId(_policy_definition_id) => {
            info!("Remediating a policy definition");
            let mut cmd = CommandBuilder::new(CommandKind::AzureCLI);
            cmd.args(["policy", "remediation", "create"]);
            cmd.args([
                "--name",
                format!("myRemediation{:x}", rand::rng().next_u32()).as_ref(),
            ]);
            cmd.args(["--policy-assignment", &policy_assignment.id.expanded_form()]);

            let scope = &policy_assignment.properties.scope;
            match scope {
                ScopeImpl::ManagementGroup(management_group_id) => {
                    cmd.args(["--management-group", &management_group_id.short_form()]);
                }
                ScopeImpl::ResourceGroup(resource_group_id) => {
                    cmd.args(["--resource-group", &resource_group_id.short_form()]);
                }
                x => {
                    bail!(
                        "Scope is of unsupported kind, expected management group or resource group, got {x:?}"
                    );
                }
            }
            cmd.should_announce(true);
            cmd.run_raw().await?;
        }
    }

    Ok(())
}