Skip to content

Commit

Permalink
twoliter: require Docker 23
Browse files Browse the repository at this point in the history
Docker versions older than 23 no longer receive community-supported
security patches.

Additionally, twoliter requires dockerfile syntax 1.4.3. Currently the
tool pulls this image from the network at build time; however, Docker 23
bundles this version internally via buildkit. We can further isolate
twoliter builds from the network by ensuring we are on Docker 23 or
greater.
  • Loading branch information
cbgbt committed Dec 23, 2024
1 parent a1b5e18 commit 9a2465a
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 2 deletions.
38 changes: 37 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ tabled = "0.10"
tar = "0.4"
tempfile = "3"
term_size = "0.3"
test-case = "3"
tinytemplate = "1"
tokio = "1"
tokio-stream = "0.1"
Expand Down
5 changes: 5 additions & 0 deletions twoliter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ filetime.workspace = true
flate2.workspace = true
futures.workspace = true
krane-static.workspace = true
lazy_static.workspace = true
log.workspace = true
oci-cli-wrapper.workspace = true
olpc-cjson.workspace = true
Expand All @@ -36,6 +37,7 @@ tokio = { workspace = true, features = ["fs", "macros", "process", "rt-multi-thr
toml.workspace = true
tracing = { workspace = true, features = ["log"] }
uuid = { workspace = true, features = ["v4"] }
which.workspace = true

# Binary dependencies. These are binaries that we want to embed in the Twoliter binary
buildsys = { workspace = true }
Expand All @@ -51,6 +53,9 @@ bytes.workspace = true
flate2.workspace = true
tar.workspace = true

[dev-dependencies]
test-case.workspace = true

[features]
default = ["integ-tests"]
integ-tests = []
6 changes: 5 additions & 1 deletion twoliter/embedded/build.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# syntax=docker/dockerfile:1.4.3
# Twoliter requires minimum Docker version 23.0.0, which ships with a bundled syntax image 1.4.3
# We refrain from an explicit `syntax` directive as this can lead to unwanted network requests at
# build time.
# See https://hub.docker.com/r/docker/dockerfile for more information

# This Dockerfile has three sections which are used to build rpm.spec packages, to create
# kits, and to create Bottlerocket images, respectively. They are marked as Sections 1-3.
# buildsys uses Section 1 during build-package calls, Section 2 during build-kit calls,
Expand Down
23 changes: 23 additions & 0 deletions twoliter/src/docker/commands.rs
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
use crate::common::exec;
use anyhow::{Context, Result};
use semver::Version;
use tokio::process::Command;

pub(crate) struct Docker;

impl Docker {
/// Fetches the version of the docker daemon
pub(crate) async fn server_version() -> Result<Version> {
let version_str = exec(
Command::new("docker").args(["version", "--format", "{{.Server.Version}}"]),
true,
)
.await
// Convert Result<Option<String>> to Option<String>
.ok()
.flatten()
.map(|s| s.trim().to_string())
.context("Failed to fetch docker version")?;

Version::parse(&version_str).context("Failed to parse docker version as semver")
}
}
1 change: 1 addition & 0 deletions twoliter/src/docker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ mod commands;
mod image;

pub(crate) use self::image::ImageUri;
pub(crate) use commands::Docker;
3 changes: 3 additions & 0 deletions twoliter/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ use anyhow::Result;
use clap::Parser;

mod cargo_make;
pub(crate) mod cleanup;
mod cmd;
mod common;
mod compatibility;
mod docker;
mod preflight;
mod project;
mod schema_version;
/// Test code that should only be compiled when running tests.
Expand All @@ -20,5 +22,6 @@ mod tools;
async fn main() -> Result<()> {
let args = Args::parse();
init_logger(args.log_level);
preflight::preflight().await?;
cmd::run(args).await
}
80 changes: 80 additions & 0 deletions twoliter/src/preflight.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! This module performs checks that the current environment is compatible with twoliter, as well
//! as any other "global" setup that must occur before the build process begins.
use anyhow::{ensure, Result};
use lazy_static::lazy_static;
use semver::{Comparator, Op, Prerelease, VersionReq};
use which::which_global;

use crate::docker::Docker;

const REQUIRED_TOOLS: &[&str] = &["docker", "gzip", "lz4"];

lazy_static! {
// Twoliter relies on minimum Dockerfile syntax 1.4.3, which is shipped in Docker 23.0.0 by default
// We do not use explicit `syntax=` directives to avoid network connections during the build.
static ref MINIMUM_DOCKER_VERSION: VersionReq = VersionReq {
comparators: [
Comparator {
op: Op::GreaterEq,
major: 23,
minor: None,
patch: None,
pre: Prerelease::default(),
}
].into()
};
}

/// Runs all common setup required for twoliter.
///
/// * Ensures that any required system tools are installed an accessible.
/// * Sets up interrupt handler to cleanup on SIGINT
pub(crate) async fn preflight() -> Result<()> {
check_environment().await?;

Ok(())
}

pub(crate) async fn check_environment() -> Result<()> {
check_for_required_tools()?;
check_docker_version().await?;

Ok(())
}

fn check_for_required_tools() -> Result<()> {
for tool in REQUIRED_TOOLS {
ensure!(
which_global(tool).is_ok(),
"Failed to find required tool `{tool}` in PATH"
);
}
Ok(())
}

async fn check_docker_version() -> Result<()> {
let docker_version = Docker::server_version().await?;

ensure!(
MINIMUM_DOCKER_VERSION.matches(&docker_version),
"docker found in PATH does not meet the minimum version requirements for twoliter: {}",
MINIMUM_DOCKER_VERSION.to_string(),
);

Ok(())
}

#[cfg(test)]
mod test {
use super::*;
use semver::Version;
use test_case::test_case;

#[test_case(Version::parse("25.0.5").unwrap(), true; "25.0.5 passes")]
#[test_case(Version::parse("27.1.4").unwrap(), true; "27.1.4 passes")]
#[test_case(Version::parse("18.0.9").unwrap(), false; "18.0.9 fails")]
#[test_case(Version::parse("20.10.27").unwrap(), false)]
fn test_docker_version_req(version: Version, is_ok: bool) {
assert_eq!(MINIMUM_DOCKER_VERSION.matches(&version), is_ok)
}
}

0 comments on commit 9a2465a

Please sign in to comment.