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