ockam_api 0.93.0

Ockam's request-response API
use crate::orchestrator::operation::Operations;
use crate::orchestrator::project::models::{AdminInfoList, CreateProject, ProjectModelList};
use crate::orchestrator::project::models::{OrchestratorVersionInfo, ProjectModel};
use crate::orchestrator::{ControllerClient, HasSecureClient, ORCHESTRATOR_AWAIT_TIMEOUT};

use super::models::AdminInfo;

use miette::{miette, IntoDiagnostic};
use tokio_retry::strategy::FixedInterval;
use tokio_retry::Retry;

use crate::orchestrator::email_address::EmailAddress;
use crate::orchestrator::project::Project;
use ockam_core::api::Request;
use ockam_node::Context;

impl ControllerClient {
    pub async fn create_project(
        &self,
        ctx: &Context,
        space_id: &str,
        name: &str,
        users: Vec<String>,
    ) -> miette::Result<ProjectModel> {
        trace!(%space_id, project_name = name, "creating project");
        let req = Request::post(format!("/v1/spaces/{space_id}/projects"))
            .body(CreateProject::new(name.to_string(), users));
        self.get_secure_client()
            .ask(ctx, "projects", req)
            .await
            .into_diagnostic()?
            .miette_success("create project")
    }

    pub async fn get_project(
        &self,
        ctx: &Context,
        project_id: &str,
    ) -> miette::Result<ProjectModel> {
        trace!(%project_id, "getting project");
        let req = Request::get(format!("/v0/{project_id}"));
        self.get_secure_client()
            .ask(ctx, "projects", req)
            .await
            .into_diagnostic()?
            .miette_success("get project")
    }

    pub async fn delete_project(
        &self,
        ctx: &Context,
        space_id: &str,
        project_id: &str,
    ) -> miette::Result<()> {
        trace!(%space_id, %project_id, "deleting project");
        let req = Request::delete(format!("/v0/{space_id}/{project_id}"));
        self.get_secure_client()
            .tell(ctx, "projects", req)
            .await
            .into_diagnostic()?
            .miette_success("delete project")
    }

    pub async fn get_orchestrator_version_info(
        &self,
        ctx: &Context,
    ) -> miette::Result<OrchestratorVersionInfo> {
        trace!("getting orchestrator version information");
        self.get_secure_client()
            .ask(ctx, "version_info", Request::get(""))
            .await
            .into_diagnostic()?
            .miette_success("get orchestrator version")
    }

    pub async fn add_space_admin(
        &self,
        ctx: &Context,
        space_id: &str,
        email: &EmailAddress,
    ) -> miette::Result<AdminInfo> {
        trace!("adding new space administrator");
        let admins: AdminInfoList = self
            .get_secure_client()
            .ask(
                ctx,
                "spaces",
                Request::put(format!("/v0/{space_id}/admins/{email}")),
            )
            .await
            .into_diagnostic()?
            .miette_success("add space admins")?;
        admins
            .0
            .into_iter()
            .find(|a| a.email == email.to_string())
            .ok_or(miette!(
                "A user with email {email} was not added to space {space_id}"
            ))
    }

    pub async fn list_space_admins(
        &self,
        ctx: &Context,
        space_id: &str,
    ) -> miette::Result<Vec<AdminInfo>> {
        trace!("listing space administrators");
        let admin_info_list: AdminInfoList = self
            .get_secure_client()
            .ask(
                ctx,
                "spaces",
                Request::get(format!("/v0/{space_id}/admins")),
            )
            .await
            .into_diagnostic()?
            .miette_success("get space admins")?;
        Ok(admin_info_list.0)
    }

    pub async fn delete_space_admin(
        &self,
        ctx: &Context,
        space_id: &str,
        email: &EmailAddress,
    ) -> miette::Result<()> {
        trace!("deleting space administrator");
        let _admins: AdminInfoList = self
            .get_secure_client()
            .ask(
                ctx,
                "spaces",
                Request::delete(format!("/v0/{space_id}/admins/{email}")),
            )
            .await
            .into_diagnostic()?
            .miette_success("delete space admins")?;
        Ok(())
    }

