Skip to content

Commit

Permalink
feat: improve the Docker backend. (#12)
Browse files Browse the repository at this point in the history
* feat: improve the Docker backend.

* add support for specifying inputs by host file path.
* implement inputs as bind mounts rather than files uploaded to the container.
* implement mounts for tasks that use services rather than containers.
* make the Docker backend ensure the task execution's image exists locally
  before creating the container or service.
* use `alpine` for the examples as it is smaller than `ubuntu`.

* chore: update CHANGELOG.md.
  • Loading branch information
peterhuene authored Feb 15, 2025
1 parent f1af2ac commit 21cbbf7
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 106 deletions.
10 changes: 4 additions & 6 deletions crankshaft-docker/src/bin/docker-driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,9 @@ enum Command {
/// existing).
EnsureImage {
/// The name of the image.
///
/// If a tag is not specified, a default tag of `latest` is used.
image: String,

#[arg(short, long, default_value = "latest")]
/// The tag for the image.
tag: String,
},

/// Lists all images.
Expand Down Expand Up @@ -156,8 +154,8 @@ async fn run(args: Args) -> Result<()> {
container.remove().await?;
}
}
Command::EnsureImage { image, tag } => {
docker.ensure_image(image, tag).await?;
Command::EnsureImage { image } => {
docker.ensure_image(image).await?;
}
Command::ListImages => {
docker.list_images().await?;
Expand Down
2 changes: 1 addition & 1 deletion crankshaft-docker/src/container/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl Builder {
/// Sets multiple environment variables.
pub fn envs(
mut self,
variables: impl Iterator<Item = (impl Into<String>, impl Into<String>)>,
variables: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
self.env
.extend(variables.into_iter().map(|(k, v)| (k.into(), v.into())));
Expand Down
25 changes: 11 additions & 14 deletions crankshaft-docker/src/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,20 @@ pub(crate) async fn list_images(docker: &Docker) -> Result<Vec<ImageSummary>> {

/// Ensures that an image exists in the Docker daemon.
///
/// If the image does not specify a tag, a default tag of `latest` will be used.
///
/// It does this by:
///
/// * Confirming that the image already exists there, or
/// * Pulling the image from the remote repository.
pub(crate) async fn ensure_image(
docker: &Docker,
name: impl AsRef<str>,
tag: impl AsRef<str>,
) -> Result<()> {
let name = name.as_ref();
let tag = tag.as_ref();
debug!("ensuring image: `{name}:{tag}`");
pub(crate) async fn ensure_image(docker: &Docker, image: impl AsRef<str>) -> Result<()> {
let image = image.as_ref();
debug!("ensuring image: `{image}`");

let mut filters = HashMap::new();
filters.insert(String::from("reference"), vec![format!("{name}:{tag}")]);
filters.insert("reference", vec![image]);

debug!("checking if image exists locally: `{name}:{tag}`");
debug!("checking if image exists locally: `{image}`");
let results = docker
.inner()
.list_images(Some(ListImagesOptions {
Expand All @@ -83,7 +80,7 @@ pub(crate) async fn ensure_image(
.map_err(Error::Docker)?;

if !results.is_empty() {
debug!("image `{name}:{tag}` exists locally");
debug!("image `{image}` exists locally");

if enabled!(Level::TRACE) {
trace!(
Expand All @@ -95,11 +92,11 @@ pub(crate) async fn ensure_image(
return Ok(());
}

debug!("image `{name}:{tag}` does not exist locally; attempting to pull from remote");
debug!("image `{image}` does not exist locally; attempting to pull from remote");
let mut stream = docker.inner().create_image(
Some(CreateImageOptions {
from_image: name,
tag,
from_image: image,
tag: if image.contains(':') { "" } else { "latest" },
..Default::default()
}),
None,
Expand Down
7 changes: 5 additions & 2 deletions crankshaft-docker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,15 @@ impl Docker {

/// Ensures that an image exists in the Docker daemon.
///
/// If the image does not specify a tag, a default tag of `latest` will be
/// used.
///
/// It does this by:
///
/// * Confirming that the image already exists there, or
/// * Pulling the image from the remote repository.
pub async fn ensure_image(&self, name: impl AsRef<str>, tag: impl AsRef<str>) -> Result<()> {
ensure_image(self, name, tag).await
pub async fn ensure_image(&self, image: impl AsRef<str>) -> Result<()> {
ensure_image(self, image).await
}

/// Removes an image from the Docker daemon.
Expand Down
22 changes: 20 additions & 2 deletions crankshaft-docker/src/service/builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Builders for containers.
use bollard::Docker;
use bollard::secret::Mount;
use bollard::secret::ServiceSpec;
use bollard::secret::ServiceSpecMode;
use bollard::secret::ServiceSpecModeReplicated;
Expand All @@ -16,7 +17,7 @@ use super::Service;
use crate::Error;
use crate::Result;

/// A builder for a [`Container`].
/// A builder for a [`Service`].
pub struct Builder {
/// A reference to the [`Docker`] client that will be used to create this
/// container.
Expand All @@ -43,6 +44,9 @@ pub struct Builder {
/// The working directory.
work_dir: Option<String>,

/// The mounts for the service's task template.
mounts: Vec<Mount>,

/// The task resources for the service.
resources: Option<TaskSpecResources>,
}
Expand All @@ -59,6 +63,7 @@ impl Builder {
attach_stderr: false,
env: Default::default(),
work_dir: Default::default(),
mounts: Default::default(),
resources: Default::default(),
}
}
Expand Down Expand Up @@ -96,7 +101,7 @@ impl Builder {
/// Sets multiple environment variables.
pub fn envs(
mut self,
variables: impl Iterator<Item = (impl Into<String>, impl Into<String>)>,
variables: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
self.env
.extend(variables.into_iter().map(|(k, v)| (k.into(), v.into())));
Expand All @@ -121,6 +126,18 @@ impl Builder {
self
}

/// Sets a mount for the service.
pub fn mount(mut self, mount: impl Into<Mount>) -> Self {
self.mounts.push(mount.into());
self
}

/// Sets multiple mounts for the service.
pub fn mounts(mut self, mounts: impl IntoIterator<Item = impl Into<Mount>>) -> Self {
self.mounts.extend(mounts.into_iter().map(Into::into));
self
}

/// Sets the task resources.
pub fn resources(mut self, resources: TaskSpecResources) -> Self {
self.resources = Some(resources);
Expand Down Expand Up @@ -152,6 +169,7 @@ impl Builder {
args: Some(self.args),
dir: self.work_dir,
env: Some(self.env.iter().map(|(k, v)| format!("{k}={v}")).collect()),
mounts: Some(self.mounts),
..Default::default()
}),
resources: self.resources,
Expand Down
6 changes: 6 additions & 0 deletions crankshaft-engine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Added support for bind mounting inputs to the Docker backend ([#12](https://github.com/stjude-rust-labs/crankshaft/pull/12)).
* Added cancellation support to the engine and ctrl-c handling in the examples (#[11](https://github.com/stjude-rust-labs/crankshaft/pull/11)).
* Added support for Docker Swarm in the docker backend (#[11](https://github.com/stjude-rust-labs/crankshaft/pull/11)).
* Adds the initial version of the crate.
Expand All @@ -32,3 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Swaps out most of the bespoke builders for `bon`.
* Removes `#[builder(into)]` for numerical types
([#10](https://github.com/stjude-rust-labs/crankshaft/pull/10)).

### Fixed

* The Docker backend now ensures task execution images are present locally
before creating any containers ([#12](https://github.com/stjude-rust-labs/crankshaft/pull/12)).
Loading

0 comments on commit 21cbbf7

Please sign in to comment.