cargo-lambda-deploy 1.9.1

Cargo subcommand to work with AWS Lambda
Documentation
use cargo_lambda_build::{BinaryArchive, BinaryModifiedAt};
use cargo_lambda_metadata::cargo::deploy::{Deploy, FunctionDeployConfig};
use cargo_lambda_remote::{DEFAULT_REGION, aws_sdk_config::SdkConfig};
use miette::Result;
use serde::Serialize;
use std::{collections::HashMap, fmt::Display, path::PathBuf};

use crate::binary_name_or_default;

#[derive(PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum DeployKind {
    Function,
    Extension,
}

impl Display for DeployKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            DeployKind::Function => write!(f, "function"),
            DeployKind::Extension => write!(f, "extension"),
        }
    }
}

#[derive(Serialize)]
struct DisplaySdkConfig {
    region: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    profile: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    endpoint_url: Option<String>,
}

#[derive(Serialize)]
pub(crate) struct DeployOutput {
    kind: DeployKind,
    name: String,
    path: PathBuf,
    arch: String,
    files: Vec<String>,
    runtimes: Vec<String>,
    tags: Option<String>,
    bucket: Option<String>,
    config: FunctionDeployConfig,
    sdk_config: DisplaySdkConfig,
    binary_modified_at: BinaryModifiedAt,
}

impl Display for DeployOutput {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "🔍 deployment for {} `{}`:", self.kind, self.name)?;
        writeln!(f, "🏠 zip file located at {}", self.path.display())?;
        writeln!(
            f,
            "🛠️ binary last compiled {}",
            self.binary_modified_at.humanize()
        )?;
        writeln!(f, "🏗️  architecture {}", self.arch)?;

        if let Some(tags) = &self.tags {
            writeln!(f, "🏷️  tagged with {}", tags.replace(',', ", "))?;
        }

        if let Some(bucket) = &self.bucket {
            writeln!(f, "🪣 stored on S3 bucket `{bucket}`")?;
        }

        if !self.runtimes.is_empty() {
            write!(f, "👟 compatible with {}", self.runtimes.join(", "))?;
        }

        if !self.files.is_empty() {
            writeln!(f, "🗃️  files included in the zip file:")?;
            for file in &self.files {
                writeln!(f, "  - {file}")?;
            }
        }

        writeln!(f, "🛫  AWS SDK configuration:")?;
        writeln!(
            f,
            "  - region: {}",
            self.sdk_config.region.as_deref().unwrap_or(DEFAULT_REGION)
        )?;
        if let Some(profile) = &self.sdk_config.profile {
            writeln!(f, "  - profile: {profile}")?;
        }
        if let Some(endpoint_url) = &self.sdk_config.endpoint_url {
            writeln!(f, "  - endpoint: {endpoint_url}")?;
        }

        if self.kind == DeployKind::Function {
            writeln!(f, "🍿 function configuration:")?;
            writeln!(f, "  - timeout: {:?}", self.config.timeout)?;
            writeln!(f, "  - memory: {:?}", self.config.memory)?;
            writeln!(
                f,
                "  - enable_function_url: {}",
                self.config.enable_function_url
            )?;
            writeln!(
                f,
                "  - disable_function_url: {}",
                self.config.disable_function_url
            )?;
            writeln!(f, "  - tracing: {:?}", self.config.tracing)?;
            writeln!(f, "  - role: {:?}", self.config.role)?;
            writeln!(f, "  - layer: {:?}", self.config.layer)?;

            if let Some(vpc) = &self.config.vpc {
                writeln!(f, "  - VPC subnets: {:?}", vpc.subnet_ids)?;
                writeln!(f, "  - VPC security groups: {:?}", vpc.security_group_ids)?;
                writeln!(
                    f,
                    "  - VPC IPv6 allowed: {}",
                    vpc.ipv6_allowed_for_dual_stack
                )?;
            }

            writeln!(f, "  - runtime: {:?}", self.config.runtime())?;
            if let Some(env_options) = &self.config.env_options {
                let env = env_options
                    .lambda_environment(&HashMap::new())
                    .unwrap_or_default();
                writeln!(f, "  - env_options: {env:?}")?;
            }
        }

        Ok(())
    }
}

impl DeployOutput {
    pub(crate) fn new(
        config: &Deploy,
        name: &str,
        sdk_config: &SdkConfig,
        archive: &BinaryArchive,
    ) -> Result<Self> {
        let (kind, name, runtimes) = if config.extension {
            (
                DeployKind::Extension,
                name.to_owned(),
                config.compatible_runtimes(),
            )
        } else {
            let binary_name = binary_name_or_default(config, name);
            (DeployKind::Function, binary_name, vec![])
        };

        let display_sdk_config = DisplaySdkConfig {
            region: sdk_config.region().map(|r| r.to_string()),
            profile: config
                .remote_config
                .as_ref()
                .and_then(|r| r.profile.clone()),
            endpoint_url: sdk_config.endpoint_url().map(|u| u.to_string()),
        };

        Ok(DeployOutput {
            kind,
            name,
            runtimes,
            path: archive.path.clone(),
            arch: archive.architecture.clone(),
            bucket: config.s3_bucket.clone(),
            tags: config.s3_tags(),
            config: config.function_config.clone(),
            sdk_config: display_sdk_config,
            files: archive.list()?,
            binary_modified_at: archive.binary_modified_at.clone(),
        })
    }
}