greentic-pack-lib 0.4.67

Greentic pack builder and reader
Documentation
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::str::FromStr;

use anyhow::{Context, Result, anyhow};
use greentic_pack::builder::PackManifest;
use greentic_pack::plan::infer_base_deployment_plan;
use greentic_pack::reader::{SigningPolicy, open_pack};
use greentic_types::component::ComponentManifest;
use greentic_types::{EnvId, SecretRequirement, TenantCtx, TenantId};
use zip::ZipArchive;

use super::PlanArgs;
use crate::input::materialize_pack_path;

pub fn run(args: &PlanArgs) -> Result<()> {
    let (temp, pack_path) = materialize_pack_path(&args.input, args.verbose)?;
    let tenant_ctx = build_tenant_ctx(&args.environment, &args.tenant)?;
    let plan = plan_for_pack(&pack_path, &tenant_ctx, &args.environment)?;

    if args.json {
        println!("{}", serde_json::to_string(&plan)?);
    } else {
        println!("{}", serde_json::to_string_pretty(&plan)?);
    }

    drop(temp);
    Ok(())
}

fn plan_for_pack(
    path: &Path,
    tenant: &TenantCtx,
    environment: &str,
) -> Result<greentic_types::deployment::DeploymentPlan> {
    let load = open_pack(path, SigningPolicy::DevOk).map_err(|err| anyhow!(err.message))?;
    let connectors = load.manifest.meta.annotations.get("connectors");
    let components = load_component_manifests(path, &load.manifest)?;
    let secret_requirements = load_secret_requirements(path).unwrap_or(None);

    Ok(infer_base_deployment_plan(
        &load.manifest.meta,
        &load.manifest.flows,
        connectors,
        &components,
        secret_requirements,
        tenant,
        environment,
    ))
}

fn build_tenant_ctx(environment: &str, tenant: &str) -> Result<TenantCtx> {
    let env_id = EnvId::from_str(environment)
        .with_context(|| format!("invalid environment id `{}`", environment))?;
    let tenant_id =
        TenantId::from_str(tenant).with_context(|| format!("invalid tenant id `{}`", tenant))?;
    Ok(TenantCtx::new(env_id, tenant_id))
}

fn load_component_manifests(
    pack_path: &Path,
    pack_manifest: &PackManifest,
) -> Result<HashMap<String, ComponentManifest>> {
    let file =
        File::open(pack_path).with_context(|| format!("failed to open {}", pack_path.display()))?;
    let mut archive = ZipArchive::new(file)
        .with_context(|| format!("{} is not a valid gtpack archive", pack_path.display()))?;

    let mut manifests = HashMap::new();
    for component in &pack_manifest.components {
        if let Some(manifest_path) = component.manifest_file.as_deref() {
            let mut entry = archive
                .by_name(manifest_path)
                .with_context(|| format!("component manifest `{}` missing", manifest_path))?;
            let manifest: ComponentManifest =
                serde_json::from_reader(&mut entry).with_context(|| {
                    format!("failed to parse component manifest `{}`", manifest_path)
                })?;
            manifests.insert(component.name.clone(), manifest);
        }
    }

    Ok(manifests)
}

fn load_secret_requirements(path: &Path) -> Result<Option<Vec<SecretRequirement>>> {
    let file = File::open(path).with_context(|| format!("failed to open {}", path.display()))?;
    let mut archive = ZipArchive::new(file)
        .with_context(|| format!("{} is not a valid gtpack archive", path.display()))?;

    for name in [
        "assets/secret-requirements.json",
        "secret-requirements.json",
    ] {
        if let Ok(mut entry) = archive.by_name(name) {
            let mut buf = String::new();
            entry
                .read_to_string(&mut buf)
                .context("failed to read secret requirements file")?;
            let reqs: Vec<SecretRequirement> =
                serde_json::from_str(&buf).context("secret requirements file is invalid JSON")?;
            return Ok(Some(reqs));
        }
    }

    Ok(None)
}