1
0
Fork 0
mirror of https://github.com/imjasonh/krust synced 2026-07-08 06:45:32 +00:00

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
This commit is contained in:
Jason Hall 2025-06-07 22:14:21 -04:00
parent 5d254c7eca
commit 11083dc527
Failed to extract signature
6 changed files with 74 additions and 7 deletions

View file

@ -144,10 +144,23 @@ The tradeoff is that musl has slightly different behavior than glibc in some edg
## Configuration ## 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 ```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" default_registry = "ghcr.io"
[build] [build]
@ -160,6 +173,13 @@ password = "mytoken"
Note: Registry authentication is not yet implemented. Currently, krust uses anonymous authentication. 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 ## Key Features
- **Docker-free** - Builds OCI container images without requiring Docker daemon - **Docker-free** - Builds OCI container images without requiring Docker daemon

View file

@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
# Example: Configure krust to use a specific base image for this project
[package.metadata.krust]
base-image = "cgr.dev/chainguard/static:latest"

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::{Path, PathBuf};
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -45,7 +45,7 @@ pub struct RegistryAuth {
} }
fn default_base_image() -> String { fn default_base_image() -> String {
"gcr.io/distroless/static:nonroot".to_string() "cgr.dev/chainguard/static:latest".to_string()
} }
impl Default for Config { 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<String>,
}
impl Config { impl Config {
pub fn load() -> anyhow::Result<Self> { pub fn load() -> anyhow::Result<Self> {
if let Some(config_dir) = dirs::config_dir() { if let Some(config_dir) = dirs::config_dir() {
@ -71,4 +79,27 @@ impl Config {
} }
Ok(Config::default()) Ok(Config::default())
} }
/// Load project-specific configuration from Cargo.toml
pub fn load_project_config(project_path: &Path) -> anyhow::Result<ProjectConfig> {
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())
}
} }

View file

@ -5,7 +5,7 @@ mod tests {
#[test] #[test]
fn test_default_config() { fn test_default_config() {
let config = Config::default(); 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.default_registry.is_none());
assert!(config.registries.is_empty()); assert!(config.registries.is_empty());
} }

View file

@ -38,6 +38,14 @@ async fn main() -> Result<()> {
let config = Config::load()?; let config = Config::load()?;
let project_path = path.unwrap_or_else(|| PathBuf::from(".")); 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 // Determine the image name
let image_ref = if let Some(image) = image { let image_ref = if let Some(image) = image {
// Use explicit image if provided // Use explicit image if provided
@ -56,8 +64,7 @@ async fn main() -> Result<()> {
let binary_path = builder.build()?; let binary_path = builder.build()?;
// Build container image // Build container image
let image_builder = let image_builder = ImageBuilder::new(binary_path, base_image, platform.clone());
ImageBuilder::new(binary_path, config.base_image.clone(), platform.clone());
let (config_data, layer_data, manifest) = image_builder.build()?; let (config_data, layer_data, manifest) = image_builder.build()?;

View file

@ -53,6 +53,7 @@ fn test_build_requires_repo_or_image() -> Result<()> {
let mut cmd = Command::cargo_bin("krust")?; let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("build") cmd.arg("build")
.arg("--no-push") .arg("--no-push")
.arg(".") // Explicitly pass current directory
.current_dir(&example_dir) .current_dir(&example_dir)
.env_remove("KRUST_REPO"); .env_remove("KRUST_REPO");
@ -71,6 +72,7 @@ fn test_build_with_krust_repo_env() -> Result<()> {
let mut cmd = Command::cargo_bin("krust")?; let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("build") cmd.arg("build")
.arg("--no-push") .arg("--no-push")
.arg(".") // Explicitly pass current directory
.env("KRUST_REPO", "test.local") .env("KRUST_REPO", "test.local")
.current_dir(&example_dir); .current_dir(&example_dir);
@ -95,6 +97,7 @@ fn test_command_substitution_syntax() -> Result<()> {
.arg("--no-push") .arg("--no-push")
.arg("--image") .arg("--image")
.arg("test.local/hello:latest") .arg("test.local/hello:latest")
.arg(".") // Explicitly pass current directory
.current_dir(&example_dir) .current_dir(&example_dir)
.output()?; .output()?;
@ -122,6 +125,7 @@ fn test_verbose_logging() -> Result<()> {
.arg("--no-push") .arg("--no-push")
.arg("--image") .arg("--image")
.arg("test.local/hello:latest") .arg("test.local/hello:latest")
.arg(".") // Explicitly pass current directory
.current_dir(&example_dir); .current_dir(&example_dir);
cmd.assert() cmd.assert()
@ -151,6 +155,7 @@ fn test_full_build_and_run_workflow() -> Result<()> {
let mut cmd = Command::cargo_bin("krust")?; let mut cmd = Command::cargo_bin("krust")?;
let output = cmd let output = cmd
.arg("build") .arg("build")
.arg(".") // Explicitly pass current directory
.env("KRUST_REPO", "ttl.sh/krust-test") .env("KRUST_REPO", "ttl.sh/krust-test")
.current_dir(&example_dir) .current_dir(&example_dir)
.output()?; .output()?;