diff --git a/tests/integration-tests/src/twoliter_update.rs b/tests/integration-tests/src/twoliter_update.rs index a565aa5cf..4672f291a 100644 --- a/tests/integration-tests/src/twoliter_update.rs +++ b/tests/integration-tests/src/twoliter_update.rs @@ -1,8 +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" diff --git a/twoliter/src/cmd/fetch.rs b/twoliter/src/cmd/fetch.rs index 92ad46478..5db655900 100644 --- a/twoliter/src/cmd/fetch.rs +++ b/twoliter/src/cmd/fetch.rs @@ -10,6 +10,7 @@ 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, } diff --git a/twoliter/src/lock.rs b/twoliter/src/lock.rs index 782fea616..b4a9d565f 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,48 +258,42 @@ 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 { - let lock_file_path = project.project_dir().join(TWOLITER_LOCK); - 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?; - } - let lock = Self::resolve(project).await?; - let lock_str = toml::to_string(&lock).context("failed to serialize lock file")?; + let lock_state = Self::resolve(project).await?; + 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_state) + } + + pub(crate) async fn load(project: &Project) -> Result { + let lock_file_path = project.project_dir().join(TWOLITER_LOCK); + ensure!( + lock_file_path.exists(), + "Twoliter.lock does not exist, please run `twoliter update` first" + ); + let lock_state = Self::resolve(project).await?; + 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")?; + ensure!(lock_state == lock, "changes have occured to Twoliter.toml or the remote kit images that require an update to Twoliter.lock"); Ok(lock) } @@ -457,8 +457,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(),