mirror of
https://github.com/marcoallegretti/karapace.git
synced 2026-03-26 21:43:09 +00:00
core: add build options
Introduce BuildOptions to parameterize build and rebuild. Add build_with_options/rebuild_with_options to support locked, offline, and require-pinned-image modes. Locked mode verifies an existing lock file and fails on drift. Offline mode fails fast when system packages are requested. Also re-export BuildOptions from karapace-core.
This commit is contained in:
parent
cbf954bead
commit
6e66c58e5e
3 changed files with 74 additions and 8 deletions
|
|
@ -36,6 +36,13 @@ pub struct BuildResult {
|
||||||
pub lock_file: LockFile,
|
pub lock_file: LockFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct BuildOptions {
|
||||||
|
pub locked: bool,
|
||||||
|
pub offline: bool,
|
||||||
|
pub require_pinned_image: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Create a new engine rooted at the given store directory.
|
/// Create a new engine rooted at the given store directory.
|
||||||
///
|
///
|
||||||
|
|
@ -148,14 +155,51 @@ impl Engine {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
pub fn build(&self, manifest_path: &Path) -> Result<BuildResult, CoreError> {
|
pub fn build(&self, manifest_path: &Path) -> Result<BuildResult, CoreError> {
|
||||||
|
self.build_with_options(manifest_path, BuildOptions::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub fn build_with_options(
|
||||||
|
&self,
|
||||||
|
manifest_path: &Path,
|
||||||
|
options: BuildOptions,
|
||||||
|
) -> Result<BuildResult, CoreError> {
|
||||||
info!("building environment from {}", manifest_path.display());
|
info!("building environment from {}", manifest_path.display());
|
||||||
self.layout.initialize()?;
|
self.layout.initialize()?;
|
||||||
|
|
||||||
let manifest = parse_manifest_file(manifest_path)?;
|
let manifest = parse_manifest_file(manifest_path)?;
|
||||||
let normalized = manifest.normalize()?;
|
let normalized = manifest.normalize()?;
|
||||||
|
|
||||||
|
if options.offline && !normalized.system_packages.is_empty() {
|
||||||
|
return Err(CoreError::Runtime(karapace_runtime::RuntimeError::ExecFailed(
|
||||||
|
"offline mode: cannot resolve system packages".to_owned(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.require_pinned_image
|
||||||
|
&& !(normalized.base_image.starts_with("http://")
|
||||||
|
|| normalized.base_image.starts_with("https://"))
|
||||||
|
{
|
||||||
|
return Err(CoreError::Manifest(
|
||||||
|
karapace_schema::ManifestError::UnpinnedBaseImage(normalized.base_image.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lock_path = manifest_path
|
||||||
|
.parent()
|
||||||
|
.unwrap_or(Path::new("."))
|
||||||
|
.join("karapace.lock");
|
||||||
|
|
||||||
|
let locked = if options.locked {
|
||||||
|
let lock = LockFile::read_from_file(&lock_path)?;
|
||||||
|
let _ = lock.verify_integrity()?;
|
||||||
|
lock.verify_manifest_intent(&normalized)?;
|
||||||
|
Some(lock)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let policy = SecurityPolicy::from_manifest(&normalized);
|
let policy = SecurityPolicy::from_manifest(&normalized);
|
||||||
policy.validate_mounts(&normalized)?;
|
policy.validate_mounts(&normalized)?;
|
||||||
policy.validate_devices(&normalized)?;
|
policy.validate_devices(&normalized)?;
|
||||||
|
|
@ -182,6 +226,7 @@ impl Engine {
|
||||||
.to_string(),
|
.to_string(),
|
||||||
store_root: store_str.clone(),
|
store_root: store_str.clone(),
|
||||||
manifest: normalized.clone(),
|
manifest: normalized.clone(),
|
||||||
|
offline: options.offline,
|
||||||
};
|
};
|
||||||
let resolution = backend.resolve(&preliminary_spec)?;
|
let resolution = backend.resolve(&preliminary_spec)?;
|
||||||
debug!(
|
debug!(
|
||||||
|
|
@ -196,6 +241,17 @@ impl Engine {
|
||||||
let lock = LockFile::from_resolved(&normalized, &resolution);
|
let lock = LockFile::from_resolved(&normalized, &resolution);
|
||||||
let identity = lock.compute_identity();
|
let identity = lock.compute_identity();
|
||||||
|
|
||||||
|
if let Some(existing) = locked {
|
||||||
|
if existing.env_id != identity.env_id.as_str() {
|
||||||
|
return Err(CoreError::Lock(karapace_schema::LockError::ManifestDrift(
|
||||||
|
format!(
|
||||||
|
"locked mode: lock env_id '{}' does not match resolved env_id '{}'",
|
||||||
|
existing.env_id, identity.env_id
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"canonical env_id: {} ({})",
|
"canonical env_id: {} ({})",
|
||||||
identity.env_id, identity.short_id
|
identity.env_id, identity.short_id
|
||||||
|
|
@ -223,6 +279,7 @@ impl Engine {
|
||||||
overlay_path: env_dir.to_string_lossy().to_string(),
|
overlay_path: env_dir.to_string_lossy().to_string(),
|
||||||
store_root: store_str,
|
store_root: store_str,
|
||||||
manifest: normalized.clone(),
|
manifest: normalized.clone(),
|
||||||
|
offline: options.offline,
|
||||||
};
|
};
|
||||||
if let Err(e) = backend.build(&spec) {
|
if let Err(e) = backend.build(&spec) {
|
||||||
let _ = std::fs::remove_dir_all(&env_dir);
|
let _ = std::fs::remove_dir_all(&env_dir);
|
||||||
|
|
@ -285,11 +342,9 @@ impl Engine {
|
||||||
}
|
}
|
||||||
self.meta_store.put(&meta)?;
|
self.meta_store.put(&meta)?;
|
||||||
|
|
||||||
let lock_path = manifest_path
|
if !options.locked {
|
||||||
.parent()
|
lock.write_to_file(&lock_path)?;
|
||||||
.unwrap_or(Path::new("."))
|
}
|
||||||
.join("karapace.lock");
|
|
||||||
lock.write_to_file(&lock_path)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -322,6 +377,7 @@ impl Engine {
|
||||||
overlay_path: env_path_str,
|
overlay_path: env_path_str,
|
||||||
store_root: self.store_root_str.clone(),
|
store_root: self.store_root_str.clone(),
|
||||||
manifest,
|
manifest,
|
||||||
|
offline: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -545,6 +601,14 @@ impl Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild(&self, manifest_path: &Path) -> Result<BuildResult, CoreError> {
|
pub fn rebuild(&self, manifest_path: &Path) -> Result<BuildResult, CoreError> {
|
||||||
|
self.rebuild_with_options(manifest_path, BuildOptions::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rebuild_with_options(
|
||||||
|
&self,
|
||||||
|
manifest_path: &Path,
|
||||||
|
options: BuildOptions,
|
||||||
|
) -> Result<BuildResult, CoreError> {
|
||||||
// Collect the old env_id(s) to clean up AFTER a successful build.
|
// Collect the old env_id(s) to clean up AFTER a successful build.
|
||||||
// This ensures we don't lose the old environment if the new build fails.
|
// This ensures we don't lose the old environment if the new build fails.
|
||||||
let lock_path = manifest_path
|
let lock_path = manifest_path
|
||||||
|
|
@ -568,7 +632,7 @@ impl Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build first — if this fails, old environment is preserved.
|
// Build first — if this fails, old environment is preserved.
|
||||||
let result = self.build(manifest_path)?;
|
let result = self.build_with_options(manifest_path, options)?;
|
||||||
|
|
||||||
// Only destroy the old environment(s) after the new build succeeds.
|
// Only destroy the old environment(s) after the new build succeeds.
|
||||||
for old_id in &old_env_ids {
|
for old_id in &old_env_ids {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub mod lifecycle;
|
||||||
|
|
||||||
pub use concurrency::{install_signal_handler, shutdown_requested, StoreLock};
|
pub use concurrency::{install_signal_handler, shutdown_requested, StoreLock};
|
||||||
pub use drift::{commit_overlay, diff_overlay, export_overlay, DriftReport};
|
pub use drift::{commit_overlay, diff_overlay, export_overlay, DriftReport};
|
||||||
pub use engine::{BuildResult, Engine};
|
pub use engine::{BuildOptions, BuildResult, Engine};
|
||||||
pub use lifecycle::validate_transition;
|
pub use lifecycle::validate_transition;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ pub enum ManifestError {
|
||||||
UnsupportedVersion(u32),
|
UnsupportedVersion(u32),
|
||||||
#[error("base.image must not be empty")]
|
#[error("base.image must not be empty")]
|
||||||
EmptyBaseImage,
|
EmptyBaseImage,
|
||||||
|
#[error("base.image is not pinned: '{0}' (expected http(s)://...)")]
|
||||||
|
UnpinnedBaseImage(String),
|
||||||
#[error("mount label must not be empty")]
|
#[error("mount label must not be empty")]
|
||||||
EmptyMountLabel,
|
EmptyMountLabel,
|
||||||
#[error("invalid mount declaration for '{label}': '{spec}', expected '<host>:<container>'")]
|
#[error("invalid mount declaration for '{label}': '{spec}', expected '<host>:<container>'")]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue