diff --git a/CHANGELOG.md b/CHANGELOG.md index a48ebd9..afbbb13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [unreleased] – … + +### Added +- Podman is now officially supported and a related regression introduced in v0.27.1 was fixed +- Is is now possible to provide the path to the Podman or Docker binary on the command-line + ## [0.27.1] - 2026-09-01 ### Changed diff --git a/Dockerfile b/Dockerfile index bd2691c..a1894bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,12 @@ FROM --platform=$TARGETPLATFORM alpine:3.23.4 # Install the Docker CLI. RUN apk add --no-cache docker-cli +# Install Podman-Remote without pulling in the misspecified dependencies for local execution. +RUN apk fetch podman-remote && tar -xvf podman-remote-*.apk -C / usr/bin/podman-remote && rm podman-remote-*.apk + +# Make Podman-Remote available under its usual name. +RUN ln /usr/bin/podman-remote /usr/bin/podman + # Install Docuum. COPY --from=build /usr/local/bin/docuum /usr/local/bin/docuum diff --git a/README.md b/README.md index 7dfcda9..87ca344 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Build status](https://github.com/stepchowfun/docuum/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/stepchowfun/docuum/actions?query=branch%3Amain) -*Docuum* performs least recently used (LRU) eviction of Docker images to keep the disk usage below a given threshold. +*Docuum* performs least recently used (LRU) eviction of Docker images in Docker or Podman to keep the disk usage below a given threshold. -Docker's built-in `docker image prune --all --filter until=…` command serves a similar purpose. However, the built-in solution isn't ideal since it uses the image creation time, rather than the last usage time, to determine which images to remove. That means it can delete frequently used images, which may be expensive to rebuild or time-consuming to pull. +Docker’s built-in `docker image prune --all --filter until=…` command serves a similar purpose. However, the built-in solution isn't ideal since it uses the image creation time, rather than the last usage time, to determine which images to remove. That means it can delete frequently used images, which may be expensive to rebuild or time-consuming to pull. Docuum is ideal for use cases such as continuous integration (CI) workers, developer workstations, or any other environment in which Docker images accumulate on disk over time. Docuum works well with tools like [Toast](https://github.com/stepchowfun/toast) and [Docker Compose](https://docs.docker.com/compose/). @@ -17,7 +17,7 @@ Docuum is used by Netflix (on its production Kubernetes nodes) and Airbnb (on it When Docuum first starts and subsequently whenever a new Docker event comes in, LRU eviction is performed until the total disk usage due to Docker images is below the given threshold. This design has a few advantages over evicting images based on a fixed [time to live](https://en.wikipedia.org/wiki/Time_to_live) (TTL), which is what various other tools in the Docker ecosystem do: 1. There is no need to configure and tune an interval to run on. Docuum evicts images immediately whenever the disk usage exceeds the threshold without waiting for any timers. -2. Docuum uses no CPU resources when there is no Docker activity. You can run it on your laptop without worrying about draining your battery. +2. Docuum uses no CPU resources when there is no Docker/Podman activity. You can run it on your laptop without worrying about draining your battery. 3. In order to prevent your disk from filling up, it's more straightforward to set a threshold based on disk usage rather than guessing an appropriate maximum image age. Docuum also respects the parent-child relationships between images. In particular, it will delete children of a parent before deleting the parent (even if the children were used more recently than the parent), because Docker doesn't allow images with children to be deleted. @@ -30,7 +30,7 @@ Once Docuum is [installed](#installation-instructions), you can run it manually docuum --threshold '10 GB' ``` -Docuum will then start listening for Docker events. You can use `Ctrl`+`C` to stop it. +Docuum will then start listening for Docker or Podman events. You can use `Ctrl`+`C` to stop it. You probably want to run Docuum as a [daemon](https://en.wikipedia.org/wiki/Daemon_\(computing\)), e.g., with [launchd](https://www.launchd.info/), [systemd](https://www.freedesktop.org/wiki/Software/systemd/), etc. See the [Configuring your operating system to run the binary as a daemon](#configuring-your-operating-system-to-run-the-binary-as-a-daemon) section below for instructions. @@ -42,6 +42,10 @@ Usage: docuum [OPTIONS] Options: -v, --version Print version + -e, --engine [docker|podman] + Container engine to target [default: docker] + -c, --command + Path to engine binary to execute [default: "docker" or "podman" depending on `--engine`] -t, --threshold Set the maximum amount of space to use for Docker images [default: "10 GB"] -k, --keep @@ -139,6 +143,19 @@ docker container run \ stephanmisc/docuum --threshold '10 GB' ``` +For Podman, you must have Podman listening in daemon mode running on the host (`podman system service`), then you can Docuum using Podman: + +```sh +docker container run \ + --init \ + --rm \ + --tty \ + --name docuum \ + --mount type=bind,src=/run/podman/podman.sock,dst=/run/podman/podman.sock \ + --mount type=volume,source=docuum,target=/root \ + stephanmisc/docuum --threshold '10 GB' --engine podman +``` + If you're on a Windows system configured to run Linux containers, use this command: ```powershell @@ -154,10 +171,10 @@ docker container run ` We don't currently publish a Windows-based image, because some Windows machines (namely, those which run containers with process isolation rather than Hyper-V) can only run Windows containers that were built for the exact build of Windows (e.g., 1809) which is running on the host. This makes Windows-based images less portable, and as a result we'd need to publish a separate Windows-based image for each build of Windows we want to support. At this time, we don't have the infrastructure to do that. -The instructions below for configuring your operating system to run Docuum as a daemon assume it's installed as an executable binary. If you prefer to run it as a Docker container, change the relevant service definition to run a Docker command like the relevant one above, with the following adjustments: +The instructions below for configuring your operating system to run Docuum as a daemon assume it's installed as an executable binary. If you prefer to run it as a Docker container, change the relevant service definition to run a Docker or Podman command like the relevant one above, with the following adjustments: - Omit the `--tty` flag. This prevents Docuum from printing colored logs, which you probably don't want for a daemon. -- Configure Docker as a hard dependency. Ordinarily, Docuum and Docker can be started in any order, and Docuum will patiently wait for Docker to start if needed. However, when running Docuum as a Docker container, then of course Docker must be started first. +- If using Docker, configure Docker as a hard dependency. Ordinarily, Docuum and Docker can be started in any order, and Docuum will patiently wait for Docker to start if needed. However, when running Docuum as a Docker container, then of course Docker must be started first. ### Configuring your operating system to run the binary as a daemon diff --git a/integration-test.sh b/integration-test.sh index dad0d67..2079e49 100755 --- a/integration-test.sh +++ b/integration-test.sh @@ -4,10 +4,22 @@ set -euxo pipefail # See [ref:integration_test_step] for how to run this integration test. +ENGINE=docker +if [ $# -gt 0 ]; +then + if [ "$1" = "podman" ]; + then + ENGINE=podman + elif [ "$1" != "docker" ]; + then + echo "Unexpected argument: Must be “docker” or “podman”" >&2 + exit 1 + fi +fi # Wait for the Docker daemon to start up. -echo 'Waiting for Docker to start…' -while ! docker container ls > /dev/null 2>&1; do +echo 'Waiting for $ENGINE to start…' +while ! "$ENGINE" container ls > /dev/null 2>&1; do sleep 1 done @@ -54,13 +66,13 @@ build_test_image() { # Regenerate the payload so Docker cannot deduplicate layers across test images. echo "Building $IMAGE…" head -c "$IMAGE_SIZE_BYTES" /dev/urandom > "$BUILD_CONTEXT_DIR/payload" - docker image build --no-cache --tag "$IMAGE" "$BUILD_CONTEXT_DIR" + "$ENGINE" image build --no-cache --tag "$IMAGE" "$BUILD_CONTEXT_DIR" } # Print Docker's current image space usage in bytes. image_space_bytes() { # Parse the human-readable image size reported by `docker system df`. - docker system df --format '{{.Type}}\t{{.Size}}' | awk ' + "$ENGINE" system df --format '{{.Type}}\t{{.Size}}' | awk ' $1 == "Images" { SIZE = $2 if (SIZE ~ /kB$/) { @@ -99,7 +111,7 @@ echo "Using a threshold of $THRESHOLD_BYTES bytes…" # Start Docuum in the background, capturing its output in a log file. Trace-level logging is # needed because the synchronization logic below relies on Docuum logging each incoming event. echo 'Starting Docuum…' -LOG_LEVEL=trace /docuum-x86_64-unknown-linux-musl --threshold "$THRESHOLD_BYTES B" \ +LOG_LEVEL=trace /docuum-x86_64-unknown-linux-musl --engine "$ENGINE" --threshold "$THRESHOLD_BYTES B" \ --keep "$PROTECTED_IMAGE:latest" \ > "$LOG_FILE" 2>&1 & DOCUUM_PID="$!" @@ -116,8 +128,8 @@ wait_for_docuum() { # Emit a sentinel network event. echo 'Waiting for Docuum to process events…' SENTINEL_INDEX="$((SENTINEL_INDEX + 1))" - docker network create "docuum-sentinel-$SENTINEL_INDEX" > /dev/null - docker network rm "docuum-sentinel-$SENTINEL_INDEX" > /dev/null + "$ENGINE" network create "docuum-sentinel-$SENTINEL_INDEX" > /dev/null + "$ENGINE" network rm "docuum-sentinel-$SENTINEL_INDEX" > /dev/null # Wait until Docuum has received the sentinel event. while ! grep -q "docuum-sentinel-$SENTINEL_INDEX" "$LOG_FILE"; do @@ -142,8 +154,8 @@ wait_for_docuum_to_start() { local PROBE_INDEX=0 while ! grep -q 'docuum-probe-' "$LOG_FILE"; do PROBE_INDEX="$((PROBE_INDEX + 1))" - docker network create "docuum-probe-$PROBE_INDEX" > /dev/null - docker network rm "docuum-probe-$PROBE_INDEX" > /dev/null + "$ENGINE" network create "docuum-probe-$PROBE_INDEX" > /dev/null + "$ENGINE" network rm "docuum-probe-$PROBE_INDEX" > /dev/null sleep 1 done } @@ -153,7 +165,7 @@ wait_for_docuum_to_start # Assert that the first non-protected image is still present before the threshold has been exceeded. echo 'Checking that the first non-protected image is still present…' -docker image inspect "$EVICTED_IMAGE" > /dev/null 2>&1 +"$ENGINE" image inspect "$EVICTED_IMAGE" > /dev/null 2>&1 # Build the latest image after Docuum is listening so the build emits events Docuum must process. build_test_image "$LATEST_IMAGE" @@ -163,15 +175,15 @@ wait_for_docuum # Assert that the image protected by the `--keep` flag is still present. echo 'Checking that the protected image is still present…' -docker image inspect "$PROTECTED_IMAGE" > /dev/null 2>&1 +"$ENGINE" image inspect "$PROTECTED_IMAGE" > /dev/null 2>&1 # Assert that the last image is still present. echo 'Checking that the last image is still present…' -docker image inspect "$LATEST_IMAGE" > /dev/null 2>&1 +"$ENGINE" image inspect "$LATEST_IMAGE" > /dev/null 2>&1 # Assert that the first non-protected image was deleted. echo 'Checking that the first non-protected image was deleted…' -if docker image inspect "$EVICTED_IMAGE" > /dev/null 2>&1 +if "$ENGINE" image inspect "$EVICTED_IMAGE" > /dev/null 2>&1 then echo "The image wasn't deleted." exit 1 diff --git a/src/main.rs b/src/main.rs index b5f5280..d1fc656 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,14 @@ mod state; use crate::{format::CodeStr, run::run}; use byte_unit::Byte; use chrono::Local; -use clap::{ArgAction, Parser}; +use clap::{ArgAction, Parser, ValueEnum}; use env_logger::{Builder, fmt::style::Effects}; use humantime::parse_duration; use log::LevelFilter; use regex::RegexSet; use std::{ env, + fmt::{Display, Formatter}, io::{self, IsTerminal, Write}, process::exit, str::FromStr, @@ -111,6 +112,22 @@ struct Cli { #[arg(short, long, help = "Print version", action = ArgAction::Version)] _version: Option, + #[arg( + value_enum, + short, + long, + help = "Container engine to target", + default_value_t = Engine::Docker + )] + engine: Engine, + + #[arg( + short, + long, + help = "Path to engine binary to execute\n\n[default: \"docker\" or \"podman\" depending on `--engine`]", + )] + command: Option, + #[arg( short, long, @@ -145,14 +162,34 @@ struct Cli { min_age: Option, } -// This struct represents the parsed command-line arguments. +/// Represents the parsed command-line arguments. pub struct Settings { + engine: Engine, + command: String, deletion_chunk_size: usize, keep: Option, min_age: Option, threshold: Threshold, } +/// Container engine to target. +#[derive(Clone, Copy, Debug, PartialEq, ValueEnum)] +enum Engine { + /// Docker container engine + Docker, + /// Podman container engine + Podman, +} + +impl Display for Engine { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Docker => "Docker", + Self::Podman => "Podman", + }) + } +} + // Set up the logger. fn set_up_logging() { Builder::new() @@ -212,6 +249,13 @@ fn settings() -> io::Result { let threshold = Threshold::from_str(&cli.threshold)?; Ok(Settings { + engine: cli.engine, + command: cli.command.unwrap_or_else(|| + String::from(match cli.engine { + Engine::Docker => "docker", + Engine::Podman => "podman", + }) + ), deletion_chunk_size, keep, min_age, diff --git a/src/run.rs b/src/run.rs index 5cf17ce..edc318c 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,5 +1,5 @@ use crate::{ - Settings, Threshold, + Engine, Settings, Threshold, format::CodeStr, state::{self, State}, }; @@ -11,8 +11,10 @@ use chrono::{DateTime, Utc}; use regex::RegexSet; use serde::{Deserialize, Serialize}; use std::{ + borrow::Cow, cmp::max, collections::{HashMap, HashSet, hash_map::Entry}, + ffi::{OsStr, OsString}, io::{self, BufRead, BufReader}, ops::Deref, process::{Command, Stdio}, @@ -113,9 +115,9 @@ struct ImageNode { } // Ask Docker for the ID of an image. -fn image_id(image: &str) -> io::Result { +fn image_id(command: &str, image: &str) -> io::Result { // Query Docker for the image ID. - let output = Command::new("docker") + let output = Command::new(command) .args(["image", "inspect", "--format", "{{.ID}}", image]) .stderr(Stdio::inherit()) .output()?; @@ -135,14 +137,14 @@ fn image_id(image: &str) -> io::Result { } // Get the ID of the parent of an image (if the parent exists), querying Docker if necessary. -fn parent_id(state: &State, image_id: &str) -> io::Result> { +fn parent_id(command: &str, state: &State, image_id: &str) -> io::Result> { // If we already know the parent, just return it. if let Some(image) = state.images.get(image_id) { return Ok(image.parent_id.clone()); } // Query Docker for the parent image ID. - let output = Command::new("docker") + let output = Command::new(command) .args(["image", "inspect", "--format", "{{.Parent}}", image_id]) .output()?; @@ -185,9 +187,9 @@ fn parent_id(state: &State, image_id: &str) -> io::Result> { } // Query Docker for all the images. -fn list_image_records(state: &State) -> io::Result> { +fn list_image_records(command: &str, state: &State) -> io::Result> { // Get the IDs and creation timestamps of all the images. - let output = Command::new("docker") + let output = Command::new(command) .args([ "image", "ls", @@ -229,7 +231,7 @@ fn list_image_records(state: &State) -> io::Result> } Entry::Vacant(entry) => { entry.insert(ImageRecord { - parent_id: parent_id(state, id)?, + parent_id: parent_id(command, state, id)?, created_since_epoch: parse_docker_date(date_str)?, repository_tags: vec![repository_tag], }); @@ -237,7 +239,7 @@ fn list_image_records(state: &State) -> io::Result> } } else { return Err(io::Error::other( - "Failed to parse image list output from Docker.", + "Failed to parse image list output.", )); } } @@ -246,26 +248,37 @@ fn list_image_records(state: &State) -> io::Result> } // Ask Docker for the IDs of the images currently in use by containers. -fn image_ids_in_use() -> io::Result> { +fn image_ids_in_use(command: &str, engine: Engine) -> io::Result> { + fn str_to_oscow(s: &str) -> Cow<'_, OsStr> { + >::as_ref(s).into() + } + + // Build argument vector for querying list of container IDs + // + // Works around issue #237 + // (Docker reports unactionable container states.) + let mut container_ids_args: Vec> = + ["container", "ls", "--all"].into_iter().map(str_to_oscow).collect(); + if engine == Engine::Docker { + container_ids_args.extend( + CONTAINER_STATUSES + .iter() + .filter(|&&status| + !UNINSPECTABLE_CONTAINER_STATUSES.contains(&status) + ) + .flat_map(|&status| [ + str_to_oscow("--filter"), + OsString::from(format!("status={status}")).into() + ]) + ); + } + container_ids_args.extend( + ["--no-trunc", "--format", "{{.ID}}"].into_iter().map(str_to_oscow) + ); + // Query Docker for all the container IDs. - let container_ids_output = Command::new("docker") - .args( - ["container", "ls", "--all"] - .into_iter() - .map(std::string::ToString::to_string) - .chain( - CONTAINER_STATUSES - .iter() - .filter(|&&status| !UNINSPECTABLE_CONTAINER_STATUSES.contains(&status)) - .flat_map(|&status| [String::from("--filter"), format!("status={status}")]), - ) - .chain( - ["--no-trunc", "--format", "{{.ID}}"] - .into_iter() - .map(std::string::ToString::to_string), - ) - .collect::>(), - ) + let container_ids_output = Command::new(command) + .args(container_ids_args) .stderr(Stdio::inherit()) .output()?; @@ -296,7 +309,7 @@ fn image_ids_in_use() -> io::Result> { let mut image_ids = HashSet::new(); for chunk in container_ids.chunks(CONTAINER_IDS_CHUNK_SIZE) { // Query Docker for the image IDs for this chunk. - let image_ids_output = Command::new("docker") + let image_ids_output = Command::new(command) .args( ["container", "inspect", "--format", "{{.Image}}"] .iter() @@ -339,9 +352,9 @@ fn image_ids_in_use() -> io::Result> { // Determine Docker's root directory. #[cfg(target_os = "linux")] -fn docker_root_dir() -> io::Result { +fn docker_root_dir(command: &str) -> io::Result { // Query Docker for it. - let output = Command::new("docker") + let output = Command::new(command) .args(["system", "info", "--format", "{{.DockerRootDir}}"]) .stderr(Stdio::inherit()) .output()?; @@ -349,7 +362,7 @@ fn docker_root_dir() -> io::Result { // Ensure the command succeeded. if !output.status.success() { return Err(io::Error::other( - "Unable to determine the Docker root directory.", + "Unable to determine root directory.", )); } @@ -376,8 +389,8 @@ fn get_disk_by_file<'a>(disks: &'a [Disk], path: &Path) -> io::Result<&'a Disk> // Find size of filesystem on which docker root directory is stored. #[cfg(target_os = "linux")] -fn docker_root_dir_filesystem_size() -> io::Result { - let root_dir = docker_root_dir()?; +fn docker_root_dir_filesystem_size(command: &str) -> io::Result { + let root_dir = docker_root_dir(command)?; let disks = Disks::new_with_refreshed_list(); let disk = get_disk_by_file(&disks, &root_dir)?; Ok(Byte::from(disk.total_space())) @@ -385,9 +398,9 @@ fn docker_root_dir_filesystem_size() -> io::Result { // Get the total space used by Docker images. #[allow(clippy::map_err_ignore)] -fn space_usage() -> io::Result { +fn space_usage(command: &str) -> io::Result { // Query Docker for the space usage. - let output = Command::new("docker") + let output = Command::new(command) .args(["system", "df", "--format", "{{json .}}"]) .stderr(Stdio::inherit()) .output()?; @@ -428,11 +441,11 @@ fn space_usage() -> io::Result { } // Delete a Docker image. -fn delete_image(image: &str) -> io::Result<()> { +fn delete_image(command: &str, image: &str) -> io::Result<()> { info!("Deleting image {}\u{2026}", image.code_str()); // Tell Docker to delete the image. - let mut child = Command::new("docker") + let mut child = Command::new(command) .args(["image", "rm", "--force", "--no-prune", image]) .spawn()?; @@ -449,7 +462,7 @@ fn delete_image(image: &str) -> io::Result<()> { // Update the timestamp for an image. // Returns a boolean indicating if a new entry was created for the image. -fn touch_image(state: &mut State, image_id: &str, verbose: bool) -> io::Result { +fn touch_image(command: &str, state: &mut State, image_id: &str, verbose: bool) -> io::Result { if verbose { debug!( "Updating last-used timestamp for image {}\u{2026}", @@ -471,7 +484,7 @@ fn touch_image(state: &mut State, image_id: &str, verbose: bool) -> io::Result, deletion_chunk_size: usize, min_age: Option, ) -> io::Result<()> { // Find all images. - let image_records = list_image_records(state)?; + let image_records = list_image_records(command, state)?; // Find all images in use by containers. - let image_ids_in_use = image_ids_in_use()?; + let image_ids_in_use = image_ids_in_use(command, engine)?; // Construct a polyforest of image nodes that reflects their parent-child relationships. let polyforest = construct_polyforest(state, first_run, &image_records, &image_ids_in_use)?; @@ -723,10 +739,11 @@ fn vacuum( // Check if we're over the threshold. let mut deleted_image_ids = HashSet::new(); - let space = space_usage()?; + let space = space_usage(command)?; if space > threshold { info!( - "Docker images are currently using {}, but the limit is {}.", + "{} images are currently using {}, but the limit is {}.", + engine, space .get_appropriate_unit(UnitType::Decimal) .to_string() @@ -741,7 +758,7 @@ fn vacuum( for image_ids in sorted_image_nodes.chunks_mut(deletion_chunk_size) { for (image_id, _) in image_ids { // Delete the image. - if let Err(error) = delete_image(image_id) { + if let Err(error) = delete_image(command, image_id) { // The deletion failed. Just log the error and proceed. error!("{error}"); } else { @@ -751,10 +768,10 @@ fn vacuum( } // Break if we're within the threshold. - let new_space = space_usage()?; + let new_space = space_usage(command)?; if new_space <= threshold { info!( - "Docker images are now using {}, which is within the limit of {}.", + "{engine} images are now using {}, which is within the limit of {}.", new_space .get_appropriate_unit(UnitType::Decimal) .to_string() @@ -769,7 +786,7 @@ fn vacuum( } } else { debug!( - "Docker images are using {}, which is within the limit of {}.", + "{engine} images are using {}, which is within the limit of {}.", space .get_appropriate_unit(UnitType::Decimal) .to_string() @@ -800,6 +817,7 @@ fn vacuum( // Stream Docker events and vacuum when necessary. #[allow(clippy::type_complexity)] +#[allow(clippy::too_many_lines)] pub fn run( settings: &Settings, state: &mut State, @@ -822,7 +840,7 @@ pub fn run( clippy::cast_possible_truncation, clippy::cast_sign_loss )] - Byte::from_u128((p * docker_root_dir_filesystem_size()?.as_u128() as f64) as u128) + Byte::from_u128((p * docker_root_dir_filesystem_size(&settings.command)?.as_u128() as f64) as u128) .unwrap() } }; @@ -836,6 +854,8 @@ pub fn run( state, *first_run, threshold, + &settings.command, + settings.engine, settings.keep.as_ref(), settings.deletion_chunk_size, settings.min_age, @@ -844,7 +864,7 @@ pub fn run( *first_run = false; // Spawn `docker system events --format '{{json .}}'`. - let mut child = Command::new("docker") + let mut child = Command::new(&settings.command) .args(["system", "events", "--format", "{{json .}}"]) .stdout(Stdio::piped()) // [tag:stdout] .spawn()?; @@ -864,7 +884,7 @@ pub fn run( // Handle each incoming event. The integration test relies on this log message to determine // when Docuum has finished starting up [tag:listening_for_docker_events]. - info!("Listening for Docker events\u{2026}"); + info!("Listening for {0} events\u{2026}", settings.engine); for line_option in reader.lines() { // Unwrap the line. let line = line_option?; @@ -900,7 +920,7 @@ pub fn run( trace!("Skipping due to irrelevance."); continue; }; - let image_id = match image_id(&image_reference) { + let image_id = match image_id(&settings.command, &image_reference) { Ok(image_id) => image_id, Err(error) => { if event.r#type == "image" && matches!(event.action.as_str(), "delete" | "untag") { @@ -921,12 +941,14 @@ pub fn run( debug!("Waking up\u{2026}"); // Update the timestamp for this image. - if touch_image(state, &image_id, true)? { + if touch_image(&settings.command, state, &image_id, true)? { // Run the main vacuum logic only if a new image came in. vacuum( state, *first_run, threshold, + &settings.command, + settings.engine, settings.keep.as_ref(), settings.deletion_chunk_size, settings.min_age, diff --git a/toast.yml b/toast.yml index 7fcb916..b814a42 100644 --- a/toast.yml +++ b/toast.yml @@ -261,7 +261,7 @@ tasks: target/aarch64-unknown-linux-musl/release/docuum \ artifacts/docuum-aarch64-unknown-linux-musl - validate_release: + validate_release_docker: description: Validate the release. dependencies: - release @@ -290,7 +290,35 @@ tasks: docker container exec dind apk add bash docker container cp "artifacts/docuum-x86_64-unknown-linux-musl" dind:/ docker container cp integration-test.sh dind:/ - docker container exec dind ./integration-test.sh + docker container exec dind ./integration-test.sh docker + + validate_release_podman: + description: Validate the release. + dependencies: + - release + input_paths: + - integration-test.sh + cache: false + mount_paths: + - /var/run/docker.sock + user: root + command: | + # This will be called when the task finishes, regardless of whether it succeeded. + function cleanup { + # Delete the container created below to run the integration tests. + docker container rm --force quay.io/podman/stable + } + trap cleanup EXIT + + # Run the integration test suite [tag:integration_test_step]. Uses Podman-in-Docker. + docker container run \ + --privileged \ + --name podman \ + --detach \ + quay.io/podman/stable + docker container cp "artifacts/docuum-x86_64-unknown-linux-musl" podman:/ + docker container cp integration-test.sh podman:/ + docker container exec podman ./integration-test.sh podman publish: description: Publish the crate to crates.io.