    pub async fn add_project_admin(
        &self,
        ctx: &Context,
        project_id: &str,
        email: &EmailAddress,
    ) -> miette::Result<AdminInfo> {
        trace!("adding new project administrator");
        let admins: AdminInfoList = self
            .get_secure_client()
            .ask(
                ctx,
                "projects",
                Request::put(format!("/v0/{project_id}/admins/{email}")),
            )
            .await
            .into_diagnostic()?
            .miette_success("add project admins")?;
        admins
            .0
            .into_iter()
            .find(|a| a.email == email.to_string())
            .ok_or(miette!(
                "A user with email {email} was not added to proejct {project_id}"
            ))
    }

    pub async fn list_project_admins(
        &self,
        ctx: &Context,
        project_id: &str,
    ) -> miette::Result<Vec<AdminInfo>> {
        trace!("listing project administrators");
        let admin_info_list: AdminInfoList = self
            .get_secure_client()
            .ask(
                ctx,
                "projects",
                Request::get(format!("/v0/{project_id}/admins")),
            )
            .await
            .into_diagnostic()?
            .miette_success("get project admins")?;
        Ok(admin_info_list.0)
    }

    pub async fn delete_project_admin(
        &self,
        ctx: &Context,
        project_id: &str,
        email: &EmailAddress,
    ) -> miette::Result<()> {
        trace!("deleting project administrator");
        let _admins: AdminInfoList = self
            .get_secure_client()
            .ask(
                ctx,
                "projects",
                Request::delete(format!("/v0/{project_id}/admins/{email}")),
            )
            .await
            .into_diagnostic()?
            .miette_success("delete project admins")?;
        Ok(())
    }

    #[instrument(skip_all)]
    pub async fn list_projects(&self, ctx: &Context) -> miette::Result<Vec<ProjectModel>> {
        let req = Request::get("/v0");
        let project_model_list: ProjectModelList = self
            .get_secure_client()
            .ask(ctx, "projects", req)
            .await
            .into_diagnostic()?
            .miette_success("list projects")?;
        Ok(project_model_list.0)
    }

    pub async fn wait_until_project_creation_operation_is_complete(
        &self,
        ctx: &Context,
        project: &ProjectModel,
    ) -> miette::Result<ProjectModel> {
        let operation_id = match &project.operation_id {
            Some(operation_id) => operation_id,
            // if no operation id is present this means that the operation is already complete
            None => return Ok(project.clone()),
        };

        let result = self
            .wait_until_operation_is_complete(ctx, operation_id)
            .await;
        match result {
            Ok(()) => self.get_project(ctx, &project.id).await,
            Err(e) => Err(miette!("The project creation did not complete: {:?}", e)),
        }
    }

    pub async fn wait_until_project_is_ready(
        &self,
        ctx: &Context,
        project: &ProjectModel,
    ) -> miette::Result<ProjectModel> {
        let retry_strategy = FixedInterval::from_millis(5000)
            .take((ORCHESTRATOR_AWAIT_TIMEOUT.as_millis() / 5000) as usize);
        Retry::spawn(retry_strategy.clone(), || async {
            if let Ok(project_model) = self.get_project(ctx, &project.id).await {
                let project = Project::import(project_model.clone())
                    .await
                    .into_diagnostic()?;
                if project.is_ready() {
                    Ok(project_model)
                } else {
                    debug!(
                        "the project {} is not ready yet. Retrying...",
                        project.project_id()
                    );
                    Err(miette!(
                        "The project {} is not ready. Please try again.",
                        project.project_id()
                    ))
                }
            } else {
                Err(miette!(
                    "The project {} could not be retrieved",
                    &project.id
                ))
            }
        })
        .await
    }
}