Skip to content

Commit

Permalink
add sdk to project schema
Browse files Browse the repository at this point in the history
Add the ability to specify the desired Bottlerocket SDK in
Twoliter.toml.
  • Loading branch information
webern committed Oct 2, 2023
1 parent 88ddea6 commit d12d4af
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 35 deletions.
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions twoliter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ env_logger = "0.10"
flate2 = "1"
hex = "0.4"
log = "0.4"
non-empty-string = { version = "0.2", features = [ "serde" ] }
serde = { version = "1", features = ["derive"] }
sha2 = "0.10"
tar = "0.4"
Expand Down
6 changes: 3 additions & 3 deletions twoliter/src/cmd/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::docker;
use crate::project::{Project, Sdk};
use crate::project::Project;
use crate::{docker, project};
use anyhow::Result;
use clap::Parser;
use log::debug;
Expand Down Expand Up @@ -44,7 +44,7 @@ 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 sdk = project::default_sdk();
let _ = docker::create_twoliter_image_if_not_exists(&sdk.uri(&self.arch)).await?;
Ok(())
}
Expand Down
20 changes: 14 additions & 6 deletions twoliter/src/docker/image.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -71,15 +73,15 @@ impl ImageArchUri {
/// Create a new `ImageArchUri`.
pub(crate) fn new<S1, S2, S3>(registry: Option<String>, name: S1, arch: S2, tag: S3) -> Self
where
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
S1: AsRef<str>,
S2: AsRef<str>,
S3: AsRef<str>,
{
Self {
registry,
name: name.into(),
arch: arch.into(),
tag: tag.into(),
name: name.as_ref().into(),
arch: arch.as_ref().into(),
tag: tag.as_ref().into(),
}
}

Expand All @@ -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");
Expand Down
5 changes: 0 additions & 5 deletions twoliter/src/docker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
126 changes: 108 additions & 18 deletions twoliter/src/project.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
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};
use non_empty_string::NonEmptyString;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
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-sdk";
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).
Expand All @@ -24,14 +29,22 @@ pub(crate) async fn load_or_find_project(user_path: Option<PathBuf>) -> Result<P
}

/// Represents the structure of a `Twoliter.toml` project file.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct Project {
#[serde(skip)]
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.
sdk: Option<ImageName>,

/// The Bottlerocket Toolchain container image.
toolchain: Option<ImageName>,
}

impl Project {
Expand Down Expand Up @@ -90,29 +103,61 @@ 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 toolchain_name(&self) -> Option<&ImageName> {
self.toolchain.as_ref()
}

pub(crate) fn sdk(&self, arch: &str) -> Option<ImageArchUri> {
self.sdk_name().map(|s| s.uri(arch))
}

pub(crate) fn toolchain(&self, arch: &str) -> Option<ImageArchUri> {
self.toolchain_name().map(|s| s.uri(arch))
}
}

#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
/// 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, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct Sdk {
pub(crate) registry: Option<String>,
pub(crate) name: String,
pub(crate) version: String,
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<NonEmptyString>,
/// The base name of the image that can be suffixed. For example `bottlerocket` can become
/// `bottlerocket-sdk` or `bottlerocket-toolchain`.
pub(crate) name: NonEmptyString,
/// The version tag, for example `v0.50.0`
pub(crate) version: NonEmptyString,
}

impl Default for Sdk {
fn default() -> Self {
Self {
registry: Some(DEFAULT_REGISTRY.to_string()),
name: DEFAULT_SDK_NAME.to_string(),
version: DEFAULT_SDK_VERSION.to_string(),
}
pub(crate) fn default_sdk() -> ImageName {
ImageName {
registry: Some(DEFAULT_REGISTRY.try_into().unwrap()),
name: DEFAULT_SDK_NAME.try_into().unwrap(),
version: DEFAULT_SDK_VERSION.try_into().unwrap(),
}
}

impl Sdk {
pub(crate) fn uri<S: Into<String>>(&self, arch: S) -> ImageArchUri {
ImageArchUri::new(self.registry.clone(), &self.name, arch, &self.version)
impl ImageName {
pub(crate) fn uri<S>(&self, arch: S) -> ImageArchUri
where
S: AsRef<str>,
{
ImageArchUri::new(
self.registry.as_ref().map(|s| s.to_string()),
self.name.clone(),
arch.as_ref(),
&self.version,
)
}
}

Expand Down Expand Up @@ -192,6 +237,23 @@ mod test {

// Add checks here as desired to validate deserialization.
assert_eq!(SchemaVersion::<1>::default(), deserialized.schema_version);
let sdk_name = deserialized.sdk_name().unwrap();
let toolchain_name = deserialized.toolchain_name().unwrap();
assert_eq!("a.com/b", sdk_name.registry.as_ref().unwrap().as_str());
assert_eq!(
"my-bottlerocket-sdk",
deserialized.sdk_name().unwrap().name.as_str()
);
assert_eq!("v1.2.3", deserialized.sdk_name().unwrap().version.as_str());
assert_eq!("c.co/d", toolchain_name.registry.as_ref().unwrap().as_str());
assert_eq!(
"toolchainz",
deserialized.toolchain_name().unwrap().name.as_str()
);
assert_eq!(
"v3.4.5",
deserialized.toolchain_name().unwrap().version.as_str()
);
}

/// Ensure that a `Twoliter.toml` cannot be serialized if the `schema_version` is incorrect.
Expand Down Expand Up @@ -222,4 +284,32 @@ 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".try_into().unwrap()),
name: "foo-abc".try_into().unwrap(),
version: "version1".try_into().unwrap(),
}),
toolchain: Some(ImageName {
registry: Some("example.com".try_into().unwrap()),
name: "foo-def".try_into().unwrap(),
version: "version2".try_into().unwrap(),
}),
};

assert_eq!(
"example.com/foo-abc-x86_64:version1",
project.sdk("x86_64").unwrap().to_string()
);
assert_eq!(
"example.com/foo-def-aarch64:version2",
project.toolchain("aarch64").unwrap().to_string()
);
}
}
9 changes: 7 additions & 2 deletions twoliter/src/test/data/Twoliter-1.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ project-name = "sample-project"
project-version = "v1.0.0"

[sdk]
registry = "example.com/my-repos"
registry = "a.com/b"
name = "my-bottlerocket-sdk"
version = "1.2.3"
version = "v1.2.3"

[toolchain]
registry = "c.co/d"
name = "toolchainz"
version = "v3.4.5"
7 changes: 6 additions & 1 deletion twoliter/src/test/data/Twoliter-invalid-version.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ project-version = "v1.0.0"
[sdk]
registry = "example.com/my-repos"
name = "my-bottlerocket-sdk"
version = "1.2.3"
version = "v1.2.3"

[toolchain]
registry = "example.com/my-repos"
name = "my-bottlerocket-toolchain"
version = "v1.2.3"

0 comments on commit d12d4af

Please sign in to comment.