cloud_terrastodon_azure 0.36.0

Helpers for interacting with Azure for the Cloud Terrastodon project
use cloud_terrastodon_azure_types::AzureProviderOperationsMetadata;
use cloud_terrastodon_azure_types::AzureTenantId;
use cloud_terrastodon_command::CacheKey;
use cloud_terrastodon_command::CacheableCommand;
use cloud_terrastodon_command::async_trait;
use cloud_terrastodon_rest::RestRequest;
use eyre::Result;
use serde::Deserialize;
use std::path::PathBuf;
use tracing::debug;

const PROVIDER_OPERATIONS_API_VERSION: &str = "2022-04-01";

#[must_use = "This is a future request, you must .await it"]
pub struct RoleOperationListRequest {
    pub tenant_id: AzureTenantId,
}

pub fn fetch_all_role_operation_metadata(tenant_id: AzureTenantId) -> RoleOperationListRequest {
    RoleOperationListRequest { tenant_id }
}

#[async_trait]
impl CacheableCommand for RoleOperationListRequest {
    type Output = Vec<AzureProviderOperationsMetadata>;

    fn cache_key(&self) -> CacheKey {
        CacheKey::new(PathBuf::from_iter([
            "az",
            "rest",
            "GET",
            "providerOperations",
            self.tenant_id.to_string().as_str(),
        ]))
    }

    async fn run(self) -> Result<Self::Output> {
        #[derive(Deserialize)]
        #[cfg_attr(debug_assertions, serde(deny_unknown_fields))]
        #[serde(rename_all = "camelCase")]
        struct Response {
            next_link: Option<String>,
            value: Vec<AzureProviderOperationsMetadata>,
        }

        let tenant_id = self.tenant_id.to_string();
        let mut next_url = Some(format!(
            "https://management.azure.com/providers/Microsoft.Authorization/providerOperations?$expand=resourceTypes&api-version={PROVIDER_OPERATIONS_API_VERSION}"
        ));
        let mut page_index = 0usize;
        let mut operations = Vec::new();

        while let Some(url) = next_url.take() {
            debug!(page_index, %url, %tenant_id, "Fetching Azure provider operations metadata");
            let mut response: Response = RestRequest::new(http::Method::GET, &url)?
                .tenant(self.tenant_id)
                .cache(CacheKey {
                    path: self.cache_key().path.join(page_index.to_string()),
                    valid_for: self.cache_key().valid_for,
                })
                .receive()
                .await?;
            operations.append(&mut response.value);
            next_url = response.next_link;
            page_index += 1;
        }

        debug!(count = operations.len(), %tenant_id, "Fetched Azure provider operations metadata");
        Ok(operations)
    }
}

cloud_terrastodon_command::impl_cacheable_into_future!(RoleOperationListRequest);

#[cfg(test)]
mod tests {
    use super::*;
    use crate::get_test_tenant_id;

    #[tokio::test]
    async fn it_works() -> Result<()> {
        let results = fetch_all_role_operation_metadata(get_test_tenant_id().await?).await?;
        assert!(!results.is_empty());
        Ok(())
    }

    #[tokio::test]
    async fn includes_microsoft_authorization_provider() -> Result<()> {
        let results = fetch_all_role_operation_metadata(get_test_tenant_id().await?).await?;
        assert!(
            results
                .iter()
                .any(|provider| provider.name == "Microsoft.Authorization")
        );
        Ok(())
    }
}