feat: Add bootc support (#448)
Adds support for using `bootc` as the preferred method for booting from a locally created image. This new method gets rid of the need to create a tarball and move it to the correct place and instead it will make use of `podman scp` which copies the image to the root `containers-storage` and then has `rpm-ostree` and `bootc` boot from that store. Closes #418 Closes #200
This commit is contained in:
parent
2c525854c9
commit
3a0be4099a
65 changed files with 2991 additions and 1857 deletions
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
|
|
@ -207,8 +207,8 @@ jobs:
|
|||
|
||||
arm64-build:
|
||||
timeout-minutes: 90
|
||||
# runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04-arm
|
||||
# runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
|
@ -218,8 +218,8 @@ jobs:
|
|||
- name: Maximize build space
|
||||
uses: ublue-os/remove-unwanted-software@cc0becac701cf642c8f0a6613bbdaf5dc36b259e # v9
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
# - name: Set up QEMU
|
||||
# uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
|
||||
|
||||
|
|
|
|||
445
Cargo.lock
generated
445
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -18,10 +18,13 @@ colored = "2"
|
|||
comlexr = "1"
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
indicatif = { version = "0.18", features = ["improved_unicode", "rayon"] }
|
||||
lazy-regex = "3"
|
||||
log = "0.4"
|
||||
miette = "7"
|
||||
nix = { version = "0.29" }
|
||||
oci-distribution = { version = "0.11", default-features = false }
|
||||
pretty_assertions = "1"
|
||||
regex = "1"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
||||
rstest = "0.18"
|
||||
semver = "1"
|
||||
|
|
@ -78,7 +81,6 @@ jsonschema = "0.30"
|
|||
open = "5"
|
||||
os_info = "3"
|
||||
rayon = "1"
|
||||
regex = "1"
|
||||
requestty = { version = "0.5", features = ["macros", "termion"] }
|
||||
shadow-rs = { version = "1", default-features = false }
|
||||
thiserror = "2"
|
||||
|
|
@ -94,6 +96,7 @@ indicatif.workspace = true
|
|||
log.workspace = true
|
||||
miette = { workspace = true, features = ["fancy"] }
|
||||
oci-distribution.workspace = true
|
||||
regex.workspace = true
|
||||
reqwest.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
@ -109,6 +112,11 @@ users.workspace = true
|
|||
# Top level features
|
||||
default = []
|
||||
|
||||
v0_10_0 = [
|
||||
"bootc"
|
||||
]
|
||||
bootc = ["blue-build-process-management/bootc"]
|
||||
|
||||
[dev-dependencies]
|
||||
rusty-hook = "0.11"
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ build-full:
|
|||
switch:
|
||||
FROM +test-base
|
||||
|
||||
RUN mkdir -p /etc/bluebuild && touch $BB_TEST_LOCAL_IMAGE
|
||||
RUN --no-cache bluebuild -v switch recipes/recipe.yml
|
||||
RUN --no-cache bluebuild -v switch --boot-driver rpm-ostree recipes/recipe.yml
|
||||
RUN --no-cache bluebuild -v switch --boot-driver bootc recipes/recipe.yml
|
||||
|
||||
validate:
|
||||
FROM +test-base
|
||||
|
|
@ -92,7 +92,7 @@ init:
|
|||
|
||||
legacy-base:
|
||||
FROM ../+blue-build-cli --RELEASE=false
|
||||
ENV BB_TEST_LOCAL_IMAGE=/etc/bluebuild/cli_test-legacy.tar.gz
|
||||
ENV BB_TEST_LOCAL_IMAGE=localhost/cli/test:latest
|
||||
ENV CLICOLOR_FORCE=1
|
||||
|
||||
COPY ./mock-scripts/ /usr/bin/
|
||||
|
|
@ -103,13 +103,14 @@ legacy-base:
|
|||
DO ../+INSTALL --OUT_DIR="/usr/bin/" --BUILD_TARGET="x86_64-unknown-linux-musl" --TAGGED="true"
|
||||
|
||||
DO +GEN_KEYPAIR
|
||||
ENV USER=root
|
||||
|
||||
test-base:
|
||||
FROM ../+blue-build-cli --RELEASE=false
|
||||
RUN git config --global user.email "you@example.com" && \
|
||||
git config --global user.name "Your Name"
|
||||
|
||||
ENV BB_TEST_LOCAL_IMAGE=/etc/bluebuild/cli_test.tar.gz
|
||||
ENV BB_TEST_LOCAL_IMAGE=localhost/cli/test:latest
|
||||
ENV CLICOLOR_FORCE=1
|
||||
|
||||
ARG MOCK="true"
|
||||
|
|
@ -121,6 +122,7 @@ test-base:
|
|||
COPY ./test-repo /test
|
||||
|
||||
DO +GEN_KEYPAIR
|
||||
ENV USER=root
|
||||
|
||||
GEN_KEYPAIR:
|
||||
FUNCTION
|
||||
|
|
|
|||
41
integration-tests/mock-scripts/bootc
Executable file
41
integration-tests/mock-scripts/bootc
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$1" = "switch" ]; then
|
||||
if [[ "$2" == "--transport=containers-storage" && "$3" == "$BB_TEST_LOCAL_IMAGE" ]]; then
|
||||
echo "Rebased to local image $BB_TEST_LOCAL_IMAGE"
|
||||
else
|
||||
echo "Failed to rebase"
|
||||
exit 1
|
||||
fi
|
||||
elif [ "$1" = "upgrade" ]; then
|
||||
echo "Performing upgrade for $BB_TEST_LOCAL_IMAGE"
|
||||
elif [ "$1" = "status" ]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"status": {
|
||||
"staged": null,
|
||||
"booted": {
|
||||
"image": {
|
||||
"image": {
|
||||
"image": "ghcr.io/blue-build/cli/test",
|
||||
"transport": "registry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rollback": {
|
||||
"image": {
|
||||
"image": {
|
||||
"image": "ghcr.io/blue-build/cli/test",
|
||||
"transport": "registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo "Arg $1 is not recognized"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -13,6 +13,10 @@ main() {
|
|||
echo "Exporting image to a tarball (JK JUST A MOCK!)"
|
||||
echo "${tarpath}"
|
||||
touch $tarpath
|
||||
elif [[ "$1" == "image" && "$2" == "scp" ]]; then
|
||||
echo "Copying image $3 to $4"
|
||||
elif [[ "$1" == "rmi" && "$2" == "$BB_TEST_LOCAL_IMAGE" ]]; then
|
||||
echo "Removing image $2"
|
||||
else
|
||||
echo 'Running podman'
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
set -euo pipefail
|
||||
|
||||
if [ "$1" = "rebase" ]; then
|
||||
if [ "$2" = "ostree-unverified-image:oci-archive:$BB_TEST_LOCAL_IMAGE" ]; then
|
||||
if [ "$2" = "ostree-unverified-image:containers-storage:$BB_TEST_LOCAL_IMAGE" ]; then
|
||||
echo "Rebased to local image $BB_TEST_LOCAL_IMAGE"
|
||||
else
|
||||
echo "Failed to rebase"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test-arm64
|
||||
description: This is my personal OS image.
|
||||
base-image: quay.io/fedora/fedora-silverblue
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: latest
|
||||
stages:
|
||||
- from-file: stages.yml
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test-buildah
|
||||
description: This is my personal OS image.
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: latest
|
||||
stages:
|
||||
- from-file: stages.yml
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test-docker-external
|
||||
description: This is my personal OS image.
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: latest
|
||||
stages:
|
||||
- from-file: stages.yml
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test
|
||||
description: This is my personal OS image.
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: 40
|
||||
stages:
|
||||
- from-file: invalid-stages.yml
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test-invalid-module
|
||||
description: This is my personal OS image.
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: 40
|
||||
stages:
|
||||
- from-file: stages.yml
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test-invalid-stage
|
||||
description: This is my personal OS image.
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: 40
|
||||
stages:
|
||||
- name: ubuntu-test
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test-invalid
|
||||
description: 10
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version:
|
||||
- 40
|
||||
- 39
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test-podman
|
||||
description: This is my personal OS image.
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: latest
|
||||
stages:
|
||||
- from-file: stages.yml
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test-rechunk
|
||||
description: This is my personal OS image.
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: latest
|
||||
stages:
|
||||
- from-file: stages.yml
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||
name: cli/test
|
||||
description: This is my personal OS image.
|
||||
base-image: ghcr.io/ublue-os/silverblue-main
|
||||
base-image: quay.io/fedora/fedora-bootc
|
||||
image-version: latest
|
||||
stages:
|
||||
- from-file: stages.yml
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ colored.workspace = true
|
|||
comlexr.workspace = true
|
||||
indicatif.workspace = true
|
||||
indexmap.workspace = true
|
||||
lazy-regex.workspace = true
|
||||
log.workspace = true
|
||||
miette.workspace = true
|
||||
nix = { workspace = true, features = ["signal"] }
|
||||
|
|
@ -44,8 +45,12 @@ uuid.workspace = true
|
|||
zeroize.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
rstest.workspace = true
|
||||
blue-build-utils = { version = "=0.9.22", path = "../utils", features = ["test"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
bootc = []
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use std::{
|
|||
borrow::Borrow,
|
||||
fmt::Debug,
|
||||
process::{ExitStatus, Output},
|
||||
sync::{Mutex, RwLock},
|
||||
sync::{LazyLock, RwLock, atomic::AtomicBool},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
|
|
@ -24,12 +24,13 @@ use log::{info, trace, warn};
|
|||
use miette::{Result, miette};
|
||||
use oci_distribution::Reference;
|
||||
use opts::{
|
||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, CreateContainerOpts, GenerateImageNameOpts,
|
||||
GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts, RemoveContainerOpts,
|
||||
RemoveImageOpts, RunOpts, SignOpts, TagOpts, VerifyOpts,
|
||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, ContainerOpts, CopyOciDirOpts,
|
||||
CreateContainerOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts,
|
||||
GetMetadataOpts, PruneOpts, PushOpts, RechunkOpts, RemoveContainerOpts, RemoveImageOpts,
|
||||
RunOpts, SignOpts, SwitchOpts, TagOpts, VerifyOpts, VolumeOpts,
|
||||
};
|
||||
use types::{
|
||||
BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType, Platform,
|
||||
BootDriverType, BuildDriverType, CiDriverType, ImageMetadata, InspectDriverType, Platform,
|
||||
RunDriverType, SigningDriverType,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
|
@ -39,10 +40,15 @@ use crate::logging::Logger;
|
|||
pub use self::{
|
||||
buildah_driver::BuildahDriver, cosign_driver::CosignDriver, docker_driver::DockerDriver,
|
||||
github_driver::GithubDriver, gitlab_driver::GitlabDriver, local_driver::LocalDriver,
|
||||
podman_driver::PodmanDriver, sigstore_driver::SigstoreDriver, skopeo_driver::SkopeoDriver,
|
||||
traits::*,
|
||||
podman_driver::PodmanDriver, rpm_ostree_driver::RpmOstreeDriver,
|
||||
sigstore_driver::SigstoreDriver, skopeo_driver::SkopeoDriver, traits::*,
|
||||
};
|
||||
|
||||
#[cfg(feature = "bootc")]
|
||||
pub use bootc_driver::BootcDriver;
|
||||
|
||||
#[cfg(feature = "bootc")]
|
||||
mod bootc_driver;
|
||||
mod buildah_driver;
|
||||
mod cosign_driver;
|
||||
mod docker_driver;
|
||||
|
|
@ -52,22 +58,25 @@ mod gitlab_driver;
|
|||
mod local_driver;
|
||||
pub mod opts;
|
||||
mod podman_driver;
|
||||
mod rpm_ostree_driver;
|
||||
mod sigstore_driver;
|
||||
mod skopeo_driver;
|
||||
mod traits;
|
||||
pub mod types;
|
||||
|
||||
static INIT: std::sync::LazyLock<Mutex<bool>> = std::sync::LazyLock::new(|| Mutex::new(false));
|
||||
static SELECTED_BUILD_DRIVER: std::sync::LazyLock<RwLock<Option<BuildDriverType>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_INSPECT_DRIVER: std::sync::LazyLock<RwLock<Option<InspectDriverType>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_RUN_DRIVER: std::sync::LazyLock<RwLock<Option<RunDriverType>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_SIGNING_DRIVER: std::sync::LazyLock<RwLock<Option<SigningDriverType>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_CI_DRIVER: std::sync::LazyLock<RwLock<Option<CiDriverType>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
||||
static INIT: AtomicBool = AtomicBool::new(false);
|
||||
static SELECTED_BUILD_DRIVER: LazyLock<RwLock<Option<BuildDriverType>>> =
|
||||
LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_INSPECT_DRIVER: LazyLock<RwLock<Option<InspectDriverType>>> =
|
||||
LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_RUN_DRIVER: LazyLock<RwLock<Option<RunDriverType>>> =
|
||||
LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_SIGNING_DRIVER: LazyLock<RwLock<Option<SigningDriverType>>> =
|
||||
LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_CI_DRIVER: LazyLock<RwLock<Option<CiDriverType>>> =
|
||||
LazyLock::new(|| RwLock::new(None));
|
||||
static SELECTED_BOOT_DRIVER: LazyLock<RwLock<Option<BootDriverType>>> =
|
||||
LazyLock::new(|| RwLock::new(None));
|
||||
|
||||
/// Args for selecting the various drivers to use for runtime.
|
||||
///
|
||||
|
|
@ -95,6 +104,9 @@ pub struct DriverArgs {
|
|||
/// containers.
|
||||
#[arg(short = 'R', long)]
|
||||
run_driver: Option<RunDriverType>,
|
||||
|
||||
#[arg(short = 'T', long)]
|
||||
boot_driver: Option<BootDriverType>,
|
||||
}
|
||||
|
||||
macro_rules! impl_driver_type {
|
||||
|
|
@ -108,12 +120,13 @@ macro_rules! impl_driver_init {
|
|||
(@) => { };
|
||||
($init:ident; $($tail:tt)*) => {
|
||||
{
|
||||
let mut initialized = $init.lock().expect("Must lock INIT");
|
||||
|
||||
if !*initialized {
|
||||
if $init.compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire
|
||||
).is_ok() {
|
||||
impl_driver_init!(@ $($tail)*);
|
||||
|
||||
*initialized = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -162,6 +175,7 @@ impl Driver {
|
|||
args.inspect_driver => SELECTED_INSPECT_DRIVER;
|
||||
args.run_driver => SELECTED_RUN_DRIVER;
|
||||
args.signing_driver => SELECTED_SIGNING_DRIVER;
|
||||
args.boot_driver => SELECTED_BOOT_DRIVER;
|
||||
default => SELECTED_CI_DRIVER;
|
||||
}
|
||||
}
|
||||
|
|
@ -206,7 +220,7 @@ impl Driver {
|
|||
info!("Retrieving OS version from {oci_ref}");
|
||||
|
||||
let os_version = Self::get_metadata(
|
||||
&GetMetadataOpts::builder()
|
||||
GetMetadataOpts::builder()
|
||||
.image(oci_ref)
|
||||
.maybe_platform(platform)
|
||||
.build(),
|
||||
|
|
@ -247,6 +261,10 @@ impl Driver {
|
|||
pub fn get_ci_driver() -> CiDriverType {
|
||||
impl_driver_type!(SELECTED_CI_DRIVER)
|
||||
}
|
||||
|
||||
pub fn get_boot_driver() -> BootDriverType {
|
||||
impl_driver_type!(SELECTED_BOOT_DRIVER)
|
||||
}
|
||||
}
|
||||
|
||||
#[cached(
|
||||
|
|
@ -278,9 +296,9 @@ fn get_version_run_image(oci_ref: &Reference) -> Result<u64> {
|
|||
};
|
||||
|
||||
let output = Driver::run_output(
|
||||
&RunOpts::builder()
|
||||
.image(oci_ref.to_string())
|
||||
.args(bon::vec![
|
||||
RunOpts::builder()
|
||||
.image(&oci_ref.to_string())
|
||||
.args(&bon::vec![
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
r#"awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release"#,
|
||||
|
|
@ -291,7 +309,7 @@ fn get_version_run_image(oci_ref: &Reference) -> Result<u64> {
|
|||
)?;
|
||||
|
||||
if should_remove {
|
||||
Driver::remove_image(&RemoveImageOpts::builder().image(oci_ref).build())?;
|
||||
Driver::remove_image(RemoveImageOpts::builder().image(oci_ref).build())?;
|
||||
}
|
||||
|
||||
progress.finish_and_clear();
|
||||
|
|
@ -314,15 +332,15 @@ macro_rules! impl_build_driver {
|
|||
}
|
||||
|
||||
impl BuildDriver for Driver {
|
||||
fn build(opts: &BuildOpts) -> Result<()> {
|
||||
fn build(opts: BuildOpts) -> Result<()> {
|
||||
impl_build_driver!(build(opts))
|
||||
}
|
||||
|
||||
fn tag(opts: &TagOpts) -> Result<()> {
|
||||
fn tag(opts: TagOpts) -> Result<()> {
|
||||
impl_build_driver!(tag(opts))
|
||||
}
|
||||
|
||||
fn push(opts: &PushOpts) -> Result<()> {
|
||||
fn push(opts: PushOpts) -> Result<()> {
|
||||
impl_build_driver!(push(opts))
|
||||
}
|
||||
|
||||
|
|
@ -330,11 +348,11 @@ impl BuildDriver for Driver {
|
|||
impl_build_driver!(login())
|
||||
}
|
||||
|
||||
fn prune(opts: &opts::PruneOpts) -> Result<()> {
|
||||
fn prune(opts: PruneOpts) -> Result<()> {
|
||||
impl_build_driver!(prune(opts))
|
||||
}
|
||||
|
||||
fn build_tag_push(opts: &BuildTagPushOpts) -> Result<Vec<String>> {
|
||||
fn build_tag_push(opts: BuildTagPushOpts) -> Result<Vec<String>> {
|
||||
impl_build_driver!(build_tag_push(opts))
|
||||
}
|
||||
}
|
||||
|
|
@ -349,19 +367,19 @@ macro_rules! impl_signing_driver {
|
|||
}
|
||||
|
||||
impl SigningDriver for Driver {
|
||||
fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> {
|
||||
fn generate_key_pair(opts: GenerateKeyPairOpts) -> Result<()> {
|
||||
impl_signing_driver!(generate_key_pair(opts))
|
||||
}
|
||||
|
||||
fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()> {
|
||||
fn check_signing_files(opts: CheckKeyPairOpts) -> Result<()> {
|
||||
impl_signing_driver!(check_signing_files(opts))
|
||||
}
|
||||
|
||||
fn sign(opts: &SignOpts) -> Result<()> {
|
||||
fn sign(opts: SignOpts) -> Result<()> {
|
||||
impl_signing_driver!(sign(opts))
|
||||
}
|
||||
|
||||
fn verify(opts: &VerifyOpts) -> Result<()> {
|
||||
fn verify(opts: VerifyOpts) -> Result<()> {
|
||||
impl_signing_driver!(verify(opts))
|
||||
}
|
||||
|
||||
|
|
@ -381,7 +399,7 @@ macro_rules! impl_inspect_driver {
|
|||
}
|
||||
|
||||
impl InspectDriver for Driver {
|
||||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
impl_inspect_driver!(get_metadata(opts))
|
||||
}
|
||||
}
|
||||
|
|
@ -396,23 +414,23 @@ macro_rules! impl_run_driver {
|
|||
}
|
||||
|
||||
impl RunDriver for Driver {
|
||||
fn run(opts: &RunOpts) -> Result<ExitStatus> {
|
||||
fn run(opts: RunOpts) -> Result<ExitStatus> {
|
||||
impl_run_driver!(run(opts))
|
||||
}
|
||||
|
||||
fn run_output(opts: &RunOpts) -> Result<Output> {
|
||||
fn run_output(opts: RunOpts) -> Result<Output> {
|
||||
impl_run_driver!(run_output(opts))
|
||||
}
|
||||
|
||||
fn create_container(opts: &CreateContainerOpts) -> Result<types::ContainerId> {
|
||||
fn create_container(opts: CreateContainerOpts) -> Result<types::ContainerId> {
|
||||
impl_run_driver!(create_container(opts))
|
||||
}
|
||||
|
||||
fn remove_container(opts: &RemoveContainerOpts) -> Result<()> {
|
||||
fn remove_container(opts: RemoveContainerOpts) -> Result<()> {
|
||||
impl_run_driver!(remove_container(opts))
|
||||
}
|
||||
|
||||
fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
|
||||
fn remove_image(opts: RemoveImageOpts) -> Result<()> {
|
||||
impl_run_driver!(remove_image(opts))
|
||||
}
|
||||
|
||||
|
|
@ -444,7 +462,7 @@ impl CiDriver for Driver {
|
|||
impl_ci_driver!(oidc_provider())
|
||||
}
|
||||
|
||||
fn generate_tags(opts: &GenerateTagsOpts) -> Result<Vec<String>> {
|
||||
fn generate_tags(opts: GenerateTagsOpts) -> Result<Vec<String>> {
|
||||
impl_ci_driver!(generate_tags(opts))
|
||||
}
|
||||
|
||||
|
|
@ -469,27 +487,52 @@ impl CiDriver for Driver {
|
|||
}
|
||||
|
||||
impl ContainerMountDriver for Driver {
|
||||
fn mount_container(opts: &opts::ContainerOpts) -> Result<types::MountId> {
|
||||
fn mount_container(opts: ContainerOpts) -> Result<types::MountId> {
|
||||
PodmanDriver::mount_container(opts)
|
||||
}
|
||||
|
||||
fn unmount_container(opts: &opts::ContainerOpts) -> Result<()> {
|
||||
fn unmount_container(opts: ContainerOpts) -> Result<()> {
|
||||
PodmanDriver::unmount_container(opts)
|
||||
}
|
||||
|
||||
fn remove_volume(opts: &opts::VolumeOpts) -> Result<()> {
|
||||
fn remove_volume(opts: VolumeOpts) -> Result<()> {
|
||||
PodmanDriver::remove_volume(opts)
|
||||
}
|
||||
}
|
||||
|
||||
impl OciCopy for Driver {
|
||||
fn copy_oci_dir(opts: &opts::CopyOciDirOpts) -> Result<()> {
|
||||
fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()> {
|
||||
SkopeoDriver::copy_oci_dir(opts)
|
||||
}
|
||||
}
|
||||
|
||||
impl RechunkDriver for Driver {
|
||||
fn rechunk(opts: &opts::RechunkOpts) -> Result<Vec<String>> {
|
||||
fn rechunk(opts: RechunkOpts) -> Result<Vec<String>> {
|
||||
PodmanDriver::rechunk(opts)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_boot_driver {
|
||||
($func:ident($($args:expr),*)) => {
|
||||
match Self::get_boot_driver() {
|
||||
#[cfg(feature = "bootc")]
|
||||
BootDriverType::Bootc => BootcDriver::$func($($args,)*),
|
||||
BootDriverType::RpmOstree => RpmOstreeDriver::$func($($args,)*),
|
||||
BootDriverType::None => ::miette::bail!("Cannot perform boot operation when no boot driver exists."),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl BootDriver for Driver {
|
||||
fn status() -> Result<Box<dyn BootStatus>> {
|
||||
impl_boot_driver!(status())
|
||||
}
|
||||
|
||||
fn switch(opts: SwitchOpts) -> Result<()> {
|
||||
impl_boot_driver!(switch(opts))
|
||||
}
|
||||
|
||||
fn upgrade(opts: SwitchOpts) -> Result<()> {
|
||||
impl_boot_driver!(upgrade(opts))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
90
process/drivers/bootc_driver.rs
Normal file
90
process/drivers/bootc_driver.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
use std::ops::Not;
|
||||
|
||||
use blue_build_utils::sudo_cmd;
|
||||
use log::trace;
|
||||
use miette::{Context, IntoDiagnostic, Result, bail};
|
||||
|
||||
use crate::logging::CommandLogging;
|
||||
|
||||
use super::{BootDriver, BootStatus, opts::SwitchOpts};
|
||||
|
||||
mod status;
|
||||
|
||||
pub use status::*;
|
||||
|
||||
const SUDO_PROMPT: &str = "Password needed to run bootc";
|
||||
|
||||
pub struct BootcDriver;
|
||||
|
||||
impl BootDriver for BootcDriver {
|
||||
fn status() -> Result<Box<dyn BootStatus>> {
|
||||
let output = {
|
||||
let c = sudo_cmd!(prompt = SUDO_PROMPT, "bootc", "status", "--format=json");
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.output()
|
||||
.into_diagnostic()?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("Failed to get `bootc` status!");
|
||||
}
|
||||
|
||||
trace!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
|
||||
Ok(Box::new(
|
||||
serde_json::from_slice::<BootcStatus>(&output.stdout)
|
||||
.into_diagnostic()
|
||||
.wrap_err_with(|| {
|
||||
format!(
|
||||
"Failed to deserialize bootc status:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
})?,
|
||||
))
|
||||
}
|
||||
|
||||
fn switch(opts: SwitchOpts) -> Result<()> {
|
||||
let status = {
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
"bootc",
|
||||
"switch",
|
||||
"--transport=containers-storage",
|
||||
opts.image.to_string(),
|
||||
);
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.build_status(
|
||||
opts.image.to_string(),
|
||||
format!("Switching to {}", opts.image),
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success().not() {
|
||||
bail!("Failed to switch to {}", opts.image);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade(opts: SwitchOpts) -> Result<()> {
|
||||
let status = {
|
||||
let c = sudo_cmd!(prompt = SUDO_PROMPT, "bootc", "upgrade");
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.build_status(
|
||||
opts.image.to_string(),
|
||||
format!("Switching to {}", opts.image),
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success().not() {
|
||||
bail!("Failed to switch to {}", opts.image);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
85
process/drivers/bootc_driver/status.rs
Normal file
85
process/drivers/bootc_driver/status.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use blue_build_utils::constants::OCI_ARCHIVE;
|
||||
use log::warn;
|
||||
use oci_distribution::Reference;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::drivers::{BootStatus, types::ImageRef};
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct BootcStatus {
|
||||
status: BootcStatusExt,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct BootcStatusExt {
|
||||
staged: Option<BootcStatusImage>,
|
||||
booted: BootcStatusImage,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct BootcStatusImage {
|
||||
image: BootcStatusImageInfo,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct BootcStatusImageInfo {
|
||||
image: BootcStatusImageInfoRef,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct BootcStatusImageInfoRef {
|
||||
image: String,
|
||||
transport: String,
|
||||
}
|
||||
|
||||
impl BootStatus for BootcStatus {
|
||||
fn transaction_in_progress(&self) -> bool {
|
||||
// Any call to bootc when a transaction is in progress
|
||||
// will cause the process to block effectively making
|
||||
// this check useless since bootc will continue with
|
||||
// the operation as soon as the current transaction is
|
||||
// completed.
|
||||
false
|
||||
}
|
||||
|
||||
fn booted_image(&self) -> Option<ImageRef<'_>> {
|
||||
match self.status.booted.image.image.transport.as_str() {
|
||||
"registry" | "containers-storage" => Some(ImageRef::Remote(Cow::Owned(
|
||||
Reference::try_from(self.status.booted.image.image.image.as_str())
|
||||
.inspect_err(|e| {
|
||||
warn!(
|
||||
"Failed to parse image ref {}:\n{e}",
|
||||
self.status.booted.image.image.image
|
||||
);
|
||||
})
|
||||
.ok()?,
|
||||
))),
|
||||
transport if transport == OCI_ARCHIVE => Some(ImageRef::LocalTar(Cow::Owned(
|
||||
PathBuf::from(&self.status.booted.image.image.image),
|
||||
))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn staged_image(&self) -> Option<ImageRef<'_>> {
|
||||
let staged = self.status.staged.as_ref()?;
|
||||
match staged.image.image.transport.as_str() {
|
||||
"registry" | "containers-storage" => Some(ImageRef::Remote(Cow::Owned(
|
||||
Reference::try_from(staged.image.image.image.as_str())
|
||||
.inspect_err(|e| {
|
||||
warn!(
|
||||
"Failed to parse image ref {}:\n{e}",
|
||||
staged.image.image.image
|
||||
);
|
||||
})
|
||||
.ok()?,
|
||||
))),
|
||||
transport if transport == OCI_ARCHIVE => Some(ImageRef::LocalTar(Cow::Owned(
|
||||
PathBuf::from(&staged.image.image.image),
|
||||
))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ use crate::logging::CommandLogging;
|
|||
|
||||
use super::{
|
||||
BuildDriver, DriverVersion,
|
||||
opts::{BuildOpts, PushOpts, TagOpts},
|
||||
opts::{BuildOpts, PruneOpts, PushOpts, TagOpts},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
@ -48,7 +48,7 @@ impl DriverVersion for BuildahDriver {
|
|||
}
|
||||
|
||||
impl BuildDriver for BuildahDriver {
|
||||
fn build(opts: &BuildOpts) -> Result<()> {
|
||||
fn build(opts: BuildOpts) -> Result<()> {
|
||||
trace!("BuildahDriver::build({opts:#?})");
|
||||
|
||||
let temp_dir = TempDir::new()
|
||||
|
|
@ -83,7 +83,7 @@ impl BuildDriver for BuildahDriver {
|
|||
),
|
||||
],
|
||||
"-f",
|
||||
&*opts.containerfile,
|
||||
opts.containerfile,
|
||||
"-t",
|
||||
opts.image.to_string(),
|
||||
);
|
||||
|
|
@ -101,7 +101,7 @@ impl BuildDriver for BuildahDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn tag(opts: &TagOpts) -> Result<()> {
|
||||
fn tag(opts: TagOpts) -> Result<()> {
|
||||
trace!("BuildahDriver::tag({opts:#?})");
|
||||
|
||||
let dest_image_str = opts.dest_image.to_string();
|
||||
|
|
@ -122,7 +122,7 @@ impl BuildDriver for BuildahDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn push(opts: &PushOpts) -> Result<()> {
|
||||
fn push(opts: PushOpts) -> Result<()> {
|
||||
trace!("BuildahDriver::push({opts:#?})");
|
||||
|
||||
let image_str = opts.image.to_string();
|
||||
|
|
@ -195,7 +195,7 @@ impl BuildDriver for BuildahDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
||||
fn prune(opts: PruneOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::prune({opts:?})");
|
||||
|
||||
let status = cmd!(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use super::{
|
|||
pub struct CosignDriver;
|
||||
|
||||
impl SigningDriver for CosignDriver {
|
||||
fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> {
|
||||
fn generate_key_pair(opts: GenerateKeyPairOpts) -> Result<()> {
|
||||
let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
|
||||
|
||||
let status = {
|
||||
|
|
@ -47,7 +47,7 @@ impl SigningDriver for CosignDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()> {
|
||||
fn check_signing_files(opts: CheckKeyPairOpts) -> Result<()> {
|
||||
let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
|
||||
let priv_key = get_private_key(path)?;
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ impl SigningDriver for CosignDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn sign(opts: &SignOpts) -> Result<()> {
|
||||
fn sign(opts: SignOpts) -> Result<()> {
|
||||
if opts.image.digest().is_none() {
|
||||
bail!(
|
||||
"Image ref {} is not a digest ref",
|
||||
|
|
@ -140,7 +140,7 @@ impl SigningDriver for CosignDriver {
|
|||
};
|
||||
"cosign",
|
||||
"sign",
|
||||
if let Some(ref key) = opts.key => format!("--key={key}"),
|
||||
if let Some(key) = opts.key => format!("--key={key}"),
|
||||
"--recursive",
|
||||
opts.image.to_string(),
|
||||
);
|
||||
|
|
@ -157,7 +157,7 @@ impl SigningDriver for CosignDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn verify(opts: &VerifyOpts) -> Result<()> {
|
||||
fn verify(opts: VerifyOpts) -> Result<()> {
|
||||
let status = {
|
||||
let c = cmd!(
|
||||
"cosign",
|
||||
|
|
@ -205,9 +205,8 @@ mod test {
|
|||
fn generate_key_pair() {
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
|
||||
let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
|
||||
|
||||
CosignDriver::generate_key_pair(&gen_opts).unwrap();
|
||||
CosignDriver::generate_key_pair(GenerateKeyPairOpts::builder().dir(tempdir.path()).build())
|
||||
.unwrap();
|
||||
|
||||
eprintln!(
|
||||
"Private key:\n{}",
|
||||
|
|
@ -218,18 +217,15 @@ mod test {
|
|||
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
||||
);
|
||||
|
||||
let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
|
||||
|
||||
CosignDriver::check_signing_files(&check_opts).unwrap();
|
||||
CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(tempdir.path()).build())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_key_pairs() {
|
||||
let path = Path::new("../test-files/keys");
|
||||
|
||||
let opts = CheckKeyPairOpts::builder().dir(path).build();
|
||||
|
||||
CosignDriver::check_signing_files(&opts).unwrap();
|
||||
CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(path).build()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -238,9 +234,8 @@ mod test {
|
|||
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
|
||||
let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
|
||||
|
||||
CosignDriver::generate_key_pair(&gen_opts).unwrap();
|
||||
CosignDriver::generate_key_pair(GenerateKeyPairOpts::builder().dir(tempdir.path()).build())
|
||||
.unwrap();
|
||||
|
||||
eprintln!(
|
||||
"Private key:\n{}",
|
||||
|
|
@ -251,8 +246,9 @@ mod test {
|
|||
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
||||
);
|
||||
|
||||
let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
|
||||
|
||||
SigstoreDriver::check_signing_files(&check_opts).unwrap();
|
||||
SigstoreDriver::check_signing_files(
|
||||
CheckKeyPairOpts::builder().dir(tempdir.path()).build(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ use crate::{
|
|||
signal_handler::{ContainerRuntime, ContainerSignalId, add_cid, remove_cid},
|
||||
};
|
||||
|
||||
use super::opts::{CreateContainerOpts, RemoveContainerOpts, RemoveImageOpts};
|
||||
use super::opts::{CreateContainerOpts, PruneOpts, RemoveContainerOpts, RemoveImageOpts};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct VerisonJsonClient {
|
||||
|
|
@ -192,7 +192,7 @@ impl DriverVersion for DockerDriver {
|
|||
}
|
||||
|
||||
impl BuildDriver for DockerDriver {
|
||||
fn build(opts: &BuildOpts) -> Result<()> {
|
||||
fn build(opts: BuildOpts) -> Result<()> {
|
||||
trace!("DockerDriver::build({opts:#?})");
|
||||
|
||||
let temp_dir = TempDir::new()
|
||||
|
|
@ -232,7 +232,7 @@ impl BuildDriver for DockerDriver {
|
|||
repository = cache_to.repository(),
|
||||
),
|
||||
],
|
||||
&*opts.containerfile,
|
||||
opts.containerfile,
|
||||
".",
|
||||
);
|
||||
trace!("{c:?}");
|
||||
|
|
@ -249,7 +249,7 @@ impl BuildDriver for DockerDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn tag(opts: &TagOpts) -> Result<()> {
|
||||
fn tag(opts: TagOpts) -> Result<()> {
|
||||
trace!("DockerDriver::tag({opts:#?})");
|
||||
|
||||
let dest_image_str = opts.dest_image.to_string();
|
||||
|
|
@ -270,7 +270,7 @@ impl BuildDriver for DockerDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn push(opts: &PushOpts) -> Result<()> {
|
||||
fn push(opts: PushOpts) -> Result<()> {
|
||||
trace!("DockerDriver::push({opts:#?})");
|
||||
|
||||
let image_str = opts.image.to_string();
|
||||
|
|
@ -328,7 +328,7 @@ impl BuildDriver for DockerDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
||||
fn prune(opts: PruneOpts) -> Result<()> {
|
||||
trace!("DockerDriver::prune({opts:?})");
|
||||
|
||||
let (system, buildx) = std::thread::scope(
|
||||
|
|
@ -385,7 +385,7 @@ impl BuildDriver for DockerDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn build_tag_push(opts: &BuildTagPushOpts) -> Result<Vec<String>> {
|
||||
fn build_tag_push(opts: BuildTagPushOpts) -> Result<Vec<String>> {
|
||||
trace!("DockerDriver::build_tag_push({opts:#?})");
|
||||
|
||||
let temp_dir = TempDir::new()
|
||||
|
|
@ -420,7 +420,7 @@ impl BuildDriver for DockerDriver {
|
|||
}
|
||||
|
||||
fn build_tag_push_cmd(
|
||||
opts: &BuildTagPushOpts<'_>,
|
||||
opts: BuildTagPushOpts<'_>,
|
||||
first_image: &str,
|
||||
temp_dir: &TempDir,
|
||||
) -> Result<Command> {
|
||||
|
|
@ -461,7 +461,7 @@ fn build_tag_push_cmd(
|
|||
platform.to_string(),
|
||||
],
|
||||
"-f",
|
||||
&*opts.containerfile,
|
||||
opts.containerfile,
|
||||
if let Some(cache_from) = opts.cache_from.as_ref() => [
|
||||
"--cache-from",
|
||||
format!(
|
||||
|
|
@ -479,7 +479,7 @@ fn build_tag_push_cmd(
|
|||
Ok(c)
|
||||
}
|
||||
|
||||
fn get_final_images(opts: &BuildTagPushOpts<'_>) -> Vec<String> {
|
||||
fn get_final_images(opts: BuildTagPushOpts<'_>) -> Vec<String> {
|
||||
match &opts.image {
|
||||
ImageRef::Remote(image) => {
|
||||
if opts.tags.is_empty() {
|
||||
|
|
@ -495,11 +495,14 @@ fn get_final_images(opts: &BuildTagPushOpts<'_>) -> Vec<String> {
|
|||
ImageRef::LocalTar(archive_path) => {
|
||||
string_vec![archive_path.display().to_string()]
|
||||
}
|
||||
ImageRef::Other(other) => {
|
||||
string_vec![&**other]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InspectDriver for DockerDriver {
|
||||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
get_metadata_cache(opts)
|
||||
}
|
||||
}
|
||||
|
|
@ -510,7 +513,7 @@ impl InspectDriver for DockerDriver {
|
|||
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
||||
sync_writes = "by_key"
|
||||
)]
|
||||
fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
fn get_metadata_cache(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
trace!("DockerDriver::get_metadata({opts:#?})");
|
||||
let image_str = opts.image.to_string();
|
||||
|
||||
|
|
@ -547,7 +550,7 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
|||
}
|
||||
|
||||
impl RunDriver for DockerDriver {
|
||||
fn run(opts: &RunOpts) -> Result<ExitStatus> {
|
||||
fn run(opts: RunOpts) -> Result<ExitStatus> {
|
||||
trace!("DockerDriver::run({opts:#?})");
|
||||
|
||||
let cid_path = TempDir::new().into_diagnostic()?;
|
||||
|
|
@ -557,7 +560,7 @@ impl RunDriver for DockerDriver {
|
|||
add_cid(&cid);
|
||||
|
||||
let status = docker_run(opts, &cid_file)
|
||||
.build_status(&*opts.image, "Running container")
|
||||
.build_status(opts.image, "Running container")
|
||||
.into_diagnostic()?;
|
||||
|
||||
remove_cid(&cid);
|
||||
|
|
@ -565,7 +568,7 @@ impl RunDriver for DockerDriver {
|
|||
Ok(status)
|
||||
}
|
||||
|
||||
fn run_output(opts: &RunOpts) -> Result<std::process::Output> {
|
||||
fn run_output(opts: RunOpts) -> Result<std::process::Output> {
|
||||
trace!("DockerDriver::run({opts:#?})");
|
||||
|
||||
let cid_path = TempDir::new().into_diagnostic()?;
|
||||
|
|
@ -581,7 +584,7 @@ impl RunDriver for DockerDriver {
|
|||
Ok(output)
|
||||
}
|
||||
|
||||
fn create_container(opts: &CreateContainerOpts) -> Result<super::types::ContainerId> {
|
||||
fn create_container(opts: CreateContainerOpts) -> Result<super::types::ContainerId> {
|
||||
trace!("DockerDriver::create_container({opts:?})");
|
||||
|
||||
let output = {
|
||||
|
|
@ -601,7 +604,7 @@ impl RunDriver for DockerDriver {
|
|||
))
|
||||
}
|
||||
|
||||
fn remove_container(opts: &RemoveContainerOpts) -> Result<()> {
|
||||
fn remove_container(opts: RemoveContainerOpts) -> Result<()> {
|
||||
trace!("DockerDriver::remove_container({opts:?})");
|
||||
|
||||
let output = {
|
||||
|
|
@ -619,7 +622,7 @@ impl RunDriver for DockerDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
|
||||
fn remove_image(opts: RemoveImageOpts) -> Result<()> {
|
||||
trace!("DockerDriver::remove_image({opts:?})");
|
||||
|
||||
let output = {
|
||||
|
|
@ -675,7 +678,7 @@ impl RunDriver for DockerDriver {
|
|||
}
|
||||
}
|
||||
|
||||
fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
|
||||
fn docker_run(opts: RunOpts, cid_file: &Path) -> Command {
|
||||
let command = cmd!(
|
||||
"docker",
|
||||
"run",
|
||||
|
|
@ -693,7 +696,7 @@ fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
|
|||
"--env",
|
||||
format!("{key}={value}"),
|
||||
],
|
||||
&*opts.image,
|
||||
opts.image,
|
||||
for arg in opts.args.iter() => &**arg,
|
||||
);
|
||||
trace!("{command:?}");
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ impl CiDriver for GithubDriver {
|
|||
Ok(GITHUB_TOKEN_ISSUER_URL.to_string())
|
||||
}
|
||||
|
||||
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||
fn generate_tags(opts: GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||
const PR_EVENT: &str = "pull_request";
|
||||
let timestamp = blue_build_utils::get_tag_timestamp();
|
||||
let os_version = Driver::get_os_version()
|
||||
|
|
@ -142,8 +142,6 @@ impl CiDriver for GithubDriver {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use blue_build_utils::{
|
||||
constants::{
|
||||
GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER,
|
||||
|
|
@ -286,7 +284,7 @@ mod test {
|
|||
)]
|
||||
fn generate_tags(
|
||||
#[case] setup: impl FnOnce(),
|
||||
#[case] alt_tags: Option<Vec<Cow<'_, str>>>,
|
||||
#[case] alt_tags: Option<Vec<String>>,
|
||||
#[case] mut expected: Vec<String>,
|
||||
) {
|
||||
setup();
|
||||
|
|
@ -294,9 +292,9 @@ mod test {
|
|||
let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
|
||||
|
||||
let mut tags = GithubDriver::generate_tags(
|
||||
&GenerateTagsOpts::builder()
|
||||
GenerateTagsOpts::builder()
|
||||
.oci_ref(&oci_ref)
|
||||
.maybe_alt_tags(alt_tags)
|
||||
.maybe_alt_tags(alt_tags.as_deref())
|
||||
.platform(Platform::LinuxAmd64)
|
||||
.build(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ impl CiDriver for GitlabDriver {
|
|||
))
|
||||
}
|
||||
|
||||
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||
fn generate_tags(opts: GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||
const MR_EVENT: &str = "merge_request_event";
|
||||
let os_version = Driver::get_os_version()
|
||||
.oci_ref(opts.oci_ref)
|
||||
|
|
@ -151,8 +151,6 @@ impl CiDriver for GitlabDriver {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use blue_build_utils::{
|
||||
constants::{
|
||||
CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID,
|
||||
|
|
@ -293,7 +291,7 @@ mod test {
|
|||
)]
|
||||
fn generate_tags(
|
||||
#[case] setup: impl FnOnce(),
|
||||
#[case] alt_tags: Option<Vec<Cow<'_, str>>>,
|
||||
#[case] alt_tags: Option<Vec<String>>,
|
||||
#[case] mut expected: Vec<String>,
|
||||
) {
|
||||
setup();
|
||||
|
|
@ -301,9 +299,9 @@ mod test {
|
|||
let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
|
||||
|
||||
let mut tags = GitlabDriver::generate_tags(
|
||||
&GenerateTagsOpts::builder()
|
||||
GenerateTagsOpts::builder()
|
||||
.oci_ref(&oci_ref)
|
||||
.maybe_alt_tags(alt_tags)
|
||||
.maybe_alt_tags(alt_tags.as_deref())
|
||||
.platform(crate::drivers::types::Platform::LinuxAmd64)
|
||||
.build(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::path::PathBuf;
|
|||
use blue_build_utils::string_vec;
|
||||
use comlexr::cmd;
|
||||
use log::trace;
|
||||
use miette::bail;
|
||||
|
||||
use super::{CiDriver, Driver, opts::GenerateTagsOpts};
|
||||
|
||||
|
|
@ -15,14 +16,14 @@ impl CiDriver for LocalDriver {
|
|||
}
|
||||
|
||||
fn keyless_cert_identity() -> miette::Result<String> {
|
||||
unimplemented!()
|
||||
bail!("Unimplemented for local")
|
||||
}
|
||||
|
||||
fn oidc_provider() -> miette::Result<String> {
|
||||
unimplemented!()
|
||||
bail!("Unimplemented for local")
|
||||
}
|
||||
|
||||
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||
fn generate_tags(opts: GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||
trace!("LocalDriver::generate_tags({opts:?})");
|
||||
let os_version = Driver::get_os_version()
|
||||
.oci_ref(opts.oci_ref)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use clap::ValueEnum;
|
||||
|
||||
pub use boot::*;
|
||||
pub use build::*;
|
||||
pub use ci::*;
|
||||
pub use inspect::*;
|
||||
|
|
@ -7,6 +8,7 @@ pub use rechunk::*;
|
|||
pub use run::*;
|
||||
pub use signing::*;
|
||||
|
||||
mod boot;
|
||||
mod build;
|
||||
mod ci;
|
||||
mod inspect;
|
||||
|
|
|
|||
10
process/drivers/opts/boot.rs
Normal file
10
process/drivers/opts/boot.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
use bon::Builder;
|
||||
use oci_distribution::Reference;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct SwitchOpts<'scope> {
|
||||
pub image: &'scope Reference,
|
||||
|
||||
#[builder(default)]
|
||||
pub reboot: bool,
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, collections::HashSet, path::Path};
|
||||
use std::path::Path;
|
||||
|
||||
use blue_build_utils::secret::Secret;
|
||||
use bon::Builder;
|
||||
|
|
@ -9,16 +9,14 @@ use crate::drivers::types::{ImageRef, Platform};
|
|||
use super::CompressionType;
|
||||
|
||||
/// Options for building
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct BuildOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub image: ImageRef<'scope>,
|
||||
pub image: &'scope ImageRef<'scope>,
|
||||
|
||||
#[builder(default)]
|
||||
pub squash: bool,
|
||||
|
||||
#[builder(into)]
|
||||
pub containerfile: Cow<'scope, Path>,
|
||||
pub containerfile: &'scope Path,
|
||||
|
||||
pub platform: Option<Platform>,
|
||||
|
||||
|
|
@ -27,18 +25,14 @@ pub struct BuildOpts<'scope> {
|
|||
|
||||
#[builder(default)]
|
||||
pub privileged: bool,
|
||||
|
||||
#[builder(into)]
|
||||
pub cache_from: Option<&'scope Reference>,
|
||||
|
||||
#[builder(into)]
|
||||
pub cache_to: Option<&'scope Reference>,
|
||||
|
||||
#[builder(default)]
|
||||
pub secrets: HashSet<&'scope Secret>,
|
||||
pub secrets: &'scope [&'scope Secret],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct TagOpts<'scope> {
|
||||
pub src_image: &'scope Reference,
|
||||
pub dest_image: &'scope Reference,
|
||||
|
|
@ -47,7 +41,7 @@ pub struct TagOpts<'scope> {
|
|||
pub privileged: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct PushOpts<'scope> {
|
||||
pub image: &'scope Reference,
|
||||
pub compression_type: Option<CompressionType>,
|
||||
|
|
@ -56,7 +50,7 @@ pub struct PushOpts<'scope> {
|
|||
pub privileged: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct PruneOpts {
|
||||
pub all: bool,
|
||||
pub volumes: bool,
|
||||
|
|
@ -64,19 +58,17 @@ pub struct PruneOpts {
|
|||
|
||||
/// Options for building, tagging, and pusing images.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct BuildTagPushOpts<'scope> {
|
||||
/// The base image name.
|
||||
#[builder(into)]
|
||||
pub image: ImageRef<'scope>,
|
||||
pub image: &'scope ImageRef<'scope>,
|
||||
|
||||
/// The path to the Containerfile to build.
|
||||
#[builder(into)]
|
||||
pub containerfile: Cow<'scope, Path>,
|
||||
pub containerfile: &'scope Path,
|
||||
|
||||
/// The list of tags for the image being built.
|
||||
#[builder(default, into)]
|
||||
pub tags: Vec<Cow<'scope, str>>,
|
||||
#[builder(default)]
|
||||
pub tags: &'scope [String],
|
||||
|
||||
/// Enable pushing the image.
|
||||
#[builder(default)]
|
||||
|
|
@ -115,5 +107,5 @@ pub struct BuildTagPushOpts<'scope> {
|
|||
|
||||
/// Secrets to mount
|
||||
#[builder(default)]
|
||||
pub secrets: HashSet<&'scope Secret>,
|
||||
pub secrets: &'scope [&'scope Secret],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,21 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use bon::Builder;
|
||||
use oci_distribution::Reference;
|
||||
|
||||
use crate::drivers::types::Platform;
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct GenerateTagsOpts<'scope> {
|
||||
pub oci_ref: &'scope Reference,
|
||||
|
||||
#[builder(into)]
|
||||
pub alt_tags: Option<Vec<Cow<'scope, str>>>,
|
||||
pub alt_tags: Option<&'scope [String]>,
|
||||
|
||||
pub platform: Option<Platform>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct GenerateImageNameOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub name: Cow<'scope, str>,
|
||||
|
||||
#[builder(into)]
|
||||
pub registry: Option<Cow<'scope, str>>,
|
||||
|
||||
#[builder(into)]
|
||||
pub registry_namespace: Option<Cow<'scope, str>>,
|
||||
pub name: &'scope str,
|
||||
pub registry: Option<&'scope str>,
|
||||
pub registry_namespace: Option<&'scope str>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use oci_distribution::Reference;
|
|||
|
||||
use crate::drivers::types::Platform;
|
||||
|
||||
#[derive(Debug, Clone, Builder, Hash)]
|
||||
#[derive(Debug, Clone, Copy, Builder, Hash)]
|
||||
#[builder(derive(Clone))]
|
||||
pub struct GetMetadataOpts<'scope> {
|
||||
#[builder(into)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, collections::HashSet, path::Path};
|
||||
use std::path::Path;
|
||||
|
||||
use blue_build_utils::secret::Secret;
|
||||
use bon::Builder;
|
||||
|
|
@ -8,25 +8,22 @@ use crate::drivers::types::{ContainerId, OciDir, Platform};
|
|||
|
||||
use super::CompressionType;
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[builder(on(Cow<'_, str>, into))]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct RechunkOpts<'scope> {
|
||||
pub image: Cow<'scope, str>,
|
||||
|
||||
#[builder(into)]
|
||||
pub containerfile: Cow<'scope, Path>,
|
||||
pub image: &'scope str,
|
||||
pub containerfile: &'scope Path,
|
||||
|
||||
pub platform: Option<Platform>,
|
||||
pub version: Cow<'scope, str>,
|
||||
pub name: Cow<'scope, str>,
|
||||
pub description: Cow<'scope, str>,
|
||||
pub base_digest: Cow<'scope, str>,
|
||||
pub base_image: Cow<'scope, str>,
|
||||
pub repo: Cow<'scope, str>,
|
||||
pub version: &'scope str,
|
||||
pub name: &'scope str,
|
||||
pub description: &'scope str,
|
||||
pub base_digest: &'scope str,
|
||||
pub base_image: &'scope str,
|
||||
pub repo: &'scope str,
|
||||
|
||||
/// The list of tags for the image being built.
|
||||
#[builder(default, into)]
|
||||
pub tags: Vec<Cow<'scope, str>>,
|
||||
#[builder(default)]
|
||||
pub tags: &'scope [String],
|
||||
|
||||
/// Enable pushing the image.
|
||||
#[builder(default)]
|
||||
|
|
@ -57,10 +54,10 @@ pub struct RechunkOpts<'scope> {
|
|||
pub cache_to: Option<&'scope Reference>,
|
||||
|
||||
#[builder(default)]
|
||||
pub secrets: HashSet<&'scope Secret>,
|
||||
pub secrets: &'scope [&'scope Secret],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct ContainerOpts<'scope> {
|
||||
pub container_id: &'scope ContainerId,
|
||||
|
||||
|
|
@ -68,16 +65,15 @@ pub struct ContainerOpts<'scope> {
|
|||
pub privileged: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct VolumeOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub volume_id: Cow<'scope, str>,
|
||||
pub volume_id: &'scope str,
|
||||
|
||||
#[builder(default)]
|
||||
pub privileged: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct CopyOciDirOpts<'scope> {
|
||||
pub oci_dir: &'scope OciDir,
|
||||
pub registry: &'scope Reference,
|
||||
|
|
|
|||
|
|
@ -1,26 +1,21 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use bon::Builder;
|
||||
use oci_distribution::Reference;
|
||||
|
||||
use crate::drivers::types::ContainerId;
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct RunOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub image: Cow<'scope, str>,
|
||||
pub image: &'scope str,
|
||||
|
||||
#[builder(default, into)]
|
||||
pub args: Vec<Cow<'scope, str>>,
|
||||
#[builder(default)]
|
||||
pub args: &'scope [String],
|
||||
|
||||
#[builder(default, into)]
|
||||
pub env_vars: Vec<RunOptsEnv<'scope>>,
|
||||
#[builder(default)]
|
||||
pub env_vars: &'scope [RunOptsEnv<'scope>],
|
||||
|
||||
#[builder(default, into)]
|
||||
pub volumes: Vec<RunOptsVolume<'scope>>,
|
||||
|
||||
#[builder(into)]
|
||||
pub user: Option<Cow<'scope, str>>,
|
||||
#[builder(default)]
|
||||
pub volumes: &'scope [RunOptsVolume<'scope>],
|
||||
pub user: Option<&'scope str>,
|
||||
|
||||
#[builder(default)]
|
||||
pub privileged: bool,
|
||||
|
|
@ -32,13 +27,10 @@ pub struct RunOpts<'scope> {
|
|||
pub remove: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct RunOptsVolume<'scope> {
|
||||
#[builder(into)]
|
||||
pub path_or_vol_name: Cow<'scope, str>,
|
||||
|
||||
#[builder(into)]
|
||||
pub container_path: Cow<'scope, str>,
|
||||
pub path_or_vol_name: &'scope str,
|
||||
pub container_path: &'scope str,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
@ -55,13 +47,10 @@ macro_rules! run_volumes {
|
|||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct RunOptsEnv<'scope> {
|
||||
#[builder(into)]
|
||||
pub key: Cow<'scope, str>,
|
||||
|
||||
#[builder(into)]
|
||||
pub value: Cow<'scope, str>,
|
||||
pub key: &'scope str,
|
||||
pub value: &'scope str,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
@ -78,7 +67,7 @@ macro_rules! run_envs {
|
|||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct CreateContainerOpts<'scope> {
|
||||
pub image: &'scope Reference,
|
||||
|
||||
|
|
@ -86,7 +75,7 @@ pub struct CreateContainerOpts<'scope> {
|
|||
pub privileged: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct RemoveContainerOpts<'scope> {
|
||||
pub container_id: &'scope ContainerId,
|
||||
|
||||
|
|
@ -94,7 +83,7 @@ pub struct RemoveContainerOpts<'scope> {
|
|||
pub privileged: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct RemoveImageOpts<'scope> {
|
||||
pub image: &'scope Reference,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
|
@ -12,6 +11,7 @@ use zeroize::{Zeroize, Zeroizing};
|
|||
|
||||
use crate::drivers::types::Platform;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PrivateKey {
|
||||
Env(String),
|
||||
Path(PathBuf),
|
||||
|
|
@ -56,53 +56,42 @@ impl PrivateKeyContents<String> for PrivateKey {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct GenerateKeyPairOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub dir: Option<Cow<'scope, Path>>,
|
||||
pub dir: Option<&'scope Path>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct CheckKeyPairOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub dir: Option<Cow<'scope, Path>>,
|
||||
pub dir: Option<&'scope Path>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct SignOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub image: &'scope Reference,
|
||||
|
||||
#[builder(into)]
|
||||
pub key: Option<Cow<'scope, str>>,
|
||||
|
||||
#[builder(into)]
|
||||
pub dir: Option<Cow<'scope, Path>>,
|
||||
pub key: Option<&'scope PrivateKey>,
|
||||
pub dir: Option<&'scope Path>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum VerifyType<'scope> {
|
||||
File(Cow<'scope, Path>),
|
||||
File(&'scope Path),
|
||||
Keyless {
|
||||
issuer: Cow<'scope, str>,
|
||||
identity: Cow<'scope, str>,
|
||||
issuer: &'scope str,
|
||||
identity: &'scope str,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct VerifyOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub image: &'scope Reference,
|
||||
pub verify_type: VerifyType<'scope>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[derive(Debug, Clone, Copy, Builder)]
|
||||
pub struct SignVerifyOpts<'scope> {
|
||||
#[builder(into)]
|
||||
pub image: &'scope Reference,
|
||||
|
||||
#[builder(into)]
|
||||
pub dir: Option<Cow<'scope, Path>>,
|
||||
pub dir: Option<&'scope Path>,
|
||||
|
||||
/// Enable retry logic for pushing.
|
||||
#[builder(default)]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Not,
|
||||
path::Path,
|
||||
process::{Command, ExitStatus},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use blue_build_utils::{
|
||||
constants::SUDO_ASKPASS, credentials::Credentials, has_env_var, running_as_root,
|
||||
secret::SecretArgs, semver::Version,
|
||||
constants::USER, credentials::Credentials, get_env_var, secret::SecretArgs, semver::Version,
|
||||
sudo_cmd,
|
||||
};
|
||||
use cached::proc_macro::cached;
|
||||
use colored::Colorize;
|
||||
|
|
@ -21,7 +22,10 @@ use tempfile::TempDir;
|
|||
|
||||
use super::{
|
||||
ContainerMountDriver, RechunkDriver,
|
||||
opts::{CreateContainerOpts, RemoveContainerOpts, RemoveImageOpts},
|
||||
opts::{
|
||||
ContainerOpts, CreateContainerOpts, PruneOpts, RemoveContainerOpts, RemoveImageOpts,
|
||||
VolumeOpts,
|
||||
},
|
||||
types::{ContainerId, MountId},
|
||||
};
|
||||
use crate::{
|
||||
|
|
@ -108,6 +112,42 @@ struct PodmanVersionJson {
|
|||
#[derive(Debug)]
|
||||
pub struct PodmanDriver;
|
||||
|
||||
impl PodmanDriver {
|
||||
/// Copy an image from the user container
|
||||
/// store to the root container store for
|
||||
/// booting off of.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the image can't be copied.
|
||||
pub fn copy_image_to_root_store(image: &Reference) -> Result<()> {
|
||||
let image = image.whole();
|
||||
let status = {
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
"podman",
|
||||
"image",
|
||||
"scp",
|
||||
format!("{}@localhost::{image}", get_env_var(USER)?),
|
||||
"root@localhost::"
|
||||
);
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.build_status(&image, "Copying image to root container store")
|
||||
// .status()
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success().not() {
|
||||
bail!(
|
||||
"Failed to copy image {} to root container store",
|
||||
image.bold()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DriverVersion for PodmanDriver {
|
||||
// First podman version to use buildah v1.24
|
||||
// https://github.com/containers/podman/blob/main/RELEASE_NOTES.md#400
|
||||
|
|
@ -134,29 +174,17 @@ impl DriverVersion for PodmanDriver {
|
|||
}
|
||||
|
||||
impl BuildDriver for PodmanDriver {
|
||||
fn build(opts: &BuildOpts) -> Result<()> {
|
||||
fn build(opts: BuildOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::build({opts:#?})");
|
||||
|
||||
let temp_dir = TempDir::new()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to create temporary directory for secrets")?;
|
||||
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
let command = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => [
|
||||
"--preserve-env",
|
||||
let command = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
],
|
||||
"build",
|
||||
if let Some(platform) = opts.platform => [
|
||||
"--platform",
|
||||
|
|
@ -182,7 +210,7 @@ impl BuildDriver for PodmanDriver {
|
|||
if opts.host_network => "--net=host",
|
||||
format!("--layers={}", !opts.squash),
|
||||
"-f",
|
||||
&*opts.containerfile,
|
||||
opts.containerfile,
|
||||
"-t",
|
||||
opts.image.to_string(),
|
||||
for opts.secrets.args(&temp_dir)?,
|
||||
|
|
@ -203,24 +231,15 @@ impl BuildDriver for PodmanDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn tag(opts: &TagOpts) -> Result<()> {
|
||||
fn tag(opts: TagOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::tag({opts:#?})");
|
||||
|
||||
let dest_image_str = opts.dest_image.to_string();
|
||||
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
let mut command = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let mut command = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"tag",
|
||||
opts.src_image.to_string(),
|
||||
&dest_image_str
|
||||
|
|
@ -237,24 +256,15 @@ impl BuildDriver for PodmanDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn push(opts: &PushOpts) -> Result<()> {
|
||||
fn push(opts: PushOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::push({opts:#?})");
|
||||
|
||||
let image_str = opts.image.to_string();
|
||||
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
let command = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let command = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"push",
|
||||
format!(
|
||||
"--compression-format={}",
|
||||
|
|
@ -312,7 +322,7 @@ impl BuildDriver for PodmanDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
||||
fn prune(opts: PruneOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::prune({opts:?})");
|
||||
|
||||
let status = {
|
||||
|
|
@ -339,7 +349,7 @@ impl BuildDriver for PodmanDriver {
|
|||
}
|
||||
|
||||
impl InspectDriver for PodmanDriver {
|
||||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
get_metadata_cache(opts)
|
||||
}
|
||||
}
|
||||
|
|
@ -350,7 +360,7 @@ impl InspectDriver for PodmanDriver {
|
|||
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
||||
sync_writes = "by_key"
|
||||
)]
|
||||
fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
fn get_metadata_cache(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
trace!("PodmanDriver::get_metadata({opts:#?})");
|
||||
|
||||
let image_str = opts.image.to_string();
|
||||
|
|
@ -409,21 +419,12 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
|||
}
|
||||
|
||||
impl ContainerMountDriver for PodmanDriver {
|
||||
fn mount_container(opts: &super::opts::ContainerOpts) -> Result<MountId> {
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
fn mount_container(opts: ContainerOpts) -> Result<MountId> {
|
||||
let output = {
|
||||
let c = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"mount",
|
||||
opts.container_id,
|
||||
);
|
||||
|
|
@ -442,21 +443,12 @@ impl ContainerMountDriver for PodmanDriver {
|
|||
))
|
||||
}
|
||||
|
||||
fn unmount_container(opts: &super::opts::ContainerOpts) -> Result<()> {
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
fn unmount_container(opts: ContainerOpts) -> Result<()> {
|
||||
let output = {
|
||||
let c = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"unmount",
|
||||
opts.container_id
|
||||
);
|
||||
|
|
@ -473,24 +465,15 @@ impl ContainerMountDriver for PodmanDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_volume(opts: &super::opts::VolumeOpts) -> Result<()> {
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
fn remove_volume(opts: VolumeOpts) -> Result<()> {
|
||||
let output = {
|
||||
let c = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"volume",
|
||||
"rm",
|
||||
&*opts.volume_id
|
||||
opts.volume_id
|
||||
);
|
||||
trace!("{c:?}");
|
||||
c
|
||||
|
|
@ -509,7 +492,7 @@ impl ContainerMountDriver for PodmanDriver {
|
|||
impl RechunkDriver for PodmanDriver {}
|
||||
|
||||
impl RunDriver for PodmanDriver {
|
||||
fn run(opts: &RunOpts) -> Result<ExitStatus> {
|
||||
fn run(opts: RunOpts) -> Result<ExitStatus> {
|
||||
trace!("PodmanDriver::run({opts:#?})");
|
||||
|
||||
let cid_path = TempDir::new().into_diagnostic()?;
|
||||
|
|
@ -520,7 +503,7 @@ impl RunDriver for PodmanDriver {
|
|||
add_cid(&cid);
|
||||
|
||||
let status = podman_run(opts, &cid_file)
|
||||
.build_status(&*opts.image, "Running container")
|
||||
.build_status(opts.image, "Running container")
|
||||
.into_diagnostic()?;
|
||||
|
||||
remove_cid(&cid);
|
||||
|
|
@ -528,7 +511,7 @@ impl RunDriver for PodmanDriver {
|
|||
Ok(status)
|
||||
}
|
||||
|
||||
fn run_output(opts: &RunOpts) -> Result<std::process::Output> {
|
||||
fn run_output(opts: RunOpts) -> Result<std::process::Output> {
|
||||
trace!("PodmanDriver::run_output({opts:#?})");
|
||||
|
||||
let cid_path = TempDir::new().into_diagnostic()?;
|
||||
|
|
@ -545,23 +528,14 @@ impl RunDriver for PodmanDriver {
|
|||
Ok(output)
|
||||
}
|
||||
|
||||
fn create_container(opts: &CreateContainerOpts) -> Result<ContainerId> {
|
||||
fn create_container(opts: CreateContainerOpts) -> Result<ContainerId> {
|
||||
trace!("PodmanDriver::create_container({opts:?})");
|
||||
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
let output = {
|
||||
let c = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"create",
|
||||
opts.image.to_string(),
|
||||
"bash"
|
||||
|
|
@ -581,23 +555,14 @@ impl RunDriver for PodmanDriver {
|
|||
))
|
||||
}
|
||||
|
||||
fn remove_container(opts: &RemoveContainerOpts) -> Result<()> {
|
||||
fn remove_container(opts: RemoveContainerOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::remove_container({opts:?})");
|
||||
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
let output = {
|
||||
let c = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"rm",
|
||||
opts.container_id,
|
||||
);
|
||||
|
|
@ -614,23 +579,14 @@ impl RunDriver for PodmanDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
|
||||
fn remove_image(opts: RemoveImageOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::remove_image({opts:?})");
|
||||
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
let output = {
|
||||
let c = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"rmi",
|
||||
opts.image.to_string()
|
||||
);
|
||||
|
|
@ -656,20 +612,11 @@ impl RunDriver for PodmanDriver {
|
|||
|
||||
trace!("PodmanDriver::list_images({privileged})");
|
||||
|
||||
let use_sudo = privileged && !running_as_root();
|
||||
let output = {
|
||||
let c = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
let c = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = privileged,
|
||||
"podman",
|
||||
"images",
|
||||
"--format",
|
||||
"json"
|
||||
|
|
@ -698,20 +645,11 @@ impl RunDriver for PodmanDriver {
|
|||
}
|
||||
}
|
||||
|
||||
fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
|
||||
let use_sudo = opts.privileged && !running_as_root();
|
||||
let command = cmd!(
|
||||
if use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
"podman"
|
||||
},
|
||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
SUDO_PROMPT,
|
||||
],
|
||||
if use_sudo => "podman",
|
||||
fn podman_run(opts: RunOpts, cid_file: &Path) -> Command {
|
||||
let command = sudo_cmd!(
|
||||
prompt = SUDO_PROMPT,
|
||||
sudo_check = opts.privileged,
|
||||
"podman",
|
||||
"run",
|
||||
format!("--cidfile={}", cid_file.display()),
|
||||
if opts.privileged => [
|
||||
|
|
@ -729,7 +667,7 @@ fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
|
|||
"--env",
|
||||
format!("{key}={value}"),
|
||||
],
|
||||
&*opts.image,
|
||||
opts.image,
|
||||
for arg in opts.args.iter() => &**arg,
|
||||
);
|
||||
trace!("{command:?}");
|
||||
|
|
|
|||
88
process/drivers/rpm_ostree_driver.rs
Normal file
88
process/drivers/rpm_ostree_driver.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use std::ops::Not;
|
||||
|
||||
use blue_build_utils::constants::OSTREE_UNVERIFIED_IMAGE;
|
||||
use comlexr::cmd;
|
||||
use log::trace;
|
||||
use miette::{Context, IntoDiagnostic, bail};
|
||||
|
||||
use crate::logging::CommandLogging;
|
||||
|
||||
use super::{BootDriver, BootStatus, opts::SwitchOpts};
|
||||
|
||||
mod status;
|
||||
|
||||
pub use status::*;
|
||||
|
||||
pub struct RpmOstreeDriver;
|
||||
|
||||
impl BootDriver for RpmOstreeDriver {
|
||||
fn status() -> miette::Result<Box<dyn BootStatus>> {
|
||||
let output = {
|
||||
let c = cmd!("rpm-ostree", "status", "--json");
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.output()
|
||||
.into_diagnostic()?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("Failed to get `rpm-ostree` status!");
|
||||
}
|
||||
|
||||
trace!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
|
||||
Ok(Box::new(
|
||||
serde_json::from_slice::<Status>(&output.stdout)
|
||||
.into_diagnostic()
|
||||
.wrap_err_with(|| {
|
||||
format!(
|
||||
"Failed to deserialize rpm-ostree status:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
})?,
|
||||
))
|
||||
}
|
||||
|
||||
fn switch(opts: SwitchOpts) -> miette::Result<()> {
|
||||
let status = {
|
||||
let c = cmd!(
|
||||
"rpm-ostree",
|
||||
"rebase",
|
||||
format!("{OSTREE_UNVERIFIED_IMAGE}:containers-storage:{}", opts.image),
|
||||
if opts.reboot => "--reboot",
|
||||
);
|
||||
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.build_status(format!("{}", opts.image), "Switching to new image")
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success().not() {
|
||||
bail!("Failed to switch to image {}", opts.image);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade(opts: SwitchOpts) -> miette::Result<()> {
|
||||
let status = {
|
||||
let c = cmd!(
|
||||
"rpm-ostree",
|
||||
"upgrade",
|
||||
if opts.reboot => "--reboot",
|
||||
);
|
||||
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.build_status(format!("{}", opts.image), "Switching to new image")
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success().not() {
|
||||
bail!("Failed to switch to image {}", opts.image);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
173
process/drivers/rpm_ostree_driver/status.rs
Normal file
173
process/drivers/rpm_ostree_driver/status.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
use image_ref::DeploymentImageRef;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::drivers::{BootStatus, types::ImageRef};
|
||||
|
||||
mod image_ref;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Status {
|
||||
deployments: Vec<Deployment>,
|
||||
transactions: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl BootStatus for Status {
|
||||
/// Checks if there is a transaction in progress.
|
||||
fn transaction_in_progress(&self) -> bool {
|
||||
self.transactions.as_ref().is_some_and(|tr| !tr.is_empty())
|
||||
}
|
||||
|
||||
/// Get the booted image's reference.
|
||||
fn booted_image(&self) -> Option<ImageRef<'_>> {
|
||||
(&self
|
||||
.deployments
|
||||
.iter()
|
||||
.find(|deployment| deployment.booted)?
|
||||
.container_image_reference)
|
||||
.try_into()
|
||||
.inspect_err(|e| {
|
||||
log::warn!("{e}");
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Get the booted image's reference.
|
||||
fn staged_image(&self) -> Option<ImageRef<'_>> {
|
||||
(&self
|
||||
.deployments
|
||||
.iter()
|
||||
.find(|deployment| deployment.staged)?
|
||||
.container_image_reference)
|
||||
.try_into()
|
||||
.inspect_err(|e| {
|
||||
log::warn!("{e}");
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct Deployment {
|
||||
container_image_reference: DeploymentImageRef,
|
||||
booted: bool,
|
||||
staged: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use blue_build_utils::constants::{
|
||||
ARCHIVE_SUFFIX, LOCAL_BUILD, OCI_ARCHIVE, OSTREE_IMAGE_SIGNED, OSTREE_UNVERIFIED_IMAGE,
|
||||
};
|
||||
|
||||
use crate::drivers::{BootStatus, types::ImageRef};
|
||||
|
||||
use super::{Deployment, Status};
|
||||
|
||||
fn create_image_status() -> Status {
|
||||
Status {
|
||||
deployments: vec![
|
||||
Deployment {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test"
|
||||
)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
booted: true,
|
||||
staged: false,
|
||||
},
|
||||
Deployment {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test:last"
|
||||
)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
booted: false,
|
||||
staged: false,
|
||||
},
|
||||
],
|
||||
transactions: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_transaction_status() -> Status {
|
||||
Status {
|
||||
deployments: vec![
|
||||
Deployment {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test"
|
||||
)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
booted: true,
|
||||
staged: false,
|
||||
},
|
||||
Deployment {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test:last"
|
||||
)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
booted: false,
|
||||
staged: false,
|
||||
},
|
||||
],
|
||||
transactions: Some(bon::vec!["Upgrade", "/"]),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_archive_staged_status() -> Status {
|
||||
Status {
|
||||
deployments: vec![
|
||||
Deployment {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_UNVERIFIED_IMAGE}:{OCI_ARCHIVE}:{LOCAL_BUILD}/cli_test.{ARCHIVE_SUFFIX}"
|
||||
).try_into().unwrap(),
|
||||
booted: false,
|
||||
staged: true,
|
||||
},
|
||||
Deployment {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_UNVERIFIED_IMAGE}:{OCI_ARCHIVE}:{LOCAL_BUILD}/cli_test.{ARCHIVE_SUFFIX}"
|
||||
).try_into().unwrap(),
|
||||
booted: true,
|
||||
staged: false,
|
||||
},
|
||||
Deployment {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test:last"
|
||||
).try_into().unwrap(),
|
||||
booted: false,
|
||||
staged: false,
|
||||
},
|
||||
],
|
||||
transactions: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_booted_image() {
|
||||
assert!(matches!(
|
||||
create_image_status()
|
||||
.booted_image()
|
||||
.expect("Contains image"),
|
||||
ImageRef::Remote(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_staged_image() {
|
||||
assert!(matches!(
|
||||
create_archive_staged_status()
|
||||
.staged_image()
|
||||
.expect("Contains image"),
|
||||
ImageRef::LocalTar(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_in_progress() {
|
||||
assert!(create_transaction_status().transaction_in_progress());
|
||||
assert!(!create_image_status().transaction_in_progress());
|
||||
}
|
||||
}
|
||||
738
process/drivers/rpm_ostree_driver/status/image_ref.rs
Normal file
738
process/drivers/rpm_ostree_driver/status/image_ref.rs
Normal file
|
|
@ -0,0 +1,738 @@
|
|||
use std::{ops::Not, path::PathBuf, str::FromStr};
|
||||
|
||||
use blue_build_utils::impl_de_fromstr;
|
||||
use lazy_regex::{regex_if, regex_switch};
|
||||
use miette::{IntoDiagnostic, bail};
|
||||
use oci_distribution::Reference;
|
||||
|
||||
use crate::drivers::types::ImageRef;
|
||||
|
||||
impl_de_fromstr!(
|
||||
DeploymentImageRef,
|
||||
ImageTransport,
|
||||
RefIndex,
|
||||
DockerDaemon,
|
||||
DigestAlgorithm,
|
||||
StorageSpecifier,
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DeploymentImageRef {
|
||||
UnverifiedImage(ImageTransport),
|
||||
UnverifiedRegistry(Reference),
|
||||
RemoteImage {
|
||||
remote: String,
|
||||
reference: Reference,
|
||||
},
|
||||
RemoteRegistry {
|
||||
remote: String,
|
||||
reference: Reference,
|
||||
},
|
||||
ImageSigned(ImageTransport),
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a DeploymentImageRef> for ImageRef<'a> {
|
||||
type Error = miette::Error;
|
||||
|
||||
fn try_from(value: &'a DeploymentImageRef) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
DeploymentImageRef::UnverifiedImage(
|
||||
ImageTransport::Registry(reference)
|
||||
| ImageTransport::Docker(reference)
|
||||
| ImageTransport::DockerDaemon(DockerDaemon::Reference(reference))
|
||||
| ImageTransport::ContainersStorage {
|
||||
storage_specifier: _,
|
||||
reference,
|
||||
},
|
||||
)
|
||||
| DeploymentImageRef::ImageSigned(
|
||||
ImageTransport::Registry(reference)
|
||||
| ImageTransport::Docker(reference)
|
||||
| ImageTransport::DockerDaemon(DockerDaemon::Reference(reference))
|
||||
| ImageTransport::ContainersStorage {
|
||||
storage_specifier: _,
|
||||
reference,
|
||||
},
|
||||
) => Self::Remote(std::borrow::Cow::Borrowed(reference)),
|
||||
DeploymentImageRef::UnverifiedRegistry(reference) => {
|
||||
Self::Remote(std::borrow::Cow::Borrowed(reference))
|
||||
}
|
||||
DeploymentImageRef::UnverifiedImage(ImageTransport::OciArchive {
|
||||
path,
|
||||
reference: _,
|
||||
})
|
||||
| DeploymentImageRef::ImageSigned(ImageTransport::OciArchive { path, reference: _ }) => {
|
||||
Self::LocalTar(std::borrow::Cow::Borrowed(path))
|
||||
}
|
||||
_ => bail!("Failed to convert {value} into an image ref"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DeploymentImageRef {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::UnverifiedImage(transport) => format!("ostree-unverified-image:{transport}"),
|
||||
Self::UnverifiedRegistry(reference) =>
|
||||
format!("ostree-unverified-registry:{reference}"),
|
||||
Self::RemoteImage { remote, reference } =>
|
||||
format!("ostree-remote-image:{remote}:registry:{reference}"),
|
||||
Self::RemoteRegistry { remote, reference } =>
|
||||
format!("ostree-remote-registry:{remote}:{reference}"),
|
||||
Self::ImageSigned(transport) => format!("ostree-image-signed:{transport}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DeploymentImageRef {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
regex_switch!(
|
||||
s,
|
||||
r"ostree-unverified-image:(?<reference>.*)" => {
|
||||
Self::UnverifiedImage(reference.try_into()?)
|
||||
}
|
||||
r"ostree-unverified-registry:(?<reference>.*)" => {
|
||||
Self::UnverifiedRegistry(reference.try_into().into_diagnostic()?)
|
||||
}
|
||||
r"ostree-remote-image:(?<remote>[^:]+):registry:(?<reference>.*)" => {
|
||||
Self::RemoteImage {
|
||||
remote: remote.into(),
|
||||
reference: reference.try_into().into_diagnostic()?,
|
||||
}
|
||||
}
|
||||
r"ostree-remote-registry:(?<remote>[^:]+):(?<reference>.*)" => {
|
||||
Self::RemoteRegistry {
|
||||
remote: remote.into(),
|
||||
reference: reference.try_into().into_diagnostic()?,
|
||||
}
|
||||
}
|
||||
r"ostree-image-signed:(?<transport>.*)" => {
|
||||
Self::ImageSigned(transport.try_into()?)
|
||||
}
|
||||
)
|
||||
.ok_or_else(|| miette::miette!("Failed to parse '{s}' as an image transport"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ImageTransport {
|
||||
Registry(Reference),
|
||||
Docker(Reference),
|
||||
DockerArchive {
|
||||
path: PathBuf,
|
||||
ref_index: Option<RefIndex>,
|
||||
},
|
||||
DockerDaemon(DockerDaemon),
|
||||
Dir(PathBuf),
|
||||
Oci {
|
||||
path: PathBuf,
|
||||
ref_index: Option<RefIndex>,
|
||||
},
|
||||
OciArchive {
|
||||
path: PathBuf,
|
||||
reference: Option<Reference>,
|
||||
},
|
||||
ContainersStorage {
|
||||
storage_specifier: Option<StorageSpecifier>,
|
||||
reference: Reference,
|
||||
},
|
||||
Ostree {
|
||||
reference: Reference,
|
||||
repo_path: Option<PathBuf>,
|
||||
},
|
||||
Sif(PathBuf),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ImageTransport {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Registry(reference) => format!("registry:{reference}"),
|
||||
Self::Docker(reference) => format!("docker://{reference}"),
|
||||
Self::DockerArchive {
|
||||
path,
|
||||
ref_index: None,
|
||||
} => format!("docker-archive:{}", path.display()),
|
||||
Self::DockerArchive {
|
||||
path,
|
||||
ref_index: Some(ref_index),
|
||||
} => format!("docker-archive:{}:{ref_index}", path.display()),
|
||||
Self::DockerDaemon(daemon) => format!("docker-daemon:{daemon}"),
|
||||
Self::Dir(path) => format!("dir:{}", path.display()),
|
||||
Self::Oci {
|
||||
path,
|
||||
ref_index: None,
|
||||
} => format!("oci:{}", path.display()),
|
||||
Self::Oci {
|
||||
path,
|
||||
ref_index: Some(ref_index),
|
||||
} => format!("oci:{}:{ref_index}", path.display()),
|
||||
Self::OciArchive {
|
||||
path,
|
||||
reference: None,
|
||||
} => format!("oci-archive:{}", path.display()),
|
||||
Self::OciArchive {
|
||||
path,
|
||||
reference: Some(reference),
|
||||
} => format!("oci-archive:{}:{reference}", path.display()),
|
||||
Self::ContainersStorage {
|
||||
storage_specifier: None,
|
||||
reference,
|
||||
} => format!("containers-storage:{reference}"),
|
||||
Self::ContainersStorage {
|
||||
storage_specifier: Some(storage_specifier),
|
||||
reference,
|
||||
} => format!("containers-storage:[{storage_specifier}]{reference}"),
|
||||
Self::Ostree {
|
||||
reference,
|
||||
repo_path: None,
|
||||
} => format!("ostree:{reference}"),
|
||||
Self::Ostree {
|
||||
reference,
|
||||
repo_path: Some(repo_path),
|
||||
} => format!("ostree:{reference}@{}", repo_path.display()),
|
||||
Self::Sif(path) => format!("sif:{}", path.display()),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ImageTransport {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
regex_switch!(
|
||||
s,
|
||||
r"registry:(?<reference>.*)" => {
|
||||
Self::Registry(reference.try_into().into_diagnostic()?)
|
||||
}
|
||||
r"docker://(?<reference>.*)" => {
|
||||
Self::Docker(reference.try_into().into_diagnostic()?)
|
||||
}
|
||||
r"docker-archive:(?<path>[^:]+)(?::(?<ref_index>.*))?" => {
|
||||
let ref_index = if ref_index.is_empty().not() {
|
||||
Some(ref_index.try_into()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self::DockerArchive { path: path.into(), ref_index }
|
||||
}
|
||||
r"docker-daemon:(?<reference>.*)" => {
|
||||
Self::DockerDaemon(reference.try_into()?)
|
||||
}
|
||||
r"dir:(?<path>.*)" => {
|
||||
Self::Dir(path.into())
|
||||
}
|
||||
r"oci:(?<path>[^:]+)(?::(?<ref_index>.*))?" => {
|
||||
let ref_index = if ref_index.is_empty().not() {
|
||||
Some(ref_index.try_into()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self::Oci { path: path.into(), ref_index }
|
||||
}
|
||||
r"oci-archive:(?<path>[^:]+)(?::(?<reference>.*))?" => {
|
||||
let reference = if reference.is_empty().not() {
|
||||
Some(reference.try_into().into_diagnostic()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self::OciArchive { path: path.into(), reference }
|
||||
}
|
||||
r"containers-storage:(?:\[(?<storage_specifier>.*)\])?(?<reference>.*)" => {
|
||||
let storage_specifier = if storage_specifier.is_empty().not() {
|
||||
Some(storage_specifier.try_into()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self::ContainersStorage { storage_specifier, reference: reference.parse().into_diagnostic()? }
|
||||
}
|
||||
r"ostree:(?<reference>[^@]+)(?:@(?<repo_path>.*))?" => {
|
||||
let repo_path = if repo_path.is_empty().not() {
|
||||
Some(repo_path.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self::Ostree { reference: reference.parse().into_diagnostic()?, repo_path }
|
||||
}
|
||||
r"sif:(?<path>.*)" => {
|
||||
Self::Sif(path.into())
|
||||
}
|
||||
)
|
||||
.ok_or_else(|| miette::miette!("Failed to parse '{s}' as an image transport"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RefIndex {
|
||||
Reference(Reference),
|
||||
Index(usize),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RefIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Reference(reference) => format!("{reference}"),
|
||||
Self::Index(index) => format!("{index}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RefIndex {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match (Reference::try_from(s), s.parse::<usize>()) {
|
||||
(_, Ok(index)) => Self::Index(index),
|
||||
(Ok(reference), _) => Self::Reference(reference),
|
||||
_ => bail!("Failed to parse '{s}' into a reference or index"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DockerDaemon {
|
||||
Reference(Reference),
|
||||
Algo {
|
||||
algo: DigestAlgorithm,
|
||||
digest: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DockerDaemon {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Reference(reference) => format!("{reference}"),
|
||||
Self::Algo { algo, digest } => format!(
|
||||
"{}:{digest}",
|
||||
match algo {
|
||||
DigestAlgorithm::Sha256 => "sha256",
|
||||
DigestAlgorithm::Sha384 => "sha384",
|
||||
DigestAlgorithm::Sha512 => "sha512",
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DockerDaemon {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(
|
||||
match (
|
||||
s.split_once(':').map(|(algo, digest)| {
|
||||
(
|
||||
DigestAlgorithm::try_from(algo),
|
||||
regex_if!(r"[a-f0-9]+", digest, digest),
|
||||
)
|
||||
}),
|
||||
Reference::try_from(s),
|
||||
) {
|
||||
(Some((Ok(algo), Some(digest))), _) => Self::Algo {
|
||||
algo,
|
||||
digest: digest.into(),
|
||||
},
|
||||
(_, Ok(reference)) => Self::Reference(reference),
|
||||
_ => bail!("Failed to parse '{s}' as a docker daemon reference"),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DigestAlgorithm {
|
||||
Sha256,
|
||||
Sha384,
|
||||
Sha512,
|
||||
}
|
||||
|
||||
impl FromStr for DigestAlgorithm {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"sha256" => Self::Sha256,
|
||||
"sha384" => Self::Sha384,
|
||||
"sha512" => Self::Sha512,
|
||||
_ => bail!("Failed to parse '{s}' as a digest algorithm"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DigestAlgorithm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Sha256 => "sha256",
|
||||
Self::Sha384 => "sha384",
|
||||
Self::Sha512 => "sha512",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StorageSpecifier {
|
||||
driver: Option<String>,
|
||||
root: PathBuf,
|
||||
run_root: Option<PathBuf>,
|
||||
options: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for StorageSpecifier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{driver}{root}{run_root}{options}",
|
||||
driver = self
|
||||
.driver
|
||||
.as_ref()
|
||||
.map(|d| format!("{d}@"))
|
||||
.unwrap_or_default(),
|
||||
root = self.root.display(),
|
||||
run_root = self
|
||||
.run_root
|
||||
.as_ref()
|
||||
.map(|r| format!("+{}", r.display()))
|
||||
.unwrap_or_default(),
|
||||
options = self
|
||||
.options
|
||||
.as_ref()
|
||||
.map(|o| format!(":{o}"))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for StorageSpecifier {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
regex_if!(
|
||||
r"(?:(?<driver>[\w-]+)@)?(?<root>[\w\/-]+)(?:\+(?<run_root>[\w\/-]+))?(?:\:(?<options>[\w,=-]+))?",
|
||||
s,
|
||||
{
|
||||
Self {
|
||||
driver: driver.is_empty().not().then(|| driver.into()),
|
||||
root: root.into(),
|
||||
run_root: run_root.is_empty().not().then(|| run_root.into()),
|
||||
options: options.is_empty().not().then(|| options.into()),
|
||||
}
|
||||
}
|
||||
)
|
||||
.ok_or_else(|| miette::miette!("Failed to parse storage specifier"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use oci_distribution::Reference;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
macro_rules! test_parse {
|
||||
($($test:ident {
|
||||
typ: $typ:ty,
|
||||
value: $val:literal,
|
||||
variant: $var:pat$(,)?
|
||||
}),* $(,)?) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $test() {
|
||||
let transport = <$typ>::try_from($val).unwrap();
|
||||
assert!(
|
||||
matches!(
|
||||
&transport,
|
||||
$var
|
||||
)
|
||||
);
|
||||
assert_eq!($val, transport.to_string().as_str());
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
test_parse!(
|
||||
parse_image_transport_registry {
|
||||
typ: ImageTransport,
|
||||
value: "registry:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::Registry(_),
|
||||
},
|
||||
parse_image_transport_docker {
|
||||
typ: ImageTransport,
|
||||
value: "docker://ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::Docker(_),
|
||||
},
|
||||
parse_image_transport_docker_archive {
|
||||
typ: ImageTransport,
|
||||
value: "docker-archive:/test/path",
|
||||
variant: ImageTransport::DockerArchive {
|
||||
path: _,
|
||||
ref_index: None,
|
||||
}
|
||||
},
|
||||
parse_image_transport_docker_archive_index {
|
||||
typ: ImageTransport,
|
||||
value: "docker-archive:/test/path:42",
|
||||
variant: ImageTransport::DockerArchive {
|
||||
path: _,
|
||||
ref_index: Some(RefIndex::Index(_)),
|
||||
}
|
||||
},
|
||||
parse_image_transport_docker_archive_ref {
|
||||
typ: ImageTransport,
|
||||
value: "docker-archive:/test/path:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::DockerArchive {
|
||||
path: _,
|
||||
ref_index: Some(RefIndex::Reference(_)),
|
||||
}
|
||||
},
|
||||
parse_image_transport_docker_daemon_ref {
|
||||
typ: ImageTransport,
|
||||
value: "docker-daemon:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::DockerDaemon(DockerDaemon::Reference(_)),
|
||||
},
|
||||
parse_image_transport_docker_daemon_digest {
|
||||
typ: ImageTransport,
|
||||
value: "docker-daemon:sha256:e6cbc801b77c4cfe164f08b6b29de7e588f6d98e8ac0c52c0de0a9ae45f717ab",
|
||||
variant: ImageTransport::DockerDaemon(DockerDaemon::Algo {
|
||||
algo: DigestAlgorithm::Sha256,
|
||||
digest: _,
|
||||
}),
|
||||
},
|
||||
parse_image_transport_dir {
|
||||
typ: ImageTransport,
|
||||
value: "dir:/test/path",
|
||||
variant: ImageTransport::Dir(_),
|
||||
},
|
||||
parse_image_transport_oci {
|
||||
typ: ImageTransport,
|
||||
value: "oci:/test/path",
|
||||
variant: ImageTransport::Oci {
|
||||
path: _,
|
||||
ref_index: None
|
||||
}
|
||||
},
|
||||
parse_image_transport_oci_ref {
|
||||
typ: ImageTransport,
|
||||
value: "oci:/test/path:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::Oci {
|
||||
path: _,
|
||||
ref_index: Some(RefIndex::Reference(_)),
|
||||
}
|
||||
},
|
||||
parse_image_transport_oci_ref_index {
|
||||
typ: ImageTransport,
|
||||
value: "oci:/test/path:42",
|
||||
variant: ImageTransport::Oci {
|
||||
path: _,
|
||||
ref_index: Some(RefIndex::Index(_))
|
||||
}
|
||||
},
|
||||
parse_image_transport_oci_archive {
|
||||
typ: ImageTransport,
|
||||
value: "oci-archive:/test/path",
|
||||
variant: ImageTransport::OciArchive {
|
||||
path: _,
|
||||
reference: None
|
||||
}
|
||||
},
|
||||
parse_image_transport_oci_archive_ref {
|
||||
typ: ImageTransport,
|
||||
value: "oci-archive:/test/path:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::OciArchive {
|
||||
path: _,
|
||||
reference: Some(_)
|
||||
}
|
||||
},
|
||||
parse_image_transport_containers_storage {
|
||||
typ: ImageTransport,
|
||||
value: "containers-storage:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::ContainersStorage {
|
||||
storage_specifier: None,
|
||||
reference: _
|
||||
}
|
||||
},
|
||||
parse_image_transport_containers_storage_specifier {
|
||||
typ: ImageTransport,
|
||||
value: "containers-storage:[overlayfs@/test/path]ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::ContainersStorage {
|
||||
storage_specifier: Some(StorageSpecifier {
|
||||
driver: Some(_),
|
||||
root: _,
|
||||
run_root: None,
|
||||
options: None
|
||||
}),
|
||||
reference: _
|
||||
}
|
||||
},
|
||||
parse_image_transport_ostree {
|
||||
typ: ImageTransport,
|
||||
value: "ostree:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: ImageTransport::Ostree {
|
||||
reference: _,
|
||||
repo_path: None
|
||||
}
|
||||
},
|
||||
parse_image_transport_ostree_repo_path {
|
||||
typ: ImageTransport,
|
||||
value: "ostree:ghcr.io/ublue-os/main-kinoite:42@/test/path",
|
||||
variant: ImageTransport::Ostree {
|
||||
reference: _,
|
||||
repo_path: Some(_)
|
||||
}
|
||||
},
|
||||
parse_image_transport_sif {
|
||||
typ: ImageTransport,
|
||||
value: "sif:/test/path",
|
||||
variant: ImageTransport::Sif(_),
|
||||
},
|
||||
parse_deployment_image_ref_unverified_image {
|
||||
typ: DeploymentImageRef,
|
||||
value: "ostree-unverified-image:registry:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: DeploymentImageRef::UnverifiedImage(ImageTransport::Registry(_)),
|
||||
},
|
||||
parse_deployment_image_ref_unverified_registry {
|
||||
typ: DeploymentImageRef,
|
||||
value: "ostree-unverified-registry:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: DeploymentImageRef::UnverifiedRegistry(_),
|
||||
},
|
||||
parse_deployment_image_ref_remote_image {
|
||||
typ: DeploymentImageRef,
|
||||
value: "ostree-remote-image:origin:registry:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: DeploymentImageRef::RemoteImage {
|
||||
remote: _,
|
||||
reference: _
|
||||
}
|
||||
},
|
||||
parse_deployment_image_ref_remote_registry {
|
||||
typ: DeploymentImageRef,
|
||||
value: "ostree-remote-registry:origin:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: DeploymentImageRef::RemoteRegistry {
|
||||
remote: _,
|
||||
reference: _
|
||||
}
|
||||
},
|
||||
parse_deployment_image_ref_image_signed {
|
||||
typ: DeploymentImageRef,
|
||||
value: "ostree-image-signed:registry:ghcr.io/ublue-os/main-kinoite:42",
|
||||
variant: DeploymentImageRef::ImageSigned(ImageTransport::Registry(_)),
|
||||
}
|
||||
);
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
"ghcr.io/ublue-os/main-kinoite:42",
|
||||
Some("ghcr.io/ublue-os/main-kinoite:42".try_into().unwrap()),
|
||||
None
|
||||
)]
|
||||
#[case(
|
||||
"sha256:e6cbc801b77c4cfe164f08b6b29de7e588f6d98e8ac0c52c0de0a9ae45f717ab",
|
||||
None,
|
||||
Some((
|
||||
"sha256",
|
||||
"e6cbc801b77c4cfe164f08b6b29de7e588f6d98e8ac0c52c0de0a9ae45f717ab",
|
||||
))
|
||||
)]
|
||||
fn parse_docker_daemon(
|
||||
#[case] value: &str,
|
||||
#[case] reference: Option<Reference>,
|
||||
#[case] algo_digest: Option<(&str, &str)>,
|
||||
) {
|
||||
let expected = match (reference, algo_digest) {
|
||||
(Some(reference), None) => DockerDaemon::Reference(reference),
|
||||
(None, Some((algo, digest))) => DockerDaemon::Algo {
|
||||
algo: algo.try_into().unwrap(),
|
||||
digest: digest.into(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert_eq!(DockerDaemon::try_from(value).unwrap(), expected);
|
||||
assert_eq!(value, &expected.to_string());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("/test/path", None, "/test/path", None, None)]
|
||||
#[case("overlayfs@/test/path", Some("overlayfs"), "/test/path", None, None)]
|
||||
#[case(
|
||||
"/test/path+/test/run/path",
|
||||
None,
|
||||
"/test/path",
|
||||
Some("/test/run/path"),
|
||||
None
|
||||
)]
|
||||
#[case(
|
||||
"/test/path:param_1=test,param_2=anotherTest",
|
||||
None,
|
||||
"/test/path",
|
||||
None,
|
||||
Some("param_1=test,param_2=anotherTest")
|
||||
)]
|
||||
#[case(
|
||||
"/test/path+/test/run/path:param_1=test,param_2=anotherTest",
|
||||
None,
|
||||
"/test/path",
|
||||
Some("/test/run/path"),
|
||||
Some("param_1=test,param_2=anotherTest")
|
||||
)]
|
||||
#[case(
|
||||
"overlayfs@/test/path+/test/run/path",
|
||||
Some("overlayfs"),
|
||||
"/test/path",
|
||||
Some("/test/run/path"),
|
||||
None
|
||||
)]
|
||||
#[case(
|
||||
"overlayfs@/test/path:param_1=test,param_2=anotherTest",
|
||||
Some("overlayfs"),
|
||||
"/test/path",
|
||||
None,
|
||||
Some("param_1=test,param_2=anotherTest")
|
||||
)]
|
||||
#[case(
|
||||
"overlayfs@/test/path+/test/run/path:param_1=test,param_2=anotherTest",
|
||||
Some("overlayfs"),
|
||||
"/test/path",
|
||||
Some("/test/run/path"),
|
||||
Some("param_1=test,param_2=anotherTest")
|
||||
)]
|
||||
fn parse_storage_specifier(
|
||||
#[case] value: &str,
|
||||
#[case] driver: Option<&str>,
|
||||
#[case] root: &str,
|
||||
#[case] run_root: Option<&str>,
|
||||
#[case] options: Option<&str>,
|
||||
) {
|
||||
let expected = StorageSpecifier {
|
||||
driver: driver.map(Into::into),
|
||||
root: root.into(),
|
||||
run_root: run_root.map(Into::into),
|
||||
options: options.map(Into::into),
|
||||
};
|
||||
|
||||
assert_eq!(StorageSpecifier::try_from(value).unwrap(), expected);
|
||||
assert_eq!(value, expected.to_string().as_str());
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ use zeroize::Zeroizing;
|
|||
pub struct SigstoreDriver;
|
||||
|
||||
impl SigningDriver for SigstoreDriver {
|
||||
fn generate_key_pair(opts: &GenerateKeyPairOpts) -> miette::Result<()> {
|
||||
fn generate_key_pair(opts: GenerateKeyPairOpts) -> miette::Result<()> {
|
||||
let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
|
||||
let priv_key_path = path.join(COSIGN_PRIV_PATH);
|
||||
let pub_key_path = path.join(COSIGN_PUB_PATH);
|
||||
|
|
@ -70,7 +70,7 @@ impl SigningDriver for SigstoreDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn check_signing_files(opts: &CheckKeyPairOpts) -> miette::Result<()> {
|
||||
fn check_signing_files(opts: CheckKeyPairOpts) -> miette::Result<()> {
|
||||
trace!("SigstoreDriver::check_signing_files({opts:?})");
|
||||
|
||||
let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
|
||||
|
|
@ -105,7 +105,7 @@ impl SigningDriver for SigstoreDriver {
|
|||
}
|
||||
}
|
||||
|
||||
fn sign(opts: &SignOpts) -> miette::Result<()> {
|
||||
fn sign(opts: SignOpts) -> miette::Result<()> {
|
||||
trace!("SigstoreDriver::sign({opts:?})");
|
||||
|
||||
if opts.image.digest().is_none() {
|
||||
|
|
@ -176,7 +176,7 @@ impl SigningDriver for SigstoreDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn verify(opts: &VerifyOpts) -> miette::Result<()> {
|
||||
fn verify(opts: VerifyOpts) -> miette::Result<()> {
|
||||
let mut client = ClientBuilder::default().build().into_diagnostic()?;
|
||||
|
||||
let image_digest: OciReference = opts.image.to_string().parse().into_diagnostic()?;
|
||||
|
|
@ -253,9 +253,10 @@ mod test {
|
|||
fn generate_key_pair() {
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
|
||||
let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
|
||||
|
||||
SigstoreDriver::generate_key_pair(&gen_opts).unwrap();
|
||||
SigstoreDriver::generate_key_pair(
|
||||
GenerateKeyPairOpts::builder().dir(tempdir.path()).build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
eprintln!(
|
||||
"Private key:\n{}",
|
||||
|
|
@ -266,27 +267,27 @@ mod test {
|
|||
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
||||
);
|
||||
|
||||
let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
|
||||
|
||||
SigstoreDriver::check_signing_files(&check_opts).unwrap();
|
||||
SigstoreDriver::check_signing_files(
|
||||
CheckKeyPairOpts::builder().dir(tempdir.path()).build(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_key_pairs() {
|
||||
let path = Path::new("../test-files/keys");
|
||||
|
||||
let opts = CheckKeyPairOpts::builder().dir(path).build();
|
||||
|
||||
SigstoreDriver::check_signing_files(&opts).unwrap();
|
||||
SigstoreDriver::check_signing_files(CheckKeyPairOpts::builder().dir(path).build()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compatibility() {
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
|
||||
let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
|
||||
|
||||
SigstoreDriver::generate_key_pair(&gen_opts).unwrap();
|
||||
SigstoreDriver::generate_key_pair(
|
||||
GenerateKeyPairOpts::builder().dir(tempdir.path()).build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
eprintln!(
|
||||
"Private key:\n{}",
|
||||
|
|
@ -297,8 +298,7 @@ mod test {
|
|||
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
||||
);
|
||||
|
||||
let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
|
||||
|
||||
CosignDriver::check_signing_files(&check_opts).unwrap();
|
||||
CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(tempdir.path()).build())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,17 @@ use miette::{IntoDiagnostic, Result, bail};
|
|||
|
||||
use crate::{drivers::types::Platform, logging::Logger};
|
||||
|
||||
use super::{InspectDriver, opts::GetMetadataOpts, types::ImageMetadata};
|
||||
use super::{
|
||||
InspectDriver,
|
||||
opts::{CopyOciDirOpts, GetMetadataOpts},
|
||||
types::ImageMetadata,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SkopeoDriver;
|
||||
|
||||
impl InspectDriver for SkopeoDriver {
|
||||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
get_metadata_cache(opts)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +30,7 @@ impl InspectDriver for SkopeoDriver {
|
|||
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
||||
sync_writes = "by_key"
|
||||
)]
|
||||
fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
fn get_metadata_cache(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||
trace!("SkopeoDriver::get_metadata({opts:#?})");
|
||||
|
||||
let image_str = opts.image.to_string();
|
||||
|
|
@ -68,7 +72,7 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
|||
}
|
||||
|
||||
impl super::OciCopy for SkopeoDriver {
|
||||
fn copy_oci_dir(opts: &super::opts::CopyOciDirOpts) -> Result<()> {
|
||||
fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()> {
|
||||
use crate::logging::CommandLogging;
|
||||
|
||||
let use_sudo = opts.privileged && !blue_build_utils::running_as_root();
|
||||
|
|
|
|||
|
|
@ -17,22 +17,16 @@ use crate::drivers::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
buildah_driver::BuildahDriver,
|
||||
cosign_driver::CosignDriver,
|
||||
docker_driver::DockerDriver,
|
||||
github_driver::GithubDriver,
|
||||
gitlab_driver::GitlabDriver,
|
||||
local_driver::LocalDriver,
|
||||
opts::{
|
||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, CreateContainerOpts, GenerateImageNameOpts,
|
||||
GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts, RechunkOpts,
|
||||
RemoveContainerOpts, RemoveImageOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts,
|
||||
VerifyOpts, VerifyType,
|
||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, ContainerOpts, CopyOciDirOpts,
|
||||
CreateContainerOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts,
|
||||
GetMetadataOpts, PushOpts, RechunkOpts, RemoveContainerOpts, RemoveImageOpts, RunOpts,
|
||||
SignOpts, SignVerifyOpts, SwitchOpts, TagOpts, VerifyOpts, VerifyType, VolumeOpts,
|
||||
},
|
||||
types::{
|
||||
BootDriverType, BuildDriverType, ContainerId, ImageMetadata, InspectDriverType, MountId,
|
||||
RunDriverType, SigningDriverType,
|
||||
},
|
||||
podman_driver::PodmanDriver,
|
||||
sigstore_driver::SigstoreDriver,
|
||||
skopeo_driver::SkopeoDriver,
|
||||
types::{ContainerId, ImageMetadata, MountId},
|
||||
};
|
||||
|
||||
trait PrivateDriver {}
|
||||
|
|
@ -46,19 +40,37 @@ macro_rules! impl_private_driver {
|
|||
}
|
||||
|
||||
impl_private_driver!(
|
||||
Driver,
|
||||
DockerDriver,
|
||||
PodmanDriver,
|
||||
BuildahDriver,
|
||||
GithubDriver,
|
||||
GitlabDriver,
|
||||
LocalDriver,
|
||||
CosignDriver,
|
||||
SkopeoDriver,
|
||||
CiDriverType,
|
||||
SigstoreDriver,
|
||||
super::Driver,
|
||||
super::docker_driver::DockerDriver,
|
||||
super::podman_driver::PodmanDriver,
|
||||
super::buildah_driver::BuildahDriver,
|
||||
super::github_driver::GithubDriver,
|
||||
super::gitlab_driver::GitlabDriver,
|
||||
super::local_driver::LocalDriver,
|
||||
super::cosign_driver::CosignDriver,
|
||||
super::skopeo_driver::SkopeoDriver,
|
||||
super::sigstore_driver::SigstoreDriver,
|
||||
super::rpm_ostree_driver::RpmOstreeDriver,
|
||||
super::rpm_ostree_driver::Status,
|
||||
Option<BuildDriverType>,
|
||||
Option<RunDriverType>,
|
||||
Option<InspectDriverType>,
|
||||
Option<SigningDriverType>,
|
||||
Option<CiDriverType>,
|
||||
Option<BootDriverType>,
|
||||
);
|
||||
|
||||
#[cfg(feature = "bootc")]
|
||||
impl_private_driver!(
|
||||
super::bootc_driver::BootcDriver,
|
||||
super::bootc_driver::BootcStatus
|
||||
);
|
||||
|
||||
#[allow(private_bounds)]
|
||||
pub trait DetermineDriver<T>: PrivateDriver {
|
||||
fn determine_driver(&mut self) -> T;
|
||||
}
|
||||
|
||||
/// Trait for retrieving version of a driver.
|
||||
#[allow(private_bounds)]
|
||||
pub trait DriverVersion: PrivateDriver {
|
||||
|
|
@ -88,19 +100,19 @@ pub trait BuildDriver: PrivateDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if the build fails.
|
||||
fn build(opts: &BuildOpts) -> Result<()>;
|
||||
fn build(opts: BuildOpts) -> Result<()>;
|
||||
|
||||
/// Runs the tag logic for the driver.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the tagging fails.
|
||||
fn tag(opts: &TagOpts) -> Result<()>;
|
||||
fn tag(opts: TagOpts) -> Result<()>;
|
||||
|
||||
/// Runs the push logic for the driver
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the push fails.
|
||||
fn push(opts: &PushOpts) -> Result<()>;
|
||||
fn push(opts: PushOpts) -> Result<()>;
|
||||
|
||||
/// Runs the login logic for the driver.
|
||||
///
|
||||
|
|
@ -112,27 +124,27 @@ pub trait BuildDriver: PrivateDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if the driver fails to prune.
|
||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()>;
|
||||
fn prune(opts: super::opts::PruneOpts) -> Result<()>;
|
||||
|
||||
/// Runs the logic for building, tagging, and pushing an image.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if building, tagging, or pusing fails.
|
||||
fn build_tag_push(opts: &BuildTagPushOpts) -> Result<Vec<String>> {
|
||||
fn build_tag_push(opts: BuildTagPushOpts) -> Result<Vec<String>> {
|
||||
trace!("BuildDriver::build_tag_push({opts:#?})");
|
||||
|
||||
let build_opts = BuildOpts::builder()
|
||||
.image(&opts.image)
|
||||
.image(opts.image)
|
||||
.containerfile(opts.containerfile.as_ref())
|
||||
.maybe_platform(opts.platform)
|
||||
.squash(opts.squash)
|
||||
.maybe_cache_from(opts.cache_from)
|
||||
.maybe_cache_to(opts.cache_to)
|
||||
.secrets(opts.secrets.clone())
|
||||
.secrets(opts.secrets)
|
||||
.build();
|
||||
|
||||
info!("Building image {}", opts.image);
|
||||
Self::build(&build_opts)?;
|
||||
Self::build(build_opts)?;
|
||||
|
||||
let image_list: Vec<String> = match &opts.image {
|
||||
ImageRef::Remote(image) if !opts.tags.is_empty() => {
|
||||
|
|
@ -140,7 +152,7 @@ pub trait BuildDriver: PrivateDriver {
|
|||
|
||||
let mut image_list = Vec::with_capacity(opts.tags.len());
|
||||
|
||||
for tag in &opts.tags {
|
||||
for tag in opts.tags {
|
||||
debug!("Tagging {} with {tag}", &image);
|
||||
let tagged_image = Reference::with_tag(
|
||||
image.registry().into(),
|
||||
|
|
@ -153,7 +165,7 @@ pub trait BuildDriver: PrivateDriver {
|
|||
.dest_image(&tagged_image)
|
||||
.build();
|
||||
|
||||
Self::tag(&tag_opts)?;
|
||||
Self::tag(tag_opts)?;
|
||||
image_list.push(tagged_image.to_string());
|
||||
|
||||
if opts.push {
|
||||
|
|
@ -169,7 +181,7 @@ pub trait BuildDriver: PrivateDriver {
|
|||
.compression_type(opts.compression)
|
||||
.build();
|
||||
|
||||
Self::push(&push_opts)
|
||||
Self::push(push_opts)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
|
@ -177,7 +189,7 @@ pub trait BuildDriver: PrivateDriver {
|
|||
image_list
|
||||
}
|
||||
_ => {
|
||||
string_vec![&opts.image]
|
||||
string_vec![opts.image]
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -192,7 +204,7 @@ pub trait InspectDriver: PrivateDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if it is unable to get the labels.
|
||||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata>;
|
||||
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata>;
|
||||
}
|
||||
|
||||
/// Allows agnostic running of containers.
|
||||
|
|
@ -202,31 +214,31 @@ pub trait RunDriver: PrivateDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if there is an issue running the container.
|
||||
fn run(opts: &RunOpts) -> Result<ExitStatus>;
|
||||
fn run(opts: RunOpts) -> Result<ExitStatus>;
|
||||
|
||||
/// Run a container to perform an action and capturing output.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if there is an issue running the container.
|
||||
fn run_output(opts: &RunOpts) -> Result<Output>;
|
||||
fn run_output(opts: RunOpts) -> Result<Output>;
|
||||
|
||||
/// Creates container
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the container create command fails.
|
||||
fn create_container(opts: &CreateContainerOpts) -> Result<ContainerId>;
|
||||
fn create_container(opts: CreateContainerOpts) -> Result<ContainerId>;
|
||||
|
||||
/// Removes a container
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the container remove command fails.
|
||||
fn remove_container(opts: &RemoveContainerOpts) -> Result<()>;
|
||||
fn remove_container(opts: RemoveContainerOpts) -> Result<()>;
|
||||
|
||||
/// Removes an image
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the image remove command fails.
|
||||
fn remove_image(opts: &RemoveImageOpts) -> Result<()>;
|
||||
fn remove_image(opts: RemoveImageOpts) -> Result<()>;
|
||||
|
||||
/// List all images in the local image registry.
|
||||
///
|
||||
|
|
@ -241,23 +253,23 @@ pub(super) trait ContainerMountDriver: PrivateDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if the container mount command fails.
|
||||
fn mount_container(opts: &super::opts::ContainerOpts) -> Result<MountId>;
|
||||
fn mount_container(opts: ContainerOpts) -> Result<MountId>;
|
||||
|
||||
/// Unmount the container
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the container unmount command fails.
|
||||
fn unmount_container(opts: &super::opts::ContainerOpts) -> Result<()>;
|
||||
fn unmount_container(opts: ContainerOpts) -> Result<()>;
|
||||
|
||||
/// Remove a volume
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the volume remove command fails.
|
||||
fn remove_volume(opts: &super::opts::VolumeOpts) -> Result<()>;
|
||||
fn remove_volume(opts: VolumeOpts) -> Result<()>;
|
||||
}
|
||||
|
||||
pub(super) trait OciCopy {
|
||||
fn copy_oci_dir(opts: &super::opts::CopyOciDirOpts) -> Result<()>;
|
||||
fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()>;
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
|
|
@ -268,7 +280,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if the rechunk process fails.
|
||||
fn rechunk(opts: &RechunkOpts) -> Result<Vec<String>> {
|
||||
fn rechunk(opts: RechunkOpts) -> Result<Vec<String>> {
|
||||
let ostree_cache_id = &uuid::Uuid::new_v4().to_string();
|
||||
let raw_image =
|
||||
&Reference::try_from(format!("localhost/{ostree_cache_id}/raw-rechunk")).unwrap();
|
||||
|
|
@ -283,25 +295,25 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
|||
Self::login()?;
|
||||
|
||||
Self::build(
|
||||
&BuildOpts::builder()
|
||||
.image(raw_image)
|
||||
.containerfile(&*opts.containerfile)
|
||||
BuildOpts::builder()
|
||||
.image(&ImageRef::from(raw_image))
|
||||
.containerfile(opts.containerfile)
|
||||
.maybe_platform(opts.platform)
|
||||
.privileged(true)
|
||||
.squash(true)
|
||||
.host_network(true)
|
||||
.secrets(opts.secrets.clone())
|
||||
.secrets(opts.secrets)
|
||||
.build(),
|
||||
)?;
|
||||
|
||||
let container = &Self::create_container(
|
||||
&CreateContainerOpts::builder()
|
||||
CreateContainerOpts::builder()
|
||||
.image(raw_image)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
)?;
|
||||
let mount = &Self::mount_container(
|
||||
&super::opts::ContainerOpts::builder()
|
||||
super::opts::ContainerOpts::builder()
|
||||
.container_id(container)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
|
|
@ -324,7 +336,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
|||
if opts.push {
|
||||
let oci_dir = &super::types::OciDir::try_from(temp_dir.path().join(ostree_cache_id))?;
|
||||
|
||||
for tag in &opts.tags {
|
||||
for tag in opts.tags {
|
||||
let tagged_image = Reference::with_tag(
|
||||
full_image.registry().to_string(),
|
||||
full_image.repository().to_string(),
|
||||
|
|
@ -335,7 +347,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
|||
debug!("Pushing image {tagged_image}");
|
||||
|
||||
Driver::copy_oci_dir(
|
||||
&super::opts::CopyOciDirOpts::builder()
|
||||
super::opts::CopyOciDirOpts::builder()
|
||||
.oci_dir(oci_dir)
|
||||
.registry(&tagged_image)
|
||||
.privileged(true)
|
||||
|
|
@ -357,39 +369,39 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
|||
mount: &MountId,
|
||||
container: &ContainerId,
|
||||
raw_image: &Reference,
|
||||
opts: &RechunkOpts<'_>,
|
||||
opts: RechunkOpts<'_>,
|
||||
) -> Result<(), miette::Error> {
|
||||
let status = Self::run(
|
||||
&RunOpts::builder()
|
||||
RunOpts::builder()
|
||||
.image(Self::RECHUNK_IMAGE)
|
||||
.remove(true)
|
||||
.user("0:0")
|
||||
.privileged(true)
|
||||
.volumes(crate::run_volumes! {
|
||||
.volumes(&crate::run_volumes! {
|
||||
mount => "/var/tree",
|
||||
})
|
||||
.env_vars(crate::run_envs! {
|
||||
.env_vars(&crate::run_envs! {
|
||||
"TREE" => "/var/tree",
|
||||
})
|
||||
.args(bon::vec!["/sources/rechunk/1_prune.sh"])
|
||||
.args(&bon::vec!["/sources/rechunk/1_prune.sh"])
|
||||
.build(),
|
||||
)?;
|
||||
|
||||
if !status.success() {
|
||||
Self::unmount_container(
|
||||
&super::opts::ContainerOpts::builder()
|
||||
super::opts::ContainerOpts::builder()
|
||||
.container_id(container)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
)?;
|
||||
Self::remove_container(
|
||||
&RemoveContainerOpts::builder()
|
||||
RemoveContainerOpts::builder()
|
||||
.container_id(container)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
)?;
|
||||
Self::remove_image(
|
||||
&RemoveImageOpts::builder()
|
||||
RemoveImageOpts::builder()
|
||||
.image(raw_image)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
|
|
@ -409,40 +421,40 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
|||
ostree_cache_id: &str,
|
||||
container: &ContainerId,
|
||||
raw_image: &Reference,
|
||||
opts: &RechunkOpts<'_>,
|
||||
opts: RechunkOpts<'_>,
|
||||
) -> Result<()> {
|
||||
let status = Self::run(
|
||||
&RunOpts::builder()
|
||||
RunOpts::builder()
|
||||
.image(Self::RECHUNK_IMAGE)
|
||||
.remove(true)
|
||||
.user("0:0")
|
||||
.privileged(true)
|
||||
.volumes(crate::run_volumes! {
|
||||
.volumes(&crate::run_volumes! {
|
||||
mount => "/var/tree",
|
||||
ostree_cache_id => "/var/ostree",
|
||||
})
|
||||
.env_vars(crate::run_envs! {
|
||||
.env_vars(&crate::run_envs! {
|
||||
"TREE" => "/var/tree",
|
||||
"REPO" => "/var/ostree/repo",
|
||||
"RESET_TIMESTAMP" => "1",
|
||||
})
|
||||
.args(bon::vec!["/sources/rechunk/2_create.sh"])
|
||||
.args(&bon::vec!["/sources/rechunk/2_create.sh"])
|
||||
.build(),
|
||||
)?;
|
||||
Self::unmount_container(
|
||||
&super::opts::ContainerOpts::builder()
|
||||
super::opts::ContainerOpts::builder()
|
||||
.container_id(container)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
)?;
|
||||
Self::remove_container(
|
||||
&RemoveContainerOpts::builder()
|
||||
RemoveContainerOpts::builder()
|
||||
.container_id(container)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
)?;
|
||||
Self::remove_image(
|
||||
&RemoveImageOpts::builder()
|
||||
RemoveImageOpts::builder()
|
||||
.image(raw_image)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
|
|
@ -463,30 +475,16 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
|||
ostree_cache_id: &str,
|
||||
temp_dir_str: &str,
|
||||
current_dir: &str,
|
||||
opts: &RechunkOpts<'_>,
|
||||
opts: RechunkOpts<'_>,
|
||||
) -> Result<()> {
|
||||
let status = Self::run(
|
||||
&RunOpts::builder()
|
||||
.image(Self::RECHUNK_IMAGE)
|
||||
.remove(true)
|
||||
.user("0:0")
|
||||
.privileged(true)
|
||||
.volumes(crate::run_volumes! {
|
||||
ostree_cache_id => "/var/ostree",
|
||||
temp_dir_str => "/workspace",
|
||||
current_dir => "/var/git"
|
||||
})
|
||||
.env_vars(crate::run_envs! {
|
||||
"REPO" => "/var/ostree/repo",
|
||||
"PREV_REF" => &*opts.image,
|
||||
"OUT_NAME" => ostree_cache_id,
|
||||
"CLEAR_PLAN" => if opts.clear_plan { "true" } else { "" },
|
||||
"VERSION" => format!("{}", opts.version),
|
||||
"OUT_REF" => format!("oci:{ostree_cache_id}"),
|
||||
"GIT_DIR" => "/var/git",
|
||||
"LABELS" => format!(
|
||||
let out_ref = format!("oci:{ostree_cache_id}");
|
||||
let labels = format!(
|
||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
|
||||
format_args!("{}={}", blue_build_utils::constants::BUILD_ID_LABEL, Driver::get_build_id()),
|
||||
format_args!(
|
||||
"{}={}",
|
||||
blue_build_utils::constants::BUILD_ID_LABEL,
|
||||
Driver::get_build_id()
|
||||
),
|
||||
format_args!("org.opencontainers.image.title={}", &opts.name),
|
||||
format_args!("org.opencontainers.image.description={}", &opts.description),
|
||||
format_args!("org.opencontainers.image.source={}", &opts.repo),
|
||||
|
|
@ -494,14 +492,34 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
|||
format_args!("org.opencontainers.image.base.name={}", &opts.base_image),
|
||||
"org.opencontainers.image.created=<timestamp>",
|
||||
"io.artifacthub.package.readme-url=https://raw.githubusercontent.com/blue-build/cli/main/README.md",
|
||||
)
|
||||
);
|
||||
let status = Self::run(
|
||||
RunOpts::builder()
|
||||
.image(Self::RECHUNK_IMAGE)
|
||||
.remove(true)
|
||||
.user("0:0")
|
||||
.privileged(true)
|
||||
.volumes(&crate::run_volumes! {
|
||||
ostree_cache_id => "/var/ostree",
|
||||
temp_dir_str => "/workspace",
|
||||
current_dir => "/var/git"
|
||||
})
|
||||
.args(bon::vec!["/sources/rechunk/3_chunk.sh"])
|
||||
.env_vars(&crate::run_envs! {
|
||||
"REPO" => "/var/ostree/repo",
|
||||
"PREV_REF" => opts.image,
|
||||
"OUT_NAME" => ostree_cache_id,
|
||||
"CLEAR_PLAN" => if opts.clear_plan { "true" } else { "" },
|
||||
"VERSION" => opts.version,
|
||||
"OUT_REF" => &out_ref,
|
||||
"GIT_DIR" => "/var/git",
|
||||
"LABELS" => &labels,
|
||||
})
|
||||
.args(&bon::vec!["/sources/rechunk/3_chunk.sh"])
|
||||
.build(),
|
||||
)?;
|
||||
|
||||
Self::remove_volume(
|
||||
&super::opts::VolumeOpts::builder()
|
||||
super::opts::VolumeOpts::builder()
|
||||
.volume_id(ostree_cache_id)
|
||||
.privileged(true)
|
||||
.build(),
|
||||
|
|
@ -522,20 +540,20 @@ pub trait SigningDriver: PrivateDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if a key-pair couldn't be generated.
|
||||
fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()>;
|
||||
fn generate_key_pair(opts: GenerateKeyPairOpts) -> Result<()>;
|
||||
|
||||
/// Checks the signing key files to ensure
|
||||
/// they match.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the files cannot be verified.
|
||||
fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()>;
|
||||
fn check_signing_files(opts: CheckKeyPairOpts) -> Result<()>;
|
||||
|
||||
/// Signs the image digest.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if signing fails.
|
||||
fn sign(opts: &SignOpts) -> Result<()>;
|
||||
fn sign(opts: SignOpts) -> Result<()>;
|
||||
|
||||
/// Verifies the image.
|
||||
///
|
||||
|
|
@ -545,22 +563,23 @@ pub trait SigningDriver: PrivateDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if the image fails to be verified.
|
||||
fn verify(opts: &VerifyOpts) -> Result<()>;
|
||||
fn verify(opts: VerifyOpts) -> Result<()>;
|
||||
|
||||
/// Sign an image given the image name and tag.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the image fails to be signed.
|
||||
fn sign_and_verify(opts: &SignVerifyOpts) -> Result<()> {
|
||||
fn sign_and_verify(opts: SignVerifyOpts) -> Result<()> {
|
||||
trace!("sign_and_verify({opts:?})");
|
||||
|
||||
let path = opts
|
||||
.dir
|
||||
.as_ref()
|
||||
.map_or_else(|| PathBuf::from("."), |d| d.to_path_buf());
|
||||
let cosign_file_path = path.join(COSIGN_PUB_PATH);
|
||||
|
||||
let image_digest = Driver::get_metadata(
|
||||
&GetMetadataOpts::builder()
|
||||
GetMetadataOpts::builder()
|
||||
.image(opts.image)
|
||||
.maybe_platform(opts.platform)
|
||||
.build(),
|
||||
|
|
@ -573,29 +592,30 @@ pub trait SigningDriver: PrivateDriver {
|
|||
)
|
||||
.parse()
|
||||
.into_diagnostic()?;
|
||||
let issuer = Driver::oidc_provider();
|
||||
let identity = Driver::keyless_cert_identity();
|
||||
let priv_key = get_private_key(&path);
|
||||
|
||||
let (sign_opts, verify_opts) = match (Driver::get_ci_driver(), get_private_key(&path)) {
|
||||
let (sign_opts, verify_opts) =
|
||||
match (Driver::get_ci_driver(), &priv_key, &issuer, &identity) {
|
||||
// Cosign public/private key pair
|
||||
(_, Ok(priv_key)) => (
|
||||
(_, Ok(priv_key), _, _) => (
|
||||
SignOpts::builder()
|
||||
.image(&image_digest)
|
||||
.dir(&path)
|
||||
.key(priv_key.to_string())
|
||||
.key(priv_key)
|
||||
.build(),
|
||||
VerifyOpts::builder()
|
||||
.image(opts.image)
|
||||
.verify_type(VerifyType::File(path.join(COSIGN_PUB_PATH).into()))
|
||||
.verify_type(VerifyType::File(&cosign_file_path))
|
||||
.build(),
|
||||
),
|
||||
// Gitlab keyless
|
||||
(CiDriverType::Github | CiDriverType::Gitlab, _) => (
|
||||
(CiDriverType::Github | CiDriverType::Gitlab, _, Ok(issuer), Ok(identity)) => (
|
||||
SignOpts::builder().dir(&path).image(&image_digest).build(),
|
||||
VerifyOpts::builder()
|
||||
.image(opts.image)
|
||||
.verify_type(VerifyType::Keyless {
|
||||
issuer: Driver::oidc_provider()?.into(),
|
||||
identity: Driver::keyless_cert_identity()?.into(),
|
||||
})
|
||||
.verify_type(VerifyType::Keyless { issuer, identity })
|
||||
.build(),
|
||||
),
|
||||
_ => bail!("Failed to get information for signing the image"),
|
||||
|
|
@ -604,8 +624,8 @@ pub trait SigningDriver: PrivateDriver {
|
|||
let retry_count = if opts.retry_push { opts.retry_count } else { 0 };
|
||||
|
||||
retry(retry_count, 5, || {
|
||||
Self::sign(&sign_opts)?;
|
||||
Self::verify(&verify_opts)
|
||||
Self::sign(sign_opts)?;
|
||||
Self::verify(verify_opts)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -665,7 +685,7 @@ pub trait CiDriver: PrivateDriver {
|
|||
///
|
||||
/// # Errors
|
||||
/// Will error if the environment variables aren't set.
|
||||
fn generate_tags(opts: &GenerateTagsOpts) -> Result<Vec<String>>;
|
||||
fn generate_tags(opts: GenerateTagsOpts) -> Result<Vec<String>>;
|
||||
|
||||
/// Generates the image name based on CI.
|
||||
///
|
||||
|
|
@ -722,3 +742,36 @@ pub trait CiDriver: PrivateDriver {
|
|||
|
||||
fn default_ci_file_path() -> PathBuf;
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
pub trait BootDriver: PrivateDriver {
|
||||
/// Get the status of the current booted image.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if we fail to get the status.
|
||||
fn status() -> Result<Box<dyn BootStatus>>;
|
||||
|
||||
/// Switch to a new image.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if we fail to switch to a new image.
|
||||
fn switch(opts: SwitchOpts) -> Result<()>;
|
||||
|
||||
/// Upgrade an image.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if we fail to upgrade to a new image.
|
||||
fn upgrade(opts: SwitchOpts) -> Result<()>;
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
pub trait BootStatus: PrivateDriver {
|
||||
/// Checks to see if there's a transaction in progress.
|
||||
fn transaction_in_progress(&self) -> bool;
|
||||
|
||||
/// Gets the booted image.
|
||||
fn booted_image(&self) -> Option<ImageRef<'_>>;
|
||||
|
||||
/// Gets the staged image.
|
||||
fn staged_image(&self) -> Option<ImageRef<'_>>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,501 +1,9 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use blue_build_utils::{
|
||||
constants::{GITHUB_ACTIONS, GITLAB_CI, IMAGE_VERSION_LABEL},
|
||||
get_env_var,
|
||||
semver::Version,
|
||||
string,
|
||||
};
|
||||
use clap::ValueEnum;
|
||||
use log::{trace, warn};
|
||||
use oci_distribution::Reference;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::drivers::{
|
||||
DriverVersion, buildah_driver::BuildahDriver, docker_driver::DockerDriver,
|
||||
podman_driver::PodmanDriver,
|
||||
};
|
||||
|
||||
mod private {
|
||||
pub trait Private {}
|
||||
}
|
||||
|
||||
pub(super) trait DetermineDriver<T> {
|
||||
fn determine_driver(&mut self) -> T;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum InspectDriverType {
|
||||
Skopeo,
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl DetermineDriver<InspectDriverType> for Option<InspectDriverType> {
|
||||
fn determine_driver(&mut self) -> InspectDriverType {
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("skopeo"),
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(_skopeo), _, _) => InspectDriverType::Skopeo,
|
||||
(_, Ok(_docker), _) => InspectDriverType::Docker,
|
||||
(_, _, Ok(_podman)) => InspectDriverType::Podman,
|
||||
_ => panic!(
|
||||
"{}{}",
|
||||
"Could not determine inspection strategy. ",
|
||||
"You need either skopeo, docker, or podman",
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum BuildDriverType {
|
||||
Buildah,
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl DetermineDriver<BuildDriverType> for Option<BuildDriverType> {
|
||||
fn determine_driver(&mut self) -> BuildDriverType {
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
blue_build_utils::check_command_exists("buildah"),
|
||||
) {
|
||||
(Ok(_docker), _, _)
|
||||
if DockerDriver::is_supported_version() && DockerDriver::has_buildx() =>
|
||||
{
|
||||
BuildDriverType::Docker
|
||||
}
|
||||
(_, Ok(_podman), _) if PodmanDriver::is_supported_version() => {
|
||||
BuildDriverType::Podman
|
||||
}
|
||||
(_, _, Ok(_buildah)) if BuildahDriver::is_supported_version() => {
|
||||
BuildDriverType::Buildah
|
||||
}
|
||||
_ => panic!(
|
||||
"{}{}{}{}",
|
||||
"Could not determine strategy, ",
|
||||
format_args!(
|
||||
"need either docker version {} with buildx, ",
|
||||
DockerDriver::VERSION_REQ,
|
||||
),
|
||||
format_args!("podman version {}, ", PodmanDriver::VERSION_REQ,),
|
||||
format_args!(
|
||||
"or buildah version {} to continue",
|
||||
BuildahDriver::VERSION_REQ,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum SigningDriverType {
|
||||
Cosign,
|
||||
Sigstore,
|
||||
}
|
||||
|
||||
impl DetermineDriver<SigningDriverType> for Option<SigningDriverType> {
|
||||
fn determine_driver(&mut self) -> SigningDriverType {
|
||||
trace!("SigningDriverType::determine_signing_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
blue_build_utils::check_command_exists("cosign")
|
||||
.map_or(SigningDriverType::Sigstore, |()| SigningDriverType::Cosign),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum RunDriverType {
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl From<RunDriverType> for String {
|
||||
fn from(value: RunDriverType) -> Self {
|
||||
match value {
|
||||
RunDriverType::Podman => "podman".to_string(),
|
||||
RunDriverType::Docker => "docker".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DetermineDriver<RunDriverType> for Option<RunDriverType> {
|
||||
fn determine_driver(&mut self) -> RunDriverType {
|
||||
trace!("RunDriver::determine_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(_docker), _) if DockerDriver::is_supported_version() => RunDriverType::Docker,
|
||||
(_, Ok(_podman)) if PodmanDriver::is_supported_version() => RunDriverType::Podman,
|
||||
_ => panic!(
|
||||
"{}{}{}{}",
|
||||
"Could not determine strategy, ",
|
||||
format_args!("need either docker version {}, ", DockerDriver::VERSION_REQ),
|
||||
format_args!("podman version {}, ", PodmanDriver::VERSION_REQ),
|
||||
format_args!(
|
||||
"or buildah version {} to continue",
|
||||
BuildahDriver::VERSION_REQ
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum CiDriverType {
|
||||
Local,
|
||||
Gitlab,
|
||||
Github,
|
||||
}
|
||||
|
||||
impl DetermineDriver<CiDriverType> for Option<CiDriverType> {
|
||||
fn determine_driver(&mut self) -> CiDriverType {
|
||||
trace!("CiDriverType::determine_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
get_env_var(GITLAB_CI).ok(),
|
||||
get_env_var(GITHUB_ACTIONS).ok(),
|
||||
) {
|
||||
(Some(_gitlab_ci), None) => CiDriverType::Gitlab,
|
||||
(None, Some(_github_actions)) => CiDriverType::Github,
|
||||
_ => CiDriverType::Local,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Hash)]
|
||||
pub enum Platform {
|
||||
#[value(name = "linux/amd64")]
|
||||
LinuxAmd64,
|
||||
|
||||
#[value(name = "linux/amd64/v2")]
|
||||
LinuxAmd64V2,
|
||||
|
||||
#[value(name = "linux/arm64")]
|
||||
LinuxArm64,
|
||||
|
||||
#[value(name = "linux/arm")]
|
||||
LinuxArm,
|
||||
|
||||
#[value(name = "linux/arm/v6")]
|
||||
LinuxArmV6,
|
||||
|
||||
#[value(name = "linux/arm/v7")]
|
||||
LinuxArmV7,
|
||||
|
||||
#[value(name = "linux/386")]
|
||||
Linux386,
|
||||
|
||||
#[value(name = "linux/loong64")]
|
||||
LinuxLoong64,
|
||||
|
||||
#[value(name = "linux/mips")]
|
||||
LinuxMips,
|
||||
|
||||
#[value(name = "linux/mipsle")]
|
||||
LinuxMipsle,
|
||||
|
||||
#[value(name = "linux/mips64")]
|
||||
LinuxMips64,
|
||||
|
||||
#[value(name = "linux/mips64le")]
|
||||
LinuxMips64le,
|
||||
|
||||
#[value(name = "linux/ppc64")]
|
||||
LinuxPpc64,
|
||||
|
||||
#[value(name = "linux/ppc64le")]
|
||||
LinuxPpc64le,
|
||||
|
||||
#[value(name = "linux/riscv64")]
|
||||
LinuxRiscv64,
|
||||
|
||||
#[value(name = "linux/s390x")]
|
||||
LinuxS390x,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
/// The architecture of the platform.
|
||||
#[must_use]
|
||||
pub const fn arch(&self) -> &str {
|
||||
match *self {
|
||||
Self::LinuxAmd64 | Self::LinuxAmd64V2 => "amd64",
|
||||
Self::LinuxArm64 => "arm64",
|
||||
Self::LinuxArm | Self::LinuxArmV6 | Self::LinuxArmV7 => "arm",
|
||||
Self::Linux386 => "386",
|
||||
Self::LinuxLoong64 => "loong64",
|
||||
Self::LinuxMips => "mips",
|
||||
Self::LinuxMipsle => "mipsle",
|
||||
Self::LinuxMips64 => "mips64",
|
||||
Self::LinuxMips64le => "mips64le",
|
||||
Self::LinuxPpc64 => "ppc64",
|
||||
Self::LinuxPpc64le => "ppc64le",
|
||||
Self::LinuxRiscv64 => "riscv64",
|
||||
Self::LinuxS390x => "s390x",
|
||||
}
|
||||
}
|
||||
|
||||
/// The variant of the platform.
|
||||
#[must_use]
|
||||
pub const fn variant(&self) -> Option<&str> {
|
||||
match *self {
|
||||
Self::LinuxAmd64V2 => Some("v2"),
|
||||
Self::LinuxArmV6 => Some("v6"),
|
||||
Self::LinuxArmV7 => Some("v7"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Platform {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::LinuxAmd64 => "linux/amd64",
|
||||
Self::LinuxAmd64V2 => "linux/amd64/v2",
|
||||
Self::LinuxArm64 => "linux/arm64",
|
||||
Self::LinuxArm => "linux/arm",
|
||||
Self::LinuxArmV6 => "linux/arm/v6",
|
||||
Self::LinuxArmV7 => "linux/arm/v7",
|
||||
Self::Linux386 => "linux/386",
|
||||
Self::LinuxLoong64 => "linux/loong64",
|
||||
Self::LinuxMips => "linux/mips",
|
||||
Self::LinuxMipsle => "linux/mipsle",
|
||||
Self::LinuxMips64 => "linux/mips64",
|
||||
Self::LinuxMips64le => "linux/mips64le",
|
||||
Self::LinuxPpc64 => "linux/ppc64",
|
||||
Self::LinuxPpc64le => "linux/ppc64le",
|
||||
Self::LinuxRiscv64 => "linux/riscv64",
|
||||
Self::LinuxS390x => "linux/s390x",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl private::Private for Option<Platform> {}
|
||||
|
||||
pub trait PlatformInfo: private::Private {
|
||||
/// The string representation of the platform.
|
||||
///
|
||||
/// If `None`, then the native architecture will be used.
|
||||
fn to_string(&self) -> String;
|
||||
|
||||
/// The string representation of the architecture.
|
||||
///
|
||||
/// If `None`, then the native architecture will be used.
|
||||
fn arch(&self) -> &str;
|
||||
}
|
||||
|
||||
impl PlatformInfo for Option<Platform> {
|
||||
fn to_string(&self) -> String {
|
||||
self.map_or_else(
|
||||
|| match std::env::consts::ARCH {
|
||||
"x86_64" => string!("linux/amd64"),
|
||||
"aarch64" => string!("linux/arm64"),
|
||||
arch => unimplemented!("Arch {arch} is unsupported"),
|
||||
},
|
||||
|platform| format!("{platform}"),
|
||||
)
|
||||
}
|
||||
|
||||
fn arch(&self) -> &str {
|
||||
self.as_ref().map_or_else(
|
||||
|| match std::env::consts::ARCH {
|
||||
"x86_64" => "amd64",
|
||||
"aarch64" => "arm64",
|
||||
arch => unimplemented!("Arch {arch} is unsupported"),
|
||||
},
|
||||
Platform::arch,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct ImageMetadata {
|
||||
pub labels: HashMap<String, Value>,
|
||||
pub digest: String,
|
||||
}
|
||||
|
||||
impl ImageMetadata {
|
||||
#[must_use]
|
||||
pub fn get_version(&self) -> Option<u64> {
|
||||
Some(
|
||||
self.labels
|
||||
.get(IMAGE_VERSION_LABEL)
|
||||
.map(ToOwned::to_owned)
|
||||
.and_then(|v| {
|
||||
serde_json::from_value::<Version>(v)
|
||||
.inspect_err(|e| warn!("Failed to parse version:\n{e}"))
|
||||
.ok()
|
||||
})?
|
||||
.major,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContainerId(pub(super) String);
|
||||
|
||||
impl std::fmt::Display for ContainerId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for ContainerId {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MountId(pub(super) String);
|
||||
|
||||
impl std::fmt::Display for MountId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for MountId {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MountId> for std::borrow::Cow<'a, str> {
|
||||
fn from(value: &'a MountId) -> Self {
|
||||
Self::Borrowed(&value.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OciDir(String);
|
||||
|
||||
impl std::fmt::Display for OciDir {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for OciDir {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<std::path::PathBuf> for OciDir {
|
||||
type Error = miette::Report;
|
||||
|
||||
fn try_from(value: std::path::PathBuf) -> Result<Self, Self::Error> {
|
||||
if !value.is_dir() {
|
||||
miette::bail!("OCI directory doesn't exist at {}", value.display());
|
||||
}
|
||||
|
||||
Ok(Self(format!("oci:{}", value.display())))
|
||||
}
|
||||
}
|
||||
|
||||
/// An image ref that could reference
|
||||
/// a remote registry or a local tarball.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ImageRef<'scope> {
|
||||
Remote(Cow<'scope, Reference>),
|
||||
LocalTar(Cow<'scope, Path>),
|
||||
}
|
||||
|
||||
impl ImageRef<'_> {
|
||||
#[must_use]
|
||||
pub fn remote_ref(&self) -> Option<&Reference> {
|
||||
match self {
|
||||
Self::Remote(remote) => Some(remote.as_ref()),
|
||||
Self::LocalTar(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Self> for ImageRef<'scope> {
|
||||
fn from(value: &'scope ImageRef) -> Self {
|
||||
match value {
|
||||
Self::Remote(remote) => Self::Remote(Cow::Borrowed(remote.as_ref())),
|
||||
Self::LocalTar(path) => Self::LocalTar(Cow::Borrowed(path.as_ref())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Reference> for ImageRef<'scope> {
|
||||
fn from(value: &'scope Reference) -> Self {
|
||||
Self::Remote(Cow::Borrowed(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Reference> for ImageRef<'_> {
|
||||
fn from(value: Reference) -> Self {
|
||||
Self::Remote(Cow::Owned(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Path> for ImageRef<'scope> {
|
||||
fn from(value: &'scope Path) -> Self {
|
||||
Self::LocalTar(Cow::Borrowed(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope PathBuf> for ImageRef<'scope> {
|
||||
fn from(value: &'scope PathBuf) -> Self {
|
||||
Self::from(value.as_path())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for ImageRef<'_> {
|
||||
fn from(value: PathBuf) -> Self {
|
||||
Self::LocalTar(Cow::Owned(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImageRef<'_>> for String {
|
||||
fn from(value: ImageRef<'_>) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ImageRef<'_>> for String {
|
||||
fn from(value: &ImageRef<'_>) -> Self {
|
||||
format!("{value}")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ImageRef<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Remote(remote) => remote.whole(),
|
||||
Self::LocalTar(path) => format!("oci-archive:{}", path.display()),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
mod container;
|
||||
mod drivers;
|
||||
mod metadata;
|
||||
mod platform;
|
||||
|
||||
pub use container::*;
|
||||
pub use drivers::*;
|
||||
pub use metadata::*;
|
||||
pub use platform::*;
|
||||
|
|
|
|||
179
process/drivers/types/container.rs
Normal file
179
process/drivers/types/container.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use oci_distribution::Reference;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContainerId(pub(crate) String);
|
||||
|
||||
impl Deref for ContainerId {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ContainerId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for ContainerId {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MountId(pub(crate) String);
|
||||
|
||||
impl Deref for MountId {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MountId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for MountId {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MountId> for std::borrow::Cow<'a, str> {
|
||||
fn from(value: &'a MountId) -> Self {
|
||||
Self::Borrowed(&value.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OciDir(String);
|
||||
|
||||
impl std::fmt::Display for OciDir {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for OciDir {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<std::path::PathBuf> for OciDir {
|
||||
type Error = miette::Report;
|
||||
|
||||
fn try_from(value: std::path::PathBuf) -> Result<Self, Self::Error> {
|
||||
if !value.is_dir() {
|
||||
miette::bail!("OCI directory doesn't exist at {}", value.display());
|
||||
}
|
||||
|
||||
Ok(Self(format!("oci:{}", value.display())))
|
||||
}
|
||||
}
|
||||
|
||||
/// An image ref that could reference
|
||||
/// a remote registry or a local tarball.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ImageRef<'scope> {
|
||||
Remote(Cow<'scope, Reference>),
|
||||
LocalTar(Cow<'scope, Path>),
|
||||
Other(Cow<'scope, str>),
|
||||
}
|
||||
|
||||
impl ImageRef<'_> {
|
||||
#[must_use]
|
||||
pub fn remote_ref(&self) -> Option<&Reference> {
|
||||
match self {
|
||||
Self::Remote(remote) => Some(remote.as_ref()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Self> for ImageRef<'scope> {
|
||||
fn from(value: &'scope ImageRef) -> Self {
|
||||
match value {
|
||||
Self::Remote(remote) => Self::Remote(Cow::Borrowed(remote.as_ref())),
|
||||
Self::LocalTar(path) => Self::LocalTar(Cow::Borrowed(path.as_ref())),
|
||||
Self::Other(other) => Self::Other(Cow::Borrowed(other.as_ref())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Reference> for ImageRef<'scope> {
|
||||
fn from(value: &'scope Reference) -> Self {
|
||||
Self::Remote(Cow::Borrowed(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Reference> for ImageRef<'_> {
|
||||
fn from(value: Reference) -> Self {
|
||||
Self::Remote(Cow::Owned(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Path> for ImageRef<'scope> {
|
||||
fn from(value: &'scope Path) -> Self {
|
||||
Self::LocalTar(Cow::Borrowed(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope PathBuf> for ImageRef<'scope> {
|
||||
fn from(value: &'scope PathBuf) -> Self {
|
||||
Self::from(value.as_path())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for ImageRef<'_> {
|
||||
fn from(value: PathBuf) -> Self {
|
||||
Self::LocalTar(Cow::Owned(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImageRef<'_>> for String {
|
||||
fn from(value: ImageRef<'_>) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ImageRef<'_>> for String {
|
||||
fn from(value: &ImageRef<'_>) -> Self {
|
||||
format!("{value}")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ImageRef<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Remote(remote) => remote.whole(),
|
||||
Self::LocalTar(path) => format!("oci-archive:{}", path.display()),
|
||||
Self::Other(other) => other.to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Reference> for ImageRef<'_> {
|
||||
fn eq(&self, other: &Reference) -> bool {
|
||||
match self {
|
||||
Self::Remote(remote) => &**remote == other,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
191
process/drivers/types/drivers.rs
Normal file
191
process/drivers/types/drivers.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
use blue_build_utils::{
|
||||
constants::{GITHUB_ACTIONS, GITLAB_CI},
|
||||
get_env_var,
|
||||
};
|
||||
use clap::ValueEnum;
|
||||
use log::trace;
|
||||
|
||||
use crate::drivers::{
|
||||
DetermineDriver, DriverVersion, buildah_driver::BuildahDriver, docker_driver::DockerDriver,
|
||||
podman_driver::PodmanDriver,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum InspectDriverType {
|
||||
Skopeo,
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl DetermineDriver<InspectDriverType> for Option<InspectDriverType> {
|
||||
fn determine_driver(&mut self) -> InspectDriverType {
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("skopeo"),
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(_skopeo), _, _) => InspectDriverType::Skopeo,
|
||||
(_, Ok(_docker), _) => InspectDriverType::Docker,
|
||||
(_, _, Ok(_podman)) => InspectDriverType::Podman,
|
||||
_ => panic!(
|
||||
"{}{}",
|
||||
"Could not determine inspection strategy. ",
|
||||
"You need either skopeo, docker, or podman",
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum BuildDriverType {
|
||||
Buildah,
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl DetermineDriver<BuildDriverType> for Option<BuildDriverType> {
|
||||
fn determine_driver(&mut self) -> BuildDriverType {
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
blue_build_utils::check_command_exists("buildah"),
|
||||
) {
|
||||
(Ok(_docker), _, _)
|
||||
if DockerDriver::is_supported_version() && DockerDriver::has_buildx() =>
|
||||
{
|
||||
BuildDriverType::Docker
|
||||
}
|
||||
(_, Ok(_podman), _) if PodmanDriver::is_supported_version() => {
|
||||
BuildDriverType::Podman
|
||||
}
|
||||
(_, _, Ok(_buildah)) if BuildahDriver::is_supported_version() => {
|
||||
BuildDriverType::Buildah
|
||||
}
|
||||
_ => panic!(
|
||||
"{}{}{}{}",
|
||||
"Could not determine strategy, ",
|
||||
format_args!(
|
||||
"need either docker version {} with buildx, ",
|
||||
DockerDriver::VERSION_REQ,
|
||||
),
|
||||
format_args!("podman version {}, ", PodmanDriver::VERSION_REQ,),
|
||||
format_args!(
|
||||
"or buildah version {} to continue",
|
||||
BuildahDriver::VERSION_REQ,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum SigningDriverType {
|
||||
Cosign,
|
||||
Sigstore,
|
||||
}
|
||||
|
||||
impl DetermineDriver<SigningDriverType> for Option<SigningDriverType> {
|
||||
fn determine_driver(&mut self) -> SigningDriverType {
|
||||
trace!("SigningDriverType::determine_signing_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
blue_build_utils::check_command_exists("cosign")
|
||||
.map_or(SigningDriverType::Sigstore, |()| SigningDriverType::Cosign),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum RunDriverType {
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl From<RunDriverType> for String {
|
||||
fn from(value: RunDriverType) -> Self {
|
||||
match value {
|
||||
RunDriverType::Podman => "podman".to_string(),
|
||||
RunDriverType::Docker => "docker".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DetermineDriver<RunDriverType> for Option<RunDriverType> {
|
||||
fn determine_driver(&mut self) -> RunDriverType {
|
||||
trace!("RunDriver::determine_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(_docker), _) if DockerDriver::is_supported_version() => RunDriverType::Docker,
|
||||
(_, Ok(_podman)) if PodmanDriver::is_supported_version() => RunDriverType::Podman,
|
||||
_ => panic!(
|
||||
"{}{}{}{}",
|
||||
"Could not determine strategy, ",
|
||||
format_args!("need either docker version {}, ", DockerDriver::VERSION_REQ),
|
||||
format_args!("podman version {}, ", PodmanDriver::VERSION_REQ),
|
||||
format_args!(
|
||||
"or buildah version {} to continue",
|
||||
BuildahDriver::VERSION_REQ
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum CiDriverType {
|
||||
Local,
|
||||
Gitlab,
|
||||
Github,
|
||||
}
|
||||
|
||||
impl DetermineDriver<CiDriverType> for Option<CiDriverType> {
|
||||
fn determine_driver(&mut self) -> CiDriverType {
|
||||
trace!("CiDriverType::determine_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
get_env_var(GITLAB_CI).ok(),
|
||||
get_env_var(GITHUB_ACTIONS).ok(),
|
||||
) {
|
||||
(Some(_gitlab_ci), None) => CiDriverType::Gitlab,
|
||||
(None, Some(_github_actions)) => CiDriverType::Github,
|
||||
_ => CiDriverType::Local,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum BootDriverType {
|
||||
#[cfg(feature = "bootc")]
|
||||
Bootc,
|
||||
RpmOstree,
|
||||
None,
|
||||
}
|
||||
|
||||
impl DetermineDriver<BootDriverType> for Option<BootDriverType> {
|
||||
fn determine_driver(&mut self) -> BootDriverType {
|
||||
trace!("BootDriverType::determine_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("bootc"),
|
||||
blue_build_utils::check_command_exists("rpm-ostree"),
|
||||
) {
|
||||
#[cfg(feature = "bootc")]
|
||||
(Ok(_bootc), _) => BootDriverType::Bootc,
|
||||
(_, Ok(_rpm_ostree)) => BootDriverType::RpmOstree,
|
||||
_ => BootDriverType::None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
30
process/drivers/types/metadata.rs
Normal file
30
process/drivers/types/metadata.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use blue_build_utils::{constants::IMAGE_VERSION_LABEL, semver::Version};
|
||||
use log::warn;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct ImageMetadata {
|
||||
pub labels: HashMap<String, Value>,
|
||||
pub digest: String,
|
||||
}
|
||||
|
||||
impl ImageMetadata {
|
||||
#[must_use]
|
||||
pub fn get_version(&self) -> Option<u64> {
|
||||
Some(
|
||||
self.labels
|
||||
.get(IMAGE_VERSION_LABEL)
|
||||
.map(ToOwned::to_owned)
|
||||
.and_then(|v| {
|
||||
serde_json::from_value::<Version>(v)
|
||||
.inspect_err(|e| warn!("Failed to parse version:\n{e}"))
|
||||
.ok()
|
||||
})?
|
||||
.major,
|
||||
)
|
||||
}
|
||||
}
|
||||
155
process/drivers/types/platform.rs
Normal file
155
process/drivers/types/platform.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use blue_build_utils::string;
|
||||
use clap::ValueEnum;
|
||||
|
||||
mod private {
|
||||
pub trait Private {}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Hash)]
|
||||
pub enum Platform {
|
||||
#[value(name = "linux/amd64")]
|
||||
LinuxAmd64,
|
||||
|
||||
#[value(name = "linux/amd64/v2")]
|
||||
LinuxAmd64V2,
|
||||
|
||||
#[value(name = "linux/arm64")]
|
||||
LinuxArm64,
|
||||
|
||||
#[value(name = "linux/arm")]
|
||||
LinuxArm,
|
||||
|
||||
#[value(name = "linux/arm/v6")]
|
||||
LinuxArmV6,
|
||||
|
||||
#[value(name = "linux/arm/v7")]
|
||||
LinuxArmV7,
|
||||
|
||||
#[value(name = "linux/386")]
|
||||
Linux386,
|
||||
|
||||
#[value(name = "linux/loong64")]
|
||||
LinuxLoong64,
|
||||
|
||||
#[value(name = "linux/mips")]
|
||||
LinuxMips,
|
||||
|
||||
#[value(name = "linux/mipsle")]
|
||||
LinuxMipsle,
|
||||
|
||||
#[value(name = "linux/mips64")]
|
||||
LinuxMips64,
|
||||
|
||||
#[value(name = "linux/mips64le")]
|
||||
LinuxMips64le,
|
||||
|
||||
#[value(name = "linux/ppc64")]
|
||||
LinuxPpc64,
|
||||
|
||||
#[value(name = "linux/ppc64le")]
|
||||
LinuxPpc64le,
|
||||
|
||||
#[value(name = "linux/riscv64")]
|
||||
LinuxRiscv64,
|
||||
|
||||
#[value(name = "linux/s390x")]
|
||||
LinuxS390x,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
/// The architecture of the platform.
|
||||
#[must_use]
|
||||
pub const fn arch(&self) -> &str {
|
||||
match *self {
|
||||
Self::LinuxAmd64 | Self::LinuxAmd64V2 => "amd64",
|
||||
Self::LinuxArm64 => "arm64",
|
||||
Self::LinuxArm | Self::LinuxArmV6 | Self::LinuxArmV7 => "arm",
|
||||
Self::Linux386 => "386",
|
||||
Self::LinuxLoong64 => "loong64",
|
||||
Self::LinuxMips => "mips",
|
||||
Self::LinuxMipsle => "mipsle",
|
||||
Self::LinuxMips64 => "mips64",
|
||||
Self::LinuxMips64le => "mips64le",
|
||||
Self::LinuxPpc64 => "ppc64",
|
||||
Self::LinuxPpc64le => "ppc64le",
|
||||
Self::LinuxRiscv64 => "riscv64",
|
||||
Self::LinuxS390x => "s390x",
|
||||
}
|
||||
}
|
||||
|
||||
/// The variant of the platform.
|
||||
#[must_use]
|
||||
pub const fn variant(&self) -> Option<&str> {
|
||||
match *self {
|
||||
Self::LinuxAmd64V2 => Some("v2"),
|
||||
Self::LinuxArmV6 => Some("v6"),
|
||||
Self::LinuxArmV7 => Some("v7"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Platform {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::LinuxAmd64 => "linux/amd64",
|
||||
Self::LinuxAmd64V2 => "linux/amd64/v2",
|
||||
Self::LinuxArm64 => "linux/arm64",
|
||||
Self::LinuxArm => "linux/arm",
|
||||
Self::LinuxArmV6 => "linux/arm/v6",
|
||||
Self::LinuxArmV7 => "linux/arm/v7",
|
||||
Self::Linux386 => "linux/386",
|
||||
Self::LinuxLoong64 => "linux/loong64",
|
||||
Self::LinuxMips => "linux/mips",
|
||||
Self::LinuxMipsle => "linux/mipsle",
|
||||
Self::LinuxMips64 => "linux/mips64",
|
||||
Self::LinuxMips64le => "linux/mips64le",
|
||||
Self::LinuxPpc64 => "linux/ppc64",
|
||||
Self::LinuxPpc64le => "linux/ppc64le",
|
||||
Self::LinuxRiscv64 => "linux/riscv64",
|
||||
Self::LinuxS390x => "linux/s390x",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl private::Private for Option<Platform> {}
|
||||
|
||||
pub trait PlatformInfo: private::Private {
|
||||
/// The string representation of the platform.
|
||||
///
|
||||
/// If `None`, then the native architecture will be used.
|
||||
fn to_string(&self) -> String;
|
||||
|
||||
/// The string representation of the architecture.
|
||||
///
|
||||
/// If `None`, then the native architecture will be used.
|
||||
fn arch(&self) -> &str;
|
||||
}
|
||||
|
||||
impl PlatformInfo for Option<Platform> {
|
||||
fn to_string(&self) -> String {
|
||||
self.map_or_else(
|
||||
|| match std::env::consts::ARCH {
|
||||
"x86_64" => string!("linux/amd64"),
|
||||
"aarch64" => string!("linux/arm64"),
|
||||
arch => unimplemented!("Arch {arch} is unsupported"),
|
||||
},
|
||||
|platform| format!("{platform}"),
|
||||
)
|
||||
}
|
||||
|
||||
fn arch(&self) -> &str {
|
||||
self.as_ref().map_or_else(
|
||||
|| match std::env::consts::ARCH {
|
||||
"x86_64" => "amd64",
|
||||
"aarch64" => "arm64",
|
||||
arch => unimplemented!("Arch {arch} is unsupported"),
|
||||
},
|
||||
Platform::arch,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ impl Recipe<'_> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_secrets(&self) -> HashSet<&Secret> {
|
||||
pub fn get_secrets(&self) -> Vec<&Secret> {
|
||||
self.modules_ext
|
||||
.modules
|
||||
.iter()
|
||||
|
|
@ -154,6 +154,8 @@ impl Recipe<'_> {
|
|||
.filter_map(|module| Some(&module.required_fields.as_ref()?.secrets))
|
||||
.flatten(),
|
||||
)
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,35 @@ color_string() {
|
|||
fi
|
||||
}
|
||||
|
||||
feature_enabled() {
|
||||
# Ensure the function is called with exactly one argument
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: feature_enabled <feature_name>" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local feature="$1"
|
||||
local -a features
|
||||
|
||||
# Split BB_BUILD_FEATURES by commas and read into an array
|
||||
IFS=,
|
||||
read -r -a features <<< "$BB_BUILD_FEATURES"
|
||||
|
||||
# Loop through the array and check for a match
|
||||
for f in "${features[@]}"; do
|
||||
# Trim leading and trailing whitespace
|
||||
local trimmed_f="${f## }"
|
||||
trimmed_f="${trimmed_f%% }"
|
||||
|
||||
if [[ "$trimmed_f" == "$feature" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Feature not found
|
||||
return 1
|
||||
}
|
||||
|
||||
# Parse OS version and export it
|
||||
export OS_VERSION="$(awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release)"
|
||||
export OS_ARCH="$(uname -m)"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
. /scripts/exports.sh
|
||||
|
||||
rm -rf /tmp/* /var/*
|
||||
|
||||
# if command -v bootc > /dev/null; then
|
||||
# bootc container lint
|
||||
# fi
|
||||
if feature_enabled "bootc" && command -v bootc > /dev/null; then
|
||||
bootc container lint
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -7,20 +7,18 @@ use blue_build_process_management::{
|
|||
BuildTagPushOpts, CheckKeyPairOpts, CompressionType, GenerateImageNameOpts,
|
||||
GenerateTagsOpts, SignVerifyOpts,
|
||||
},
|
||||
types::Platform,
|
||||
types::{ImageRef, Platform},
|
||||
},
|
||||
logging::{color_str, gen_random_ansi_color},
|
||||
};
|
||||
use blue_build_recipe::Recipe;
|
||||
use blue_build_utils::{
|
||||
constants::{
|
||||
ARCHIVE_SUFFIX, BB_REGISTRY_NAMESPACE, BB_SKIP_VALIDATION, CONFIG_PATH, CONTAINER_FILE,
|
||||
RECIPE_FILE, RECIPE_PATH,
|
||||
ARCHIVE_SUFFIX, BB_REGISTRY_NAMESPACE, BB_SKIP_VALIDATION, CONFIG_PATH, RECIPE_FILE,
|
||||
RECIPE_PATH,
|
||||
},
|
||||
cowstr,
|
||||
credentials::{Credentials, CredentialsArgs},
|
||||
string,
|
||||
traits::CowCollecter,
|
||||
};
|
||||
use bon::Builder;
|
||||
use clap::Args;
|
||||
|
|
@ -163,7 +161,7 @@ impl BlueBuildCommand for BuildCommand {
|
|||
|
||||
if self.push {
|
||||
blue_build_utils::check_command_exists("cosign")?;
|
||||
Driver::check_signing_files(&CheckKeyPairOpts::builder().dir(Path::new(".")).build())?;
|
||||
Driver::check_signing_files(CheckKeyPairOpts::builder().dir(Path::new(".")).build())?;
|
||||
Driver::login()?;
|
||||
Driver::signing_login()?;
|
||||
}
|
||||
|
|
@ -191,11 +189,11 @@ impl BlueBuildCommand for BuildCommand {
|
|||
|
||||
recipe_paths.par_iter().try_for_each(|recipe| {
|
||||
GenerateCommand::builder()
|
||||
.output(tempdir.path().join(if recipe_paths.len() > 1 {
|
||||
blue_build_utils::generate_containerfile_path(recipe)?
|
||||
} else {
|
||||
PathBuf::from(CONTAINER_FILE)
|
||||
}))
|
||||
.output(
|
||||
tempdir
|
||||
.path()
|
||||
.join(blue_build_utils::generate_containerfile_path(recipe)?),
|
||||
)
|
||||
.skip_validation(self.skip_validation)
|
||||
.maybe_platform(self.platform)
|
||||
.recipe(recipe)
|
||||
|
|
@ -217,12 +215,10 @@ impl BuildCommand {
|
|||
let images = recipe_paths
|
||||
.par_iter()
|
||||
.try_fold(Vec::new, |mut images, recipe_path| -> Result<Vec<String>> {
|
||||
let containerfile = temp_dir.join(if recipe_paths.len() > 1 {
|
||||
blue_build_utils::generate_containerfile_path(recipe_path)?
|
||||
} else {
|
||||
PathBuf::from(CONTAINER_FILE)
|
||||
});
|
||||
images.extend(self.build(recipe_path, &containerfile)?);
|
||||
images.extend(self.build(
|
||||
recipe_path,
|
||||
&temp_dir.join(blue_build_utils::generate_containerfile_path(recipe_path)?),
|
||||
)?);
|
||||
Ok(images)
|
||||
})
|
||||
.try_reduce(Vec::new, |mut init, image_names| {
|
||||
|
|
@ -245,9 +241,9 @@ impl BuildCommand {
|
|||
fn build(&self, recipe_path: &Path, containerfile: &Path) -> Result<Vec<String>> {
|
||||
let recipe = Recipe::parse(recipe_path)?;
|
||||
let tags = Driver::generate_tags(
|
||||
&GenerateTagsOpts::builder()
|
||||
GenerateTagsOpts::builder()
|
||||
.oci_ref(&recipe.base_image_ref()?)
|
||||
.maybe_alt_tags(recipe.alt_tags.as_ref().map(CowCollecter::collect_cow_vec))
|
||||
.maybe_alt_tags(recipe.alt_tags.as_deref())
|
||||
.maybe_platform(self.platform)
|
||||
.build(),
|
||||
)?;
|
||||
|
|
@ -276,14 +272,29 @@ impl BuildCommand {
|
|||
&image_name,
|
||||
cache_image.as_ref(),
|
||||
)?
|
||||
} else {
|
||||
Driver::build_tag_push(&self.archive.as_ref().map_or_else(
|
||||
|| {
|
||||
} else if let Some(archive_dir) = self.archive.as_ref() {
|
||||
Driver::build_tag_push(
|
||||
BuildTagPushOpts::builder()
|
||||
.image(&image)
|
||||
.containerfile(containerfile)
|
||||
.maybe_platform(self.platform)
|
||||
.tags(tags.collect_cow_vec())
|
||||
.image(&ImageRef::from(PathBuf::from(format!(
|
||||
"{}/{}.{ARCHIVE_SUFFIX}",
|
||||
archive_dir.to_string_lossy().trim_end_matches('/'),
|
||||
recipe.name.to_lowercase().replace('/', "_"),
|
||||
))))
|
||||
.squash(self.squash)
|
||||
.maybe_cache_from(cache_image.as_ref())
|
||||
.maybe_cache_to(cache_image.as_ref())
|
||||
.secrets(&recipe.get_secrets())
|
||||
.build(),
|
||||
)?
|
||||
} else {
|
||||
Driver::build_tag_push(
|
||||
BuildTagPushOpts::builder()
|
||||
.image(&ImageRef::from(&image))
|
||||
.containerfile(containerfile)
|
||||
.maybe_platform(self.platform)
|
||||
.tags(&tags)
|
||||
.push(self.push)
|
||||
.retry_push(self.retry_push)
|
||||
.retry_count(self.retry_count)
|
||||
|
|
@ -291,30 +302,14 @@ impl BuildCommand {
|
|||
.squash(self.squash)
|
||||
.maybe_cache_from(cache_image.as_ref())
|
||||
.maybe_cache_to(cache_image.as_ref())
|
||||
.secrets(recipe.get_secrets())
|
||||
.build()
|
||||
},
|
||||
|archive_dir| {
|
||||
BuildTagPushOpts::builder()
|
||||
.containerfile(containerfile)
|
||||
.maybe_platform(self.platform)
|
||||
.image(PathBuf::from(format!(
|
||||
"{}/{}.{ARCHIVE_SUFFIX}",
|
||||
archive_dir.to_string_lossy().trim_end_matches('/'),
|
||||
recipe.name.to_lowercase().replace('/', "_"),
|
||||
)))
|
||||
.squash(self.squash)
|
||||
.maybe_cache_from(cache_image.as_ref())
|
||||
.maybe_cache_to(cache_image.as_ref())
|
||||
.secrets(recipe.get_secrets())
|
||||
.build()
|
||||
},
|
||||
))?
|
||||
.secrets(&recipe.get_secrets())
|
||||
.build(),
|
||||
)?
|
||||
};
|
||||
|
||||
if self.push && !self.no_sign {
|
||||
Driver::sign_and_verify(
|
||||
&SignVerifyOpts::builder()
|
||||
SignVerifyOpts::builder()
|
||||
.image(&image)
|
||||
.retry_push(self.retry_push)
|
||||
.retry_count(self.retry_count)
|
||||
|
|
@ -342,13 +337,13 @@ impl BuildCommand {
|
|||
.parse()
|
||||
.into_diagnostic()?;
|
||||
Driver::rechunk(
|
||||
&RechunkOpts::builder()
|
||||
RechunkOpts::builder()
|
||||
.image(image_name)
|
||||
.containerfile(containerfile)
|
||||
.maybe_platform(self.platform)
|
||||
.tags(tags.collect_cow_vec())
|
||||
.tags(tags)
|
||||
.push(self.push)
|
||||
.version(format!(
|
||||
.version(&format!(
|
||||
"{version}.<date>",
|
||||
version = Driver::get_os_version()
|
||||
.oci_ref(&recipe.base_image_ref()?)
|
||||
|
|
@ -359,23 +354,23 @@ impl BuildCommand {
|
|||
.retry_count(self.retry_count)
|
||||
.compression(self.compression_format)
|
||||
.base_digest(
|
||||
Driver::get_metadata(
|
||||
&GetMetadataOpts::builder()
|
||||
&Driver::get_metadata(
|
||||
GetMetadataOpts::builder()
|
||||
.image(&base_image)
|
||||
.maybe_platform(self.platform)
|
||||
.build(),
|
||||
)?
|
||||
.digest,
|
||||
)
|
||||
.repo(Driver::get_repo_url()?)
|
||||
.name(&*recipe.name)
|
||||
.description(&*recipe.description)
|
||||
.base_image(format!("{}:{}", &recipe.base_image, &recipe.image_version))
|
||||
.repo(&Driver::get_repo_url()?)
|
||||
.name(&recipe.name)
|
||||
.description(&recipe.description)
|
||||
.base_image(&format!("{}:{}", &recipe.base_image, &recipe.image_version))
|
||||
.maybe_tempdir(self.tempdir.as_deref())
|
||||
.clear_plan(self.rechunk_clear_plan)
|
||||
.maybe_cache_from(cache_image)
|
||||
.maybe_cache_to(cache_image)
|
||||
.secrets(recipe.get_secrets())
|
||||
.secrets(&recipe.get_secrets())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
|
@ -384,8 +379,8 @@ impl BuildCommand {
|
|||
let image_name = Driver::generate_image_name(
|
||||
GenerateImageNameOpts::builder()
|
||||
.name(recipe.name.trim())
|
||||
.maybe_registry(self.credentials.registry.as_ref().map(|r| cowstr!(r)))
|
||||
.maybe_registry_namespace(self.registry_namespace.as_ref().map(|r| cowstr!(r)))
|
||||
.maybe_registry(self.credentials.registry.as_deref())
|
||||
.maybe_registry_namespace(self.registry_namespace.as_deref())
|
||||
.build(),
|
||||
)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -142,6 +142,19 @@ impl GenerateCommand {
|
|||
let base_image: Reference = format!("{}:{}", &recipe.base_image, &recipe.image_version)
|
||||
.parse()
|
||||
.into_diagnostic()?;
|
||||
let base_digest = &Driver::get_metadata(
|
||||
GetMetadataOpts::builder()
|
||||
.image(&base_image)
|
||||
.maybe_platform(self.platform)
|
||||
.build(),
|
||||
)?
|
||||
.digest;
|
||||
let build_scripts_image = &determine_scripts_tag(self.platform)?;
|
||||
let repo = &Driver::get_repo_url()?;
|
||||
let build_features = &[
|
||||
#[cfg(feature = "bootc")]
|
||||
"bootc".into(),
|
||||
];
|
||||
|
||||
let template = ContainerFileTemplate::builder()
|
||||
.os_version(
|
||||
|
|
@ -153,19 +166,12 @@ impl GenerateCommand {
|
|||
.build_id(Driver::get_build_id())
|
||||
.recipe(&recipe)
|
||||
.recipe_path(recipe_path.as_path())
|
||||
.registry(registry)
|
||||
.repo(Driver::get_repo_url()?)
|
||||
.build_scripts_image(determine_scripts_tag(self.platform)?.to_string())
|
||||
.base_digest(
|
||||
Driver::get_metadata(
|
||||
&GetMetadataOpts::builder()
|
||||
.image(&base_image)
|
||||
.maybe_platform(self.platform)
|
||||
.build(),
|
||||
)?
|
||||
.digest,
|
||||
)
|
||||
.registry(®istry)
|
||||
.repo(repo)
|
||||
.build_scripts_image(build_scripts_image)
|
||||
.base_digest(base_digest)
|
||||
.maybe_nushell_version(recipe.nushell_version.as_ref())
|
||||
.build_features(build_features)
|
||||
.build();
|
||||
|
||||
let output_str = template.render().into_diagnostic()?;
|
||||
|
|
@ -197,7 +203,7 @@ fn determine_scripts_tag(platform: Option<Platform>) -> Result<Reference> {
|
|||
.parse()
|
||||
.into_diagnostic()
|
||||
.and_then(|image| {
|
||||
Driver::get_metadata(&opts.clone().image(&image).build())
|
||||
Driver::get_metadata(opts.clone().image(&image).build())
|
||||
.inspect_err(|e| trace!("{e:?}"))
|
||||
.map(|_| image)
|
||||
})
|
||||
|
|
@ -205,7 +211,7 @@ fn determine_scripts_tag(platform: Option<Platform>) -> Result<Reference> {
|
|||
let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:{}", shadow::BRANCH)
|
||||
.parse()
|
||||
.into_diagnostic()?;
|
||||
Driver::get_metadata(&opts.clone().image(&image).build())
|
||||
Driver::get_metadata(opts.clone().image(&image).build())
|
||||
.inspect_err(|e| trace!("{e:?}"))
|
||||
.map(|_| image)
|
||||
})
|
||||
|
|
@ -213,7 +219,7 @@ fn determine_scripts_tag(platform: Option<Platform>) -> Result<Reference> {
|
|||
let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:v{}", crate_version!())
|
||||
.parse()
|
||||
.into_diagnostic()?;
|
||||
Driver::get_metadata(&opts.image(&image).build())
|
||||
Driver::get_metadata(opts.image(&image).build())
|
||||
.inspect_err(|e| trace!("{e:?}"))
|
||||
.map(|_| image)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ use blue_build_recipe::Recipe;
|
|||
use blue_build_utils::{
|
||||
constants::{ARCHIVE_SUFFIX, BB_SKIP_VALIDATION},
|
||||
string_vec,
|
||||
traits::CowCollecter,
|
||||
};
|
||||
use bon::Builder;
|
||||
use clap::{Args, Subcommand, ValueEnum};
|
||||
|
|
@ -189,8 +188,10 @@ impl GenerateIsoCommand {
|
|||
format!("SECURE_BOOT_KEY_URL={}", self.secure_boot_url),
|
||||
format!("ENROLLMENT_PASSWORD={}", self.enrollment_password),
|
||||
];
|
||||
let image_out_dir = &image_out_dir.display().to_string();
|
||||
let output_dir = &output_dir.display().to_string();
|
||||
let mut vols = run_volumes![
|
||||
output_dir.display().to_string() => "/build-container-installer/build",
|
||||
output_dir => "/build-container-installer/build",
|
||||
"dnf-cache" => "/cache/dnf/",
|
||||
];
|
||||
|
||||
|
|
@ -239,8 +240,8 @@ impl GenerateIsoCommand {
|
|||
.call()?,
|
||||
),
|
||||
]);
|
||||
vols.extend(run_volumes![
|
||||
image_out_dir.display().to_string() => "/img_src/",
|
||||
vols.extend(&run_volumes![
|
||||
image_out_dir => "/img_src/",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -250,11 +251,11 @@ impl GenerateIsoCommand {
|
|||
.image("ghcr.io/jasonn3/build-container-installer")
|
||||
.privileged(true)
|
||||
.remove(true)
|
||||
.args(args.collect_cow_vec())
|
||||
.volumes(vols)
|
||||
.args(&args)
|
||||
.volumes(&vols)
|
||||
.build();
|
||||
|
||||
let status = Driver::run(&opts)?;
|
||||
let status = Driver::run(opts)?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to create ISO");
|
||||
|
|
|
|||
|
|
@ -518,8 +518,8 @@ impl InitCommand {
|
|||
.with_context(|| format!("Failed to delete old public file {COSIGN_PUB_PATH}"))?;
|
||||
|
||||
Driver::generate_key_pair(
|
||||
&GenerateKeyPairOpts::builder()
|
||||
.maybe_dir(self.dir.as_ref())
|
||||
GenerateKeyPairOpts::builder()
|
||||
.maybe_dir(self.dir.as_deref())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ impl BlueBuildCommand for PruneCommand {
|
|||
}
|
||||
|
||||
Driver::prune(
|
||||
&PruneOpts::builder()
|
||||
PruneOpts::builder()
|
||||
.all(self.all)
|
||||
.volumes(self.volumes)
|
||||
.build(),
|
||||
|
|
|
|||
|
|
@ -1,29 +1,19 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use blue_build_process_management::{
|
||||
drivers::{Driver, DriverArgs},
|
||||
logging::CommandLogging,
|
||||
use blue_build_process_management::drivers::{
|
||||
BootDriver, BuildDriver, CiDriver, Driver, DriverArgs, PodmanDriver, RunDriver,
|
||||
opts::{BuildOpts, GenerateImageNameOpts, RemoveImageOpts, SwitchOpts},
|
||||
types::ImageRef,
|
||||
};
|
||||
use blue_build_recipe::Recipe;
|
||||
use blue_build_utils::{
|
||||
constants::{
|
||||
ARCHIVE_SUFFIX, BB_SKIP_VALIDATION, LOCAL_BUILD, OCI_ARCHIVE, OSTREE_UNVERIFIED_IMAGE,
|
||||
SUDO_ASKPASS,
|
||||
},
|
||||
has_env_var, running_as_root,
|
||||
};
|
||||
use blue_build_utils::constants::BB_SKIP_VALIDATION;
|
||||
use bon::Builder;
|
||||
use clap::Args;
|
||||
use comlexr::cmd;
|
||||
use indicatif::ProgressBar;
|
||||
use log::{debug, trace};
|
||||
use log::trace;
|
||||
use miette::{IntoDiagnostic, Result, bail};
|
||||
use tempfile::TempDir;
|
||||
|
||||
use crate::{commands::build::BuildCommand, rpm_ostree_status::RpmOstreeStatus};
|
||||
use crate::commands::generate::GenerateCommand;
|
||||
|
||||
use super::BlueBuildCommand;
|
||||
|
||||
|
|
@ -60,238 +50,59 @@ impl BlueBuildCommand for SwitchCommand {
|
|||
|
||||
Driver::init(self.drivers);
|
||||
|
||||
let status = RpmOstreeStatus::try_new()?;
|
||||
trace!("{status:?}");
|
||||
let status = Driver::status()?;
|
||||
|
||||
if status.transaction_in_progress() {
|
||||
bail!("There is a transaction in progress. Please cancel it using `rpm-ostree cancel`");
|
||||
}
|
||||
|
||||
let recipe = Recipe::parse(&self.recipe)?;
|
||||
let image_name = Driver::generate_image_name(
|
||||
GenerateImageNameOpts::builder()
|
||||
.name(recipe.name.trim())
|
||||
.build(),
|
||||
)?;
|
||||
let tempdir = if let Some(ref dir) = self.tempdir {
|
||||
TempDir::new_in(dir).into_diagnostic()?
|
||||
} else {
|
||||
TempDir::new().into_diagnostic()?
|
||||
};
|
||||
trace!("{tempdir:?}");
|
||||
let containerfile = tempdir
|
||||
.path()
|
||||
.join(blue_build_utils::generate_containerfile_path(&self.recipe)?);
|
||||
|
||||
BuildCommand::builder()
|
||||
.recipe([self.recipe.clone()])
|
||||
.archive(tempdir.path())
|
||||
.maybe_tempdir(self.tempdir.clone())
|
||||
.skip_validation(self.skip_validation)
|
||||
GenerateCommand::builder()
|
||||
.output(&containerfile)
|
||||
.recipe(&self.recipe)
|
||||
.build()
|
||||
.try_run()?;
|
||||
PodmanDriver::build(
|
||||
BuildOpts::builder()
|
||||
.image(&ImageRef::from(&image_name))
|
||||
.containerfile(&containerfile)
|
||||
.secrets(&recipe.get_secrets())
|
||||
.build(),
|
||||
)?;
|
||||
PodmanDriver::copy_image_to_root_store(&image_name)?;
|
||||
PodmanDriver::remove_image(RemoveImageOpts::builder().image(&image_name).build())?;
|
||||
|
||||
let recipe = Recipe::parse(&self.recipe)?;
|
||||
let image_file_name = format!(
|
||||
"{}.{ARCHIVE_SUFFIX}",
|
||||
recipe.name.to_lowercase().replace('/', "_")
|
||||
);
|
||||
let temp_file_path = tempdir.path().join(&image_file_name);
|
||||
let archive_path = Path::new(LOCAL_BUILD).join(&image_file_name);
|
||||
|
||||
Self::clean_local_build_dir()?;
|
||||
Self::move_archive(&temp_file_path, &archive_path)?;
|
||||
|
||||
// We drop the tempdir ahead of time so that the directory
|
||||
// can be cleaned out.
|
||||
drop(tempdir);
|
||||
|
||||
self.switch(&archive_path, &status)
|
||||
}
|
||||
}
|
||||
|
||||
impl SwitchCommand {
|
||||
fn switch(&self, archive_path: &Path, status: &RpmOstreeStatus<'_>) -> Result<()> {
|
||||
trace!(
|
||||
"SwitchCommand::switch({}, {status:#?})",
|
||||
archive_path.display()
|
||||
);
|
||||
|
||||
let status = if status.is_booted_on_archive(archive_path)
|
||||
|| status.is_staged_on_archive(archive_path)
|
||||
if status
|
||||
.booted_image()
|
||||
.is_some_and(|booted| booted == image_name)
|
||||
{
|
||||
let command = cmd!("rpm-ostree", "upgrade", if self.reboot => "--reboot");
|
||||
|
||||
trace!("{command:?}");
|
||||
command
|
||||
} else {
|
||||
let image_ref = format!(
|
||||
"{OSTREE_UNVERIFIED_IMAGE}:{OCI_ARCHIVE}:{path}",
|
||||
path = archive_path.display()
|
||||
);
|
||||
|
||||
let command = cmd!(
|
||||
"rpm-ostree",
|
||||
"rebase",
|
||||
&image_ref,
|
||||
if self.reboot => "--reboot",
|
||||
);
|
||||
|
||||
trace!("{command:?}");
|
||||
command
|
||||
}
|
||||
.build_status(
|
||||
format!("{}", archive_path.display()),
|
||||
"Switching to new image",
|
||||
Driver::upgrade(
|
||||
SwitchOpts::builder()
|
||||
.image(&image_name)
|
||||
.reboot(self.reboot)
|
||||
.build(),
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to switch to new image!");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_archive(from: &Path, to: &Path) -> Result<()> {
|
||||
trace!(
|
||||
"SwitchCommand::move_archive({}, {})",
|
||||
from.display(),
|
||||
to.display()
|
||||
);
|
||||
|
||||
let progress = ProgressBar::new_spinner();
|
||||
progress.enable_steady_tick(Duration::from_millis(100));
|
||||
progress.set_message(format!("Moving image archive to {}...", to.display()));
|
||||
|
||||
let status = {
|
||||
let c = cmd!(
|
||||
if running_as_root() {
|
||||
"mv"
|
||||
} else {
|
||||
"sudo"
|
||||
},
|
||||
if !running_as_root() && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
format!("Password needed to move {from:?} to {to:?}"),
|
||||
],
|
||||
if !running_as_root() => "mv",
|
||||
from,
|
||||
to,
|
||||
);
|
||||
trace!("{c:?}");
|
||||
c
|
||||
Driver::switch(
|
||||
SwitchOpts::builder()
|
||||
.image(&image_name)
|
||||
.reboot(self.reboot)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
.status()
|
||||
.into_diagnostic()?;
|
||||
|
||||
progress.finish_and_clear();
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"Failed to move archive from {from} to {to}",
|
||||
from = from.display(),
|
||||
to = to.display()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clean_local_build_dir() -> Result<()> {
|
||||
trace!("SwitchCommand::clean_local_build_dir()");
|
||||
|
||||
let local_build_path = Path::new(LOCAL_BUILD);
|
||||
|
||||
if local_build_path.exists() {
|
||||
debug!("Cleaning out build dir {LOCAL_BUILD}");
|
||||
|
||||
let mut command = {
|
||||
let c = cmd!(
|
||||
if running_as_root() {
|
||||
"ls"
|
||||
} else {
|
||||
"sudo"
|
||||
},
|
||||
if !running_as_root() && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
format!("Password required to list files in {LOCAL_BUILD}"),
|
||||
],
|
||||
if !running_as_root() => "ls",
|
||||
LOCAL_BUILD
|
||||
);
|
||||
trace!("{c:?}");
|
||||
c
|
||||
};
|
||||
let output =
|
||||
String::from_utf8(command.output().into_diagnostic()?.stdout).into_diagnostic()?;
|
||||
|
||||
trace!("{output}");
|
||||
|
||||
let files = output
|
||||
.lines()
|
||||
.filter(|line| line.ends_with(ARCHIVE_SUFFIX))
|
||||
.map(|file| local_build_path.join(file).display().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !files.is_empty() {
|
||||
let progress = ProgressBar::new_spinner();
|
||||
progress.enable_steady_tick(Duration::from_millis(100));
|
||||
progress.set_message("Removing old image archive files...");
|
||||
|
||||
let status = {
|
||||
let c = cmd!(
|
||||
if running_as_root() {
|
||||
"rm"
|
||||
} else {
|
||||
"sudo"
|
||||
},
|
||||
if !running_as_root() && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
format!("Password required to remove files: {files:?}"),
|
||||
],
|
||||
if !running_as_root() => "rm",
|
||||
"-f",
|
||||
for files,
|
||||
);
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.status()
|
||||
.into_diagnostic()?;
|
||||
|
||||
progress.finish_and_clear();
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to clean out archives in {LOCAL_BUILD}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"Creating build output dir at {}",
|
||||
local_build_path.display()
|
||||
);
|
||||
|
||||
let status = {
|
||||
let c = cmd!(
|
||||
if running_as_root() {
|
||||
"mkdir"
|
||||
} else {
|
||||
"sudo"
|
||||
},
|
||||
if !running_as_root() && has_env_var(SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
format!("Password needed to create directory {local_build_path:?}"),
|
||||
],
|
||||
if !running_as_root() => "mkdir",
|
||||
"-p",
|
||||
local_build_path,
|
||||
);
|
||||
trace!("{c:?}");
|
||||
c
|
||||
}
|
||||
.status()
|
||||
.into_diagnostic()?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to create directory {LOCAL_BUILD}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@
|
|||
shadow_rs::shadow!(shadow);
|
||||
|
||||
pub mod commands;
|
||||
pub mod rpm_ostree_status;
|
||||
|
|
|
|||
|
|
@ -1,256 +0,0 @@
|
|||
use std::{borrow::Cow, path::Path};
|
||||
|
||||
use comlexr::cmd;
|
||||
use log::trace;
|
||||
use miette::{IntoDiagnostic, Result, bail};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RpmOstreeStatus<'a> {
|
||||
deployments: Cow<'a, [RpmOstreeDeployments<'a>]>,
|
||||
transactions: Option<Cow<'a, [Cow<'a, str>]>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct RpmOstreeDeployments<'a> {
|
||||
container_image_reference: Cow<'a, str>,
|
||||
booted: bool,
|
||||
staged: bool,
|
||||
}
|
||||
|
||||
impl RpmOstreeStatus<'_> {
|
||||
/// Creates a status struct for `rpm-ostree`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Errors if the command fails or deserialization fails.
|
||||
pub fn try_new() -> Result<Self> {
|
||||
blue_build_utils::check_command_exists("rpm-ostree")?;
|
||||
|
||||
trace!("rpm-ostree status --json");
|
||||
let output = cmd!("rpm-ostree", "status", "--json")
|
||||
.output()
|
||||
.into_diagnostic()?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("Failed to get `rpm-ostree` status!");
|
||||
}
|
||||
|
||||
trace!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
|
||||
serde_json::from_slice(&output.stdout).into_diagnostic()
|
||||
}
|
||||
|
||||
/// Checks if there is a transaction in progress.
|
||||
#[must_use]
|
||||
pub fn transaction_in_progress(&self) -> bool {
|
||||
self.transactions.as_ref().is_some_and(|tr| !tr.is_empty())
|
||||
}
|
||||
|
||||
/// Get the booted image's reference.
|
||||
#[must_use]
|
||||
pub fn booted_image(&self) -> Option<String> {
|
||||
Some(
|
||||
self.deployments
|
||||
.iter()
|
||||
.find(|deployment| deployment.booted)?
|
||||
.container_image_reference
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the booted image's reference.
|
||||
#[must_use]
|
||||
pub fn staged_image(&self) -> Option<String> {
|
||||
Some(
|
||||
self.deployments
|
||||
.iter()
|
||||
.find(|deployment| deployment.staged)?
|
||||
.container_image_reference
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_booted_on_archive<P>(&self, archive_path: P) -> bool
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.booted_image().is_some_and(|deployment| {
|
||||
deployment
|
||||
.split(':')
|
||||
.next_back()
|
||||
.is_some_and(|boot_ref| Path::new(boot_ref) == archive_path.as_ref())
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_staged_on_archive<P>(&self, archive_path: P) -> bool
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.staged_image().is_some_and(|deployment| {
|
||||
deployment
|
||||
.split(':')
|
||||
.next_back()
|
||||
.is_some_and(|boot_ref| Path::new(boot_ref) == archive_path.as_ref())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::path::Path;
|
||||
|
||||
use blue_build_utils::constants::{
|
||||
ARCHIVE_SUFFIX, LOCAL_BUILD, OCI_ARCHIVE, OSTREE_IMAGE_SIGNED, OSTREE_UNVERIFIED_IMAGE,
|
||||
};
|
||||
|
||||
use super::{RpmOstreeDeployments, RpmOstreeStatus};
|
||||
|
||||
fn create_image_status<'a>() -> RpmOstreeStatus<'a> {
|
||||
RpmOstreeStatus {
|
||||
deployments: vec![
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test"
|
||||
)
|
||||
.into(),
|
||||
booted: true,
|
||||
staged: false,
|
||||
},
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test:last"
|
||||
)
|
||||
.into(),
|
||||
booted: false,
|
||||
staged: false,
|
||||
},
|
||||
]
|
||||
.into(),
|
||||
transactions: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_transaction_status<'a>() -> RpmOstreeStatus<'a> {
|
||||
RpmOstreeStatus {
|
||||
deployments: vec![
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test"
|
||||
)
|
||||
.into(),
|
||||
booted: true,
|
||||
staged: false,
|
||||
},
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference: format!(
|
||||
"{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test:last"
|
||||
)
|
||||
.into(),
|
||||
booted: false,
|
||||
staged: false,
|
||||
},
|
||||
]
|
||||
.into(),
|
||||
transactions: Some(vec!["Upgrade".into(), "/".into()].into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_archive_status<'a>() -> RpmOstreeStatus<'a> {
|
||||
RpmOstreeStatus {
|
||||
deployments: vec![
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference:
|
||||
format!("{OSTREE_UNVERIFIED_IMAGE}:{OCI_ARCHIVE}:{LOCAL_BUILD}/cli_test.{ARCHIVE_SUFFIX}").into(),
|
||||
booted: true,
|
||||
staged: false,
|
||||
},
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference:
|
||||
format!("{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test:last").into(),
|
||||
booted: false,
|
||||
staged: false,
|
||||
},
|
||||
]
|
||||
.into(),
|
||||
transactions: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_archive_staged_status<'a>() -> RpmOstreeStatus<'a> {
|
||||
RpmOstreeStatus {
|
||||
deployments: vec![
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference:
|
||||
format!("{OSTREE_UNVERIFIED_IMAGE}:{OCI_ARCHIVE}:{LOCAL_BUILD}/cli_test.{ARCHIVE_SUFFIX}").into(),
|
||||
booted: false,
|
||||
staged: true,
|
||||
},
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference:
|
||||
format!("{OSTREE_UNVERIFIED_IMAGE}:{OCI_ARCHIVE}:{LOCAL_BUILD}/cli_test.{ARCHIVE_SUFFIX}").into(),
|
||||
booted: true,
|
||||
staged: false,
|
||||
},
|
||||
RpmOstreeDeployments {
|
||||
container_image_reference:
|
||||
format!("{OSTREE_IMAGE_SIGNED}:docker://ghcr.io/blue-build/cli/test:last").into(),
|
||||
booted: false,
|
||||
staged: false,
|
||||
},
|
||||
]
|
||||
.into(),
|
||||
transactions: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_booted_image() {
|
||||
assert!(
|
||||
create_image_status()
|
||||
.booted_image()
|
||||
.expect("Contains image")
|
||||
.ends_with("cli/test")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_staged_image() {
|
||||
assert!(
|
||||
create_archive_staged_status()
|
||||
.staged_image()
|
||||
.expect("Contains image")
|
||||
.ends_with(&format!("cli_test.{ARCHIVE_SUFFIX}"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_in_progress() {
|
||||
assert!(create_transaction_status().transaction_in_progress());
|
||||
assert!(!create_image_status().transaction_in_progress());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_booted_archive() {
|
||||
assert!(
|
||||
!create_archive_status()
|
||||
.is_booted_on_archive(Path::new(LOCAL_BUILD).join(format!("cli.{ARCHIVE_SUFFIX}")))
|
||||
);
|
||||
assert!(create_archive_status().is_booted_on_archive(
|
||||
Path::new(LOCAL_BUILD).join(format!("cli_test.{ARCHIVE_SUFFIX}"))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_staged_archive() {
|
||||
assert!(
|
||||
!create_archive_staged_status()
|
||||
.is_staged_on_archive(Path::new(LOCAL_BUILD).join(format!("cli.{ARCHIVE_SUFFIX}")))
|
||||
);
|
||||
assert!(create_archive_staged_status().is_staged_on_archive(
|
||||
Path::new(LOCAL_BUILD).join(format!("cli_test.{ARCHIVE_SUFFIX}"))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ license.workspace = true
|
|||
askama = { version = "0.14", features = ["serde_json"] }
|
||||
blue-build-recipe = { version = "=0.9.22", path = "../recipe" }
|
||||
blue-build-utils = { version = "=0.9.22", path = "../utils" }
|
||||
oci-distribution.workspace = true
|
||||
|
||||
chrono.workspace = true
|
||||
log.workspace = true
|
||||
|
|
|
|||
|
|
@ -9,28 +9,29 @@ use bon::Builder;
|
|||
use chrono::Utc;
|
||||
use colored::control::ShouldColorize;
|
||||
use log::{debug, error, trace, warn};
|
||||
use oci_distribution::Reference;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use askama::Template;
|
||||
|
||||
#[derive(Debug, Clone, Template, Builder)]
|
||||
#[template(path = "Containerfile.j2", escape = "none", whitespace = "minimize")]
|
||||
#[builder(on(Cow<'_, str>, into))]
|
||||
pub struct ContainerFileTemplate<'a> {
|
||||
#[builder(into)]
|
||||
recipe: &'a Recipe<'a>,
|
||||
|
||||
#[builder(into)]
|
||||
recipe_path: Cow<'a, Path>,
|
||||
recipe_path: &'a Path,
|
||||
|
||||
#[builder(into)]
|
||||
build_id: Uuid,
|
||||
os_version: u64,
|
||||
registry: Cow<'a, str>,
|
||||
build_scripts_image: Cow<'a, str>,
|
||||
repo: Cow<'a, str>,
|
||||
base_digest: Cow<'a, str>,
|
||||
registry: &'a str,
|
||||
build_scripts_image: &'a Reference,
|
||||
repo: &'a str,
|
||||
base_digest: &'a str,
|
||||
nushell_version: Option<&'a MaybeVersion>,
|
||||
|
||||
#[builder(default)]
|
||||
build_features: &'a [String],
|
||||
}
|
||||
|
||||
impl ContainerFileTemplate<'_> {
|
||||
|
|
@ -47,6 +48,15 @@ impl ContainerFileTemplate<'_> {
|
|||
Some(MaybeVersion::Version(version)) => version.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn get_features(&self) -> String {
|
||||
self.build_features
|
||||
.iter()
|
||||
.map(|feat| feat.trim())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Template, Builder)]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ FROM {{ recipe.base_image }}@{{ base_digest }} AS {{ main_stage }}
|
|||
|
||||
ARG RECIPE={{ recipe_path.display() }}
|
||||
ARG IMAGE_REGISTRY={{ registry }}
|
||||
ARG BB_BUILD_FEATURES="{{ get_features() }}"
|
||||
|
||||
{%- if self::config_dir_exists() && !self::files_dir_exists() %}
|
||||
ARG CONFIG_DIRECTORY="/tmp/config"
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ pub const OSTREE_IMAGE_SIGNED: &str = "ostree-image-signed";
|
|||
pub const OSTREE_UNVERIFIED_IMAGE: &str = "ostree-unverified-image";
|
||||
pub const SKOPEO_IMAGE: &str = "quay.io/skopeo/stable:latest";
|
||||
pub const TEMPLATE_REPO_URL: &str = "https://github.com/blue-build/template.git";
|
||||
pub const USER: &str = "USER";
|
||||
pub const UNKNOWN_SHELL: &str = "<unknown shell>";
|
||||
pub const UNKNOWN_VERSION: &str = "<unknown version>";
|
||||
pub const UNKNOWN_TERMINAL: &str = "<unknown terminal>";
|
||||
|
|
@ -110,3 +111,4 @@ pub const STAGE_SCHEMA: &str = concat!(JSON_SCHEMA, "/stage-v1.json");
|
|||
// Messages
|
||||
pub const BUG_REPORT_WARNING_MESSAGE: &str =
|
||||
"Please copy the above report and open an issue manually.";
|
||||
pub const SUDO_PROMPT: &str = "Bluebuild requires your password for sudo operation";
|
||||
|
|
|
|||
|
|
@ -39,3 +39,155 @@ macro_rules! cowstr_vec {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_de_fromstr {
|
||||
($($typ:ty),* $(,)?) => {
|
||||
$(
|
||||
impl TryFrom<&str> for $typ {
|
||||
type Error = miette::Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
value.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&String> for $typ {
|
||||
type Error = miette::Error;
|
||||
|
||||
fn try_from(value: &String) -> Result<Self, Self::Error> {
|
||||
Self::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for $typ {
|
||||
type Error = miette::Error;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Self::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::de::Deserialize<'de> for $typ {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Self::try_from(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! sudo_cmd {
|
||||
(
|
||||
prompt = $prompt:expr,
|
||||
sudo_check = $sudo_check:expr,
|
||||
$command:expr,
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
{
|
||||
let _use_sudo = ($sudo_check) && !$crate::running_as_root();
|
||||
|
||||
::comlexr::cmd!(
|
||||
if _use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
$command
|
||||
},
|
||||
if _use_sudo && $crate::has_env_var($crate::constants::SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
$prompt,
|
||||
],
|
||||
if _use_sudo => [
|
||||
"--preserve-env",
|
||||
$command,
|
||||
],
|
||||
$($rest)*
|
||||
)
|
||||
}
|
||||
};
|
||||
(
|
||||
sudo_check = $sudo_check:expr,
|
||||
$command:expr,
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
{
|
||||
let _use_sudo = ($sudo_check) && !$crate::running_as_root();
|
||||
|
||||
::comlexr::cmd!(
|
||||
if _use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
$command
|
||||
},
|
||||
if _use_sudo && $crate::has_env_var($crate::constants::SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
$crate::constants::SUDO_PROMPT,
|
||||
],
|
||||
if _use_sudo => [
|
||||
"--preserve-env",
|
||||
$command,
|
||||
],
|
||||
$($rest)*
|
||||
)
|
||||
}
|
||||
};
|
||||
(
|
||||
prompt = $prompt:expr,
|
||||
$command:expr,
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
{
|
||||
let _use_sudo = !$crate::running_as_root();
|
||||
|
||||
::comlexr::cmd!(
|
||||
if _use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
$command
|
||||
},
|
||||
if _use_sudo && $crate::has_env_var($crate::constants::SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
$prompt,
|
||||
],
|
||||
if _use_sudo => [
|
||||
"--preserve-env",
|
||||
$command,
|
||||
],
|
||||
$($rest)*
|
||||
)
|
||||
}
|
||||
};
|
||||
(
|
||||
$command:expr,
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
{
|
||||
let _use_sudo = !$crate::running_as_root();
|
||||
|
||||
::comlexr::cmd!(
|
||||
if _use_sudo {
|
||||
"sudo"
|
||||
} else {
|
||||
$command
|
||||
},
|
||||
if _use_sudo && $crate::has_env_var($crate::constants::SUDO_ASKPASS) => [
|
||||
"-A",
|
||||
"-p",
|
||||
$crate::constants::SUDO_PROMPT,
|
||||
],
|
||||
if _use_sudo => [
|
||||
"--preserve-env",
|
||||
$command,
|
||||
],
|
||||
$($rest)*
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
ops::Not,
|
||||
|
|
@ -121,7 +120,7 @@ impl SecretMounts for Vec<Secret> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<H: std::hash::BuildHasher> private::Private for HashSet<&Secret, H> {}
|
||||
impl private::Private for &[&Secret] {}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
pub trait SecretArgs: private::Private {
|
||||
|
|
@ -138,7 +137,7 @@ pub trait SecretArgs: private::Private {
|
|||
fn ssh(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<H: std::hash::BuildHasher> SecretArgs for HashSet<&Secret, H> {
|
||||
impl SecretArgs for &[&Secret] {
|
||||
fn args(&self, temp_dir: &TempDir) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.iter()
|
||||
|
|
@ -173,7 +172,7 @@ impl<H: std::hash::BuildHasher> SecretArgs for HashSet<&Secret, H> {
|
|||
}
|
||||
|
||||
fn ssh(&self) -> bool {
|
||||
self.contains(&Secret::Ssh)
|
||||
self.contains(&&Secret::Ssh)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue