From 9e1f8db042a59a13e1a1f335435d916d6f115f94 Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Thu, 28 Sep 2023 17:26:33 -0700 Subject: [PATCH] add sdk to project schema Add the ability to specify the desired Bottlerocket SDK in Twoliter.toml. --- twoliter/src/cmd/build.rs | 6 +- twoliter/src/docker/image.rs | 8 ++ twoliter/src/docker/mod.rs | 5 - twoliter/src/project.rs | 92 +++++++++++++++++-- twoliter/src/test/data/Twoliter-1.toml | 2 +- .../test/data/Twoliter-invalid-version.toml | 2 +- 6 files changed, 98 insertions(+), 17 deletions(-) diff --git a/twoliter/src/cmd/build.rs b/twoliter/src/cmd/build.rs index a80896d15..965fb8d69 100644 --- a/twoliter/src/cmd/build.rs +++ b/twoliter/src/cmd/build.rs @@ -1,5 +1,5 @@ use crate::docker; -use crate::project::{Project, Sdk}; +use crate::project::{ImageName, Project}; use anyhow::Result; use clap::Parser; use log::debug; @@ -44,8 +44,8 @@ impl BuildVariant { Some(p) => Project::load(p).await?, }; // TODO - get smart about sdk: https://github.com/bottlerocket-os/twoliter/issues/11 - let sdk = Sdk::default(); - let _ = docker::create_twoliter_image_if_not_exists(&sdk.uri(&self.arch)).await?; + let sdk = ImageName::default(); + let _ = docker::create_twoliter_image_if_not_exists(&sdk.uri("sdk", &self.arch)).await?; Ok(()) } } diff --git a/twoliter/src/docker/image.rs b/twoliter/src/docker/image.rs index 1beda8c8e..baca99b51 100644 --- a/twoliter/src/docker/image.rs +++ b/twoliter/src/docker/image.rs @@ -1,3 +1,5 @@ +use std::fmt::{Display, Formatter}; + /// Represents a docker image URI such as `public.ecr.aws/myregistry/myrepo:v0.1.0`. The registry is /// optional as it is when using `docker`. That is, it will be looked for locally first, then at /// `dockerhub.io` when the registry is absent. @@ -93,6 +95,12 @@ impl ImageArchUri { } } +impl Display for ImageArchUri { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.uri(), f) + } +} + #[test] fn image_arch_uri_no_registry() { let uri = ImageArchUri::new(None, "my-sdk", "i386", "v0.33.1"); diff --git a/twoliter/src/docker/mod.rs b/twoliter/src/docker/mod.rs index 39e99dbe8..079dec8ad 100644 --- a/twoliter/src/docker/mod.rs +++ b/twoliter/src/docker/mod.rs @@ -5,8 +5,3 @@ mod twoliter; pub(crate) use self::commands::DockerBuild; pub(crate) use self::image::{ImageArchUri, ImageUri}; pub(crate) use self::twoliter::create_twoliter_image_if_not_exists; - -pub(super) const DEFAULT_REGISTRY: &str = "public.ecr.aws/bottlerocket"; -pub(super) const DEFAULT_SDK_NAME: &str = "bottlerocket-sdk"; -// TODO - get this from lock file: https://github.com/bottlerocket-os/twoliter/issues/11 -pub(super) const DEFAULT_SDK_VERSION: &str = "v0.33.0"; diff --git a/twoliter/src/project.rs b/twoliter/src/project.rs index 8dcca73af..2616e404b 100644 --- a/twoliter/src/project.rs +++ b/twoliter/src/project.rs @@ -1,4 +1,4 @@ -use crate::docker::{ImageArchUri, DEFAULT_REGISTRY, DEFAULT_SDK_NAME, DEFAULT_SDK_VERSION}; +use crate::docker::ImageArchUri; use anyhow::{ensure, Context, Result}; use async_recursion::async_recursion; use log::{debug, trace}; @@ -8,6 +8,10 @@ use std::fmt; use std::path::{Path, PathBuf}; use tokio::fs; +const DEFAULT_REGISTRY: &str = "public.ecr.aws/bottlerocket"; +const DEFAULT_SDK_NAME: &str = "bottlerocket"; +const DEFAULT_SDK_VERSION: &str = "v0.34.1"; + /// Common functionality in commands, if the user gave a path to the `Twoliter.toml` file, /// we use it, otherwise we search for the file. Returns the `Project` and the path at which it was /// found (this is the same as `user_path` if provided). @@ -31,7 +35,12 @@ pub(crate) struct Project { filepath: PathBuf, #[serde(skip)] project_dir: PathBuf, - pub(crate) schema_version: SchemaVersion<1>, + + /// The version of this schema struct. + schema_version: SchemaVersion<1>, + + /// The Bottlerocket SDK container image to use. + sdk: Option, } impl Project { @@ -90,17 +99,39 @@ impl Project { pub(crate) fn project_dir(&self) -> PathBuf { self.project_dir.clone() } + + pub(crate) fn sdk_name(&self) -> Option<&ImageName> { + self.sdk.as_ref() + } + + pub(crate) fn sdk(&self, arch: &str) -> Option { + self.sdk_name().map(|s| s.uri("sdk", arch)) + } + + pub(crate) fn toolchain(&self, arch: &str) -> Option { + self.sdk_name().map(|s| s.uri("toolchain", arch)) + } } +/// A base name for an image that can be suffixed using a naming convention. For example, +/// `registry=public.ecr.aws/bottlerocket`, `name=bottlerocket`, `version=v0.50.0` can be suffixed +/// via naming convention to produce: +/// - `registry=public.ecr.aws/bottlerocket/bottlerocket-sdk-x86_64:v0.50.0` +/// - `registry=public.ecr.aws/bottlerocket/bottlerocket-toolchain-aarch64:v0.50.0` #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] -pub(crate) struct Sdk { +pub(crate) struct ImageName { + /// The registry, e.g. `public.ecr.aws/bottlerocket`. Optional because locally cached images may + /// not specify a registry. pub(crate) registry: Option, + /// The base name of the image that can be suffixed. For example `bottlerocket` can become + /// `bottlerocket-sdk` or `bottlerocket-toolchain`. pub(crate) name: String, + /// The version tag, for example `v0.50.0` pub(crate) version: String, } -impl Default for Sdk { +impl Default for ImageName { fn default() -> Self { Self { registry: Some(DEFAULT_REGISTRY.to_string()), @@ -110,9 +141,18 @@ impl Default for Sdk { } } -impl Sdk { - pub(crate) fn uri>(&self, arch: S) -> ImageArchUri { - ImageArchUri::new(self.registry.clone(), &self.name, arch, &self.version) +impl ImageName { + pub(crate) fn uri(&self, suffix: S1, arch: S2) -> ImageArchUri + where + S1: AsRef, + S2: AsRef, + { + ImageArchUri::new( + self.registry.clone(), + format!("{}-{}", self.name, suffix.as_ref()), + arch.as_ref(), + &self.version, + ) } } @@ -192,6 +232,21 @@ mod test { // Add checks here as desired to validate deserialization. assert_eq!(SchemaVersion::<1>::default(), deserialized.schema_version); + assert_eq!( + "example.com/my-repos", + deserialized + .sdk + .as_ref() + .unwrap() + .registry + .as_ref() + .unwrap() + ); + assert_eq!( + "my-bottlerocket-sdk", + deserialized.sdk.as_ref().unwrap().name + ); + assert_eq!("v1.2.3", deserialized.sdk.as_ref().unwrap().version); } /// Ensure that a `Twoliter.toml` cannot be serialized if the `schema_version` is incorrect. @@ -222,4 +277,27 @@ mod test { // Ensure that the file we loaded was the one we expected to load. assert_eq!(project.filepath(), twoliter_toml_path); } + + #[test] + fn test_sdk_toolchain_uri() { + let project = Project { + filepath: Default::default(), + project_dir: Default::default(), + schema_version: Default::default(), + sdk: Some(ImageName { + registry: Some("example.com".to_string()), + name: "foo".to_string(), + version: "fakever".to_string(), + }), + }; + + assert_eq!( + "example.com/foo-sdk-fakearch:fakever", + project.sdk("fakearch").unwrap().to_string() + ); + assert_eq!( + "example.com/foo-toolchain-fakearch:fakever", + project.toolchain("fakearch").unwrap().to_string() + ); + } } diff --git a/twoliter/src/test/data/Twoliter-1.toml b/twoliter/src/test/data/Twoliter-1.toml index 8912428c3..d7a3791c8 100644 --- a/twoliter/src/test/data/Twoliter-1.toml +++ b/twoliter/src/test/data/Twoliter-1.toml @@ -5,4 +5,4 @@ project-version = "v1.0.0" [sdk] registry = "example.com/my-repos" name = "my-bottlerocket-sdk" -version = "1.2.3" +version = "v1.2.3" diff --git a/twoliter/src/test/data/Twoliter-invalid-version.toml b/twoliter/src/test/data/Twoliter-invalid-version.toml index 455b8878b..4e08ad8bf 100644 --- a/twoliter/src/test/data/Twoliter-invalid-version.toml +++ b/twoliter/src/test/data/Twoliter-invalid-version.toml @@ -5,4 +5,4 @@ project-version = "v1.0.0" [sdk] registry = "example.com/my-repos" name = "my-bottlerocket-sdk" -version = "1.2.3" +version = "v1.2.3"