From be581d4cf36504091c28c5e2307d70398b93335a Mon Sep 17 00:00:00 2001 From: Rune Soerensen Date: Tue, 24 Sep 2024 12:07:11 -0400 Subject: [PATCH 1/2] Migrate to `libherokubuildpack` inventory Bump `libherokubuildpack` to version 0.24.0 --- Cargo.lock | 34 ++++----------- Cargo.toml | 1 - buildpacks/go/Cargo.toml | 3 +- buildpacks/go/src/layers/dist.rs | 15 ++++--- buildpacks/go/src/main.rs | 12 ++++-- buildpacks/go/src/tgz.rs | 20 ++++----- common/go-utils/Cargo.toml | 2 +- common/go-utils/src/bin/update_inventory.rs | 42 ++++++++++++------- common/go-utils/src/inv.rs | 46 +++++++-------------- common/go-utils/src/vrs.rs | 2 +- 10 files changed, 79 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a39d2dd..36b5ce0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,7 +443,6 @@ version = "0.0.0" dependencies = [ "flate2", "heroku-go-utils", - "heroku-inventory-utils", "hex", "libcnb", "libcnb-test", @@ -462,7 +461,7 @@ dependencies = [ name = "heroku-go-utils" version = "0.0.0" dependencies = [ - "heroku-inventory-utils", + "libherokubuildpack", "regex", "semver", "serde", @@ -472,19 +471,6 @@ dependencies = [ "ureq", ] -[[package]] -name = "heroku-inventory-utils" -version = "0.0.0" -dependencies = [ - "hex", - "semver", - "serde", - "serde_test", - "sha2", - "thiserror", - "toml", -] - [[package]] name = "hex" version = "0.4.3" @@ -680,11 +666,16 @@ dependencies = [ [[package]] name = "libherokubuildpack" -version = "0.21.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146f61983fd384cb5ab5373acdd8f53fcb4b27ecb200435a6bfb6a70b421bc9d" +checksum = "bec33ef08ce0508ced826f7bdfcd0538bdfc2270632915e447d1ea72bce5645d" dependencies = [ + "hex", + "serde", + "sha2", "termcolor", + "thiserror", + "toml", ] [[package]] @@ -1049,15 +1040,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_test" -version = "1.0.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" -dependencies = [ - "serde", -] - [[package]] name = "sha2" version = "0.10.8" diff --git a/Cargo.toml b/Cargo.toml index 2fec890..bab1a48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ resolver = "2" members = [ "buildpacks/go", "common/go-utils", - "common/inventory-utils", ] [workspace.package] diff --git a/buildpacks/go/Cargo.toml b/buildpacks/go/Cargo.toml index 238bcd0..0e34e50 100644 --- a/buildpacks/go/Cargo.toml +++ b/buildpacks/go/Cargo.toml @@ -8,13 +8,12 @@ workspace = true [dependencies] heroku-go-utils = { path = "../../common/go-utils" } -heroku-inventory-utils = { path = "../../common/inventory-utils" } hex = "0.4.3" flate2 = { version = "1", default-features = false, features = ["zlib"] } # libcnb has a much bigger impact on buildpack behaviour than any other dependencies, # so it's pinned to an exact version to isolate it from lockfile refreshes. libcnb = { version = "=0.21.0", features = ["trace"] } -libherokubuildpack = { version = "=0.21.0", default-features = false, features = ["log"] } +libherokubuildpack = { version = "=0.24.0", default-features = false, features = ["inventory", "log"] } semver = "1" serde = "1" sha2 = "0.10" diff --git a/buildpacks/go/src/layers/dist.rs b/buildpacks/go/src/layers/dist.rs index 6fd875e..381288b 100644 --- a/buildpacks/go/src/layers/dist.rs +++ b/buildpacks/go/src/layers/dist.rs @@ -1,11 +1,11 @@ use crate::{tgz, GoBuildpack, GoBuildpackError}; use heroku_go_utils::vrs::GoVersion; -use heroku_inventory_utils::inv::Artifact; use libcnb::build::BuildContext; use libcnb::data::layer_content_metadata::LayerTypes; use libcnb::layer::{ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder}; use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; use libcnb::Buildpack; +use libherokubuildpack::inventory::artifact::Artifact; use libherokubuildpack::log::log_info; use serde::{Deserialize, Serialize}; use sha2::Sha256; @@ -13,13 +13,13 @@ use std::path::Path; /// A layer that downloads and installs the Go distribution artifacts pub(crate) struct DistLayer { - pub(crate) artifact: Artifact, + pub(crate) artifact: Artifact>, } #[derive(Deserialize, Serialize, Clone, PartialEq, Eq)] pub(crate) struct DistLayerMetadata { layer_version: String, - artifact: Artifact, + artifact: Artifact>, } #[derive(thiserror::Error, Debug)] @@ -48,8 +48,8 @@ impl Layer for DistLayer { layer_path: &Path, ) -> Result, GoBuildpackError> { log_info(format!( - "Installing {} from {}", - self.artifact, self.artifact.url + "Installing {} ({}-{}) from {}", + self.artifact.version, self.artifact.os, self.artifact.arch, self.artifact.url )); tgz::fetch_strip_filter_extract_verify( &self.artifact, @@ -84,7 +84,10 @@ impl Layer for DistLayer { layer_data: &LayerData, ) -> Result::Error> { if layer_data.content_metadata.metadata == DistLayerMetadata::current(self) { - log_info(format!("Reusing {}", self.artifact)); + log_info(format!( + "Reusing {} ({}-{})", + self.artifact.version, self.artifact.os, self.artifact.arch + )); Ok(ExistingLayerStrategy::Keep) } else { Ok(ExistingLayerStrategy::Recreate) diff --git a/buildpacks/go/src/main.rs b/buildpacks/go/src/main.rs index 1f67dd6..0a458ed 100644 --- a/buildpacks/go/src/main.rs +++ b/buildpacks/go/src/main.rs @@ -5,7 +5,6 @@ mod proc; mod tgz; use heroku_go_utils::vrs::GoVersion; -use heroku_inventory_utils::inv::{resolve, Arch, Inventory, Os}; use layers::build::{BuildLayer, BuildLayerError}; use layers::deps::{DepsLayer, DepsLayerError}; use layers::dist::{DistLayer, DistLayerError}; @@ -19,6 +18,8 @@ use libcnb::generic::GenericMetadata; use libcnb::generic::GenericPlatform; use libcnb::layer_env::Scope; use libcnb::{buildpack_main, Buildpack, Env}; +use libherokubuildpack::inventory::artifact::{Arch, Os}; +use libherokubuildpack::inventory::Inventory; use libherokubuildpack::log::{log_error, log_header, log_info}; use sha2::Sha256; use std::env::{self, consts}; @@ -63,7 +64,7 @@ impl Buildpack for GoBuildpack { go_env.insert(k, v); }); - let inv: Inventory = + let inv: Inventory> = toml::from_str(INVENTORY).map_err(GoBuildpackError::InventoryParse)?; let config = cfg::read_gomod_config(context.app_dir.join("go.mod")) @@ -72,12 +73,15 @@ impl Buildpack for GoBuildpack { log_info(format!("Detected Go version requirement: {requirement}")); let artifact = match (consts::OS.parse::(), consts::ARCH.parse::()) { - (Ok(os), Ok(arch)) => resolve(inv.artifacts.as_slice(), os, arch, &requirement), + (Ok(os), Ok(arch)) => inv.resolve(os, arch, &requirement), (_, _) => None, } .ok_or(GoBuildpackError::VersionResolution(requirement.clone()))?; - log_info(format!("Resolved Go version: {artifact}")); + log_info(format!( + "Resolved Go version: {} ({}-{})", + artifact.version, artifact.os, artifact.arch + )); log_header("Installing Go distribution"); go_env = context diff --git a/buildpacks/go/src/tgz.rs b/buildpacks/go/src/tgz.rs index b20c1c7..7eb0706 100644 --- a/buildpacks/go/src/tgz.rs +++ b/buildpacks/go/src/tgz.rs @@ -1,4 +1,5 @@ use flate2::read::GzDecoder; +use libherokubuildpack::inventory::artifact::Artifact; use sha2::{ digest::{generic_array::GenericArray, OutputSizeUser}, Digest, @@ -6,8 +7,6 @@ use sha2::{ use std::{fs, io::Read, path::StripPrefixError}; use tar::Archive; -use heroku_inventory_utils::inv::Artifact; - #[derive(thiserror::Error, Debug)] pub(crate) enum Error { #[error("HTTP error while fetching archive: {0}")] @@ -44,7 +43,7 @@ pub(crate) enum Error { /// /// See `Error` for an enumeration of error scenarios. pub(crate) fn fetch_strip_filter_extract_verify<'a, D: Digest, V>( - artifact: &Artifact, + artifact: &Artifact>, strip_prefix: impl AsRef, filter_prefixes: impl Iterator, dest_dir: impl AsRef, @@ -113,9 +112,9 @@ impl Read for DigestingReader { #[cfg(test)] mod tests { - use heroku_inventory_utils::{ + use libherokubuildpack::inventory::{ + artifact::{Arch, Os}, checksum::Checksum, - inv::{Arch, Os}, }; use sha2::Sha256; @@ -123,15 +122,16 @@ mod tests { #[test] fn test_fetch_strip_filter_extract_verify() { - let artifact = Artifact:: { + let artifact = Artifact::> { version: "0.0.1".to_string(), os: Os::Linux, arch: Arch::Amd64, url: "https://mirrors.edge.kernel.org/pub/software/scm/git/git-0.01.tar.gz".to_string(), - checksum: Checksum::try_from( - "9bdf8a4198b269c5cbe4263b1f581aae885170a6cb93339a2033cb468e57dcd3".to_string(), - ) - .unwrap(), + checksum: "sha256:9bdf8a4198b269c5cbe4263b1f581aae885170a6cb93339a2033cb468e57dcd3" + .to_string() + .parse::>() + .unwrap(), + metadata: None, }; let dest = tempfile::tempdir().expect("Couldn't create test tmpdir"); diff --git a/common/go-utils/Cargo.toml b/common/go-utils/Cargo.toml index d89899e..d6daa81 100644 --- a/common/go-utils/Cargo.toml +++ b/common/go-utils/Cargo.toml @@ -14,4 +14,4 @@ serde = { version = "1", features = ["derive"] } thiserror = "1" toml = "0.8" ureq = { version = "2", features = ["json"] } -heroku-inventory-utils = { path = "../../common/inventory-utils" } +libherokubuildpack = { version = "=0.24.0", default-features = false, features = ["inventory", "log", "inventory-sha2"] } diff --git a/common/go-utils/src/bin/update_inventory.rs b/common/go-utils/src/bin/update_inventory.rs index c6f6be6..23ff98b 100644 --- a/common/go-utils/src/bin/update_inventory.rs +++ b/common/go-utils/src/bin/update_inventory.rs @@ -2,9 +2,9 @@ #![allow(unused_crate_dependencies)] use heroku_go_utils::{inv::list_upstream_artifacts, vrs::GoVersion}; -use heroku_inventory_utils::inv::{read_inventory_file, Artifact, Inventory}; +use libherokubuildpack::inventory::{artifact::Artifact, Inventory}; use sha2::Sha256; -use std::{collections::HashSet, env, fs, process}; +use std::{env, fs, process}; /// Updates the local go inventory.toml with versions published on go.dev. fn main() { @@ -13,15 +13,17 @@ fn main() { process::exit(2); }); - let inventory_artifacts: HashSet> = - read_inventory_file(&inventory_path) - .unwrap_or_else(|e| { - eprintln!("Error reading inventory at '{inventory_path}': {e}"); - std::process::exit(1); - }) - .artifacts - .into_iter() - .collect(); + let inventory_artifacts = fs::read_to_string(&inventory_path) + .unwrap_or_else(|e| { + eprintln!("Error reading inventory at '{inventory_path}': {e}"); + std::process::exit(1); + }) + .parse::>>() + .unwrap_or_else(|e| { + eprintln!("Error parsing inventory file at '{inventory_path}': {e}"); + process::exit(1); + }) + .artifacts; // List available upstream release versions. let remote_artifacts = list_upstream_artifacts().unwrap_or_else(|e| { @@ -43,25 +45,33 @@ fn main() { process::exit(7); }); - let remote_artifacts: HashSet> = + let remote_artifacts: Vec>> = inventory.artifacts.into_iter().collect(); [ - ("Added", &remote_artifacts - &inventory_artifacts), - ("Removed", &inventory_artifacts - &remote_artifacts), + ("Added", difference(&remote_artifacts, &inventory_artifacts)), + ( + "Removed", + difference(&inventory_artifacts, &remote_artifacts), + ), ] .iter() .filter(|(_, artifact_diff)| !artifact_diff.is_empty()) .for_each(|(action, artifacts)| { - let mut list: Vec<&Artifact> = artifacts.iter().collect(); + let mut list: Vec<_> = artifacts.iter().collect(); list.sort_by_key(|a| &a.version); println!( "{} {}.", action, list.iter() - .map(ToString::to_string) + .map(|artifact| format!("{} ({}-{})", artifact.version, artifact.os, artifact.arch)) .collect::>() .join(", ") ); }); } + +/// Finds the difference between two slices. +fn difference<'a, T: Eq>(a: &'a [T], b: &'a [T]) -> Vec<&'a T> { + a.iter().filter(|&artifact| !b.contains(artifact)).collect() +} diff --git a/common/go-utils/src/inv.rs b/common/go-utils/src/inv.rs index ded8605..77ac187 100644 --- a/common/go-utils/src/inv.rs +++ b/common/go-utils/src/inv.rs @@ -1,6 +1,8 @@ use crate::vrs; -use heroku_inventory_utils::checksum::{self, Checksum}; -use heroku_inventory_utils::inv::{Arch, Artifact, Os, UnsupportedArchError, UnsupportedOsError}; +use libherokubuildpack::inventory::{ + artifact::{Arch, Artifact, Os, UnsupportedArchError, UnsupportedOsError}, + checksum::{self, Checksum}, +}; use serde::Deserialize; use sha2::Sha256; use vrs::{GoVersion, GoVersionParseError}; @@ -31,10 +33,10 @@ pub enum GoFileConversionError { #[error(transparent)] Os(#[from] UnsupportedOsError), #[error(transparent)] - Checksum(#[from] checksum::Error), + Checksum(#[from] checksum::ChecksumParseError), } -impl TryFrom<&GoFile> for Artifact { +impl TryFrom<&GoFile> for Artifact> { type Error = GoFileConversionError; fn try_from(value: &GoFile) -> Result { @@ -42,8 +44,9 @@ impl TryFrom<&GoFile> for Artifact { version: value.version.clone().try_into()?, os: value.os.parse::()?, arch: value.arch.parse::()?, - checksum: Checksum::try_from(value.sha256.clone())?, + checksum: format!("sha256:{}", value.sha256.clone()).parse::>()?, url: format!("{}/{}", GO_HOST_URL, value.filename), + metadata: None, }) } } @@ -71,7 +74,7 @@ pub enum ListUpstreamArtifactsError { /// HTTP issues connecting to the upstream releases endpoint, as well /// as json and Go version parsing issues, will return an error. pub fn list_upstream_artifacts( -) -> Result>, ListUpstreamArtifactsError> { +) -> Result>>, ListUpstreamArtifactsError> { ureq::get(GO_RELEASES_URL) .call() .map_err(|e| ListUpstreamArtifactsError::InvalidResponse(Box::new(e)))? @@ -91,39 +94,20 @@ pub fn list_upstream_artifacts( #[cfg(test)] mod tests { use super::*; - use std::hash::{BuildHasher, RandomState}; - fn create_artifact() -> Artifact { + fn create_artifact() -> Artifact> { Artifact { version: GoVersion::try_from("1.7.2".to_string()).unwrap(), os: Os::Linux, arch: Arch::Amd64, url: String::from("foo"), - checksum: Checksum::try_from( - "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string(), - ) - .unwrap(), + checksum: "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + .parse::>() + .unwrap(), + metadata: None, } } - #[test] - fn test_artifact_display_format() { - let artifact = create_artifact(); - - assert_eq!("1.7.2 (linux-amd64)", artifact.to_string()); - } - - #[test] - fn test_artifact_hash_implementation() { - let artifact = create_artifact(); - - let state = RandomState::new(); - assert_eq!( - state.hash_one(&artifact.checksum.value), - state.hash_one(&artifact) - ); - } - #[test] fn test_artifact_serialization() { let artifact = create_artifact(); @@ -132,7 +116,7 @@ mod tests { .contains("sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")); assert_eq!( artifact, - toml::from_str::>(&serialized).unwrap() + toml::from_str::>>(&serialized).unwrap() ); } } diff --git a/common/go-utils/src/vrs.rs b/common/go-utils/src/vrs.rs index f3b70c0..85adede 100644 --- a/common/go-utils/src/vrs.rs +++ b/common/go-utils/src/vrs.rs @@ -1,4 +1,4 @@ -use heroku_inventory_utils::inv::VersionRequirement; +use libherokubuildpack::inventory::version::VersionRequirement; use regex::Regex; use semver; use serde::{Deserialize, Serialize}; From ad6fa4a218ce136a4fccf1686818097b6692484e Mon Sep 17 00:00:00 2001 From: Rune Soerensen Date: Tue, 24 Sep 2024 12:17:25 -0400 Subject: [PATCH 2/2] Delete inventory-utils --- common/inventory-utils/Cargo.toml | 18 -- common/inventory-utils/src/checksum.rs | 201 ------------------ common/inventory-utils/src/inv.rs | 269 ------------------------- common/inventory-utils/src/lib.rs | 3 - common/inventory-utils/src/semvrs.rs | 7 - 5 files changed, 498 deletions(-) delete mode 100644 common/inventory-utils/Cargo.toml delete mode 100644 common/inventory-utils/src/checksum.rs delete mode 100644 common/inventory-utils/src/inv.rs delete mode 100644 common/inventory-utils/src/lib.rs delete mode 100644 common/inventory-utils/src/semvrs.rs diff --git a/common/inventory-utils/Cargo.toml b/common/inventory-utils/Cargo.toml deleted file mode 100644 index 1f8de99..0000000 --- a/common/inventory-utils/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "heroku-inventory-utils" -rust-version.workspace = true -edition.workspace = true - -[lints] -workspace = true - -[dependencies] -hex = "0.4.3" -semver = {version = "1", features = ["serde"] } -serde = { version = "1", features = ["derive"] } -sha2 = "0.10.8" -thiserror = "1" -toml = "0.8" - -[dev-dependencies] -serde_test = "1.0.177" diff --git a/common/inventory-utils/src/checksum.rs b/common/inventory-utils/src/checksum.rs deleted file mode 100644 index 964331d..0000000 --- a/common/inventory-utils/src/checksum.rs +++ /dev/null @@ -1,201 +0,0 @@ -use hex::FromHexError; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256, Sha512}; -use std::marker::PhantomData; - -#[derive(Debug, Clone, Eq)] -pub struct Checksum { - pub value: Vec, - digest: PhantomData, -} - -impl PartialEq for Checksum { - fn eq(&self, other: &Self) -> bool { - self.value == other.value - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Invalid checksum size for: {0}")] - InvalidSize(String), - #[error("Invalid input string")] - HexError(#[from] FromHexError), -} - -impl TryFrom for Checksum -where - D: ChecksumSize, -{ - type Error = Error; - - fn try_from(input: String) -> Result { - let value: Vec = hex::decode(input.clone())?; - if value.len() == D::checksum_size() { - Ok(Checksum { - value, - digest: PhantomData, - }) - } else { - Err(Error::InvalidSize(input)) - } - } -} - -pub trait Name { - fn name() -> String; -} - -impl Name for Sha256 { - fn name() -> String { - String::from("sha256") - } -} - -impl Name for Sha512 { - fn name() -> String { - String::from("sha512") - } -} - -#[allow(clippy::module_name_repetitions)] -pub trait ChecksumSize { - fn checksum_size() -> usize; -} - -impl ChecksumSize for Sha256 { - fn checksum_size() -> usize { - Self::output_size() - } -} - -impl ChecksumSize for Sha512 { - fn checksum_size() -> usize { - Self::output_size() - } -} - -impl Serialize for Checksum -where - D: Name, -{ - fn serialize(&self, serializer: T) -> Result - where - T: serde::Serializer, - { - serializer.serialize_str(&format!("{}:{}", D::name(), hex::encode(&self.value))) - } -} - -impl<'de, D> Deserialize<'de> for Checksum -where - D: Name, -{ - fn deserialize(deserializer: T) -> Result - where - T: serde::Deserializer<'de>, - { - use serde::de::Error; - String::deserialize(deserializer)? - .strip_prefix(&format!("{}:", D::name())) - .ok_or_else(|| T::Error::custom("checksum prefix is invalid")) - .map(|value| hex::decode(value).map_err(T::Error::custom))? - .map(|value| Checksum::<_> { - value, - digest: PhantomData, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_test::{assert_de_tokens_error, assert_tokens, Token}; - use sha2::Sha512; - - impl Name for String { - fn name() -> String { - String::from("foo") - } - } - - impl ChecksumSize for String { - fn checksum_size() -> usize { - 2 - } - } - - #[test] - fn test_checksum_serialization() { - assert_tokens( - &Checksum::::try_from("abcd".to_string()).unwrap(), - &[Token::BorrowedStr("foo:abcd")], - ); - } - - #[test] - fn test_invalid_checksum_deserialization() { - assert_de_tokens_error::>( - &[Token::BorrowedStr("baz:bar")], - "checksum prefix is invalid", - ); - } - - #[test] - fn test_digest_names() { - assert_eq!("sha256", Sha256::name()); - assert_eq!("sha512", Sha512::name()); - } - - #[test] - fn test_checksum_sizes() { - assert_eq!(32, Sha256::checksum_size()); - assert_eq!(64, Sha512::checksum_size()); - } - - #[test] - fn test_invalid_checksum_size() { - assert!(matches!( - Checksum::::try_from("123456".to_string()), - Err(Error::InvalidSize(..)) - )); - } - - #[test] - fn test_invalid_hex_input() { - assert!(matches!( - Checksum::::try_from("quux".to_string()), - Err(Error::HexError(..)) - )); - } - - #[test] - fn test_sha256_checksum_parse_and_serialize() { - let result: Result, _> = Checksum::try_from( - "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string(), - ); - - assert!(result.is_ok()); - assert_tokens( - &result.unwrap(), - &[Token::BorrowedStr( - "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", - )], - ); - } - - #[test] - fn test_sha512_checksum_parse_and_serialize() { - let result: Result, _> = Checksum::try_from( - "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string(), - ); - - assert!(result.is_ok()); - assert_tokens( - &result.unwrap(), - &[Token::BorrowedStr( - "sha512:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", - )], - ); - } -} diff --git a/common/inventory-utils/src/inv.rs b/common/inventory-utils/src/inv.rs deleted file mode 100644 index 4a53f2a..0000000 --- a/common/inventory-utils/src/inv.rs +++ /dev/null @@ -1,269 +0,0 @@ -use crate::checksum::Checksum; -use crate::checksum::Name; -use core::fmt; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::hash::Hash; -use std::{fmt::Display, str::FromStr}; - -/// Represents an inventory of artifacts. -#[derive(Debug, Serialize, Deserialize)] -pub struct Inventory { - #[serde(bound = "V: Serialize + DeserializeOwned, D: Name")] - pub artifacts: Vec>, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Artifact { - #[serde(bound = "V: Serialize + DeserializeOwned")] - pub version: V, - pub os: Os, - pub arch: Arch, - pub url: String, - #[serde(bound = "D: Name")] - pub checksum: Checksum, -} - -impl PartialEq for Artifact -where - V: Eq, -{ - fn eq(&self, other: &Self) -> bool { - self.version == other.version - && self.os == other.os - && self.arch == other.arch - && self.url == other.url - && self.checksum == other.checksum - } -} - -impl Eq for Artifact where V: Eq {} - -impl Hash for Artifact { - fn hash(&self, state: &mut H) { - self.checksum.value.hash(state); - } -} - -impl Display for Artifact { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} ({}-{})", self.version, self.os, self.arch) - } -} - -#[derive(Debug, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum Os { - Darwin, - Linux, -} - -#[derive(Debug, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum Arch { - Amd64, - Arm64, -} - -impl Display for Os { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Os::Darwin => write!(f, "darwin"), - Os::Linux => write!(f, "linux"), - } - } -} - -#[derive(thiserror::Error, Debug)] -#[error("OS is not supported: {0}")] -pub struct UnsupportedOsError(String); - -impl FromStr for Os { - type Err = UnsupportedOsError; - - fn from_str(s: &str) -> Result { - match s { - "linux" => Ok(Os::Linux), - "darwin" | "osx" => Ok(Os::Darwin), - _ => Err(UnsupportedOsError(s.to_string())), - } - } -} - -impl Display for Arch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Arch::Amd64 => write!(f, "amd64"), - Arch::Arm64 => write!(f, "arm64"), - } - } -} - -#[derive(thiserror::Error, Debug)] -#[error("Arch is not supported: {0}")] -pub struct UnsupportedArchError(String); - -impl FromStr for Arch { - type Err = UnsupportedArchError; - - fn from_str(s: &str) -> Result { - match s { - "amd64" | "x86_64" => Ok(Arch::Amd64), - "arm64" | "aarch64" => Ok(Arch::Arm64), - _ => Err(UnsupportedArchError(s.to_string())), - } - } -} - -#[derive(thiserror::Error, Debug)] -pub enum ReadInventoryError { - #[error("Couldn't read inventory file: {0}")] - Io(#[from] std::io::Error), - #[error("Couldn't parse inventory toml: {0}")] - Parse(#[from] toml::de::Error), -} - -/// Reads a TOML-formatted file to an `Inventory`. -/// -/// # Errors -/// -/// Will return an Err if the file is missing, not readable, or if the -/// file contents is not formatted properly. -pub fn read_inventory_file(path: &str) -> Result, ReadInventoryError> -where - V: Serialize + DeserializeOwned, - D: Name, -{ - toml::from_str(&fs::read_to_string(path)?).map_err(ReadInventoryError::Parse) -} - -pub trait VersionRequirement { - fn satisfies(&self, version: &V) -> bool; -} - -/// Find the first artifact that satisfies a `VersionRequirement` for -/// the specified OS and arch. -pub fn resolve<'a, V, D, R>( - artifacts: &'a [Artifact], - os: Os, - arch: Arch, - requirement: &'a R, -) -> Option<&'a Artifact> -where - R: VersionRequirement, -{ - artifacts - .iter() - .filter(|artifact| artifact.os == os && artifact.arch == arch) - .find(|artifact| requirement.satisfies(&artifact.version)) -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_arch_display_format() { - let archs = [(Arch::Amd64, "amd64"), (Arch::Arm64, "arm64")]; - - for (input, expected) in archs { - assert_eq!(expected, input.to_string()); - } - } - - #[test] - fn test_arch_parsing() { - let archs = [ - ("amd64", Arch::Amd64), - ("arm64", Arch::Arm64), - ("x86_64", Arch::Amd64), - ("aarch64", Arch::Arm64), - ]; - for (input, expected) in archs { - assert_eq!(expected, input.parse::().unwrap()); - } - - assert!(matches!( - "foo".parse::().unwrap_err(), - UnsupportedArchError(..) - )); - } - - #[test] - fn test_os_display_format() { - assert_eq!("linux", Os::Linux.to_string()); - } - - #[test] - fn test_os_parsing() { - assert_eq!(Os::Linux, "linux".parse::().unwrap()); - assert_eq!(Os::Darwin, "darwin".parse::().unwrap()); - assert_eq!(Os::Darwin, "osx".parse::().unwrap()); - - assert!(matches!( - "foo".parse::().unwrap_err(), - UnsupportedOsError(..) - )); - } - - #[test] - fn test_artifact_display() { - assert_eq!( - "foo (linux-arm64)", - create_artifact("foo", Os::Linux, Arch::Arm64).to_string() - ); - } - - impl VersionRequirement for String { - fn satisfies(&self, version: &String) -> bool { - self == version - } - } - - #[test] - fn test_matching_artifact_resolution() { - assert_eq!( - "foo", - &resolve( - &[create_artifact("foo", Os::Linux, Arch::Arm64)], - Os::Linux, - Arch::Arm64, - &String::from("foo") - ) - .expect("should resolve matching artifact") - .version, - ); - } - - #[test] - fn test_dont_resolve_artifact_with_wrong_arch() { - assert!(resolve( - &[create_artifact("foo", Os::Linux, Arch::Arm64)], - Os::Linux, - Arch::Amd64, - &String::from("foo") - ) - .is_none()); - } - - #[test] - fn test_dont_resolve_artifact_with_wrong_version() { - assert!(resolve( - &[create_artifact("foo", Os::Linux, Arch::Arm64)], - Os::Linux, - Arch::Arm64, - &String::from("bar") - ) - .is_none()); - } - - fn create_artifact(version: &str, os: Os, arch: Arch) -> Artifact { - Artifact:: { - version: String::from(version), - os, - arch, - url: "https://example.com".to_string(), - checksum: Checksum::try_from("aaaa".to_string()).unwrap(), - } - } -} diff --git a/common/inventory-utils/src/lib.rs b/common/inventory-utils/src/lib.rs deleted file mode 100644 index c2984db..0000000 --- a/common/inventory-utils/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod checksum; -pub mod inv; -pub mod semvrs; diff --git a/common/inventory-utils/src/semvrs.rs b/common/inventory-utils/src/semvrs.rs deleted file mode 100644 index bae4054..0000000 --- a/common/inventory-utils/src/semvrs.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::inv::VersionRequirement; - -impl VersionRequirement for semver::VersionReq { - fn satisfies(&self, version: &semver::Version) -> bool { - self.matches(version) - } -}