Skip to content

Commit

Permalink
Support specifying images using sha256 digests
Browse files Browse the repository at this point in the history
  • Loading branch information
Boaz Berman committed Dec 31, 2022
1 parent f5e67af commit 1cb0c65
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 6 deletions.
36 changes: 36 additions & 0 deletions testcontainers/src/clients/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,42 @@ mod tests {
);
}

#[test]
fn cli_run_command_with_custom_sha256_should_attach_that() {
let image = GenericImage::new("hello", "0.0");
let image = RunnableImage::from(image).with_sha256("a_sha_256");
let command = Client::build_run_command(&image, Command::new("docker"));

assert_eq!(
format!("{:?}", command),
r#""docker" "run" "-P" "-d" "hello:0.0@sha256:a_sha_256""#
);
}

#[test]
fn cli_run_command_with_generic_image_that_includes_sha256_it_should_attach_that() {
let image = GenericImage::new("hello", "0.0").with_sha256(Some("a_sha_256"));
let image = RunnableImage::from(image);
let command = Client::build_run_command(&image, Command::new("docker"));

assert_eq!(
format!("{:?}", command),
r#""docker" "run" "-P" "-d" "hello:0.0@sha256:a_sha_256""#
);
}

#[test]
fn cli_run_command_with_sha256_should_attach_that() {
let image = GenericImage::new_with_sha256("hello", "a_sha_256");
let image = RunnableImage::from(image);
let command = Client::build_run_command(&image, Command::new("docker"));

assert_eq!(
format!("{:?}", command),
r#""docker" "run" "-P" "-d" "hello:latest@sha256:a_sha_256""#
);
}

#[test]
fn cli_run_command_should_include_privileged() {
let image = GenericImage::new("hello", "0.0");
Expand Down
32 changes: 27 additions & 5 deletions testcontainers/src/core/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ where

/// Implementations are encouraged to include a tag that will not change (i.e. NOT latest)
/// in order to prevent test code from randomly breaking because the underlying docker
/// suddenly changed.
/// suddenly changed. It is advised to also attach a sha256.
fn tag(&self) -> String;

/// Every docker image has a unique sha256 generated corresponding to it. It is strongly
/// encouraged to use sha256 digests over versions, as versions might be overwritten
/// by the publisher, which could cause compatability issues, but more importantly,
/// pose a security risk.
fn sha256(&self) -> Option<String> {
None
}

/// Returns a list of conditions that need to be met before a started container is considered ready.
///
/// This method is the **🍞 and butter** of the whole testcontainers library. Containers are
Expand Down Expand Up @@ -158,6 +166,7 @@ pub struct RunnableImage<I: Image> {
image: I,
image_args: I::Args,
image_tag: Option<String>,
image_sha256: Option<String>,
container_name: Option<String>,
network: Option<String>,
env_vars: BTreeMap<String, String>,
Expand Down Expand Up @@ -210,11 +219,17 @@ impl<I: Image> RunnableImage<I> {
}

pub fn descriptor(&self) -> String {
if let Some(tag) = &self.image_tag {
format!("{}:{}", self.image.name(), tag)
} else {
format!("{}:{}", self.image.name(), self.image.tag())
let mut descriptor = self.image.name();

descriptor.push(':');
descriptor.push_str(&self.image_tag.clone().unwrap_or_else(|| self.image.tag()));

if let Some(sha256) = &self.image_sha256.clone().or_else(|| self.image.sha256()) {
descriptor.push_str("@sha256:");
descriptor.push_str(sha256);
}

descriptor
}

pub fn ready_conditions(&self) -> Vec<WaitFor> {
Expand All @@ -240,6 +255,12 @@ impl<I: Image> RunnableImage<I> {
}
}

pub fn with_sha256(mut self, sha256: impl Into<String>) -> Self {
self.image_sha256 = Some(sha256.into());

self
}

pub fn with_container_name(self, name: impl Into<String>) -> Self {
Self {
container_name: Some(name.into()),
Expand Down Expand Up @@ -311,6 +332,7 @@ impl<I: Image> From<(I, I::Args)> for RunnableImage<I> {
ports: None,
privileged: false,
shm_size: None,
image_sha256: None,
}
}
}
Expand Down
44 changes: 43 additions & 1 deletion testcontainers/src/images/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ impl ImageArgs for Vec<String> {
pub struct GenericImage {
name: String,
tag: String,
sha256: Option<String>,
volumes: BTreeMap<String, String>,
env_vars: BTreeMap<String, String>,
wait_for: Vec<WaitFor>,
Expand All @@ -23,7 +24,8 @@ impl Default for GenericImage {
fn default() -> Self {
Self {
name: "".to_owned(),
tag: "".to_owned(),
tag: "latest".to_owned(),
sha256: None,
volumes: BTreeMap::new(),
env_vars: BTreeMap::new(),
wait_for: Vec::new(),
Expand All @@ -42,11 +44,24 @@ impl GenericImage {
}
}

pub fn new_with_sha256<S: Into<String>>(name: S, sha256: S) -> GenericImage {
Self {
name: name.into(),
sha256: Some(sha256.into()),
..Default::default()
}
}

pub fn with_volume<F: Into<String>, D: Into<String>>(mut self, from: F, dest: D) -> Self {
self.volumes.insert(from.into(), dest.into());
self
}

pub fn with_sha256<S: Into<String>>(mut self, value: Option<S>) -> Self {
self.sha256 = value.map(|it| it.into());
self
}

pub fn with_env_var<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
self.env_vars.insert(key.into(), value.into());
self
Expand Down Expand Up @@ -79,6 +94,10 @@ impl Image for GenericImage {
self.tag.clone()
}

fn sha256(&self) -> Option<String> {
self.sha256.clone()
}

fn ready_conditions(&self) -> Vec<WaitFor> {
self.wait_for.clone()
}
Expand All @@ -104,6 +123,8 @@ impl Image for GenericImage {
mod tests {
use super::*;

const A_SHA256: &str = "a_sha256";

#[test]
fn should_return_env_vars() {
let image = GenericImage::new("hello-world", "latest")
Expand All @@ -119,4 +140,25 @@ mod tests {
assert_eq!(second_key, "two-key");
assert_eq!(second_value, "two-value");
}

#[test]
fn should_return_sha256() {
let image = GenericImage::new("hello-world", "latest").with_sha256(Some(A_SHA256));

assert_eq!(image.sha256(), Some(A_SHA256.to_string()));
}

#[test]
fn should_return_sha256_when_created_with_it() {
let image = GenericImage::new_with_sha256("hello-world", A_SHA256);

assert_eq!(image.sha256(), Some(A_SHA256.to_string()));
}

#[test]
fn should_return_none_for_sha256_when_created_with_tag() {
let image = GenericImage::new("hello-world", "latest");

assert_eq!(image.sha256(), None);
}
}

0 comments on commit 1cb0c65

Please sign in to comment.