From 11083dc52745f6d102e99dd3327de8d80b759e9b Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Sat, 7 Jun 2025 22:14:21 -0400 Subject: [PATCH] Add Cargo.toml-based configuration for base image - Add support for [package.metadata.krust] in Cargo.toml - Change default base image to cgr.dev/chainguard/static:latest - Add ProjectConfig struct to load project-specific settings - Update documentation and example to show configuration usage - Fix integration tests to explicitly pass directory argument - This is the idiomatic way for Rust build tools to be configured --- README.md | 24 +++++++++++++++++++++-- example/hello-krust/Cargo.toml | 4 ++++ src/config/mod.rs | 35 ++++++++++++++++++++++++++++++++-- src/config/tests.rs | 2 +- src/main.rs | 11 +++++++++-- tests/integration_test.rs | 5 +++++ 6 files changed, 74 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9bb491d..4549a5e 100644 --- a/README.md +++ b/README.md @@ -144,10 +144,23 @@ The tradeoff is that musl has slightly different behavior than glibc in some edg ## Configuration -krust looks for configuration at `~/.config/krust/config.toml`: +### Project Configuration (Cargo.toml) + +You can configure krust on a per-project basis by adding a `[package.metadata.krust]` section to your project's `Cargo.toml`: ```toml -base_image = "gcr.io/distroless/static:nonroot" +[package.metadata.krust] +base-image = "cgr.dev/chainguard/static:latest" # Override the default base image +``` + +This is the idiomatic way to configure build tools in Rust, similar to how `cargo-deb`, `wasm-pack`, and other Cargo extensions work. + +### Global Configuration + +krust also looks for global configuration at `~/.config/krust/config.toml`: + +```toml +base_image = "cgr.dev/chainguard/static:latest" # Default base image for all projects default_registry = "ghcr.io" [build] @@ -160,6 +173,13 @@ password = "mytoken" Note: Registry authentication is not yet implemented. Currently, krust uses anonymous authentication. +### Configuration Precedence + +When determining the base image, krust uses this precedence order: +1. Project-specific config in `Cargo.toml` (highest priority) +2. Global config in `~/.config/krust/config.toml` +3. Built-in default: `cgr.dev/chainguard/static:latest` (lowest priority) + ## Key Features - **Docker-free** - Builds OCI container images without requiring Docker daemon diff --git a/example/hello-krust/Cargo.toml b/example/hello-krust/Cargo.toml index fa4b0f2..11f6e48 100644 --- a/example/hello-krust/Cargo.toml +++ b/example/hello-krust/Cargo.toml @@ -4,3 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] + +# Example: Configure krust to use a specific base image for this project +[package.metadata.krust] +base-image = "cgr.dev/chainguard/static:latest" diff --git a/src/config/mod.rs b/src/config/mod.rs index c5c1cf5..9b1c948 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[cfg(test)] mod tests; @@ -45,7 +45,7 @@ pub struct RegistryAuth { } fn default_base_image() -> String { - "gcr.io/distroless/static:nonroot".to_string() + "cgr.dev/chainguard/static:latest".to_string() } impl Default for Config { @@ -59,6 +59,14 @@ impl Default for Config { } } +/// Project-specific configuration from Cargo.toml +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct ProjectConfig { + /// Base image for this project + #[serde(rename = "base-image")] + pub base_image: Option, +} + impl Config { pub fn load() -> anyhow::Result { if let Some(config_dir) = dirs::config_dir() { @@ -71,4 +79,27 @@ impl Config { } Ok(Config::default()) } + + /// Load project-specific configuration from Cargo.toml + pub fn load_project_config(project_path: &Path) -> anyhow::Result { + let cargo_toml_path = project_path.join("Cargo.toml"); + if !cargo_toml_path.exists() { + return Ok(ProjectConfig::default()); + } + + let content = std::fs::read_to_string(&cargo_toml_path)?; + let value: toml::Value = toml::from_str(&content)?; + + // Look for [package.metadata.krust] section + if let Some(metadata) = value + .get("package") + .and_then(|p| p.get("metadata")) + .and_then(|m| m.get("krust")) + { + let project_config: ProjectConfig = metadata.clone().try_into()?; + return Ok(project_config); + } + + Ok(ProjectConfig::default()) + } } diff --git a/src/config/tests.rs b/src/config/tests.rs index 7c570ea..95ef128 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -5,7 +5,7 @@ mod tests { #[test] fn test_default_config() { let config = Config::default(); - assert_eq!(config.base_image, "gcr.io/distroless/static:nonroot"); + assert_eq!(config.base_image, "cgr.dev/chainguard/static:latest"); assert!(config.default_registry.is_none()); assert!(config.registries.is_empty()); } diff --git a/src/main.rs b/src/main.rs index cf6f2cb..850bb19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,14 @@ async fn main() -> Result<()> { let config = Config::load()?; let project_path = path.unwrap_or_else(|| PathBuf::from(".")); + // Load project-specific config from Cargo.toml + let project_config = Config::load_project_config(&project_path)?; + + // Determine base image (project config takes precedence) + let base_image = project_config + .base_image + .unwrap_or(config.base_image.clone()); + // Determine the image name let image_ref = if let Some(image) = image { // Use explicit image if provided @@ -56,8 +64,7 @@ async fn main() -> Result<()> { let binary_path = builder.build()?; // Build container image - let image_builder = - ImageBuilder::new(binary_path, config.base_image.clone(), platform.clone()); + let image_builder = ImageBuilder::new(binary_path, base_image, platform.clone()); let (config_data, layer_data, manifest) = image_builder.build()?; diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 6d8da45..ea30910 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -53,6 +53,7 @@ fn test_build_requires_repo_or_image() -> Result<()> { let mut cmd = Command::cargo_bin("krust")?; cmd.arg("build") .arg("--no-push") + .arg(".") // Explicitly pass current directory .current_dir(&example_dir) .env_remove("KRUST_REPO"); @@ -71,6 +72,7 @@ fn test_build_with_krust_repo_env() -> Result<()> { let mut cmd = Command::cargo_bin("krust")?; cmd.arg("build") .arg("--no-push") + .arg(".") // Explicitly pass current directory .env("KRUST_REPO", "test.local") .current_dir(&example_dir); @@ -95,6 +97,7 @@ fn test_command_substitution_syntax() -> Result<()> { .arg("--no-push") .arg("--image") .arg("test.local/hello:latest") + .arg(".") // Explicitly pass current directory .current_dir(&example_dir) .output()?; @@ -122,6 +125,7 @@ fn test_verbose_logging() -> Result<()> { .arg("--no-push") .arg("--image") .arg("test.local/hello:latest") + .arg(".") // Explicitly pass current directory .current_dir(&example_dir); cmd.assert() @@ -151,6 +155,7 @@ fn test_full_build_and_run_workflow() -> Result<()> { let mut cmd = Command::cargo_bin("krust")?; let output = cmd .arg("build") + .arg(".") // Explicitly pass current directory .env("KRUST_REPO", "ttl.sh/krust-test") .current_dir(&example_dir) .output()?;