Skip to content

Commit

Permalink
Support authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
jssblck committed Dec 12, 2024
1 parent aec4b62 commit 7595d37
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 15 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ powershell -c "irm https://github.com/fossas/circe/releases/latest/download/circ
# Accepts the same values as `docker` (e.g. `linux/amd64`, `darwin/arm64`, etc).
# --overwrite
# If the target directory already exists, overwrite it.
#
# --username
# The username to use for authentication; "password" is also required if provided.
# --password
# The password to use for authentication; "username" is also required if provided.
circe extract docker.io/contribsys/faktory:latest ./faktory --layers squash --platform linux/amd64
```

Expand Down
2 changes: 1 addition & 1 deletion bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ default-run = "circe"
[dependencies]
clap = { version = "4.5.23", features = ["derive"] }
color-eyre = "0.6.3"
tap = "1.0.1"
tokio = { version = "1.42.0", features = ["full"] }
tracing = "0.1.41"
tracing-error = { version = "0.2.1" }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tracing-tree = { version = "0.4.0" }
circe_lib = { path = "../lib" }
serde_json = "1.0.133"
derive_more = { version = "1.0.0", features = ["debug"] }
25 changes: 18 additions & 7 deletions bin/src/extract.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use circe_lib::{registry::Registry, LayerDescriptor, Platform, Reference};
use circe_lib::{registry::Registry, Authentication, LayerDescriptor, Platform, Reference};
use clap::{Parser, ValueEnum};
use color_eyre::eyre::{bail, Context, Result};
use derive_more::Debug;
use std::{path::PathBuf, str::FromStr};
use tracing::{debug, info};

Expand All @@ -25,22 +26,26 @@ pub struct Options {
///
/// If the image is multi-platform and this argument is not provided,
/// the platform is chosen according to the following priority list:
///
/// 1. The first platform-independent image
///
/// 2. The current platform (if available)
///
/// 3. The `linux` platform for the current architecture
///
/// 4. The `linux` platform for the `amd64` architecture
///
/// 5. The first platform in the image manifest
#[arg(long, value_parser = Platform::from_str)]
#[arg(long, value_parser = Platform::from_str, verbatim_doc_comment)]
platform: Option<Platform>,

/// How to handle layers during extraction
#[arg(long, default_value = "squash")]
layers: Mode,

/// The username to use for authenticating to the registry
#[arg(long, requires = "password")]
username: Option<String>,

/// The password to use for authenticating to the registry
#[arg(long, requires = "username")]
#[debug(skip)]
password: Option<String>,
}

#[derive(Copy, Clone, Debug, Default, ValueEnum)]
Expand All @@ -64,10 +69,16 @@ pub enum Mode {
pub async fn main(opts: Options) -> Result<()> {
info!("extracting image");

let auth = match (opts.username, opts.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.platform)
.reference(opts.image)
.auth(auth)
.build()
.await
.context("configure remote registry")?;
Expand Down
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async-tempfile = "0.6.0"
bon = "3.3.0"
bytes = "1.9.0"
color-eyre = "0.6.3"
derive_more = { version = "1.0.0", features = ["full"] }
derive_more = { version = "1.0.0", features = ["debug", "display"] }
enum-assoc = "1.2.4"
futures-lite = "2.5.0"
hex = "0.4.3"
Expand Down
30 changes: 30 additions & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@ mod ext;
pub mod registry;
pub mod transform;

/// Authentication method for a registry.
#[derive(Debug, Clone, Default, Display)]
pub enum Authentication {
/// No authentication
#[default]
#[display("none")]
None,

/// Basic authentication
#[display("basic:{username}")]
Basic {
/// The username
username: String,

/// The password
#[debug(skip)]
password: String,
},
}

impl Authentication {
/// Create an instance for basic authentication
pub fn basic(username: impl Into<String>, password: impl Into<String>) -> Self {
Self::Basic {
username: username.into(),
password: password.into(),
}
}
}

/// Platform represents the platform a container image is built for.
/// This follows the OCI Image Spec's platform definition while also supporting
/// Docker's platform string format (e.g. "linux/amd64").
Expand Down
24 changes: 19 additions & 5 deletions lib/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ use tracing::{debug, warn};
use crate::{
ext::PriorityFind,
transform::{self, Chunk},
Digest, LayerDescriptor, LayerMediaType, LayerMediaTypeFlag, Platform, Reference, Version,
Authentication, Digest, LayerDescriptor, LayerMediaType, LayerMediaTypeFlag, Platform,
Reference, Version,
};

/// Each instance is a unique view of remote registry for a specific [`Platform`] and [`Reference`].
Expand All @@ -46,12 +47,16 @@ pub struct Registry {
impl Registry {
/// Create a new registry for a specific platform and reference.
#[builder]
pub async fn new(platform: Option<Platform>, reference: Reference) -> Result<Self> {
pub async fn new(
auth: Option<Authentication>,
platform: Option<Platform>,
reference: Reference,
) -> Result<Self> {
let client = client(platform.clone());
let reference = OciReference::from(&reference);

// Future improvement: support authentication.
let auth = RegistryAuth::Anonymous;
let auth = auth
.map(RegistryAuth::from)
.unwrap_or(RegistryAuth::Anonymous);

client
.auth(&reference, &auth, RegistryOperation::Pull)
Expand Down Expand Up @@ -314,6 +319,15 @@ impl TryFrom<OciDescriptor> for LayerDescriptor {
}
}

impl From<Authentication> for RegistryAuth {
fn from(auth: Authentication) -> Self {
match auth {
Authentication::None => RegistryAuth::Anonymous,
Authentication::Basic { username, password } => RegistryAuth::Basic(username, password),
}
}
}

/// Returns the path to the file that would be deleted by a whiteout file, if the path is a whiteout file.
/// If the path is not a whiteout file, returns `None`.
fn is_whiteout(path: &Path) -> Option<PathBuf> {
Expand Down
1 change: 1 addition & 0 deletions lib/tests/it/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fn parse(input: &str, expected: Reference) {

#[test_case(Reference::builder().host("docker.io").repository("library/ubuntu").tag("latest").build(), "docker.io/library/ubuntu:latest"; "docker.io/library/ubuntu:latest")]
#[test_case(Reference::builder().host("ghcr.io").repository("user/repo").digest(circe_lib::digest!("sha256", "123abc", 3)).build(), "ghcr.io/user/repo@sha256:123abc"; "ghcr.io/user/repo@sha256:123abc")]
#[test_case(Reference::builder().host("ghcr.io").repository("fossas/project/app").tag("sha-e01ce6b").build(), "ghcr.io/fossas/project/app:sha-e01ce6b"; "ghcr.io/fossas/project/app:sha-e01ce6b")]
#[test_case(Reference::builder().host("docker.io").repository("library/ubuntu").build(), "docker.io/library/ubuntu:latest"; "docker.io/library/ubuntu")]
#[test]
fn display(reference: Reference, expected: &str) {
Expand Down

0 comments on commit 7595d37

Please sign in to comment.