From c3f65c06c631476e6d45f6d5f9494a3b47a4c579 Mon Sep 17 00:00:00 2001 From: Jessica Black Date: Mon, 16 Dec 2024 11:21:28 -0800 Subject: [PATCH 1/5] Support docker-like references --- bin/src/extract.rs | 34 +++++++++--- bin/src/list.rs | 8 +-- lib/src/lib.rs | 109 +++++++++++++++++++++++--------------- lib/tests/it/reference.rs | 20 ++++++- 4 files changed, 116 insertions(+), 55 deletions(-) diff --git a/bin/src/extract.rs b/bin/src/extract.rs index caa322b..4302769 100644 --- a/bin/src/extract.rs +++ b/bin/src/extract.rs @@ -79,8 +79,26 @@ pub struct Options { #[derive(Debug, Args)] pub struct Target { /// Image reference being extracted (e.g. docker.io/library/ubuntu:latest) - #[arg(value_parser = Reference::from_str)] - pub image: Reference, + /// + /// If a fully specified reference is not provided, + /// the image is attempted to be resolved with the prefix + /// `docker.io/library`. + /// + /// The reference may optionally provide a digest, for example + /// `docker.io/library/ubuntu@sha256:1234567890`. + /// + /// Finally, the reference may optionally provide a tag, for example + /// `docker.io/library/ubuntu:latest` or `docker.io/library/ubuntu:24.04`. + /// If no digest or tag is provided, the tag "latest" is used. + /// + /// Put all that together and you get the following examples: + /// - `ubuntu` is resolved as `docker.io/library/ubuntu:latest` + /// - `ubuntu:24.04` is resolved as `docker.io/library/ubuntu:24.04` + /// - `docker.io/library/ubuntu` is resolved as `docker.io/library/ubuntu:latest` + /// - `docker.io/library/ubuntu@sha256:1234567890` is resolved as `docker.io/library/ubuntu@sha256:1234567890` + /// - `docker.io/library/ubuntu:24.04` is resolved as `docker.io/library/ubuntu:24.04` + #[arg(verbatim_doc_comment)] + pub image: String, /// Platform to extract (e.g. linux/amd64) /// @@ -131,20 +149,20 @@ pub enum Mode { pub async fn main(opts: Options) -> Result<()> { info!("extracting image"); - let auth = match (opts.target.username, opts.target.password) { - (Some(username), Some(password)) => Authentication::basic(username, password), - _ => Authentication::default(), - }; - + let reference = Reference::from_str(&opts.target.image)?; let layer_globs = Filters::parse_glob(opts.layer_glob.into_iter().flatten())?; let file_globs = Filters::parse_glob(opts.file_glob.into_iter().flatten())?; let layer_regexes = Filters::parse_regex(opts.layer_regex.into_iter().flatten())?; let file_regexes = Filters::parse_regex(opts.file_regex.into_iter().flatten())?; + let auth = match (opts.target.username, opts.target.password) { + (Some(username), Some(password)) => Authentication::basic(username, password), + _ => Authentication::default(), + }; let output = canonicalize_output_dir(&opts.output_dir, opts.overwrite)?; let registry = Registry::builder() .maybe_platform(opts.target.platform) - .reference(opts.target.image) + .reference(reference) .auth(auth) .layer_filters(layer_globs + layer_regexes) .file_filters(file_globs + file_regexes) diff --git a/bin/src/list.rs b/bin/src/list.rs index d488c9e..ad02eb7 100644 --- a/bin/src/list.rs +++ b/bin/src/list.rs @@ -1,9 +1,9 @@ -use circe_lib::{registry::Registry, Authentication}; +use circe_lib::{registry::Registry, Authentication, Reference}; use clap::Parser; use color_eyre::eyre::{Context, Result}; use derive_more::Debug; use pluralizer::pluralize; -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; use tracing::{debug, info}; use crate::extract::Target; @@ -19,13 +19,15 @@ pub struct Options { pub async fn main(opts: Options) -> Result<()> { info!("extracting image"); + let reference = Reference::from_str(&opts.target.image)?; let auth = match (opts.target.username, opts.target.password) { (Some(username), Some(password)) => Authentication::basic(username, password), _ => Authentication::default(), }; + let registry = Registry::builder() .maybe_platform(opts.target.platform) - .reference(opts.target.image) + .reference(reference) .auth(auth) .build() .await diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b087d18..7c464f5 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -2,7 +2,7 @@ use bon::Builder; use color_eyre::{ - eyre::{self, bail, eyre, Context}, + eyre::{self, bail, ensure, eyre, Context}, Result, Section, SectionExt, }; use derive_more::derive::{Debug, Display, From}; @@ -388,28 +388,6 @@ impl Version { } /// A parsed container image reference. -/// -/// ``` -/// # use circe_lib::{Reference, Version}; -/// # use std::str::FromStr; -/// // Default to latest tag -/// let reference = Reference::from_str("docker.io/library/ubuntu").expect("parse reference"); -/// assert_eq!(reference.host, "docker.io"); -/// assert_eq!(reference.repository, "library/ubuntu"); -/// assert_eq!(reference.version, Version::tag("latest")); -/// -/// // Parse a tag -/// let reference = Reference::from_str("docker.io/library/ubuntu:other").expect("parse reference"); -/// assert_eq!(reference.host, "docker.io"); -/// assert_eq!(reference.repository, "library/ubuntu"); -/// assert_eq!(reference.version, Version::tag("other")); -/// -/// // Parse a digest -/// let reference = Reference::from_str("docker.io/library/ubuntu@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").expect("parse reference"); -/// assert_eq!(reference.host, "docker.io"); -/// assert_eq!(reference.repository, "library/ubuntu"); -/// assert_eq!(reference.version.to_string(), "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"); -/// ``` #[derive(Debug, Clone, PartialEq, Eq, Builder)] pub struct Reference { /// Registry host (e.g. "docker.io", "ghcr.io") @@ -450,32 +428,77 @@ impl FromStr for Reference { type Err = eyre::Error; fn from_str(s: &str) -> Result { - let input_section = || s.to_string().header("Input:"); - let (host, remainder) = s.split_once('/').ok_or_else(|| { - eyre!("invalid reference: missing host separator '/'").with_section(input_section) - })?; + // Returns an owned string so that we can support multiple name segments. + fn parse_name(name: &str) -> Result<(String, Version)> { + if let Some((name, digest)) = name.split_once('@') { + let digest = Digest::from_str(digest).context("parse digest")?; + Ok((name.to_string(), Version::Digest(digest))) + } else if let Some((name, tag)) = name.split_once(':') { + Ok((name.to_string(), Version::Tag(tag.to_string()))) + } else { + Ok((name.to_string(), Version::latest())) + } + } - // Find either ':' for tag or '@' for digest. - // Check for '@' first since digest identifiers also contain ':'. - let (repository, version) = if let Some((repo, digest)) = remainder.split_once('@') { - let digest = Digest::from_str(digest).context("parse digest")?; - (repo, Version::Digest(digest)) - } else if let Some((repo, tag)) = remainder.split_once(':') { - (repo, Version::Tag(tag.to_string())) - } else { - (remainder, Version::latest()) + // Docker supports `docker pull ubuntu` and `docker pull library/ubuntu`, + // both of which are parsed as `docker.io/library/ubuntu`. + // The below recreates this behavior. + const DOCKER_IO: &str = "docker.io"; + const LIBRARY: &str = "library"; + let parts = s.split('/').collect::>(); + let (host, namespace, name, version) = match parts.as_slice() { + // For docker compatibility, `{name}` is parsed as `docker.io/library/{name}`. + [name] => { + let (name, version) = parse_name(name)?; + (DOCKER_IO, LIBRARY, name, version) + } + + // Two segments may mean "{namespace}/{name}" or may mean "docker.io/{name}". + // This is a special case for docker compatibility. + [host, name] if *host == DOCKER_IO => { + let (name, version) = parse_name(name)?; + (*host, LIBRARY, name, version) + } + [namespace, name] => { + let (name, version) = parse_name(name)?; + (DOCKER_IO, *namespace, name, version) + } + + // Some names have multiple segments, e.g. `docker.io/library/ubuntu/foo`. + // We can't handle multi-segment names in other branches since they conflict with the various shorthands, + // but handle them here since they're not ambiguous. + [host, namespace, name @ ..] => { + let name = name.join("/"); + let (name, version) = parse_name(&name)?; + (*host, *namespace, name, version) + } + _ => { + return eyre!("invalid reference format: {s}") + .with_section(|| { + [ + "Provide either a fully qualified OCI reference, or a short form.", + "Short forms are in the format `{name}` or `{namespace}/{name}`.", + "If you provide a short form, the default registry is `docker.io`.", + ] + .join("\n") + .header("Help:") + }) + .with_section(|| { + ["docker.io/library/ubuntu", "library/ubuntu", "ubuntu"] + .join("\n") + .header("Examples:") + }) + .pipe(Err) + } }; - if host.is_empty() { - return Err(eyre!("host cannot be empty").with_section(input_section)); - } - if repository.is_empty() { - return Err(eyre!("repository cannot be empty").with_section(input_section)); - } + ensure!(!host.is_empty(), "host cannot be empty: {s}"); + ensure!(!namespace.is_empty(), "namespace cannot be empty: {s}"); + ensure!(!name.is_empty(), "name cannot be empty: {s}"); Ok(Reference { host: host.to_string(), - repository: repository.to_string(), + repository: format!("{namespace}/{name}"), version, }) } diff --git a/lib/tests/it/reference.rs b/lib/tests/it/reference.rs index 2ca1e0b..a2da4e6 100644 --- a/lib/tests/it/reference.rs +++ b/lib/tests/it/reference.rs @@ -20,7 +20,25 @@ fn display(reference: Reference, expected: &str) { pretty_assertions::assert_eq!(reference.to_string(), expected); } -#[test_case("invalid:latest"; "invalid:latest")] +#[test_case("ubuntu", "docker.io/library/ubuntu:latest"; "ubuntu")] +#[test_case("ubuntu:14.04", "docker.io/library/ubuntu:14.04"; "ubuntu:14.04")] +#[test_case("ubuntu@sha256:123abc", "docker.io/library/ubuntu@sha256:123abc"; "ubuntu@sha256:123abc")] +#[test_case("library/ubuntu", "docker.io/library/ubuntu:latest"; "library/ubuntu")] +#[test_case("contribsys/faktory", "docker.io/contribsys/faktory:latest"; "contribsys/faktory")] +#[test_case("contribsys/faktory:1.0.0", "docker.io/contribsys/faktory:1.0.0"; "contribsys/faktory:1.0.0")] +#[test_case("library/ubuntu:14.04", "docker.io/library/ubuntu:14.04"; "library/ubuntu:14.04")] +#[test_case("library/ubuntu@sha256:123abc", "docker.io/library/ubuntu@sha256:123abc"; "library/ubuntu@sha256:123abc")] +#[test_case("docker.io/library/ubuntu:14.04", "docker.io/library/ubuntu:14.04"; "docker.io/library/ubuntu:14.04")] +#[test_case("docker.io/library/ubuntu@sha256:123abc", "docker.io/library/ubuntu@sha256:123abc"; "docker.io/library/ubuntu@sha256:123abc")] +#[test_case("host.dev/somecorp/someproject/someimage", "host.dev/somecorp/someproject/someimage:latest"; "host.dev/somecorp/someproject/someimage")] +#[test_case("host.dev/somecorp/someproject/someimage:1.0.0", "host.dev/somecorp/someproject/someimage:1.0.0"; "host.dev/somecorp/someproject/someimage:1.0.0")] +#[test_case("host.dev/somecorp/someproject/someimage@sha256:123abc", "host.dev/somecorp/someproject/someimage@sha256:123abc"; "host.dev/somecorp/someproject/someimage@sha256:123abc")] +#[test] +fn docker_like(input: &str, expected: &str) { + let reference = input.parse::().unwrap(); + pretty_assertions::assert_eq!(reference.to_string(), expected); +} + #[test_case("/repo:tag"; "/repo:tag")] #[test_case("host/:tag"; "host/tag")] #[test_case("host/"; "host/")] From d86afb08b4968d29297b89d5653bb7b2c560dc21 Mon Sep 17 00:00:00 2001 From: Jessica Black Date: Mon, 16 Dec 2024 11:27:19 -0800 Subject: [PATCH 2/5] Warn on expanding reference --- lib/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 7c464f5..d910697 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -11,7 +11,7 @@ use itertools::Itertools; use std::{borrow::Cow, ops::Add, str::FromStr}; use strum::{AsRefStr, EnumIter, IntoEnumIterator}; use tap::{Pipe, Tap}; -use tracing::debug; +use tracing::{debug, warn}; mod ext; pub mod registry; @@ -450,6 +450,7 @@ impl FromStr for Reference { // For docker compatibility, `{name}` is parsed as `docker.io/library/{name}`. [name] => { let (name, version) = parse_name(name)?; + warn!("expanding '{name}' to '{DOCKER_IO}/{LIBRARY}/{name}'; fully specify the reference to avoid this behavior"); (DOCKER_IO, LIBRARY, name, version) } @@ -457,10 +458,12 @@ impl FromStr for Reference { // This is a special case for docker compatibility. [host, name] if *host == DOCKER_IO => { let (name, version) = parse_name(name)?; + warn!("expanding '{host}/{name}' to '{host}/{LIBRARY}/{name}'; fully specify the reference to avoid this behavior"); (*host, LIBRARY, name, version) } [namespace, name] => { let (name, version) = parse_name(name)?; + warn!("expanding '{namespace}/{name}' to '{DOCKER_IO}/{namespace}/{name}'; fully specify the reference to avoid this behavior"); (DOCKER_IO, *namespace, name, version) } From 38bf8e069e8791d9c7552cfe5a771c8f09a0c9cd Mon Sep 17 00:00:00 2001 From: Jessica Black Date: Mon, 16 Dec 2024 11:27:53 -0800 Subject: [PATCH 3/5] Don't log targets --- bin/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/src/main.rs b/bin/src/main.rs index c0dd842..5428cab 100644 --- a/bin/src/main.rs +++ b/bin/src/main.rs @@ -40,7 +40,7 @@ async fn main() -> Result<()> { .with_deferred_spans(true) .with_bracketed_fields(true) .with_span_retrace(true) - .with_targets(true), + .with_targets(false), ) .with( tracing_subscriber::EnvFilter::builder() From 28368be905580ad7383895bce02613160c102497 Mon Sep 17 00:00:00 2001 From: Jessica Black Date: Mon, 16 Dec 2024 12:03:37 -0800 Subject: [PATCH 4/5] Support custom OCI_BASE and OCI_NAMESPACE --- README.md | 73 ++++++++++++++++++++++++++++++++++++--- lib/src/lib.rs | 48 ++++++++++++++++++------- lib/tests/it/reference.rs | 21 +++++++++++ 3 files changed, 126 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index aeace0b..2e365c4 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ powershell -c "irm https://github.com/fossas/circe/releases/latest/download/circ > [!TIP] > Check the help output for more details. -## extract +## subcommand: extract Extracts the contents of the image to disk. @@ -50,7 +50,7 @@ Extracts the contents of the image to disk. # # Arguments: # -# The image to extract. +# The image to extract. See image reference below for more details. # # The directory to which the image is extracted. # @@ -84,7 +84,7 @@ Extracts the contents of the image to disk. circe extract docker.io/contribsys/faktory:latest ./faktory --layers squash --platform linux/amd64 ``` -## list +## subcommand:list Lists the contents of an image. @@ -96,7 +96,7 @@ Lists the contents of an image. # # Arguments: # -# The image to list. +# The image to list. See image reference below for more details. # # Options for `circe list`: # --platform @@ -109,6 +109,71 @@ Lists the contents of an image. circe list docker.io/contribsys/faktory:latest ``` +## image reference + +The primary recommendation for referencing an image is to use the fully qualified reference, e.g.: + +```shell +docker.io/contribsys/faktory:latest +docker.io/library/ubuntu:14.04 +some-host.dev/some-namespace/some-project/some-image:latest +some-host.dev/some-namespace/some-project/some-image@sha256:123abc +``` + +However, for convenience, you can specify a "partial image reference" in a few different ways: + +```shell +# namespace + name + tag; infers to docker.io/contribsys/faktory:latest +circe list contribsys/faktory:latest + +# namespace + name + digest; infers to docker.io/contribsys/faktory@sha256:123abc +circe list contribsys/faktory@sha256:123abc + +# namespace + name; infers to docker.io/contribsys/faktory:latest +circe list contribsys/faktory + +# name + tag; infers to docker.io/library/ubuntu:latest +circe list ubuntu:latest + +# name + digest; infers to docker.io/library/ubuntu@sha256:123abc +circe list ubuntu@sha256:123abc + +# name; infers to docker.io/library/ubuntu:latest +circe list ubuntu +``` + +By default, `circe` fills in `docker.io` for the registry and `library` for the namespace. +However, you can customize the registry and namespace by setting the `OCI_BASE` and `OCI_NAMESPACE` environment variables: + +```shell +# Specify the registry and/or namespace: +export OCI_BASE=some-host.dev +export OCI_NAMESPACE=some-namespace + +# namespace + name + tag; infers to some-host.dev/contribsys/faktory:latest +circe list contribsys/faktory:latest + +# namespace + name + digest; infers to some-host.dev/contribsys/faktory@sha256:123abc +circe list contribsys/faktory@sha256:123abc + +# namespace + name; infers to some-host.dev/contribsys/faktory:latest +circe list contribsys/faktory + +# name + tag; infers to some-host.dev/some-namespace/ubuntu:latest +circe list ubuntu:latest + +# name + digest; infers to some-host.dev/some-namespace/ubuntu@sha256:123abc +circe list ubuntu@sha256:123abc + +# name; infers to some-host.dev/some-namespace/ubuntu:latest +circe list ubuntu +``` + +**The overall recommendation is to use fully qualified references.** +The intention with the ability to override `OCI_BASE` and `OCI_NAMESPACE` is to make setup easier for CI/CD pipelines +that need to extract multiple images from a custom host and/or namespace, but don't want to have to write scripts +to concatenate them into fully qualified references. + ## platform selection You can customize the platform used by `circe` by passing `--platform`. diff --git a/lib/src/lib.rs b/lib/src/lib.rs index d910697..8c6bc8d 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -17,6 +17,30 @@ mod ext; pub mod registry; pub mod transform; +/// Users can set this environment variable to specify the OCI base. +/// If not set, the default is [`OCI_DEFAULT_BASE`]. +pub const OCI_BASE_VAR: &str = "OCI_DEFAULT_BASE"; + +/// Users can set this environment variable to specify the OCI namespace. +/// If not set, the default is [`OCI_DEFAULT_NAMESPACE`]. +pub const OCI_NAMESPACE_VAR: &str = "OCI_DEFAULT_NAMESPACE"; + +/// The default OCI base. +pub const OCI_DEFAULT_BASE: &str = "docker.io"; + +/// The default OCI namespace. +pub const OCI_DEFAULT_NAMESPACE: &str = "library"; + +/// The OCI base. +pub fn oci_base() -> String { + std::env::var(OCI_BASE_VAR).unwrap_or(OCI_DEFAULT_BASE.to_string()) +} + +/// The OCI namespace. +pub fn oci_namespace() -> String { + std::env::var(OCI_NAMESPACE_VAR).unwrap_or(OCI_DEFAULT_NAMESPACE.to_string()) +} + /// Authentication method for a registry. #[derive(Debug, Clone, Default, Display)] pub enum Authentication { @@ -443,28 +467,28 @@ impl FromStr for Reference { // Docker supports `docker pull ubuntu` and `docker pull library/ubuntu`, // both of which are parsed as `docker.io/library/ubuntu`. // The below recreates this behavior. - const DOCKER_IO: &str = "docker.io"; - const LIBRARY: &str = "library"; + let base = oci_base(); + let namespace = oci_namespace(); let parts = s.split('/').collect::>(); let (host, namespace, name, version) = match parts.as_slice() { - // For docker compatibility, `{name}` is parsed as `docker.io/library/{name}`. + // For docker compatibility, `{name}` is parsed as `{base}/{namespace}/{name}`. [name] => { let (name, version) = parse_name(name)?; - warn!("expanding '{name}' to '{DOCKER_IO}/{LIBRARY}/{name}'; fully specify the reference to avoid this behavior"); - (DOCKER_IO, LIBRARY, name, version) + warn!("expanding '{name}' to '{base}/{namespace}/{name}'; fully specify the reference to avoid this behavior"); + (base, namespace, name, version) } - // Two segments may mean "{namespace}/{name}" or may mean "docker.io/{name}". + // Two segments may mean "{namespace}/{name}" or may mean "{base}/{name}". // This is a special case for docker compatibility. - [host, name] if *host == DOCKER_IO => { + [host, name] if *host == base => { let (name, version) = parse_name(name)?; - warn!("expanding '{host}/{name}' to '{host}/{LIBRARY}/{name}'; fully specify the reference to avoid this behavior"); - (*host, LIBRARY, name, version) + warn!("expanding '{host}/{name}' to '{base}/{namespace}/{name}'; fully specify the reference to avoid this behavior"); + (host.to_string(), namespace, name, version) } [namespace, name] => { let (name, version) = parse_name(name)?; - warn!("expanding '{namespace}/{name}' to '{DOCKER_IO}/{namespace}/{name}'; fully specify the reference to avoid this behavior"); - (DOCKER_IO, *namespace, name, version) + warn!("expanding '{namespace}/{name}' to '{base}/{namespace}/{name}'; fully specify the reference to avoid this behavior"); + (base, namespace.to_string(), name, version) } // Some names have multiple segments, e.g. `docker.io/library/ubuntu/foo`. @@ -473,7 +497,7 @@ impl FromStr for Reference { [host, namespace, name @ ..] => { let name = name.join("/"); let (name, version) = parse_name(&name)?; - (*host, *namespace, name, version) + (host.to_string(), namespace.to_string(), name, version) } _ => { return eyre!("invalid reference format: {s}") diff --git a/lib/tests/it/reference.rs b/lib/tests/it/reference.rs index a2da4e6..1c7c6b0 100644 --- a/lib/tests/it/reference.rs +++ b/lib/tests/it/reference.rs @@ -39,6 +39,27 @@ fn docker_like(input: &str, expected: &str) { pretty_assertions::assert_eq!(reference.to_string(), expected); } +#[test_case("ubuntu", "host.dev/somecorp/someproject/ubuntu:latest"; "ubuntu")] +#[test_case("ubuntu:14.04", "host.dev/somecorp/someproject/ubuntu:14.04"; "ubuntu:14.04")] +#[test_case("ubuntu@sha256:123abc", "host.dev/somecorp/someproject/ubuntu@sha256:123abc"; "ubuntu@sha256:123abc")] +#[test_case("library/ubuntu", "host.dev/library/ubuntu:latest"; "library/ubuntu")] +#[test_case("contribsys/faktory", "host.dev/contribsys/faktory:latest"; "contribsys/faktory")] +#[test_case("contribsys/faktory:1.0.0", "host.dev/contribsys/faktory:1.0.0"; "contribsys/faktory:1.0.0")] +#[test_case("library/ubuntu:14.04", "host.dev/library/ubuntu:14.04"; "library/ubuntu:14.04")] +#[test_case("library/ubuntu@sha256:123abc", "host.dev/library/ubuntu@sha256:123abc"; "library/ubuntu@sha256:123abc")] +#[test_case("docker.io/library/ubuntu:14.04", "docker.io/library/ubuntu:14.04"; "docker.io/library/ubuntu:14.04")] +#[test_case("docker.io/library/ubuntu@sha256:123abc", "docker.io/library/ubuntu@sha256:123abc"; "docker.io/library/ubuntu@sha256:123abc")] +#[test_case("host.dev/somecorp/someproject/someimage", "host.dev/somecorp/someproject/someimage:latest"; "host.dev/somecorp/someproject/someimage")] +#[test_case("host.dev/somecorp/someproject/someimage:1.0.0", "host.dev/somecorp/someproject/someimage:1.0.0"; "host.dev/somecorp/someproject/someimage:1.0.0")] +#[test_case("host.dev/somecorp/someproject/someimage@sha256:123abc", "host.dev/somecorp/someproject/someimage@sha256:123abc"; "host.dev/somecorp/someproject/someimage@sha256:123abc")] +#[test] +fn docker_like_custom_base_namespace(input: &str, expected: &str) { + std::env::set_var(circe_lib::OCI_BASE_VAR, "host.dev"); + std::env::set_var(circe_lib::OCI_NAMESPACE_VAR, "somecorp/someproject"); + let reference = input.parse::().unwrap(); + pretty_assertions::assert_eq!(reference.to_string(), expected); +} + #[test_case("/repo:tag"; "/repo:tag")] #[test_case("host/:tag"; "host/tag")] #[test_case("host/"; "host/")] From 3a77b6be927000413cfb431ea49091d2e7419125 Mon Sep 17 00:00:00 2001 From: Jessica Black Date: Mon, 16 Dec 2024 12:04:51 -0800 Subject: [PATCH 5/5] Readme touchup --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2e365c4..b109dc1 100644 --- a/README.md +++ b/README.md @@ -114,10 +114,10 @@ circe list docker.io/contribsys/faktory:latest The primary recommendation for referencing an image is to use the fully qualified reference, e.g.: ```shell -docker.io/contribsys/faktory:latest -docker.io/library/ubuntu:14.04 -some-host.dev/some-namespace/some-project/some-image:latest -some-host.dev/some-namespace/some-project/some-image@sha256:123abc +circe list docker.io/contribsys/faktory:latest +circe list docker.io/library/ubuntu:14.04 +circe list some-host.dev/some-namespace/some-project/some-image:latest +circe list some-host.dev/some-namespace/some-project/some-image@sha256:123abc ``` However, for convenience, you can specify a "partial image reference" in a few different ways: