diff --git a/tests/integration-tests/src/twoliter_update.rs b/tests/integration-tests/src/twoliter_update.rs index a565aa5cf..401296141 100644 --- a/tests/integration-tests/src/twoliter_update.rs +++ b/tests/integration-tests/src/twoliter_update.rs @@ -1,9 +1,6 @@ use super::{run_command, test_projects_dir, TWOLITER_PATH}; const EXPECTED_LOCKFILE: &str = r#"schema-version = 1 -release-version = "1.0.0" -digest = "m/6DbBacnIBHMo34GCuzA4pAHzrnQJ2G/XJMMguZXjw=" - [sdk] name = "bottlerocket-sdk" version = "0.42.0" diff --git a/twoliter/src/cmd/build.rs b/twoliter/src/cmd/build.rs index 26457c1ca..3221f2b1d 100644 --- a/twoliter/src/cmd/build.rs +++ b/twoliter/src/cmd/build.rs @@ -53,7 +53,7 @@ pub(crate) struct BuildKit { impl BuildKit { pub(super) async fn run(&self) -> Result<()> { let project = project::load_or_find_project(self.project_path.clone()).await?; - let lock = Lock::load(&project).await?; + let lock = Lock::load(&project, false).await?; let toolsdir = project.project_dir().join("build/tools"); install_tools(&toolsdir).await?; let makefile_path = toolsdir.join("Makefile.toml"); @@ -113,7 +113,7 @@ pub(crate) struct BuildVariant { impl BuildVariant { pub(super) async fn run(&self) -> Result<()> { let project = project::load_or_find_project(self.project_path.clone()).await?; - let lock = Lock::load(&project).await?; + let lock = Lock::load(&project, false).await?; let toolsdir = project.project_dir().join("build/tools"); install_tools(&toolsdir).await?; let makefile_path = toolsdir.join("Makefile.toml"); diff --git a/twoliter/src/cmd/build_clean.rs b/twoliter/src/cmd/build_clean.rs index 754327d58..4c9f8fe7a 100644 --- a/twoliter/src/cmd/build_clean.rs +++ b/twoliter/src/cmd/build_clean.rs @@ -16,7 +16,7 @@ pub(crate) struct BuildClean { impl BuildClean { pub(super) async fn run(&self) -> Result<()> { let project = project::load_or_find_project(self.project_path.clone()).await?; - let lock = Lock::load(&project).await?; + let lock = Lock::load(&project, false).await?; let toolsdir = project.project_dir().join("build/tools"); tools::install_tools(&toolsdir).await?; let makefile_path = toolsdir.join("Makefile.toml"); diff --git a/twoliter/src/cmd/fetch.rs b/twoliter/src/cmd/fetch.rs index 92ad46478..e274359fd 100644 --- a/twoliter/src/cmd/fetch.rs +++ b/twoliter/src/cmd/fetch.rs @@ -10,14 +10,19 @@ pub(crate) struct Fetch { #[clap(long = "project-path")] pub(crate) project_path: Option, + /// Architecture of images to fetch #[clap(long = "arch", default_value = "x86_64")] pub(crate) arch: String, + + /// Perform an implicit twoliter update + #[clap(long = "update", default_value = "false")] + pub(crate) update: bool, } impl Fetch { pub(super) async fn run(&self) -> Result<()> { let project = project::load_or_find_project(self.project_path.clone()).await?; - let lock_file = Lock::load(&project).await?; + let lock_file = Lock::load(&project, self.update).await?; lock_file.fetch(&project, self.arch.as_str()).await?; Ok(()) } diff --git a/twoliter/src/cmd/make.rs b/twoliter/src/cmd/make.rs index 2a1c6c965..f3c394ebf 100644 --- a/twoliter/src/cmd/make.rs +++ b/twoliter/src/cmd/make.rs @@ -37,7 +37,7 @@ pub(crate) struct Make { impl Make { pub(super) async fn run(&self) -> Result<()> { let project = project::load_or_find_project(self.project_path.clone()).await?; - let lock = Lock::load(&project).await?; + let lock = Lock::load(&project, false).await?; let toolsdir = project.project_dir().join("build/tools"); install_tools(&toolsdir).await?; let makefile_path = toolsdir.join("Makefile.toml"); diff --git a/twoliter/src/cmd/mod.rs b/twoliter/src/cmd/mod.rs index 6808677a1..5849f8424 100644 --- a/twoliter/src/cmd/mod.rs +++ b/twoliter/src/cmd/mod.rs @@ -140,6 +140,7 @@ mod test { let command = Fetch { project_path: Some(project_path.to_path_buf()), arch: arch.into(), + update: false, }; command.run().await.unwrap() } diff --git a/twoliter/src/cmd/publish_kit.rs b/twoliter/src/cmd/publish_kit.rs index 8cf35fca8..1b2c7f0d2 100644 --- a/twoliter/src/cmd/publish_kit.rs +++ b/twoliter/src/cmd/publish_kit.rs @@ -37,7 +37,7 @@ pub(crate) struct PublishKit { impl PublishKit { pub(super) async fn run(&self) -> Result<()> { let project = project::load_or_find_project(self.project_path.clone()).await?; - let lock = Lock::load(&project).await?; + let lock = Lock::load(&project, false).await?; let toolsdir = project.project_dir().join("build/tools"); install_tools(&toolsdir).await?; let makefile_path = toolsdir.join("Makefile.toml"); diff --git a/twoliter/src/cmd/update.rs b/twoliter/src/cmd/update.rs index e225c5c63..87056d13a 100644 --- a/twoliter/src/cmd/update.rs +++ b/twoliter/src/cmd/update.rs @@ -14,7 +14,7 @@ pub(crate) struct Update { impl Update { pub(super) async fn run(&self) -> Result<()> { let project = project::load_or_find_project(self.project_path.clone()).await?; - Lock::create(&project).await?; + Lock::load(&project, true).await?; Ok(()) } } diff --git a/twoliter/src/lock.rs b/twoliter/src/lock.rs index 782fea616..ba339edbe 100644 --- a/twoliter/src/lock.rs +++ b/twoliter/src/lock.rs @@ -1,4 +1,4 @@ -use crate::common::fs::{create_dir_all, read, remove_dir_all, remove_file, write}; +use crate::common::fs::{create_dir_all, read, remove_dir_all, write}; use crate::project::{Image, Project, ValidIdentifier, Vendor}; use crate::schema_version::SchemaVersion; use anyhow::{ensure, Context, Result}; @@ -22,7 +22,7 @@ use tokio::fs::read_to_string; const TWOLITER_LOCK: &str = "Twoliter.lock"; /// Represents a locked dependency on an image -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, Ord, PartialOrd, Serialize, Deserialize)] pub(crate) struct LockedImage { /// The name of the dependency pub name: String, @@ -38,6 +38,12 @@ pub(crate) struct LockedImage { pub(crate) manifest: Vec, } +impl PartialEq for LockedImage { + fn eq(&self, other: &Self) -> bool { + self.source == other.source && self.digest == other.digest + } +} + impl LockedImage { pub async fn new(image_tool: &ImageTool, vendor: &Vendor, image: &Image) -> Result { let source = format!("{}/{}:v{}", vendor.registry, image.name, image.version); @@ -252,49 +258,38 @@ impl OCIArchive { } /// Represents the structure of a `Twoliter.lock` lock file. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub(crate) struct Lock { /// The version of the Twoliter.toml this was generated from pub schema_version: SchemaVersion<1>, - /// The workspace release version - pub release_version: String, /// The resolved bottlerocket sdk pub sdk: LockedImage, /// Resolved kit dependencies pub kit: Vec, - /// sha256 digest of the Project this was generated from - pub digest: String, } #[allow(dead_code)] impl Lock { - pub(crate) async fn load(project: &Project) -> Result { + pub(crate) async fn load(project: &Project, create: bool) -> Result { let lock_file_path = project.project_dir().join(TWOLITER_LOCK); + let lock_state = Self::resolve(project).await?; if lock_file_path.exists() { let lock_str = read_to_string(&lock_file_path) .await .context("failed to read lockfile")?; let lock: Self = toml::from_str(lock_str.as_str()).context("failed to deserialize lockfile")?; - // The digests must match, if changes are needed twoliter - ensure!(lock.digest == project.digest()?, "changes have occurred to Twoliter.toml that require an update to Twoliter.lock, if intentional please run twoliter update"); - return Ok(lock); - } - Self::create(project).await - } - - pub(crate) async fn create(project: &Project) -> Result { - let lock_file_path = project.project_dir().join(TWOLITER_LOCK); - if lock_file_path.exists() { - remove_file(&lock_file_path).await?; + if lock_state == lock { + return Ok(lock); + } + ensure!(create, "changes have occured to Twoliter.toml or the remote kit images that require an update to Twoliter.lock.\n If intentional, please run twoliter update or pass the --update to twoliter fetch"); } - let lock = Self::resolve(project).await?; - let lock_str = toml::to_string(&lock).context("failed to serialize lock file")?; + let lock_str = toml::to_string(&lock_state).context("failed to serialize lock file")?; write(&lock_file_path, lock_str) .await .context("failed to write lock file")?; - Ok(lock) + Ok(lock_state) } fn external_kit_metadata(&self) -> ExternalKitMetadata { @@ -457,8 +452,6 @@ impl Lock { ))?; Ok(Self { schema_version: project.schema_version(), - release_version: project.release_version().to_string(), - digest: project.digest()?, sdk: LockedImage::new(&image_tool, vendor, sdk).await?, kit: locked, }) diff --git a/twoliter/src/project.rs b/twoliter/src/project.rs index 46c97f176..c82481a6e 100644 --- a/twoliter/src/project.rs +++ b/twoliter/src/project.rs @@ -4,19 +4,16 @@ use crate::schema_version::SchemaVersion; use anyhow::{ensure, Context, Result}; use async_recursion::async_recursion; use async_walkdir::WalkDir; -use base64::Engine; use buildsys_config::{EXTERNAL_KIT_DIRECTORY, EXTERNAL_KIT_METADATA}; use futures::stream::StreamExt; use log::{debug, info, trace, warn}; use semver::Version; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use sha2::{Digest, Sha256}; use std::collections::BTreeMap; use std::ffi::OsStr; use std::fmt::{Display, Formatter}; use std::hash::Hash; -use std::io::Write; use std::path::{Path, PathBuf}; use toml::Table; @@ -201,38 +198,6 @@ impl Project { modules.sort(); Ok(modules) } - - /// Returns a base64 encoded sha256 hash of the contents of the Project structure. - /// Used for verifying if a change would occure in Twoliter.lock - pub(crate) fn digest(&self) -> Result { - let mut hash = Sha256::default(); - hash.write(self.release_version.as_bytes()) - .context("failed to encode release version in hash")?; - for (key, value) in self.vendor.iter() { - hash.write(key.to_string().as_bytes()) - .context("failed to encode vendor name in hash")?; - hash.write(value.registry.as_bytes()) - .context("failed to encode vendor registry in hash")?; - } - if let Some(sdk) = self.sdk.as_ref() { - hash.write(sdk.name.to_string().as_bytes()) - .context("failed to encode sdk name in hash")?; - hash.write(sdk.version.to_string().as_bytes()) - .context("failed to encode sdk version in hash")?; - hash.write(sdk.vendor.to_string().as_bytes()) - .context("failed to encode sdk vendor in hash")?; - } - for kit in self.kit.iter() { - hash.write(kit.name.to_string().as_bytes()) - .context("failed to encode kit name in hash")?; - hash.write(kit.version.to_string().as_bytes()) - .context("failed to encode kit version in hash")?; - hash.write(kit.vendor.to_string().as_bytes()) - .context("failed to encode kit vendor in hash")?; - } - let project_hash = hash.finalize(); - Ok(base64::engine::general_purpose::STANDARD.encode(project_hash.as_slice())) - } } /// This represents a container registry vendor that is used in resolving the kits and also diff --git a/twoliter/src/test/cargo_make.rs b/twoliter/src/test/cargo_make.rs index b9460b676..da0c3c787 100644 --- a/twoliter/src/test/cargo_make.rs +++ b/twoliter/src/test/cargo_make.rs @@ -14,8 +14,6 @@ async fn test_cargo_make() { let vendor = project.vendor().get(&vendor_id).unwrap(); let lock = Lock { schema_version: project.schema_version(), - release_version: project.release_version().to_string(), - digest: project.digest().unwrap(), kit: Vec::new(), sdk: LockedImage { name: "my-bottlerocket-sdk".to_string(),