Skip to content

Commit

Permalink
Merge pull request #337 from jmt-lab/lock-resolution-fix
Browse files Browse the repository at this point in the history
lock: update lock change resolution to fix mutating image collision
  • Loading branch information
jmt-lab committed Aug 2, 2024
2 parents 98bc37b + 777878d commit 6b086df
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 68 deletions.
2 changes: 0 additions & 2 deletions tests/integration-tests/src/twoliter_update.rs
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
1 change: 1 addition & 0 deletions twoliter/src/cmd/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub(crate) struct Fetch {
#[clap(long = "project-path")]
pub(crate) project_path: Option<PathBuf>,

/// Architecture of images to fetch
#[clap(long = "arch", default_value = "x86_64")]
pub(crate) arch: String,
}
Expand Down
56 changes: 27 additions & 29 deletions twoliter/src/lock.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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,
Expand All @@ -38,6 +38,12 @@ pub(crate) struct LockedImage {
pub(crate) manifest: Vec<u8>,
}

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<Self> {
let source = format!("{}/{}:v{}", vendor.registry, image.name, image.version);
Expand Down Expand Up @@ -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<LockedImage>,
/// 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<Self> {
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<Self> {
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<Self> {
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)
}

Expand Down Expand Up @@ -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,
})
Expand Down
35 changes: 0 additions & 35 deletions twoliter/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<String> {
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
Expand Down
2 changes: 0 additions & 2 deletions twoliter/src/test/cargo_make.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit 6b086df

Please sign in to comment.