Skip to main content

greentic_bundle/cli/
build.rs

1use std::path::PathBuf;
2
3use anyhow::Result;
4use clap::Args;
5
6#[derive(Debug, Args)]
7pub struct BuildArgs {
8    #[arg(long, default_value = ".", help = "cli.build.root.option")]
9    pub root: PathBuf,
10
11    #[arg(long, value_name = "FILE", help = "cli.build.output.option")]
12    pub output: Option<PathBuf>,
13
14    #[arg(long, default_value_t = false, help = "cli.option.dry_run")]
15    pub dry_run: bool,
16
17    /// Embed a precompiled component cache (`.cache/v1/...`) into the bundle.
18    /// Requires `greentic-start` on PATH; bigger artifact, faster cold start.
19    #[arg(long, default_value_t = false)]
20    pub warmup: bool,
21
22    #[command(flatten)]
23    pub signing: SigningArgs,
24}
25
26/// CLI flags for DSSE+Ed25519 artifact signing (C2). When `--signing-key` is
27/// passed, a `<artifact>.sig` sidecar is written next to the `.gtbundle`.
28#[derive(Debug, Default, Args, Clone)]
29pub struct SigningArgs {
30    /// Path to an Ed25519 PKCS#8 PEM private key. When set, signs the
31    /// `.gtbundle` artifact and writes the DSSE envelope sidecar.
32    #[arg(long, value_name = "FILE")]
33    pub signing_key: Option<PathBuf>,
34
35    /// Explicit DSSE `keyid`. Default: derived directly from the
36    /// `--signing-key` private PEM (hex of `SHA-256(raw 32-byte public
37    /// key)[..16]`). If a sibling `<key>.pub` SPKI PEM exists it is
38    /// cross-checked against the derived id; a mismatch (stale `.pub` from a
39    /// rotated key) is rejected. Override is only honored when it matches the
40    /// canonical id — case-insensitive.
41    #[arg(long, value_name = "HEX", requires = "signing_key")]
42    pub key_id: Option<String>,
43
44    /// SLSA `builder.id` recorded in the provenance predicate. Default:
45    /// `greentic-bundle:<library version>` — i.e. the greentic-bundle crate
46    /// version at compile time, not the calling CLI's version. Top-level
47    /// binaries that embed this signer (e.g. `gtc`) should pass `--builder-id`
48    /// to record their own identity in provenance.
49    #[arg(long, value_name = "ID", requires = "signing_key")]
50    pub builder_id: Option<String>,
51
52    /// Override of the signature sidecar path. Default: `<artifact>.sig`.
53    #[arg(long, value_name = "FILE", requires = "signing_key")]
54    pub signature_output: Option<PathBuf>,
55}
56
57impl SigningArgs {
58    /// Build a `SigningConfig` when `--signing-key` was provided.
59    pub fn to_config(&self) -> Option<crate::build::signing::SigningConfig> {
60        self.signing_key
61            .as_ref()
62            .map(|path| crate::build::signing::SigningConfig {
63                signing_key_path: path.clone(),
64                key_id_override: self.key_id.clone(),
65                builder_id: self.builder_id.clone(),
66                signature_path_override: self.signature_output.clone(),
67            })
68    }
69}
70
71impl Default for BuildArgs {
72    fn default() -> Self {
73        Self {
74            root: PathBuf::from("."),
75            output: None,
76            dry_run: false,
77            warmup: false,
78            signing: SigningArgs::default(),
79        }
80    }
81}
82
83pub fn run(args: BuildArgs) -> Result<()> {
84    let signing = args.signing.to_config();
85    let result = crate::build::build_workspace(
86        &args.root,
87        args.output.as_deref(),
88        args.dry_run,
89        args.warmup,
90        signing.as_ref(),
91    )?;
92    println!("{}", serde_json::to_string_pretty(&result)?);
93    Ok(())
94}