Skip to content

Commit

Permalink
lock: update lock change resolution to fix mutating image collision
Browse files Browse the repository at this point in the history
  • Loading branch information
jmt-lab committed Jul 26, 2024
1 parent a238374 commit 2f7b23d
Show file tree
Hide file tree
Showing 11 changed files with 30 additions and 71 deletions.
3 changes: 0 additions & 3 deletions tests/integration-tests/src/twoliter_update.rs
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
4 changes: 2 additions & 2 deletions twoliter/src/cmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion twoliter/src/cmd/build_clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
7 changes: 6 additions & 1 deletion twoliter/src/cmd/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@ 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,

/// 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(())
}
Expand Down
2 changes: 1 addition & 1 deletion twoliter/src/cmd/make.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions twoliter/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
2 changes: 1 addition & 1 deletion twoliter/src/cmd/publish_kit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion twoliter/src/cmd/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
41 changes: 17 additions & 24 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,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<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> {
pub(crate) async fn load(project: &Project, create: bool) -> Result<Self> {
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<Self> {
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 {
Expand Down Expand Up @@ -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,
})
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 2f7b23d

Please sign in to comment.