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:
|
arm64-build:
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
# runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
@ -218,8 +218,8 @@ jobs:
|
||||||
- name: Maximize build space
|
- name: Maximize build space
|
||||||
uses: ublue-os/remove-unwanted-software@cc0becac701cf642c8f0a6613bbdaf5dc36b259e # v9
|
uses: ublue-os/remove-unwanted-software@cc0becac701cf642c8f0a6613bbdaf5dc36b259e # v9
|
||||||
|
|
||||||
- name: Set up QEMU
|
# - name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
# uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||||
|
|
||||||
- uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
|
- 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"
|
comlexr = "1"
|
||||||
indexmap = { version = "2", features = ["serde"] }
|
indexmap = { version = "2", features = ["serde"] }
|
||||||
indicatif = { version = "0.18", features = ["improved_unicode", "rayon"] }
|
indicatif = { version = "0.18", features = ["improved_unicode", "rayon"] }
|
||||||
|
lazy-regex = "3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = "7"
|
miette = "7"
|
||||||
nix = { version = "0.29" }
|
nix = { version = "0.29" }
|
||||||
oci-distribution = { version = "0.11", default-features = false }
|
oci-distribution = { version = "0.11", default-features = false }
|
||||||
|
pretty_assertions = "1"
|
||||||
|
regex = "1"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
||||||
rstest = "0.18"
|
rstest = "0.18"
|
||||||
semver = "1"
|
semver = "1"
|
||||||
|
|
@ -78,7 +81,6 @@ jsonschema = "0.30"
|
||||||
open = "5"
|
open = "5"
|
||||||
os_info = "3"
|
os_info = "3"
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
regex = "1"
|
|
||||||
requestty = { version = "0.5", features = ["macros", "termion"] }
|
requestty = { version = "0.5", features = ["macros", "termion"] }
|
||||||
shadow-rs = { version = "1", default-features = false }
|
shadow-rs = { version = "1", default-features = false }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
|
|
@ -94,6 +96,7 @@ indicatif.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
miette = { workspace = true, features = ["fancy"] }
|
miette = { workspace = true, features = ["fancy"] }
|
||||||
oci-distribution.workspace = true
|
oci-distribution.workspace = true
|
||||||
|
regex.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
semver.workspace = true
|
semver.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
@ -109,6 +112,11 @@ users.workspace = true
|
||||||
# Top level features
|
# Top level features
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
|
v0_10_0 = [
|
||||||
|
"bootc"
|
||||||
|
]
|
||||||
|
bootc = ["blue-build-process-management/bootc"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rusty-hook = "0.11"
|
rusty-hook = "0.11"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ build-full:
|
||||||
switch:
|
switch:
|
||||||
FROM +test-base
|
FROM +test-base
|
||||||
|
|
||||||
RUN mkdir -p /etc/bluebuild && touch $BB_TEST_LOCAL_IMAGE
|
RUN --no-cache bluebuild -v switch --boot-driver rpm-ostree recipes/recipe.yml
|
||||||
RUN --no-cache bluebuild -v switch recipes/recipe.yml
|
RUN --no-cache bluebuild -v switch --boot-driver bootc recipes/recipe.yml
|
||||||
|
|
||||||
validate:
|
validate:
|
||||||
FROM +test-base
|
FROM +test-base
|
||||||
|
|
@ -92,7 +92,7 @@ init:
|
||||||
|
|
||||||
legacy-base:
|
legacy-base:
|
||||||
FROM ../+blue-build-cli --RELEASE=false
|
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
|
ENV CLICOLOR_FORCE=1
|
||||||
|
|
||||||
COPY ./mock-scripts/ /usr/bin/
|
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 ../+INSTALL --OUT_DIR="/usr/bin/" --BUILD_TARGET="x86_64-unknown-linux-musl" --TAGGED="true"
|
||||||
|
|
||||||
DO +GEN_KEYPAIR
|
DO +GEN_KEYPAIR
|
||||||
|
ENV USER=root
|
||||||
|
|
||||||
test-base:
|
test-base:
|
||||||
FROM ../+blue-build-cli --RELEASE=false
|
FROM ../+blue-build-cli --RELEASE=false
|
||||||
RUN git config --global user.email "you@example.com" && \
|
RUN git config --global user.email "you@example.com" && \
|
||||||
git config --global user.name "Your Name"
|
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
|
ENV CLICOLOR_FORCE=1
|
||||||
|
|
||||||
ARG MOCK="true"
|
ARG MOCK="true"
|
||||||
|
|
@ -121,6 +122,7 @@ test-base:
|
||||||
COPY ./test-repo /test
|
COPY ./test-repo /test
|
||||||
|
|
||||||
DO +GEN_KEYPAIR
|
DO +GEN_KEYPAIR
|
||||||
|
ENV USER=root
|
||||||
|
|
||||||
GEN_KEYPAIR:
|
GEN_KEYPAIR:
|
||||||
FUNCTION
|
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 "Exporting image to a tarball (JK JUST A MOCK!)"
|
||||||
echo "${tarpath}"
|
echo "${tarpath}"
|
||||||
touch $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
|
else
|
||||||
echo 'Running podman'
|
echo 'Running podman'
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [ "$1" = "rebase" ]; then
|
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"
|
echo "Rebased to local image $BB_TEST_LOCAL_IMAGE"
|
||||||
else
|
else
|
||||||
echo "Failed to rebase"
|
echo "Failed to rebase"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
elif [ "$1" = "upgrade" ]; then
|
elif [ "$1" = "upgrade" ]; then
|
||||||
echo "Performing upgrade for $BB_TEST_LOCAL_IMAGE"
|
echo "Performing upgrade for $BB_TEST_LOCAL_IMAGE"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test-arm64
|
name: cli/test-arm64
|
||||||
description: This is my personal OS image.
|
description: This is my personal OS image.
|
||||||
base-image: quay.io/fedora/fedora-silverblue
|
base-image: quay.io/fedora/fedora-bootc
|
||||||
image-version: latest
|
image-version: latest
|
||||||
stages:
|
stages:
|
||||||
- from-file: stages.yml
|
- from-file: stages.yml
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test-buildah
|
name: cli/test-buildah
|
||||||
description: This is my personal OS image.
|
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
|
image-version: latest
|
||||||
stages:
|
stages:
|
||||||
- from-file: stages.yml
|
- from-file: stages.yml
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test-docker-external
|
name: cli/test-docker-external
|
||||||
description: This is my personal OS image.
|
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
|
image-version: latest
|
||||||
stages:
|
stages:
|
||||||
- from-file: stages.yml
|
- from-file: stages.yml
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test
|
name: cli/test
|
||||||
description: This is my personal OS image.
|
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
|
image-version: 40
|
||||||
stages:
|
stages:
|
||||||
- from-file: invalid-stages.yml
|
- from-file: invalid-stages.yml
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test-invalid-module
|
name: cli/test-invalid-module
|
||||||
description: This is my personal OS image.
|
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
|
image-version: 40
|
||||||
stages:
|
stages:
|
||||||
- from-file: stages.yml
|
- from-file: stages.yml
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test-invalid-stage
|
name: cli/test-invalid-stage
|
||||||
description: This is my personal OS image.
|
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
|
image-version: 40
|
||||||
stages:
|
stages:
|
||||||
- name: ubuntu-test
|
- name: ubuntu-test
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test-invalid
|
name: cli/test-invalid
|
||||||
description: 10
|
description: 10
|
||||||
base-image: ghcr.io/ublue-os/silverblue-main
|
base-image: quay.io/fedora/fedora-bootc
|
||||||
image-version:
|
image-version:
|
||||||
- 40
|
- 40
|
||||||
- 39
|
- 39
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test-podman
|
name: cli/test-podman
|
||||||
description: This is my personal OS image.
|
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
|
image-version: latest
|
||||||
stages:
|
stages:
|
||||||
- from-file: stages.yml
|
- from-file: stages.yml
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test-rechunk
|
name: cli/test-rechunk
|
||||||
description: This is my personal OS image.
|
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
|
image-version: latest
|
||||||
stages:
|
stages:
|
||||||
- from-file: stages.yml
|
- from-file: stages.yml
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
|
||||||
name: cli/test
|
name: cli/test
|
||||||
description: This is my personal OS image.
|
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
|
image-version: latest
|
||||||
stages:
|
stages:
|
||||||
- from-file: stages.yml
|
- from-file: stages.yml
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ colored.workspace = true
|
||||||
comlexr.workspace = true
|
comlexr.workspace = true
|
||||||
indicatif.workspace = true
|
indicatif.workspace = true
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
|
lazy-regex.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
miette.workspace = true
|
miette.workspace = true
|
||||||
nix = { workspace = true, features = ["signal"] }
|
nix = { workspace = true, features = ["signal"] }
|
||||||
|
|
@ -44,8 +45,12 @@ uuid.workspace = true
|
||||||
zeroize.workspace = true
|
zeroize.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
pretty_assertions.workspace = true
|
||||||
rstest.workspace = true
|
rstest.workspace = true
|
||||||
blue-build-utils = { version = "=0.9.22", path = "../utils", features = ["test"] }
|
blue-build-utils = { version = "=0.9.22", path = "../utils", features = ["test"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
bootc = []
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
process::{ExitStatus, Output},
|
process::{ExitStatus, Output},
|
||||||
sync::{Mutex, RwLock},
|
sync::{LazyLock, RwLock, atomic::AtomicBool},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -24,12 +24,13 @@ use log::{info, trace, warn};
|
||||||
use miette::{Result, miette};
|
use miette::{Result, miette};
|
||||||
use oci_distribution::Reference;
|
use oci_distribution::Reference;
|
||||||
use opts::{
|
use opts::{
|
||||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, CreateContainerOpts, GenerateImageNameOpts,
|
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, ContainerOpts, CopyOciDirOpts,
|
||||||
GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts, RemoveContainerOpts,
|
CreateContainerOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts,
|
||||||
RemoveImageOpts, RunOpts, SignOpts, TagOpts, VerifyOpts,
|
GetMetadataOpts, PruneOpts, PushOpts, RechunkOpts, RemoveContainerOpts, RemoveImageOpts,
|
||||||
|
RunOpts, SignOpts, SwitchOpts, TagOpts, VerifyOpts, VolumeOpts,
|
||||||
};
|
};
|
||||||
use types::{
|
use types::{
|
||||||
BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType, Platform,
|
BootDriverType, BuildDriverType, CiDriverType, ImageMetadata, InspectDriverType, Platform,
|
||||||
RunDriverType, SigningDriverType,
|
RunDriverType, SigningDriverType,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
@ -39,10 +40,15 @@ use crate::logging::Logger;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
buildah_driver::BuildahDriver, cosign_driver::CosignDriver, docker_driver::DockerDriver,
|
buildah_driver::BuildahDriver, cosign_driver::CosignDriver, docker_driver::DockerDriver,
|
||||||
github_driver::GithubDriver, gitlab_driver::GitlabDriver, local_driver::LocalDriver,
|
github_driver::GithubDriver, gitlab_driver::GitlabDriver, local_driver::LocalDriver,
|
||||||
podman_driver::PodmanDriver, sigstore_driver::SigstoreDriver, skopeo_driver::SkopeoDriver,
|
podman_driver::PodmanDriver, rpm_ostree_driver::RpmOstreeDriver,
|
||||||
traits::*,
|
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 buildah_driver;
|
||||||
mod cosign_driver;
|
mod cosign_driver;
|
||||||
mod docker_driver;
|
mod docker_driver;
|
||||||
|
|
@ -52,22 +58,25 @@ mod gitlab_driver;
|
||||||
mod local_driver;
|
mod local_driver;
|
||||||
pub mod opts;
|
pub mod opts;
|
||||||
mod podman_driver;
|
mod podman_driver;
|
||||||
|
mod rpm_ostree_driver;
|
||||||
mod sigstore_driver;
|
mod sigstore_driver;
|
||||||
mod skopeo_driver;
|
mod skopeo_driver;
|
||||||
mod traits;
|
mod traits;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
static INIT: std::sync::LazyLock<Mutex<bool>> = std::sync::LazyLock::new(|| Mutex::new(false));
|
static INIT: AtomicBool = AtomicBool::new(false);
|
||||||
static SELECTED_BUILD_DRIVER: std::sync::LazyLock<RwLock<Option<BuildDriverType>>> =
|
static SELECTED_BUILD_DRIVER: LazyLock<RwLock<Option<BuildDriverType>>> =
|
||||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
LazyLock::new(|| RwLock::new(None));
|
||||||
static SELECTED_INSPECT_DRIVER: std::sync::LazyLock<RwLock<Option<InspectDriverType>>> =
|
static SELECTED_INSPECT_DRIVER: LazyLock<RwLock<Option<InspectDriverType>>> =
|
||||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
LazyLock::new(|| RwLock::new(None));
|
||||||
static SELECTED_RUN_DRIVER: std::sync::LazyLock<RwLock<Option<RunDriverType>>> =
|
static SELECTED_RUN_DRIVER: LazyLock<RwLock<Option<RunDriverType>>> =
|
||||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
LazyLock::new(|| RwLock::new(None));
|
||||||
static SELECTED_SIGNING_DRIVER: std::sync::LazyLock<RwLock<Option<SigningDriverType>>> =
|
static SELECTED_SIGNING_DRIVER: LazyLock<RwLock<Option<SigningDriverType>>> =
|
||||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
LazyLock::new(|| RwLock::new(None));
|
||||||
static SELECTED_CI_DRIVER: std::sync::LazyLock<RwLock<Option<CiDriverType>>> =
|
static SELECTED_CI_DRIVER: LazyLock<RwLock<Option<CiDriverType>>> =
|
||||||
std::sync::LazyLock::new(|| RwLock::new(None));
|
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.
|
/// Args for selecting the various drivers to use for runtime.
|
||||||
///
|
///
|
||||||
|
|
@ -95,6 +104,9 @@ pub struct DriverArgs {
|
||||||
/// containers.
|
/// containers.
|
||||||
#[arg(short = 'R', long)]
|
#[arg(short = 'R', long)]
|
||||||
run_driver: Option<RunDriverType>,
|
run_driver: Option<RunDriverType>,
|
||||||
|
|
||||||
|
#[arg(short = 'T', long)]
|
||||||
|
boot_driver: Option<BootDriverType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_driver_type {
|
macro_rules! impl_driver_type {
|
||||||
|
|
@ -108,12 +120,13 @@ macro_rules! impl_driver_init {
|
||||||
(@) => { };
|
(@) => { };
|
||||||
($init:ident; $($tail:tt)*) => {
|
($init:ident; $($tail:tt)*) => {
|
||||||
{
|
{
|
||||||
let mut initialized = $init.lock().expect("Must lock INIT");
|
if $init.compare_exchange(
|
||||||
|
false,
|
||||||
if !*initialized {
|
true,
|
||||||
|
std::sync::atomic::Ordering::AcqRel,
|
||||||
|
std::sync::atomic::Ordering::Acquire
|
||||||
|
).is_ok() {
|
||||||
impl_driver_init!(@ $($tail)*);
|
impl_driver_init!(@ $($tail)*);
|
||||||
|
|
||||||
*initialized = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -162,6 +175,7 @@ impl Driver {
|
||||||
args.inspect_driver => SELECTED_INSPECT_DRIVER;
|
args.inspect_driver => SELECTED_INSPECT_DRIVER;
|
||||||
args.run_driver => SELECTED_RUN_DRIVER;
|
args.run_driver => SELECTED_RUN_DRIVER;
|
||||||
args.signing_driver => SELECTED_SIGNING_DRIVER;
|
args.signing_driver => SELECTED_SIGNING_DRIVER;
|
||||||
|
args.boot_driver => SELECTED_BOOT_DRIVER;
|
||||||
default => SELECTED_CI_DRIVER;
|
default => SELECTED_CI_DRIVER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +220,7 @@ impl Driver {
|
||||||
info!("Retrieving OS version from {oci_ref}");
|
info!("Retrieving OS version from {oci_ref}");
|
||||||
|
|
||||||
let os_version = Self::get_metadata(
|
let os_version = Self::get_metadata(
|
||||||
&GetMetadataOpts::builder()
|
GetMetadataOpts::builder()
|
||||||
.image(oci_ref)
|
.image(oci_ref)
|
||||||
.maybe_platform(platform)
|
.maybe_platform(platform)
|
||||||
.build(),
|
.build(),
|
||||||
|
|
@ -247,6 +261,10 @@ impl Driver {
|
||||||
pub fn get_ci_driver() -> CiDriverType {
|
pub fn get_ci_driver() -> CiDriverType {
|
||||||
impl_driver_type!(SELECTED_CI_DRIVER)
|
impl_driver_type!(SELECTED_CI_DRIVER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_boot_driver() -> BootDriverType {
|
||||||
|
impl_driver_type!(SELECTED_BOOT_DRIVER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cached(
|
#[cached(
|
||||||
|
|
@ -278,9 +296,9 @@ fn get_version_run_image(oci_ref: &Reference) -> Result<u64> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let output = Driver::run_output(
|
let output = Driver::run_output(
|
||||||
&RunOpts::builder()
|
RunOpts::builder()
|
||||||
.image(oci_ref.to_string())
|
.image(&oci_ref.to_string())
|
||||||
.args(bon::vec![
|
.args(&bon::vec![
|
||||||
"/bin/bash",
|
"/bin/bash",
|
||||||
"-c",
|
"-c",
|
||||||
r#"awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release"#,
|
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 {
|
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();
|
progress.finish_and_clear();
|
||||||
|
|
@ -314,15 +332,15 @@ macro_rules! impl_build_driver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildDriver for Driver {
|
impl BuildDriver for Driver {
|
||||||
fn build(opts: &BuildOpts) -> Result<()> {
|
fn build(opts: BuildOpts) -> Result<()> {
|
||||||
impl_build_driver!(build(opts))
|
impl_build_driver!(build(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tag(opts: &TagOpts) -> Result<()> {
|
fn tag(opts: TagOpts) -> Result<()> {
|
||||||
impl_build_driver!(tag(opts))
|
impl_build_driver!(tag(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(opts: &PushOpts) -> Result<()> {
|
fn push(opts: PushOpts) -> Result<()> {
|
||||||
impl_build_driver!(push(opts))
|
impl_build_driver!(push(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,11 +348,11 @@ impl BuildDriver for Driver {
|
||||||
impl_build_driver!(login())
|
impl_build_driver!(login())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prune(opts: &opts::PruneOpts) -> Result<()> {
|
fn prune(opts: PruneOpts) -> Result<()> {
|
||||||
impl_build_driver!(prune(opts))
|
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))
|
impl_build_driver!(build_tag_push(opts))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -349,19 +367,19 @@ macro_rules! impl_signing_driver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SigningDriver for 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))
|
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))
|
impl_signing_driver!(check_signing_files(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(opts: &SignOpts) -> Result<()> {
|
fn sign(opts: SignOpts) -> Result<()> {
|
||||||
impl_signing_driver!(sign(opts))
|
impl_signing_driver!(sign(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(opts: &VerifyOpts) -> Result<()> {
|
fn verify(opts: VerifyOpts) -> Result<()> {
|
||||||
impl_signing_driver!(verify(opts))
|
impl_signing_driver!(verify(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,7 +399,7 @@ macro_rules! impl_inspect_driver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InspectDriver for 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))
|
impl_inspect_driver!(get_metadata(opts))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -396,23 +414,23 @@ macro_rules! impl_run_driver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunDriver for Driver {
|
impl RunDriver for Driver {
|
||||||
fn run(opts: &RunOpts) -> Result<ExitStatus> {
|
fn run(opts: RunOpts) -> Result<ExitStatus> {
|
||||||
impl_run_driver!(run(opts))
|
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))
|
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))
|
impl_run_driver!(create_container(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_container(opts: &RemoveContainerOpts) -> Result<()> {
|
fn remove_container(opts: RemoveContainerOpts) -> Result<()> {
|
||||||
impl_run_driver!(remove_container(opts))
|
impl_run_driver!(remove_container(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
|
fn remove_image(opts: RemoveImageOpts) -> Result<()> {
|
||||||
impl_run_driver!(remove_image(opts))
|
impl_run_driver!(remove_image(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,7 +462,7 @@ impl CiDriver for Driver {
|
||||||
impl_ci_driver!(oidc_provider())
|
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))
|
impl_ci_driver!(generate_tags(opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -469,27 +487,52 @@ impl CiDriver for Driver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerMountDriver 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)
|
PodmanDriver::mount_container(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmount_container(opts: &opts::ContainerOpts) -> Result<()> {
|
fn unmount_container(opts: ContainerOpts) -> Result<()> {
|
||||||
PodmanDriver::unmount_container(opts)
|
PodmanDriver::unmount_container(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_volume(opts: &opts::VolumeOpts) -> Result<()> {
|
fn remove_volume(opts: VolumeOpts) -> Result<()> {
|
||||||
PodmanDriver::remove_volume(opts)
|
PodmanDriver::remove_volume(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OciCopy for Driver {
|
impl OciCopy for Driver {
|
||||||
fn copy_oci_dir(opts: &opts::CopyOciDirOpts) -> Result<()> {
|
fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()> {
|
||||||
SkopeoDriver::copy_oci_dir(opts)
|
SkopeoDriver::copy_oci_dir(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RechunkDriver for Driver {
|
impl RechunkDriver for Driver {
|
||||||
fn rechunk(opts: &opts::RechunkOpts) -> Result<Vec<String>> {
|
fn rechunk(opts: RechunkOpts) -> Result<Vec<String>> {
|
||||||
PodmanDriver::rechunk(opts)
|
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::{
|
use super::{
|
||||||
BuildDriver, DriverVersion,
|
BuildDriver, DriverVersion,
|
||||||
opts::{BuildOpts, PushOpts, TagOpts},
|
opts::{BuildOpts, PruneOpts, PushOpts, TagOpts},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
@ -48,7 +48,7 @@ impl DriverVersion for BuildahDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildDriver for BuildahDriver {
|
impl BuildDriver for BuildahDriver {
|
||||||
fn build(opts: &BuildOpts) -> Result<()> {
|
fn build(opts: BuildOpts) -> Result<()> {
|
||||||
trace!("BuildahDriver::build({opts:#?})");
|
trace!("BuildahDriver::build({opts:#?})");
|
||||||
|
|
||||||
let temp_dir = TempDir::new()
|
let temp_dir = TempDir::new()
|
||||||
|
|
@ -83,7 +83,7 @@ impl BuildDriver for BuildahDriver {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
"-f",
|
"-f",
|
||||||
&*opts.containerfile,
|
opts.containerfile,
|
||||||
"-t",
|
"-t",
|
||||||
opts.image.to_string(),
|
opts.image.to_string(),
|
||||||
);
|
);
|
||||||
|
|
@ -101,7 +101,7 @@ impl BuildDriver for BuildahDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tag(opts: &TagOpts) -> Result<()> {
|
fn tag(opts: TagOpts) -> Result<()> {
|
||||||
trace!("BuildahDriver::tag({opts:#?})");
|
trace!("BuildahDriver::tag({opts:#?})");
|
||||||
|
|
||||||
let dest_image_str = opts.dest_image.to_string();
|
let dest_image_str = opts.dest_image.to_string();
|
||||||
|
|
@ -122,7 +122,7 @@ impl BuildDriver for BuildahDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(opts: &PushOpts) -> Result<()> {
|
fn push(opts: PushOpts) -> Result<()> {
|
||||||
trace!("BuildahDriver::push({opts:#?})");
|
trace!("BuildahDriver::push({opts:#?})");
|
||||||
|
|
||||||
let image_str = opts.image.to_string();
|
let image_str = opts.image.to_string();
|
||||||
|
|
@ -195,7 +195,7 @@ impl BuildDriver for BuildahDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
fn prune(opts: PruneOpts) -> Result<()> {
|
||||||
trace!("PodmanDriver::prune({opts:?})");
|
trace!("PodmanDriver::prune({opts:?})");
|
||||||
|
|
||||||
let status = cmd!(
|
let status = cmd!(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use super::{
|
||||||
pub struct CosignDriver;
|
pub struct CosignDriver;
|
||||||
|
|
||||||
impl SigningDriver for 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 path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
|
||||||
|
|
||||||
let status = {
|
let status = {
|
||||||
|
|
@ -47,7 +47,7 @@ impl SigningDriver for CosignDriver {
|
||||||
Ok(())
|
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 path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
|
||||||
let priv_key = get_private_key(path)?;
|
let priv_key = get_private_key(path)?;
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ impl SigningDriver for CosignDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(opts: &SignOpts) -> Result<()> {
|
fn sign(opts: SignOpts) -> Result<()> {
|
||||||
if opts.image.digest().is_none() {
|
if opts.image.digest().is_none() {
|
||||||
bail!(
|
bail!(
|
||||||
"Image ref {} is not a digest ref",
|
"Image ref {} is not a digest ref",
|
||||||
|
|
@ -140,7 +140,7 @@ impl SigningDriver for CosignDriver {
|
||||||
};
|
};
|
||||||
"cosign",
|
"cosign",
|
||||||
"sign",
|
"sign",
|
||||||
if let Some(ref key) = opts.key => format!("--key={key}"),
|
if let Some(key) = opts.key => format!("--key={key}"),
|
||||||
"--recursive",
|
"--recursive",
|
||||||
opts.image.to_string(),
|
opts.image.to_string(),
|
||||||
);
|
);
|
||||||
|
|
@ -157,7 +157,7 @@ impl SigningDriver for CosignDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(opts: &VerifyOpts) -> Result<()> {
|
fn verify(opts: VerifyOpts) -> Result<()> {
|
||||||
let status = {
|
let status = {
|
||||||
let c = cmd!(
|
let c = cmd!(
|
||||||
"cosign",
|
"cosign",
|
||||||
|
|
@ -205,9 +205,8 @@ mod test {
|
||||||
fn generate_key_pair() {
|
fn generate_key_pair() {
|
||||||
let tempdir = TempDir::new().unwrap();
|
let tempdir = TempDir::new().unwrap();
|
||||||
|
|
||||||
let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
|
CosignDriver::generate_key_pair(GenerateKeyPairOpts::builder().dir(tempdir.path()).build())
|
||||||
|
.unwrap();
|
||||||
CosignDriver::generate_key_pair(&gen_opts).unwrap();
|
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Private key:\n{}",
|
"Private key:\n{}",
|
||||||
|
|
@ -218,18 +217,15 @@ mod test {
|
||||||
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
|
CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(tempdir.path()).build())
|
||||||
|
.unwrap();
|
||||||
CosignDriver::check_signing_files(&check_opts).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_key_pairs() {
|
fn check_key_pairs() {
|
||||||
let path = Path::new("../test-files/keys");
|
let path = Path::new("../test-files/keys");
|
||||||
|
|
||||||
let opts = CheckKeyPairOpts::builder().dir(path).build();
|
CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(path).build()).unwrap();
|
||||||
|
|
||||||
CosignDriver::check_signing_files(&opts).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -238,9 +234,8 @@ mod test {
|
||||||
|
|
||||||
let tempdir = TempDir::new().unwrap();
|
let tempdir = TempDir::new().unwrap();
|
||||||
|
|
||||||
let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
|
CosignDriver::generate_key_pair(GenerateKeyPairOpts::builder().dir(tempdir.path()).build())
|
||||||
|
.unwrap();
|
||||||
CosignDriver::generate_key_pair(&gen_opts).unwrap();
|
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Private key:\n{}",
|
"Private key:\n{}",
|
||||||
|
|
@ -251,8 +246,9 @@ mod test {
|
||||||
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
|
SigstoreDriver::check_signing_files(
|
||||||
|
CheckKeyPairOpts::builder().dir(tempdir.path()).build(),
|
||||||
SigstoreDriver::check_signing_files(&check_opts).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ use crate::{
|
||||||
signal_handler::{ContainerRuntime, ContainerSignalId, add_cid, remove_cid},
|
signal_handler::{ContainerRuntime, ContainerSignalId, add_cid, remove_cid},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::opts::{CreateContainerOpts, RemoveContainerOpts, RemoveImageOpts};
|
use super::opts::{CreateContainerOpts, PruneOpts, RemoveContainerOpts, RemoveImageOpts};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct VerisonJsonClient {
|
struct VerisonJsonClient {
|
||||||
|
|
@ -192,7 +192,7 @@ impl DriverVersion for DockerDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildDriver for DockerDriver {
|
impl BuildDriver for DockerDriver {
|
||||||
fn build(opts: &BuildOpts) -> Result<()> {
|
fn build(opts: BuildOpts) -> Result<()> {
|
||||||
trace!("DockerDriver::build({opts:#?})");
|
trace!("DockerDriver::build({opts:#?})");
|
||||||
|
|
||||||
let temp_dir = TempDir::new()
|
let temp_dir = TempDir::new()
|
||||||
|
|
@ -232,7 +232,7 @@ impl BuildDriver for DockerDriver {
|
||||||
repository = cache_to.repository(),
|
repository = cache_to.repository(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
&*opts.containerfile,
|
opts.containerfile,
|
||||||
".",
|
".",
|
||||||
);
|
);
|
||||||
trace!("{c:?}");
|
trace!("{c:?}");
|
||||||
|
|
@ -249,7 +249,7 @@ impl BuildDriver for DockerDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tag(opts: &TagOpts) -> Result<()> {
|
fn tag(opts: TagOpts) -> Result<()> {
|
||||||
trace!("DockerDriver::tag({opts:#?})");
|
trace!("DockerDriver::tag({opts:#?})");
|
||||||
|
|
||||||
let dest_image_str = opts.dest_image.to_string();
|
let dest_image_str = opts.dest_image.to_string();
|
||||||
|
|
@ -270,7 +270,7 @@ impl BuildDriver for DockerDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(opts: &PushOpts) -> Result<()> {
|
fn push(opts: PushOpts) -> Result<()> {
|
||||||
trace!("DockerDriver::push({opts:#?})");
|
trace!("DockerDriver::push({opts:#?})");
|
||||||
|
|
||||||
let image_str = opts.image.to_string();
|
let image_str = opts.image.to_string();
|
||||||
|
|
@ -328,7 +328,7 @@ impl BuildDriver for DockerDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
fn prune(opts: PruneOpts) -> Result<()> {
|
||||||
trace!("DockerDriver::prune({opts:?})");
|
trace!("DockerDriver::prune({opts:?})");
|
||||||
|
|
||||||
let (system, buildx) = std::thread::scope(
|
let (system, buildx) = std::thread::scope(
|
||||||
|
|
@ -385,7 +385,7 @@ impl BuildDriver for DockerDriver {
|
||||||
Ok(())
|
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:#?})");
|
trace!("DockerDriver::build_tag_push({opts:#?})");
|
||||||
|
|
||||||
let temp_dir = TempDir::new()
|
let temp_dir = TempDir::new()
|
||||||
|
|
@ -420,7 +420,7 @@ impl BuildDriver for DockerDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_tag_push_cmd(
|
fn build_tag_push_cmd(
|
||||||
opts: &BuildTagPushOpts<'_>,
|
opts: BuildTagPushOpts<'_>,
|
||||||
first_image: &str,
|
first_image: &str,
|
||||||
temp_dir: &TempDir,
|
temp_dir: &TempDir,
|
||||||
) -> Result<Command> {
|
) -> Result<Command> {
|
||||||
|
|
@ -461,7 +461,7 @@ fn build_tag_push_cmd(
|
||||||
platform.to_string(),
|
platform.to_string(),
|
||||||
],
|
],
|
||||||
"-f",
|
"-f",
|
||||||
&*opts.containerfile,
|
opts.containerfile,
|
||||||
if let Some(cache_from) = opts.cache_from.as_ref() => [
|
if let Some(cache_from) = opts.cache_from.as_ref() => [
|
||||||
"--cache-from",
|
"--cache-from",
|
||||||
format!(
|
format!(
|
||||||
|
|
@ -479,7 +479,7 @@ fn build_tag_push_cmd(
|
||||||
Ok(c)
|
Ok(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_final_images(opts: &BuildTagPushOpts<'_>) -> Vec<String> {
|
fn get_final_images(opts: BuildTagPushOpts<'_>) -> Vec<String> {
|
||||||
match &opts.image {
|
match &opts.image {
|
||||||
ImageRef::Remote(image) => {
|
ImageRef::Remote(image) => {
|
||||||
if opts.tags.is_empty() {
|
if opts.tags.is_empty() {
|
||||||
|
|
@ -495,11 +495,14 @@ fn get_final_images(opts: &BuildTagPushOpts<'_>) -> Vec<String> {
|
||||||
ImageRef::LocalTar(archive_path) => {
|
ImageRef::LocalTar(archive_path) => {
|
||||||
string_vec![archive_path.display().to_string()]
|
string_vec![archive_path.display().to_string()]
|
||||||
}
|
}
|
||||||
|
ImageRef::Other(other) => {
|
||||||
|
string_vec![&**other]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InspectDriver for DockerDriver {
|
impl InspectDriver for DockerDriver {
|
||||||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||||
get_metadata_cache(opts)
|
get_metadata_cache(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -510,7 +513,7 @@ impl InspectDriver for DockerDriver {
|
||||||
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
||||||
sync_writes = "by_key"
|
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:#?})");
|
trace!("DockerDriver::get_metadata({opts:#?})");
|
||||||
let image_str = opts.image.to_string();
|
let image_str = opts.image.to_string();
|
||||||
|
|
||||||
|
|
@ -547,7 +550,7 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunDriver for DockerDriver {
|
impl RunDriver for DockerDriver {
|
||||||
fn run(opts: &RunOpts) -> Result<ExitStatus> {
|
fn run(opts: RunOpts) -> Result<ExitStatus> {
|
||||||
trace!("DockerDriver::run({opts:#?})");
|
trace!("DockerDriver::run({opts:#?})");
|
||||||
|
|
||||||
let cid_path = TempDir::new().into_diagnostic()?;
|
let cid_path = TempDir::new().into_diagnostic()?;
|
||||||
|
|
@ -557,7 +560,7 @@ impl RunDriver for DockerDriver {
|
||||||
add_cid(&cid);
|
add_cid(&cid);
|
||||||
|
|
||||||
let status = docker_run(opts, &cid_file)
|
let status = docker_run(opts, &cid_file)
|
||||||
.build_status(&*opts.image, "Running container")
|
.build_status(opts.image, "Running container")
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
|
|
||||||
remove_cid(&cid);
|
remove_cid(&cid);
|
||||||
|
|
@ -565,7 +568,7 @@ impl RunDriver for DockerDriver {
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_output(opts: &RunOpts) -> Result<std::process::Output> {
|
fn run_output(opts: RunOpts) -> Result<std::process::Output> {
|
||||||
trace!("DockerDriver::run({opts:#?})");
|
trace!("DockerDriver::run({opts:#?})");
|
||||||
|
|
||||||
let cid_path = TempDir::new().into_diagnostic()?;
|
let cid_path = TempDir::new().into_diagnostic()?;
|
||||||
|
|
@ -581,7 +584,7 @@ impl RunDriver for DockerDriver {
|
||||||
Ok(output)
|
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:?})");
|
trace!("DockerDriver::create_container({opts:?})");
|
||||||
|
|
||||||
let output = {
|
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:?})");
|
trace!("DockerDriver::remove_container({opts:?})");
|
||||||
|
|
||||||
let output = {
|
let output = {
|
||||||
|
|
@ -619,7 +622,7 @@ impl RunDriver for DockerDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
|
fn remove_image(opts: RemoveImageOpts) -> Result<()> {
|
||||||
trace!("DockerDriver::remove_image({opts:?})");
|
trace!("DockerDriver::remove_image({opts:?})");
|
||||||
|
|
||||||
let output = {
|
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!(
|
let command = cmd!(
|
||||||
"docker",
|
"docker",
|
||||||
"run",
|
"run",
|
||||||
|
|
@ -693,7 +696,7 @@ fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
|
||||||
"--env",
|
"--env",
|
||||||
format!("{key}={value}"),
|
format!("{key}={value}"),
|
||||||
],
|
],
|
||||||
&*opts.image,
|
opts.image,
|
||||||
for arg in opts.args.iter() => &**arg,
|
for arg in opts.args.iter() => &**arg,
|
||||||
);
|
);
|
||||||
trace!("{command:?}");
|
trace!("{command:?}");
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ impl CiDriver for GithubDriver {
|
||||||
Ok(GITHUB_TOKEN_ISSUER_URL.to_string())
|
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";
|
const PR_EVENT: &str = "pull_request";
|
||||||
let timestamp = blue_build_utils::get_tag_timestamp();
|
let timestamp = blue_build_utils::get_tag_timestamp();
|
||||||
let os_version = Driver::get_os_version()
|
let os_version = Driver::get_os_version()
|
||||||
|
|
@ -142,8 +142,6 @@ impl CiDriver for GithubDriver {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use blue_build_utils::{
|
use blue_build_utils::{
|
||||||
constants::{
|
constants::{
|
||||||
GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER,
|
GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER,
|
||||||
|
|
@ -286,7 +284,7 @@ mod test {
|
||||||
)]
|
)]
|
||||||
fn generate_tags(
|
fn generate_tags(
|
||||||
#[case] setup: impl FnOnce(),
|
#[case] setup: impl FnOnce(),
|
||||||
#[case] alt_tags: Option<Vec<Cow<'_, str>>>,
|
#[case] alt_tags: Option<Vec<String>>,
|
||||||
#[case] mut expected: Vec<String>,
|
#[case] mut expected: Vec<String>,
|
||||||
) {
|
) {
|
||||||
setup();
|
setup();
|
||||||
|
|
@ -294,9 +292,9 @@ mod test {
|
||||||
let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
|
let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
|
||||||
|
|
||||||
let mut tags = GithubDriver::generate_tags(
|
let mut tags = GithubDriver::generate_tags(
|
||||||
&GenerateTagsOpts::builder()
|
GenerateTagsOpts::builder()
|
||||||
.oci_ref(&oci_ref)
|
.oci_ref(&oci_ref)
|
||||||
.maybe_alt_tags(alt_tags)
|
.maybe_alt_tags(alt_tags.as_deref())
|
||||||
.platform(Platform::LinuxAmd64)
|
.platform(Platform::LinuxAmd64)
|
||||||
.build(),
|
.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";
|
const MR_EVENT: &str = "merge_request_event";
|
||||||
let os_version = Driver::get_os_version()
|
let os_version = Driver::get_os_version()
|
||||||
.oci_ref(opts.oci_ref)
|
.oci_ref(opts.oci_ref)
|
||||||
|
|
@ -151,8 +151,6 @@ impl CiDriver for GitlabDriver {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use blue_build_utils::{
|
use blue_build_utils::{
|
||||||
constants::{
|
constants::{
|
||||||
CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID,
|
CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID,
|
||||||
|
|
@ -293,7 +291,7 @@ mod test {
|
||||||
)]
|
)]
|
||||||
fn generate_tags(
|
fn generate_tags(
|
||||||
#[case] setup: impl FnOnce(),
|
#[case] setup: impl FnOnce(),
|
||||||
#[case] alt_tags: Option<Vec<Cow<'_, str>>>,
|
#[case] alt_tags: Option<Vec<String>>,
|
||||||
#[case] mut expected: Vec<String>,
|
#[case] mut expected: Vec<String>,
|
||||||
) {
|
) {
|
||||||
setup();
|
setup();
|
||||||
|
|
@ -301,9 +299,9 @@ mod test {
|
||||||
let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
|
let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
|
||||||
|
|
||||||
let mut tags = GitlabDriver::generate_tags(
|
let mut tags = GitlabDriver::generate_tags(
|
||||||
&GenerateTagsOpts::builder()
|
GenerateTagsOpts::builder()
|
||||||
.oci_ref(&oci_ref)
|
.oci_ref(&oci_ref)
|
||||||
.maybe_alt_tags(alt_tags)
|
.maybe_alt_tags(alt_tags.as_deref())
|
||||||
.platform(crate::drivers::types::Platform::LinuxAmd64)
|
.platform(crate::drivers::types::Platform::LinuxAmd64)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::path::PathBuf;
|
||||||
use blue_build_utils::string_vec;
|
use blue_build_utils::string_vec;
|
||||||
use comlexr::cmd;
|
use comlexr::cmd;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
use miette::bail;
|
||||||
|
|
||||||
use super::{CiDriver, Driver, opts::GenerateTagsOpts};
|
use super::{CiDriver, Driver, opts::GenerateTagsOpts};
|
||||||
|
|
||||||
|
|
@ -15,14 +16,14 @@ impl CiDriver for LocalDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyless_cert_identity() -> miette::Result<String> {
|
fn keyless_cert_identity() -> miette::Result<String> {
|
||||||
unimplemented!()
|
bail!("Unimplemented for local")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn oidc_provider() -> miette::Result<String> {
|
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:?})");
|
trace!("LocalDriver::generate_tags({opts:?})");
|
||||||
let os_version = Driver::get_os_version()
|
let os_version = Driver::get_os_version()
|
||||||
.oci_ref(opts.oci_ref)
|
.oci_ref(opts.oci_ref)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
|
|
||||||
|
pub use boot::*;
|
||||||
pub use build::*;
|
pub use build::*;
|
||||||
pub use ci::*;
|
pub use ci::*;
|
||||||
pub use inspect::*;
|
pub use inspect::*;
|
||||||
|
|
@ -7,6 +8,7 @@ pub use rechunk::*;
|
||||||
pub use run::*;
|
pub use run::*;
|
||||||
pub use signing::*;
|
pub use signing::*;
|
||||||
|
|
||||||
|
mod boot;
|
||||||
mod build;
|
mod build;
|
||||||
mod ci;
|
mod ci;
|
||||||
mod inspect;
|
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 blue_build_utils::secret::Secret;
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
|
|
@ -9,16 +9,14 @@ use crate::drivers::types::{ImageRef, Platform};
|
||||||
use super::CompressionType;
|
use super::CompressionType;
|
||||||
|
|
||||||
/// Options for building
|
/// Options for building
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct BuildOpts<'scope> {
|
pub struct BuildOpts<'scope> {
|
||||||
#[builder(into)]
|
pub image: &'scope ImageRef<'scope>,
|
||||||
pub image: ImageRef<'scope>,
|
|
||||||
|
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub squash: bool,
|
pub squash: bool,
|
||||||
|
|
||||||
#[builder(into)]
|
pub containerfile: &'scope Path,
|
||||||
pub containerfile: Cow<'scope, Path>,
|
|
||||||
|
|
||||||
pub platform: Option<Platform>,
|
pub platform: Option<Platform>,
|
||||||
|
|
||||||
|
|
@ -27,18 +25,14 @@ pub struct BuildOpts<'scope> {
|
||||||
|
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub privileged: bool,
|
pub privileged: bool,
|
||||||
|
|
||||||
#[builder(into)]
|
|
||||||
pub cache_from: Option<&'scope Reference>,
|
pub cache_from: Option<&'scope Reference>,
|
||||||
|
|
||||||
#[builder(into)]
|
|
||||||
pub cache_to: Option<&'scope Reference>,
|
pub cache_to: Option<&'scope Reference>,
|
||||||
|
|
||||||
#[builder(default)]
|
#[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 struct TagOpts<'scope> {
|
||||||
pub src_image: &'scope Reference,
|
pub src_image: &'scope Reference,
|
||||||
pub dest_image: &'scope Reference,
|
pub dest_image: &'scope Reference,
|
||||||
|
|
@ -47,7 +41,7 @@ pub struct TagOpts<'scope> {
|
||||||
pub privileged: bool,
|
pub privileged: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct PushOpts<'scope> {
|
pub struct PushOpts<'scope> {
|
||||||
pub image: &'scope Reference,
|
pub image: &'scope Reference,
|
||||||
pub compression_type: Option<CompressionType>,
|
pub compression_type: Option<CompressionType>,
|
||||||
|
|
@ -56,7 +50,7 @@ pub struct PushOpts<'scope> {
|
||||||
pub privileged: bool,
|
pub privileged: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct PruneOpts {
|
pub struct PruneOpts {
|
||||||
pub all: bool,
|
pub all: bool,
|
||||||
pub volumes: bool,
|
pub volumes: bool,
|
||||||
|
|
@ -64,19 +58,17 @@ pub struct PruneOpts {
|
||||||
|
|
||||||
/// Options for building, tagging, and pusing images.
|
/// Options for building, tagging, and pusing images.
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct BuildTagPushOpts<'scope> {
|
pub struct BuildTagPushOpts<'scope> {
|
||||||
/// The base image name.
|
/// The base image name.
|
||||||
#[builder(into)]
|
pub image: &'scope ImageRef<'scope>,
|
||||||
pub image: ImageRef<'scope>,
|
|
||||||
|
|
||||||
/// The path to the Containerfile to build.
|
/// The path to the Containerfile to build.
|
||||||
#[builder(into)]
|
pub containerfile: &'scope Path,
|
||||||
pub containerfile: Cow<'scope, Path>,
|
|
||||||
|
|
||||||
/// The list of tags for the image being built.
|
/// The list of tags for the image being built.
|
||||||
#[builder(default, into)]
|
#[builder(default)]
|
||||||
pub tags: Vec<Cow<'scope, str>>,
|
pub tags: &'scope [String],
|
||||||
|
|
||||||
/// Enable pushing the image.
|
/// Enable pushing the image.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
|
|
@ -115,5 +107,5 @@ pub struct BuildTagPushOpts<'scope> {
|
||||||
|
|
||||||
/// Secrets to mount
|
/// Secrets to mount
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub secrets: HashSet<&'scope Secret>,
|
pub secrets: &'scope [&'scope Secret],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,21 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
use oci_distribution::Reference;
|
use oci_distribution::Reference;
|
||||||
|
|
||||||
use crate::drivers::types::Platform;
|
use crate::drivers::types::Platform;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct GenerateTagsOpts<'scope> {
|
pub struct GenerateTagsOpts<'scope> {
|
||||||
pub oci_ref: &'scope Reference,
|
pub oci_ref: &'scope Reference,
|
||||||
|
|
||||||
#[builder(into)]
|
#[builder(into)]
|
||||||
pub alt_tags: Option<Vec<Cow<'scope, str>>>,
|
pub alt_tags: Option<&'scope [String]>,
|
||||||
|
|
||||||
pub platform: Option<Platform>,
|
pub platform: Option<Platform>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct GenerateImageNameOpts<'scope> {
|
pub struct GenerateImageNameOpts<'scope> {
|
||||||
#[builder(into)]
|
pub name: &'scope str,
|
||||||
pub name: Cow<'scope, str>,
|
pub registry: Option<&'scope str>,
|
||||||
|
pub registry_namespace: Option<&'scope str>,
|
||||||
#[builder(into)]
|
|
||||||
pub registry: Option<Cow<'scope, str>>,
|
|
||||||
|
|
||||||
#[builder(into)]
|
|
||||||
pub registry_namespace: Option<Cow<'scope, str>>,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use oci_distribution::Reference;
|
||||||
|
|
||||||
use crate::drivers::types::Platform;
|
use crate::drivers::types::Platform;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder, Hash)]
|
#[derive(Debug, Clone, Copy, Builder, Hash)]
|
||||||
#[builder(derive(Clone))]
|
#[builder(derive(Clone))]
|
||||||
pub struct GetMetadataOpts<'scope> {
|
pub struct GetMetadataOpts<'scope> {
|
||||||
#[builder(into)]
|
#[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 blue_build_utils::secret::Secret;
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
|
|
@ -8,25 +8,22 @@ use crate::drivers::types::{ContainerId, OciDir, Platform};
|
||||||
|
|
||||||
use super::CompressionType;
|
use super::CompressionType;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
#[builder(on(Cow<'_, str>, into))]
|
|
||||||
pub struct RechunkOpts<'scope> {
|
pub struct RechunkOpts<'scope> {
|
||||||
pub image: Cow<'scope, str>,
|
pub image: &'scope str,
|
||||||
|
pub containerfile: &'scope Path,
|
||||||
#[builder(into)]
|
|
||||||
pub containerfile: Cow<'scope, Path>,
|
|
||||||
|
|
||||||
pub platform: Option<Platform>,
|
pub platform: Option<Platform>,
|
||||||
pub version: Cow<'scope, str>,
|
pub version: &'scope str,
|
||||||
pub name: Cow<'scope, str>,
|
pub name: &'scope str,
|
||||||
pub description: Cow<'scope, str>,
|
pub description: &'scope str,
|
||||||
pub base_digest: Cow<'scope, str>,
|
pub base_digest: &'scope str,
|
||||||
pub base_image: Cow<'scope, str>,
|
pub base_image: &'scope str,
|
||||||
pub repo: Cow<'scope, str>,
|
pub repo: &'scope str,
|
||||||
|
|
||||||
/// The list of tags for the image being built.
|
/// The list of tags for the image being built.
|
||||||
#[builder(default, into)]
|
#[builder(default)]
|
||||||
pub tags: Vec<Cow<'scope, str>>,
|
pub tags: &'scope [String],
|
||||||
|
|
||||||
/// Enable pushing the image.
|
/// Enable pushing the image.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
|
|
@ -57,10 +54,10 @@ pub struct RechunkOpts<'scope> {
|
||||||
pub cache_to: Option<&'scope Reference>,
|
pub cache_to: Option<&'scope Reference>,
|
||||||
|
|
||||||
#[builder(default)]
|
#[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 struct ContainerOpts<'scope> {
|
||||||
pub container_id: &'scope ContainerId,
|
pub container_id: &'scope ContainerId,
|
||||||
|
|
||||||
|
|
@ -68,16 +65,15 @@ pub struct ContainerOpts<'scope> {
|
||||||
pub privileged: bool,
|
pub privileged: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct VolumeOpts<'scope> {
|
pub struct VolumeOpts<'scope> {
|
||||||
#[builder(into)]
|
pub volume_id: &'scope str,
|
||||||
pub volume_id: Cow<'scope, str>,
|
|
||||||
|
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub privileged: bool,
|
pub privileged: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct CopyOciDirOpts<'scope> {
|
pub struct CopyOciDirOpts<'scope> {
|
||||||
pub oci_dir: &'scope OciDir,
|
pub oci_dir: &'scope OciDir,
|
||||||
pub registry: &'scope Reference,
|
pub registry: &'scope Reference,
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,21 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
use oci_distribution::Reference;
|
use oci_distribution::Reference;
|
||||||
|
|
||||||
use crate::drivers::types::ContainerId;
|
use crate::drivers::types::ContainerId;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct RunOpts<'scope> {
|
pub struct RunOpts<'scope> {
|
||||||
#[builder(into)]
|
pub image: &'scope str,
|
||||||
pub image: Cow<'scope, str>,
|
|
||||||
|
|
||||||
#[builder(default, into)]
|
#[builder(default)]
|
||||||
pub args: Vec<Cow<'scope, str>>,
|
pub args: &'scope [String],
|
||||||
|
|
||||||
#[builder(default, into)]
|
#[builder(default)]
|
||||||
pub env_vars: Vec<RunOptsEnv<'scope>>,
|
pub env_vars: &'scope [RunOptsEnv<'scope>],
|
||||||
|
|
||||||
#[builder(default, into)]
|
#[builder(default)]
|
||||||
pub volumes: Vec<RunOptsVolume<'scope>>,
|
pub volumes: &'scope [RunOptsVolume<'scope>],
|
||||||
|
pub user: Option<&'scope str>,
|
||||||
#[builder(into)]
|
|
||||||
pub user: Option<Cow<'scope, str>>,
|
|
||||||
|
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub privileged: bool,
|
pub privileged: bool,
|
||||||
|
|
@ -32,13 +27,10 @@ pub struct RunOpts<'scope> {
|
||||||
pub remove: bool,
|
pub remove: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct RunOptsVolume<'scope> {
|
pub struct RunOptsVolume<'scope> {
|
||||||
#[builder(into)]
|
pub path_or_vol_name: &'scope str,
|
||||||
pub path_or_vol_name: Cow<'scope, str>,
|
pub container_path: &'scope str,
|
||||||
|
|
||||||
#[builder(into)]
|
|
||||||
pub container_path: Cow<'scope, str>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
@ -55,13 +47,10 @@ macro_rules! run_volumes {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct RunOptsEnv<'scope> {
|
pub struct RunOptsEnv<'scope> {
|
||||||
#[builder(into)]
|
pub key: &'scope str,
|
||||||
pub key: Cow<'scope, str>,
|
pub value: &'scope str,
|
||||||
|
|
||||||
#[builder(into)]
|
|
||||||
pub value: Cow<'scope, str>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
@ -78,7 +67,7 @@ macro_rules! run_envs {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct CreateContainerOpts<'scope> {
|
pub struct CreateContainerOpts<'scope> {
|
||||||
pub image: &'scope Reference,
|
pub image: &'scope Reference,
|
||||||
|
|
||||||
|
|
@ -86,7 +75,7 @@ pub struct CreateContainerOpts<'scope> {
|
||||||
pub privileged: bool,
|
pub privileged: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct RemoveContainerOpts<'scope> {
|
pub struct RemoveContainerOpts<'scope> {
|
||||||
pub container_id: &'scope ContainerId,
|
pub container_id: &'scope ContainerId,
|
||||||
|
|
||||||
|
|
@ -94,7 +83,7 @@ pub struct RemoveContainerOpts<'scope> {
|
||||||
pub privileged: bool,
|
pub privileged: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct RemoveImageOpts<'scope> {
|
pub struct RemoveImageOpts<'scope> {
|
||||||
pub image: &'scope Reference,
|
pub image: &'scope Reference,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
@ -12,6 +11,7 @@ use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
use crate::drivers::types::Platform;
|
use crate::drivers::types::Platform;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum PrivateKey {
|
pub enum PrivateKey {
|
||||||
Env(String),
|
Env(String),
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
|
|
@ -56,53 +56,42 @@ impl PrivateKeyContents<String> for PrivateKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct GenerateKeyPairOpts<'scope> {
|
pub struct GenerateKeyPairOpts<'scope> {
|
||||||
#[builder(into)]
|
pub dir: Option<&'scope Path>,
|
||||||
pub dir: Option<Cow<'scope, Path>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct CheckKeyPairOpts<'scope> {
|
pub struct CheckKeyPairOpts<'scope> {
|
||||||
#[builder(into)]
|
pub dir: Option<&'scope Path>,
|
||||||
pub dir: Option<Cow<'scope, Path>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct SignOpts<'scope> {
|
pub struct SignOpts<'scope> {
|
||||||
#[builder(into)]
|
|
||||||
pub image: &'scope Reference,
|
pub image: &'scope Reference,
|
||||||
|
pub key: Option<&'scope PrivateKey>,
|
||||||
#[builder(into)]
|
pub dir: Option<&'scope Path>,
|
||||||
pub key: Option<Cow<'scope, str>>,
|
|
||||||
|
|
||||||
#[builder(into)]
|
|
||||||
pub dir: Option<Cow<'scope, Path>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum VerifyType<'scope> {
|
pub enum VerifyType<'scope> {
|
||||||
File(Cow<'scope, Path>),
|
File(&'scope Path),
|
||||||
Keyless {
|
Keyless {
|
||||||
issuer: Cow<'scope, str>,
|
issuer: &'scope str,
|
||||||
identity: Cow<'scope, str>,
|
identity: &'scope str,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct VerifyOpts<'scope> {
|
pub struct VerifyOpts<'scope> {
|
||||||
#[builder(into)]
|
|
||||||
pub image: &'scope Reference,
|
pub image: &'scope Reference,
|
||||||
pub verify_type: VerifyType<'scope>,
|
pub verify_type: VerifyType<'scope>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Copy, Builder)]
|
||||||
pub struct SignVerifyOpts<'scope> {
|
pub struct SignVerifyOpts<'scope> {
|
||||||
#[builder(into)]
|
|
||||||
pub image: &'scope Reference,
|
pub image: &'scope Reference,
|
||||||
|
pub dir: Option<&'scope Path>,
|
||||||
#[builder(into)]
|
|
||||||
pub dir: Option<Cow<'scope, Path>>,
|
|
||||||
|
|
||||||
/// Enable retry logic for pushing.
|
/// Enable retry logic for pushing.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
ops::Not,
|
||||||
path::Path,
|
path::Path,
|
||||||
process::{Command, ExitStatus},
|
process::{Command, ExitStatus},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use blue_build_utils::{
|
use blue_build_utils::{
|
||||||
constants::SUDO_ASKPASS, credentials::Credentials, has_env_var, running_as_root,
|
constants::USER, credentials::Credentials, get_env_var, secret::SecretArgs, semver::Version,
|
||||||
secret::SecretArgs, semver::Version,
|
sudo_cmd,
|
||||||
};
|
};
|
||||||
use cached::proc_macro::cached;
|
use cached::proc_macro::cached;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
@ -21,7 +22,10 @@ use tempfile::TempDir;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ContainerMountDriver, RechunkDriver,
|
ContainerMountDriver, RechunkDriver,
|
||||||
opts::{CreateContainerOpts, RemoveContainerOpts, RemoveImageOpts},
|
opts::{
|
||||||
|
ContainerOpts, CreateContainerOpts, PruneOpts, RemoveContainerOpts, RemoveImageOpts,
|
||||||
|
VolumeOpts,
|
||||||
|
},
|
||||||
types::{ContainerId, MountId},
|
types::{ContainerId, MountId},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -108,6 +112,42 @@ struct PodmanVersionJson {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PodmanDriver;
|
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 {
|
impl DriverVersion for PodmanDriver {
|
||||||
// First podman version to use buildah v1.24
|
// First podman version to use buildah v1.24
|
||||||
// https://github.com/containers/podman/blob/main/RELEASE_NOTES.md#400
|
// https://github.com/containers/podman/blob/main/RELEASE_NOTES.md#400
|
||||||
|
|
@ -134,29 +174,17 @@ impl DriverVersion for PodmanDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildDriver for PodmanDriver {
|
impl BuildDriver for PodmanDriver {
|
||||||
fn build(opts: &BuildOpts) -> Result<()> {
|
fn build(opts: BuildOpts) -> Result<()> {
|
||||||
trace!("PodmanDriver::build({opts:#?})");
|
trace!("PodmanDriver::build({opts:#?})");
|
||||||
|
|
||||||
let temp_dir = TempDir::new()
|
let temp_dir = TempDir::new()
|
||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
.wrap_err("Failed to create temporary directory for secrets")?;
|
.wrap_err("Failed to create temporary directory for secrets")?;
|
||||||
|
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
let command = sudo_cmd!(
|
||||||
let command = cmd!(
|
prompt = SUDO_PROMPT,
|
||||||
if use_sudo {
|
sudo_check = opts.privileged,
|
||||||
"sudo"
|
"podman",
|
||||||
} else {
|
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => [
|
|
||||||
"--preserve-env",
|
|
||||||
"podman",
|
|
||||||
],
|
|
||||||
"build",
|
"build",
|
||||||
if let Some(platform) = opts.platform => [
|
if let Some(platform) = opts.platform => [
|
||||||
"--platform",
|
"--platform",
|
||||||
|
|
@ -182,7 +210,7 @@ impl BuildDriver for PodmanDriver {
|
||||||
if opts.host_network => "--net=host",
|
if opts.host_network => "--net=host",
|
||||||
format!("--layers={}", !opts.squash),
|
format!("--layers={}", !opts.squash),
|
||||||
"-f",
|
"-f",
|
||||||
&*opts.containerfile,
|
opts.containerfile,
|
||||||
"-t",
|
"-t",
|
||||||
opts.image.to_string(),
|
opts.image.to_string(),
|
||||||
for opts.secrets.args(&temp_dir)?,
|
for opts.secrets.args(&temp_dir)?,
|
||||||
|
|
@ -203,24 +231,15 @@ impl BuildDriver for PodmanDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tag(opts: &TagOpts) -> Result<()> {
|
fn tag(opts: TagOpts) -> Result<()> {
|
||||||
trace!("PodmanDriver::tag({opts:#?})");
|
trace!("PodmanDriver::tag({opts:#?})");
|
||||||
|
|
||||||
let dest_image_str = opts.dest_image.to_string();
|
let dest_image_str = opts.dest_image.to_string();
|
||||||
|
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
let mut command = sudo_cmd!(
|
||||||
let mut command = cmd!(
|
prompt = SUDO_PROMPT,
|
||||||
if use_sudo {
|
sudo_check = opts.privileged,
|
||||||
"sudo"
|
"podman",
|
||||||
} else {
|
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"tag",
|
"tag",
|
||||||
opts.src_image.to_string(),
|
opts.src_image.to_string(),
|
||||||
&dest_image_str
|
&dest_image_str
|
||||||
|
|
@ -237,24 +256,15 @@ impl BuildDriver for PodmanDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(opts: &PushOpts) -> Result<()> {
|
fn push(opts: PushOpts) -> Result<()> {
|
||||||
trace!("PodmanDriver::push({opts:#?})");
|
trace!("PodmanDriver::push({opts:#?})");
|
||||||
|
|
||||||
let image_str = opts.image.to_string();
|
let image_str = opts.image.to_string();
|
||||||
|
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
let command = sudo_cmd!(
|
||||||
let command = cmd!(
|
prompt = SUDO_PROMPT,
|
||||||
if use_sudo {
|
sudo_check = opts.privileged,
|
||||||
"sudo"
|
"podman",
|
||||||
} else {
|
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"push",
|
"push",
|
||||||
format!(
|
format!(
|
||||||
"--compression-format={}",
|
"--compression-format={}",
|
||||||
|
|
@ -312,7 +322,7 @@ impl BuildDriver for PodmanDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
fn prune(opts: PruneOpts) -> Result<()> {
|
||||||
trace!("PodmanDriver::prune({opts:?})");
|
trace!("PodmanDriver::prune({opts:?})");
|
||||||
|
|
||||||
let status = {
|
let status = {
|
||||||
|
|
@ -339,7 +349,7 @@ impl BuildDriver for PodmanDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InspectDriver for PodmanDriver {
|
impl InspectDriver for PodmanDriver {
|
||||||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||||
get_metadata_cache(opts)
|
get_metadata_cache(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +360,7 @@ impl InspectDriver for PodmanDriver {
|
||||||
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
||||||
sync_writes = "by_key"
|
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:#?})");
|
trace!("PodmanDriver::get_metadata({opts:#?})");
|
||||||
|
|
||||||
let image_str = opts.image.to_string();
|
let image_str = opts.image.to_string();
|
||||||
|
|
@ -409,21 +419,12 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerMountDriver for PodmanDriver {
|
impl ContainerMountDriver for PodmanDriver {
|
||||||
fn mount_container(opts: &super::opts::ContainerOpts) -> Result<MountId> {
|
fn mount_container(opts: ContainerOpts) -> Result<MountId> {
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
|
||||||
let output = {
|
let output = {
|
||||||
let c = cmd!(
|
let c = sudo_cmd!(
|
||||||
if use_sudo {
|
prompt = SUDO_PROMPT,
|
||||||
"sudo"
|
sudo_check = opts.privileged,
|
||||||
} else {
|
"podman",
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"mount",
|
"mount",
|
||||||
opts.container_id,
|
opts.container_id,
|
||||||
);
|
);
|
||||||
|
|
@ -442,21 +443,12 @@ impl ContainerMountDriver for PodmanDriver {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmount_container(opts: &super::opts::ContainerOpts) -> Result<()> {
|
fn unmount_container(opts: ContainerOpts) -> Result<()> {
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
|
||||||
let output = {
|
let output = {
|
||||||
let c = cmd!(
|
let c = sudo_cmd!(
|
||||||
if use_sudo {
|
prompt = SUDO_PROMPT,
|
||||||
"sudo"
|
sudo_check = opts.privileged,
|
||||||
} else {
|
"podman",
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"unmount",
|
"unmount",
|
||||||
opts.container_id
|
opts.container_id
|
||||||
);
|
);
|
||||||
|
|
@ -473,24 +465,15 @@ impl ContainerMountDriver for PodmanDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_volume(opts: &super::opts::VolumeOpts) -> Result<()> {
|
fn remove_volume(opts: VolumeOpts) -> Result<()> {
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
|
||||||
let output = {
|
let output = {
|
||||||
let c = cmd!(
|
let c = sudo_cmd!(
|
||||||
if use_sudo {
|
prompt = SUDO_PROMPT,
|
||||||
"sudo"
|
sudo_check = opts.privileged,
|
||||||
} else {
|
"podman",
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"volume",
|
"volume",
|
||||||
"rm",
|
"rm",
|
||||||
&*opts.volume_id
|
opts.volume_id
|
||||||
);
|
);
|
||||||
trace!("{c:?}");
|
trace!("{c:?}");
|
||||||
c
|
c
|
||||||
|
|
@ -509,7 +492,7 @@ impl ContainerMountDriver for PodmanDriver {
|
||||||
impl RechunkDriver for PodmanDriver {}
|
impl RechunkDriver for PodmanDriver {}
|
||||||
|
|
||||||
impl RunDriver for PodmanDriver {
|
impl RunDriver for PodmanDriver {
|
||||||
fn run(opts: &RunOpts) -> Result<ExitStatus> {
|
fn run(opts: RunOpts) -> Result<ExitStatus> {
|
||||||
trace!("PodmanDriver::run({opts:#?})");
|
trace!("PodmanDriver::run({opts:#?})");
|
||||||
|
|
||||||
let cid_path = TempDir::new().into_diagnostic()?;
|
let cid_path = TempDir::new().into_diagnostic()?;
|
||||||
|
|
@ -520,7 +503,7 @@ impl RunDriver for PodmanDriver {
|
||||||
add_cid(&cid);
|
add_cid(&cid);
|
||||||
|
|
||||||
let status = podman_run(opts, &cid_file)
|
let status = podman_run(opts, &cid_file)
|
||||||
.build_status(&*opts.image, "Running container")
|
.build_status(opts.image, "Running container")
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
|
|
||||||
remove_cid(&cid);
|
remove_cid(&cid);
|
||||||
|
|
@ -528,7 +511,7 @@ impl RunDriver for PodmanDriver {
|
||||||
Ok(status)
|
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:#?})");
|
trace!("PodmanDriver::run_output({opts:#?})");
|
||||||
|
|
||||||
let cid_path = TempDir::new().into_diagnostic()?;
|
let cid_path = TempDir::new().into_diagnostic()?;
|
||||||
|
|
@ -545,23 +528,14 @@ impl RunDriver for PodmanDriver {
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_container(opts: &CreateContainerOpts) -> Result<ContainerId> {
|
fn create_container(opts: CreateContainerOpts) -> Result<ContainerId> {
|
||||||
trace!("PodmanDriver::create_container({opts:?})");
|
trace!("PodmanDriver::create_container({opts:?})");
|
||||||
|
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
|
||||||
let output = {
|
let output = {
|
||||||
let c = cmd!(
|
let c = sudo_cmd!(
|
||||||
if use_sudo {
|
prompt = SUDO_PROMPT,
|
||||||
"sudo"
|
sudo_check = opts.privileged,
|
||||||
} else {
|
"podman",
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"create",
|
"create",
|
||||||
opts.image.to_string(),
|
opts.image.to_string(),
|
||||||
"bash"
|
"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:?})");
|
trace!("PodmanDriver::remove_container({opts:?})");
|
||||||
|
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
|
||||||
let output = {
|
let output = {
|
||||||
let c = cmd!(
|
let c = sudo_cmd!(
|
||||||
if use_sudo {
|
prompt = SUDO_PROMPT,
|
||||||
"sudo"
|
sudo_check = opts.privileged,
|
||||||
} else {
|
"podman",
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"rm",
|
"rm",
|
||||||
opts.container_id,
|
opts.container_id,
|
||||||
);
|
);
|
||||||
|
|
@ -614,23 +579,14 @@ impl RunDriver for PodmanDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
|
fn remove_image(opts: RemoveImageOpts) -> Result<()> {
|
||||||
trace!("PodmanDriver::remove_image({opts:?})");
|
trace!("PodmanDriver::remove_image({opts:?})");
|
||||||
|
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
|
||||||
let output = {
|
let output = {
|
||||||
let c = cmd!(
|
let c = sudo_cmd!(
|
||||||
if use_sudo {
|
prompt = SUDO_PROMPT,
|
||||||
"sudo"
|
sudo_check = opts.privileged,
|
||||||
} else {
|
"podman",
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"rmi",
|
"rmi",
|
||||||
opts.image.to_string()
|
opts.image.to_string()
|
||||||
);
|
);
|
||||||
|
|
@ -656,20 +612,11 @@ impl RunDriver for PodmanDriver {
|
||||||
|
|
||||||
trace!("PodmanDriver::list_images({privileged})");
|
trace!("PodmanDriver::list_images({privileged})");
|
||||||
|
|
||||||
let use_sudo = privileged && !running_as_root();
|
|
||||||
let output = {
|
let output = {
|
||||||
let c = cmd!(
|
let c = sudo_cmd!(
|
||||||
if use_sudo {
|
prompt = SUDO_PROMPT,
|
||||||
"sudo"
|
sudo_check = privileged,
|
||||||
} else {
|
"podman",
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"images",
|
"images",
|
||||||
"--format",
|
"--format",
|
||||||
"json"
|
"json"
|
||||||
|
|
@ -698,20 +645,11 @@ impl RunDriver for PodmanDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
|
fn podman_run(opts: RunOpts, cid_file: &Path) -> Command {
|
||||||
let use_sudo = opts.privileged && !running_as_root();
|
let command = sudo_cmd!(
|
||||||
let command = cmd!(
|
prompt = SUDO_PROMPT,
|
||||||
if use_sudo {
|
sudo_check = opts.privileged,
|
||||||
"sudo"
|
"podman",
|
||||||
} else {
|
|
||||||
"podman"
|
|
||||||
},
|
|
||||||
if use_sudo && has_env_var(SUDO_ASKPASS) => [
|
|
||||||
"-A",
|
|
||||||
"-p",
|
|
||||||
SUDO_PROMPT,
|
|
||||||
],
|
|
||||||
if use_sudo => "podman",
|
|
||||||
"run",
|
"run",
|
||||||
format!("--cidfile={}", cid_file.display()),
|
format!("--cidfile={}", cid_file.display()),
|
||||||
if opts.privileged => [
|
if opts.privileged => [
|
||||||
|
|
@ -729,7 +667,7 @@ fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
|
||||||
"--env",
|
"--env",
|
||||||
format!("{key}={value}"),
|
format!("{key}={value}"),
|
||||||
],
|
],
|
||||||
&*opts.image,
|
opts.image,
|
||||||
for arg in opts.args.iter() => &**arg,
|
for arg in opts.args.iter() => &**arg,
|
||||||
);
|
);
|
||||||
trace!("{command:?}");
|
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;
|
pub struct SigstoreDriver;
|
||||||
|
|
||||||
impl SigningDriver for 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 path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
|
||||||
let priv_key_path = path.join(COSIGN_PRIV_PATH);
|
let priv_key_path = path.join(COSIGN_PRIV_PATH);
|
||||||
let pub_key_path = path.join(COSIGN_PUB_PATH);
|
let pub_key_path = path.join(COSIGN_PUB_PATH);
|
||||||
|
|
@ -70,7 +70,7 @@ impl SigningDriver for SigstoreDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_signing_files(opts: &CheckKeyPairOpts) -> miette::Result<()> {
|
fn check_signing_files(opts: CheckKeyPairOpts) -> miette::Result<()> {
|
||||||
trace!("SigstoreDriver::check_signing_files({opts:?})");
|
trace!("SigstoreDriver::check_signing_files({opts:?})");
|
||||||
|
|
||||||
let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
|
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:?})");
|
trace!("SigstoreDriver::sign({opts:?})");
|
||||||
|
|
||||||
if opts.image.digest().is_none() {
|
if opts.image.digest().is_none() {
|
||||||
|
|
@ -176,7 +176,7 @@ impl SigningDriver for SigstoreDriver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(opts: &VerifyOpts) -> miette::Result<()> {
|
fn verify(opts: VerifyOpts) -> miette::Result<()> {
|
||||||
let mut client = ClientBuilder::default().build().into_diagnostic()?;
|
let mut client = ClientBuilder::default().build().into_diagnostic()?;
|
||||||
|
|
||||||
let image_digest: OciReference = opts.image.to_string().parse().into_diagnostic()?;
|
let image_digest: OciReference = opts.image.to_string().parse().into_diagnostic()?;
|
||||||
|
|
@ -253,9 +253,10 @@ mod test {
|
||||||
fn generate_key_pair() {
|
fn generate_key_pair() {
|
||||||
let tempdir = TempDir::new().unwrap();
|
let tempdir = TempDir::new().unwrap();
|
||||||
|
|
||||||
let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
|
SigstoreDriver::generate_key_pair(
|
||||||
|
GenerateKeyPairOpts::builder().dir(tempdir.path()).build(),
|
||||||
SigstoreDriver::generate_key_pair(&gen_opts).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Private key:\n{}",
|
"Private key:\n{}",
|
||||||
|
|
@ -266,27 +267,27 @@ mod test {
|
||||||
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
|
SigstoreDriver::check_signing_files(
|
||||||
|
CheckKeyPairOpts::builder().dir(tempdir.path()).build(),
|
||||||
SigstoreDriver::check_signing_files(&check_opts).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_key_pairs() {
|
fn check_key_pairs() {
|
||||||
let path = Path::new("../test-files/keys");
|
let path = Path::new("../test-files/keys");
|
||||||
|
|
||||||
let opts = CheckKeyPairOpts::builder().dir(path).build();
|
SigstoreDriver::check_signing_files(CheckKeyPairOpts::builder().dir(path).build()).unwrap();
|
||||||
|
|
||||||
SigstoreDriver::check_signing_files(&opts).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compatibility() {
|
fn compatibility() {
|
||||||
let tempdir = TempDir::new().unwrap();
|
let tempdir = TempDir::new().unwrap();
|
||||||
|
|
||||||
let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
|
SigstoreDriver::generate_key_pair(
|
||||||
|
GenerateKeyPairOpts::builder().dir(tempdir.path()).build(),
|
||||||
SigstoreDriver::generate_key_pair(&gen_opts).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Private key:\n{}",
|
"Private key:\n{}",
|
||||||
|
|
@ -297,8 +298,7 @@ mod test {
|
||||||
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
|
CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(tempdir.path()).build())
|
||||||
|
.unwrap();
|
||||||
CosignDriver::check_signing_files(&check_opts).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,17 @@ use miette::{IntoDiagnostic, Result, bail};
|
||||||
|
|
||||||
use crate::{drivers::types::Platform, logging::Logger};
|
use crate::{drivers::types::Platform, logging::Logger};
|
||||||
|
|
||||||
use super::{InspectDriver, opts::GetMetadataOpts, types::ImageMetadata};
|
use super::{
|
||||||
|
InspectDriver,
|
||||||
|
opts::{CopyOciDirOpts, GetMetadataOpts},
|
||||||
|
types::ImageMetadata,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SkopeoDriver;
|
pub struct SkopeoDriver;
|
||||||
|
|
||||||
impl InspectDriver for SkopeoDriver {
|
impl InspectDriver for SkopeoDriver {
|
||||||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
|
||||||
get_metadata_cache(opts)
|
get_metadata_cache(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +30,7 @@ impl InspectDriver for SkopeoDriver {
|
||||||
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#,
|
||||||
sync_writes = "by_key"
|
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:#?})");
|
trace!("SkopeoDriver::get_metadata({opts:#?})");
|
||||||
|
|
||||||
let image_str = opts.image.to_string();
|
let image_str = opts.image.to_string();
|
||||||
|
|
@ -68,7 +72,7 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::OciCopy for SkopeoDriver {
|
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;
|
use crate::logging::CommandLogging;
|
||||||
|
|
||||||
let use_sudo = opts.privileged && !blue_build_utils::running_as_root();
|
let use_sudo = opts.privileged && !blue_build_utils::running_as_root();
|
||||||
|
|
|
||||||
|
|
@ -17,22 +17,16 @@ use crate::drivers::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
buildah_driver::BuildahDriver,
|
|
||||||
cosign_driver::CosignDriver,
|
|
||||||
docker_driver::DockerDriver,
|
|
||||||
github_driver::GithubDriver,
|
|
||||||
gitlab_driver::GitlabDriver,
|
|
||||||
local_driver::LocalDriver,
|
|
||||||
opts::{
|
opts::{
|
||||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, CreateContainerOpts, GenerateImageNameOpts,
|
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, ContainerOpts, CopyOciDirOpts,
|
||||||
GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts, RechunkOpts,
|
CreateContainerOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts,
|
||||||
RemoveContainerOpts, RemoveImageOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts,
|
GetMetadataOpts, PushOpts, RechunkOpts, RemoveContainerOpts, RemoveImageOpts, RunOpts,
|
||||||
VerifyOpts, VerifyType,
|
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 {}
|
trait PrivateDriver {}
|
||||||
|
|
@ -46,19 +40,37 @@ macro_rules! impl_private_driver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_private_driver!(
|
impl_private_driver!(
|
||||||
Driver,
|
super::Driver,
|
||||||
DockerDriver,
|
super::docker_driver::DockerDriver,
|
||||||
PodmanDriver,
|
super::podman_driver::PodmanDriver,
|
||||||
BuildahDriver,
|
super::buildah_driver::BuildahDriver,
|
||||||
GithubDriver,
|
super::github_driver::GithubDriver,
|
||||||
GitlabDriver,
|
super::gitlab_driver::GitlabDriver,
|
||||||
LocalDriver,
|
super::local_driver::LocalDriver,
|
||||||
CosignDriver,
|
super::cosign_driver::CosignDriver,
|
||||||
SkopeoDriver,
|
super::skopeo_driver::SkopeoDriver,
|
||||||
CiDriverType,
|
super::sigstore_driver::SigstoreDriver,
|
||||||
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.
|
/// Trait for retrieving version of a driver.
|
||||||
#[allow(private_bounds)]
|
#[allow(private_bounds)]
|
||||||
pub trait DriverVersion: PrivateDriver {
|
pub trait DriverVersion: PrivateDriver {
|
||||||
|
|
@ -88,19 +100,19 @@ pub trait BuildDriver: PrivateDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the build fails.
|
/// Will error if the build fails.
|
||||||
fn build(opts: &BuildOpts) -> Result<()>;
|
fn build(opts: BuildOpts) -> Result<()>;
|
||||||
|
|
||||||
/// Runs the tag logic for the driver.
|
/// Runs the tag logic for the driver.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the tagging fails.
|
/// Will error if the tagging fails.
|
||||||
fn tag(opts: &TagOpts) -> Result<()>;
|
fn tag(opts: TagOpts) -> Result<()>;
|
||||||
|
|
||||||
/// Runs the push logic for the driver
|
/// Runs the push logic for the driver
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the push fails.
|
/// Will error if the push fails.
|
||||||
fn push(opts: &PushOpts) -> Result<()>;
|
fn push(opts: PushOpts) -> Result<()>;
|
||||||
|
|
||||||
/// Runs the login logic for the driver.
|
/// Runs the login logic for the driver.
|
||||||
///
|
///
|
||||||
|
|
@ -112,27 +124,27 @@ pub trait BuildDriver: PrivateDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the driver fails to prune.
|
/// 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.
|
/// Runs the logic for building, tagging, and pushing an image.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if building, tagging, or pusing fails.
|
/// 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:#?})");
|
trace!("BuildDriver::build_tag_push({opts:#?})");
|
||||||
|
|
||||||
let build_opts = BuildOpts::builder()
|
let build_opts = BuildOpts::builder()
|
||||||
.image(&opts.image)
|
.image(opts.image)
|
||||||
.containerfile(opts.containerfile.as_ref())
|
.containerfile(opts.containerfile.as_ref())
|
||||||
.maybe_platform(opts.platform)
|
.maybe_platform(opts.platform)
|
||||||
.squash(opts.squash)
|
.squash(opts.squash)
|
||||||
.maybe_cache_from(opts.cache_from)
|
.maybe_cache_from(opts.cache_from)
|
||||||
.maybe_cache_to(opts.cache_to)
|
.maybe_cache_to(opts.cache_to)
|
||||||
.secrets(opts.secrets.clone())
|
.secrets(opts.secrets)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
info!("Building image {}", opts.image);
|
info!("Building image {}", opts.image);
|
||||||
Self::build(&build_opts)?;
|
Self::build(build_opts)?;
|
||||||
|
|
||||||
let image_list: Vec<String> = match &opts.image {
|
let image_list: Vec<String> = match &opts.image {
|
||||||
ImageRef::Remote(image) if !opts.tags.is_empty() => {
|
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());
|
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);
|
debug!("Tagging {} with {tag}", &image);
|
||||||
let tagged_image = Reference::with_tag(
|
let tagged_image = Reference::with_tag(
|
||||||
image.registry().into(),
|
image.registry().into(),
|
||||||
|
|
@ -153,7 +165,7 @@ pub trait BuildDriver: PrivateDriver {
|
||||||
.dest_image(&tagged_image)
|
.dest_image(&tagged_image)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Self::tag(&tag_opts)?;
|
Self::tag(tag_opts)?;
|
||||||
image_list.push(tagged_image.to_string());
|
image_list.push(tagged_image.to_string());
|
||||||
|
|
||||||
if opts.push {
|
if opts.push {
|
||||||
|
|
@ -169,7 +181,7 @@ pub trait BuildDriver: PrivateDriver {
|
||||||
.compression_type(opts.compression)
|
.compression_type(opts.compression)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Self::push(&push_opts)
|
Self::push(push_opts)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +189,7 @@ pub trait BuildDriver: PrivateDriver {
|
||||||
image_list
|
image_list
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
string_vec![&opts.image]
|
string_vec![opts.image]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -192,7 +204,7 @@ pub trait InspectDriver: PrivateDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if it is unable to get the labels.
|
/// 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.
|
/// Allows agnostic running of containers.
|
||||||
|
|
@ -202,31 +214,31 @@ pub trait RunDriver: PrivateDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if there is an issue running the container.
|
/// 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.
|
/// Run a container to perform an action and capturing output.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if there is an issue running the container.
|
/// 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
|
/// Creates container
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the container create command fails.
|
/// 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
|
/// Removes a container
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the container remove command fails.
|
/// Will error if the container remove command fails.
|
||||||
fn remove_container(opts: &RemoveContainerOpts) -> Result<()>;
|
fn remove_container(opts: RemoveContainerOpts) -> Result<()>;
|
||||||
|
|
||||||
/// Removes an image
|
/// Removes an image
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the image remove command fails.
|
/// 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.
|
/// List all images in the local image registry.
|
||||||
///
|
///
|
||||||
|
|
@ -241,23 +253,23 @@ pub(super) trait ContainerMountDriver: PrivateDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the container mount command fails.
|
/// 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
|
/// Unmount the container
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the container unmount command fails.
|
/// 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
|
/// Remove a volume
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the volume remove command fails.
|
/// 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 {
|
pub(super) trait OciCopy {
|
||||||
fn copy_oci_dir(opts: &super::opts::CopyOciDirOpts) -> Result<()>;
|
fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(private_bounds)]
|
#[allow(private_bounds)]
|
||||||
|
|
@ -268,7 +280,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the rechunk process fails.
|
/// 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 ostree_cache_id = &uuid::Uuid::new_v4().to_string();
|
||||||
let raw_image =
|
let raw_image =
|
||||||
&Reference::try_from(format!("localhost/{ostree_cache_id}/raw-rechunk")).unwrap();
|
&Reference::try_from(format!("localhost/{ostree_cache_id}/raw-rechunk")).unwrap();
|
||||||
|
|
@ -283,25 +295,25 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
||||||
Self::login()?;
|
Self::login()?;
|
||||||
|
|
||||||
Self::build(
|
Self::build(
|
||||||
&BuildOpts::builder()
|
BuildOpts::builder()
|
||||||
.image(raw_image)
|
.image(&ImageRef::from(raw_image))
|
||||||
.containerfile(&*opts.containerfile)
|
.containerfile(opts.containerfile)
|
||||||
.maybe_platform(opts.platform)
|
.maybe_platform(opts.platform)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.squash(true)
|
.squash(true)
|
||||||
.host_network(true)
|
.host_network(true)
|
||||||
.secrets(opts.secrets.clone())
|
.secrets(opts.secrets)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let container = &Self::create_container(
|
let container = &Self::create_container(
|
||||||
&CreateContainerOpts::builder()
|
CreateContainerOpts::builder()
|
||||||
.image(raw_image)
|
.image(raw_image)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
let mount = &Self::mount_container(
|
let mount = &Self::mount_container(
|
||||||
&super::opts::ContainerOpts::builder()
|
super::opts::ContainerOpts::builder()
|
||||||
.container_id(container)
|
.container_id(container)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
|
|
@ -324,7 +336,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
||||||
if opts.push {
|
if opts.push {
|
||||||
let oci_dir = &super::types::OciDir::try_from(temp_dir.path().join(ostree_cache_id))?;
|
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(
|
let tagged_image = Reference::with_tag(
|
||||||
full_image.registry().to_string(),
|
full_image.registry().to_string(),
|
||||||
full_image.repository().to_string(),
|
full_image.repository().to_string(),
|
||||||
|
|
@ -335,7 +347,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
||||||
debug!("Pushing image {tagged_image}");
|
debug!("Pushing image {tagged_image}");
|
||||||
|
|
||||||
Driver::copy_oci_dir(
|
Driver::copy_oci_dir(
|
||||||
&super::opts::CopyOciDirOpts::builder()
|
super::opts::CopyOciDirOpts::builder()
|
||||||
.oci_dir(oci_dir)
|
.oci_dir(oci_dir)
|
||||||
.registry(&tagged_image)
|
.registry(&tagged_image)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
|
|
@ -357,39 +369,39 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
||||||
mount: &MountId,
|
mount: &MountId,
|
||||||
container: &ContainerId,
|
container: &ContainerId,
|
||||||
raw_image: &Reference,
|
raw_image: &Reference,
|
||||||
opts: &RechunkOpts<'_>,
|
opts: RechunkOpts<'_>,
|
||||||
) -> Result<(), miette::Error> {
|
) -> Result<(), miette::Error> {
|
||||||
let status = Self::run(
|
let status = Self::run(
|
||||||
&RunOpts::builder()
|
RunOpts::builder()
|
||||||
.image(Self::RECHUNK_IMAGE)
|
.image(Self::RECHUNK_IMAGE)
|
||||||
.remove(true)
|
.remove(true)
|
||||||
.user("0:0")
|
.user("0:0")
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.volumes(crate::run_volumes! {
|
.volumes(&crate::run_volumes! {
|
||||||
mount => "/var/tree",
|
mount => "/var/tree",
|
||||||
})
|
})
|
||||||
.env_vars(crate::run_envs! {
|
.env_vars(&crate::run_envs! {
|
||||||
"TREE" => "/var/tree",
|
"TREE" => "/var/tree",
|
||||||
})
|
})
|
||||||
.args(bon::vec!["/sources/rechunk/1_prune.sh"])
|
.args(&bon::vec!["/sources/rechunk/1_prune.sh"])
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
Self::unmount_container(
|
Self::unmount_container(
|
||||||
&super::opts::ContainerOpts::builder()
|
super::opts::ContainerOpts::builder()
|
||||||
.container_id(container)
|
.container_id(container)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
Self::remove_container(
|
Self::remove_container(
|
||||||
&RemoveContainerOpts::builder()
|
RemoveContainerOpts::builder()
|
||||||
.container_id(container)
|
.container_id(container)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
Self::remove_image(
|
Self::remove_image(
|
||||||
&RemoveImageOpts::builder()
|
RemoveImageOpts::builder()
|
||||||
.image(raw_image)
|
.image(raw_image)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
|
|
@ -409,40 +421,40 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
||||||
ostree_cache_id: &str,
|
ostree_cache_id: &str,
|
||||||
container: &ContainerId,
|
container: &ContainerId,
|
||||||
raw_image: &Reference,
|
raw_image: &Reference,
|
||||||
opts: &RechunkOpts<'_>,
|
opts: RechunkOpts<'_>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let status = Self::run(
|
let status = Self::run(
|
||||||
&RunOpts::builder()
|
RunOpts::builder()
|
||||||
.image(Self::RECHUNK_IMAGE)
|
.image(Self::RECHUNK_IMAGE)
|
||||||
.remove(true)
|
.remove(true)
|
||||||
.user("0:0")
|
.user("0:0")
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.volumes(crate::run_volumes! {
|
.volumes(&crate::run_volumes! {
|
||||||
mount => "/var/tree",
|
mount => "/var/tree",
|
||||||
ostree_cache_id => "/var/ostree",
|
ostree_cache_id => "/var/ostree",
|
||||||
})
|
})
|
||||||
.env_vars(crate::run_envs! {
|
.env_vars(&crate::run_envs! {
|
||||||
"TREE" => "/var/tree",
|
"TREE" => "/var/tree",
|
||||||
"REPO" => "/var/ostree/repo",
|
"REPO" => "/var/ostree/repo",
|
||||||
"RESET_TIMESTAMP" => "1",
|
"RESET_TIMESTAMP" => "1",
|
||||||
})
|
})
|
||||||
.args(bon::vec!["/sources/rechunk/2_create.sh"])
|
.args(&bon::vec!["/sources/rechunk/2_create.sh"])
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
Self::unmount_container(
|
Self::unmount_container(
|
||||||
&super::opts::ContainerOpts::builder()
|
super::opts::ContainerOpts::builder()
|
||||||
.container_id(container)
|
.container_id(container)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
Self::remove_container(
|
Self::remove_container(
|
||||||
&RemoveContainerOpts::builder()
|
RemoveContainerOpts::builder()
|
||||||
.container_id(container)
|
.container_id(container)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
Self::remove_image(
|
Self::remove_image(
|
||||||
&RemoveImageOpts::builder()
|
RemoveImageOpts::builder()
|
||||||
.image(raw_image)
|
.image(raw_image)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
|
|
@ -463,45 +475,51 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
|
||||||
ostree_cache_id: &str,
|
ostree_cache_id: &str,
|
||||||
temp_dir_str: &str,
|
temp_dir_str: &str,
|
||||||
current_dir: &str,
|
current_dir: &str,
|
||||||
opts: &RechunkOpts<'_>,
|
opts: RechunkOpts<'_>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
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!("org.opencontainers.image.title={}", &opts.name),
|
||||||
|
format_args!("org.opencontainers.image.description={}", &opts.description),
|
||||||
|
format_args!("org.opencontainers.image.source={}", &opts.repo),
|
||||||
|
format_args!("org.opencontainers.image.base.digest={}", &opts.base_digest),
|
||||||
|
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(
|
let status = Self::run(
|
||||||
&RunOpts::builder()
|
RunOpts::builder()
|
||||||
.image(Self::RECHUNK_IMAGE)
|
.image(Self::RECHUNK_IMAGE)
|
||||||
.remove(true)
|
.remove(true)
|
||||||
.user("0:0")
|
.user("0:0")
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.volumes(crate::run_volumes! {
|
.volumes(&crate::run_volumes! {
|
||||||
ostree_cache_id => "/var/ostree",
|
ostree_cache_id => "/var/ostree",
|
||||||
temp_dir_str => "/workspace",
|
temp_dir_str => "/workspace",
|
||||||
current_dir => "/var/git"
|
current_dir => "/var/git"
|
||||||
})
|
})
|
||||||
.env_vars(crate::run_envs! {
|
.env_vars(&crate::run_envs! {
|
||||||
"REPO" => "/var/ostree/repo",
|
"REPO" => "/var/ostree/repo",
|
||||||
"PREV_REF" => &*opts.image,
|
"PREV_REF" => opts.image,
|
||||||
"OUT_NAME" => ostree_cache_id,
|
"OUT_NAME" => ostree_cache_id,
|
||||||
"CLEAR_PLAN" => if opts.clear_plan { "true" } else { "" },
|
"CLEAR_PLAN" => if opts.clear_plan { "true" } else { "" },
|
||||||
"VERSION" => format!("{}", opts.version),
|
"VERSION" => opts.version,
|
||||||
"OUT_REF" => format!("oci:{ostree_cache_id}"),
|
"OUT_REF" => &out_ref,
|
||||||
"GIT_DIR" => "/var/git",
|
"GIT_DIR" => "/var/git",
|
||||||
"LABELS" => format!(
|
"LABELS" => &labels,
|
||||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
|
})
|
||||||
format_args!("{}={}", blue_build_utils::constants::BUILD_ID_LABEL, Driver::get_build_id()),
|
.args(&bon::vec!["/sources/rechunk/3_chunk.sh"])
|
||||||
format_args!("org.opencontainers.image.title={}", &opts.name),
|
.build(),
|
||||||
format_args!("org.opencontainers.image.description={}", &opts.description),
|
|
||||||
format_args!("org.opencontainers.image.source={}", &opts.repo),
|
|
||||||
format_args!("org.opencontainers.image.base.digest={}", &opts.base_digest),
|
|
||||||
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",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.args(bon::vec!["/sources/rechunk/3_chunk.sh"])
|
|
||||||
.build(),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Self::remove_volume(
|
Self::remove_volume(
|
||||||
&super::opts::VolumeOpts::builder()
|
super::opts::VolumeOpts::builder()
|
||||||
.volume_id(ostree_cache_id)
|
.volume_id(ostree_cache_id)
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.build(),
|
.build(),
|
||||||
|
|
@ -522,20 +540,20 @@ pub trait SigningDriver: PrivateDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if a key-pair couldn't be generated.
|
/// 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
|
/// Checks the signing key files to ensure
|
||||||
/// they match.
|
/// they match.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the files cannot be verified.
|
/// 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.
|
/// Signs the image digest.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if signing fails.
|
/// Will error if signing fails.
|
||||||
fn sign(opts: &SignOpts) -> Result<()>;
|
fn sign(opts: SignOpts) -> Result<()>;
|
||||||
|
|
||||||
/// Verifies the image.
|
/// Verifies the image.
|
||||||
///
|
///
|
||||||
|
|
@ -545,22 +563,23 @@ pub trait SigningDriver: PrivateDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the image fails to be verified.
|
/// 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.
|
/// Sign an image given the image name and tag.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the image fails to be signed.
|
/// 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:?})");
|
trace!("sign_and_verify({opts:?})");
|
||||||
|
|
||||||
let path = opts
|
let path = opts
|
||||||
.dir
|
.dir
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or_else(|| PathBuf::from("."), |d| d.to_path_buf());
|
.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(
|
let image_digest = Driver::get_metadata(
|
||||||
&GetMetadataOpts::builder()
|
GetMetadataOpts::builder()
|
||||||
.image(opts.image)
|
.image(opts.image)
|
||||||
.maybe_platform(opts.platform)
|
.maybe_platform(opts.platform)
|
||||||
.build(),
|
.build(),
|
||||||
|
|
@ -573,39 +592,40 @@ pub trait SigningDriver: PrivateDriver {
|
||||||
)
|
)
|
||||||
.parse()
|
.parse()
|
||||||
.into_diagnostic()?;
|
.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) =
|
||||||
// Cosign public/private key pair
|
match (Driver::get_ci_driver(), &priv_key, &issuer, &identity) {
|
||||||
(_, Ok(priv_key)) => (
|
// Cosign public/private key pair
|
||||||
SignOpts::builder()
|
(_, Ok(priv_key), _, _) => (
|
||||||
.image(&image_digest)
|
SignOpts::builder()
|
||||||
.dir(&path)
|
.image(&image_digest)
|
||||||
.key(priv_key.to_string())
|
.dir(&path)
|
||||||
.build(),
|
.key(priv_key)
|
||||||
VerifyOpts::builder()
|
.build(),
|
||||||
.image(opts.image)
|
VerifyOpts::builder()
|
||||||
.verify_type(VerifyType::File(path.join(COSIGN_PUB_PATH).into()))
|
.image(opts.image)
|
||||||
.build(),
|
.verify_type(VerifyType::File(&cosign_file_path))
|
||||||
),
|
.build(),
|
||||||
// Gitlab keyless
|
),
|
||||||
(CiDriverType::Github | CiDriverType::Gitlab, _) => (
|
// Gitlab keyless
|
||||||
SignOpts::builder().dir(&path).image(&image_digest).build(),
|
(CiDriverType::Github | CiDriverType::Gitlab, _, Ok(issuer), Ok(identity)) => (
|
||||||
VerifyOpts::builder()
|
SignOpts::builder().dir(&path).image(&image_digest).build(),
|
||||||
.image(opts.image)
|
VerifyOpts::builder()
|
||||||
.verify_type(VerifyType::Keyless {
|
.image(opts.image)
|
||||||
issuer: Driver::oidc_provider()?.into(),
|
.verify_type(VerifyType::Keyless { issuer, identity })
|
||||||
identity: Driver::keyless_cert_identity()?.into(),
|
.build(),
|
||||||
})
|
),
|
||||||
.build(),
|
_ => bail!("Failed to get information for signing the image"),
|
||||||
),
|
};
|
||||||
_ => bail!("Failed to get information for signing the image"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let retry_count = if opts.retry_push { opts.retry_count } else { 0 };
|
let retry_count = if opts.retry_push { opts.retry_count } else { 0 };
|
||||||
|
|
||||||
retry(retry_count, 5, || {
|
retry(retry_count, 5, || {
|
||||||
Self::sign(&sign_opts)?;
|
Self::sign(sign_opts)?;
|
||||||
Self::verify(&verify_opts)
|
Self::verify(verify_opts)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -665,7 +685,7 @@ pub trait CiDriver: PrivateDriver {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will error if the environment variables aren't set.
|
/// 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.
|
/// Generates the image name based on CI.
|
||||||
///
|
///
|
||||||
|
|
@ -722,3 +742,36 @@ pub trait CiDriver: PrivateDriver {
|
||||||
|
|
||||||
fn default_ci_file_path() -> PathBuf;
|
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::{
|
mod container;
|
||||||
borrow::Cow,
|
mod drivers;
|
||||||
collections::HashMap,
|
mod metadata;
|
||||||
path::{Path, PathBuf},
|
mod platform;
|
||||||
};
|
|
||||||
|
pub use container::*;
|
||||||
use blue_build_utils::{
|
pub use drivers::*;
|
||||||
constants::{GITHUB_ACTIONS, GITLAB_CI, IMAGE_VERSION_LABEL},
|
pub use metadata::*;
|
||||||
get_env_var,
|
pub use platform::*;
|
||||||
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()),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
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]
|
#[must_use]
|
||||||
pub fn get_secrets(&self) -> HashSet<&Secret> {
|
pub fn get_secrets(&self) -> Vec<&Secret> {
|
||||||
self.modules_ext
|
self.modules_ext
|
||||||
.modules
|
.modules
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -154,6 +154,8 @@ impl Recipe<'_> {
|
||||||
.filter_map(|module| Some(&module.required_fields.as_ref()?.secrets))
|
.filter_map(|module| Some(&module.required_fields.as_ref()?.secrets))
|
||||||
.flatten(),
|
.flatten(),
|
||||||
)
|
)
|
||||||
|
.collect::<HashSet<_>>()
|
||||||
|
.into_iter()
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,35 @@ color_string() {
|
||||||
fi
|
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
|
# Parse OS version and export it
|
||||||
export OS_VERSION="$(awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release)"
|
export OS_VERSION="$(awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release)"
|
||||||
export OS_ARCH="$(uname -m)"
|
export OS_ARCH="$(uname -m)"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
. /scripts/exports.sh
|
||||||
|
|
||||||
rm -rf /tmp/* /var/*
|
rm -rf /tmp/* /var/*
|
||||||
|
|
||||||
# if command -v bootc > /dev/null; then
|
if feature_enabled "bootc" && command -v bootc > /dev/null; then
|
||||||
# bootc container lint
|
bootc container lint
|
||||||
# fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,18 @@ use blue_build_process_management::{
|
||||||
BuildTagPushOpts, CheckKeyPairOpts, CompressionType, GenerateImageNameOpts,
|
BuildTagPushOpts, CheckKeyPairOpts, CompressionType, GenerateImageNameOpts,
|
||||||
GenerateTagsOpts, SignVerifyOpts,
|
GenerateTagsOpts, SignVerifyOpts,
|
||||||
},
|
},
|
||||||
types::Platform,
|
types::{ImageRef, Platform},
|
||||||
},
|
},
|
||||||
logging::{color_str, gen_random_ansi_color},
|
logging::{color_str, gen_random_ansi_color},
|
||||||
};
|
};
|
||||||
use blue_build_recipe::Recipe;
|
use blue_build_recipe::Recipe;
|
||||||
use blue_build_utils::{
|
use blue_build_utils::{
|
||||||
constants::{
|
constants::{
|
||||||
ARCHIVE_SUFFIX, BB_REGISTRY_NAMESPACE, BB_SKIP_VALIDATION, CONFIG_PATH, CONTAINER_FILE,
|
ARCHIVE_SUFFIX, BB_REGISTRY_NAMESPACE, BB_SKIP_VALIDATION, CONFIG_PATH, RECIPE_FILE,
|
||||||
RECIPE_FILE, RECIPE_PATH,
|
RECIPE_PATH,
|
||||||
},
|
},
|
||||||
cowstr,
|
|
||||||
credentials::{Credentials, CredentialsArgs},
|
credentials::{Credentials, CredentialsArgs},
|
||||||
string,
|
string,
|
||||||
traits::CowCollecter,
|
|
||||||
};
|
};
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
@ -163,7 +161,7 @@ impl BlueBuildCommand for BuildCommand {
|
||||||
|
|
||||||
if self.push {
|
if self.push {
|
||||||
blue_build_utils::check_command_exists("cosign")?;
|
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::login()?;
|
||||||
Driver::signing_login()?;
|
Driver::signing_login()?;
|
||||||
}
|
}
|
||||||
|
|
@ -191,11 +189,11 @@ impl BlueBuildCommand for BuildCommand {
|
||||||
|
|
||||||
recipe_paths.par_iter().try_for_each(|recipe| {
|
recipe_paths.par_iter().try_for_each(|recipe| {
|
||||||
GenerateCommand::builder()
|
GenerateCommand::builder()
|
||||||
.output(tempdir.path().join(if recipe_paths.len() > 1 {
|
.output(
|
||||||
blue_build_utils::generate_containerfile_path(recipe)?
|
tempdir
|
||||||
} else {
|
.path()
|
||||||
PathBuf::from(CONTAINER_FILE)
|
.join(blue_build_utils::generate_containerfile_path(recipe)?),
|
||||||
}))
|
)
|
||||||
.skip_validation(self.skip_validation)
|
.skip_validation(self.skip_validation)
|
||||||
.maybe_platform(self.platform)
|
.maybe_platform(self.platform)
|
||||||
.recipe(recipe)
|
.recipe(recipe)
|
||||||
|
|
@ -217,12 +215,10 @@ impl BuildCommand {
|
||||||
let images = recipe_paths
|
let images = recipe_paths
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.try_fold(Vec::new, |mut images, recipe_path| -> Result<Vec<String>> {
|
.try_fold(Vec::new, |mut images, recipe_path| -> Result<Vec<String>> {
|
||||||
let containerfile = temp_dir.join(if recipe_paths.len() > 1 {
|
images.extend(self.build(
|
||||||
blue_build_utils::generate_containerfile_path(recipe_path)?
|
recipe_path,
|
||||||
} else {
|
&temp_dir.join(blue_build_utils::generate_containerfile_path(recipe_path)?),
|
||||||
PathBuf::from(CONTAINER_FILE)
|
)?);
|
||||||
});
|
|
||||||
images.extend(self.build(recipe_path, &containerfile)?);
|
|
||||||
Ok(images)
|
Ok(images)
|
||||||
})
|
})
|
||||||
.try_reduce(Vec::new, |mut init, image_names| {
|
.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>> {
|
fn build(&self, recipe_path: &Path, containerfile: &Path) -> Result<Vec<String>> {
|
||||||
let recipe = Recipe::parse(recipe_path)?;
|
let recipe = Recipe::parse(recipe_path)?;
|
||||||
let tags = Driver::generate_tags(
|
let tags = Driver::generate_tags(
|
||||||
&GenerateTagsOpts::builder()
|
GenerateTagsOpts::builder()
|
||||||
.oci_ref(&recipe.base_image_ref()?)
|
.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)
|
.maybe_platform(self.platform)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -276,45 +272,44 @@ impl BuildCommand {
|
||||||
&image_name,
|
&image_name,
|
||||||
cache_image.as_ref(),
|
cache_image.as_ref(),
|
||||||
)?
|
)?
|
||||||
|
} else if let Some(archive_dir) = self.archive.as_ref() {
|
||||||
|
Driver::build_tag_push(
|
||||||
|
BuildTagPushOpts::builder()
|
||||||
|
.containerfile(containerfile)
|
||||||
|
.maybe_platform(self.platform)
|
||||||
|
.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 {
|
} else {
|
||||||
Driver::build_tag_push(&self.archive.as_ref().map_or_else(
|
Driver::build_tag_push(
|
||||||
|| {
|
BuildTagPushOpts::builder()
|
||||||
BuildTagPushOpts::builder()
|
.image(&ImageRef::from(&image))
|
||||||
.image(&image)
|
.containerfile(containerfile)
|
||||||
.containerfile(containerfile)
|
.maybe_platform(self.platform)
|
||||||
.maybe_platform(self.platform)
|
.tags(&tags)
|
||||||
.tags(tags.collect_cow_vec())
|
.push(self.push)
|
||||||
.push(self.push)
|
.retry_push(self.retry_push)
|
||||||
.retry_push(self.retry_push)
|
.retry_count(self.retry_count)
|
||||||
.retry_count(self.retry_count)
|
.compression(self.compression_format)
|
||||||
.compression(self.compression_format)
|
.squash(self.squash)
|
||||||
.squash(self.squash)
|
.maybe_cache_from(cache_image.as_ref())
|
||||||
.maybe_cache_from(cache_image.as_ref())
|
.maybe_cache_to(cache_image.as_ref())
|
||||||
.maybe_cache_to(cache_image.as_ref())
|
.secrets(&recipe.get_secrets())
|
||||||
.secrets(recipe.get_secrets())
|
.build(),
|
||||||
.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()
|
|
||||||
},
|
|
||||||
))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.push && !self.no_sign {
|
if self.push && !self.no_sign {
|
||||||
Driver::sign_and_verify(
|
Driver::sign_and_verify(
|
||||||
&SignVerifyOpts::builder()
|
SignVerifyOpts::builder()
|
||||||
.image(&image)
|
.image(&image)
|
||||||
.retry_push(self.retry_push)
|
.retry_push(self.retry_push)
|
||||||
.retry_count(self.retry_count)
|
.retry_count(self.retry_count)
|
||||||
|
|
@ -342,13 +337,13 @@ impl BuildCommand {
|
||||||
.parse()
|
.parse()
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
Driver::rechunk(
|
Driver::rechunk(
|
||||||
&RechunkOpts::builder()
|
RechunkOpts::builder()
|
||||||
.image(image_name)
|
.image(image_name)
|
||||||
.containerfile(containerfile)
|
.containerfile(containerfile)
|
||||||
.maybe_platform(self.platform)
|
.maybe_platform(self.platform)
|
||||||
.tags(tags.collect_cow_vec())
|
.tags(tags)
|
||||||
.push(self.push)
|
.push(self.push)
|
||||||
.version(format!(
|
.version(&format!(
|
||||||
"{version}.<date>",
|
"{version}.<date>",
|
||||||
version = Driver::get_os_version()
|
version = Driver::get_os_version()
|
||||||
.oci_ref(&recipe.base_image_ref()?)
|
.oci_ref(&recipe.base_image_ref()?)
|
||||||
|
|
@ -359,23 +354,23 @@ impl BuildCommand {
|
||||||
.retry_count(self.retry_count)
|
.retry_count(self.retry_count)
|
||||||
.compression(self.compression_format)
|
.compression(self.compression_format)
|
||||||
.base_digest(
|
.base_digest(
|
||||||
Driver::get_metadata(
|
&Driver::get_metadata(
|
||||||
&GetMetadataOpts::builder()
|
GetMetadataOpts::builder()
|
||||||
.image(&base_image)
|
.image(&base_image)
|
||||||
.maybe_platform(self.platform)
|
.maybe_platform(self.platform)
|
||||||
.build(),
|
.build(),
|
||||||
)?
|
)?
|
||||||
.digest,
|
.digest,
|
||||||
)
|
)
|
||||||
.repo(Driver::get_repo_url()?)
|
.repo(&Driver::get_repo_url()?)
|
||||||
.name(&*recipe.name)
|
.name(&recipe.name)
|
||||||
.description(&*recipe.description)
|
.description(&recipe.description)
|
||||||
.base_image(format!("{}:{}", &recipe.base_image, &recipe.image_version))
|
.base_image(&format!("{}:{}", &recipe.base_image, &recipe.image_version))
|
||||||
.maybe_tempdir(self.tempdir.as_deref())
|
.maybe_tempdir(self.tempdir.as_deref())
|
||||||
.clear_plan(self.rechunk_clear_plan)
|
.clear_plan(self.rechunk_clear_plan)
|
||||||
.maybe_cache_from(cache_image)
|
.maybe_cache_from(cache_image)
|
||||||
.maybe_cache_to(cache_image)
|
.maybe_cache_to(cache_image)
|
||||||
.secrets(recipe.get_secrets())
|
.secrets(&recipe.get_secrets())
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -384,8 +379,8 @@ impl BuildCommand {
|
||||||
let image_name = Driver::generate_image_name(
|
let image_name = Driver::generate_image_name(
|
||||||
GenerateImageNameOpts::builder()
|
GenerateImageNameOpts::builder()
|
||||||
.name(recipe.name.trim())
|
.name(recipe.name.trim())
|
||||||
.maybe_registry(self.credentials.registry.as_ref().map(|r| cowstr!(r)))
|
.maybe_registry(self.credentials.registry.as_deref())
|
||||||
.maybe_registry_namespace(self.registry_namespace.as_ref().map(|r| cowstr!(r)))
|
.maybe_registry_namespace(self.registry_namespace.as_deref())
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,19 @@ impl GenerateCommand {
|
||||||
let base_image: Reference = format!("{}:{}", &recipe.base_image, &recipe.image_version)
|
let base_image: Reference = format!("{}:{}", &recipe.base_image, &recipe.image_version)
|
||||||
.parse()
|
.parse()
|
||||||
.into_diagnostic()?;
|
.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()
|
let template = ContainerFileTemplate::builder()
|
||||||
.os_version(
|
.os_version(
|
||||||
|
|
@ -153,19 +166,12 @@ impl GenerateCommand {
|
||||||
.build_id(Driver::get_build_id())
|
.build_id(Driver::get_build_id())
|
||||||
.recipe(&recipe)
|
.recipe(&recipe)
|
||||||
.recipe_path(recipe_path.as_path())
|
.recipe_path(recipe_path.as_path())
|
||||||
.registry(registry)
|
.registry(®istry)
|
||||||
.repo(Driver::get_repo_url()?)
|
.repo(repo)
|
||||||
.build_scripts_image(determine_scripts_tag(self.platform)?.to_string())
|
.build_scripts_image(build_scripts_image)
|
||||||
.base_digest(
|
.base_digest(base_digest)
|
||||||
Driver::get_metadata(
|
|
||||||
&GetMetadataOpts::builder()
|
|
||||||
.image(&base_image)
|
|
||||||
.maybe_platform(self.platform)
|
|
||||||
.build(),
|
|
||||||
)?
|
|
||||||
.digest,
|
|
||||||
)
|
|
||||||
.maybe_nushell_version(recipe.nushell_version.as_ref())
|
.maybe_nushell_version(recipe.nushell_version.as_ref())
|
||||||
|
.build_features(build_features)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let output_str = template.render().into_diagnostic()?;
|
let output_str = template.render().into_diagnostic()?;
|
||||||
|
|
@ -197,7 +203,7 @@ fn determine_scripts_tag(platform: Option<Platform>) -> Result<Reference> {
|
||||||
.parse()
|
.parse()
|
||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
.and_then(|image| {
|
.and_then(|image| {
|
||||||
Driver::get_metadata(&opts.clone().image(&image).build())
|
Driver::get_metadata(opts.clone().image(&image).build())
|
||||||
.inspect_err(|e| trace!("{e:?}"))
|
.inspect_err(|e| trace!("{e:?}"))
|
||||||
.map(|_| image)
|
.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)
|
let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:{}", shadow::BRANCH)
|
||||||
.parse()
|
.parse()
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
Driver::get_metadata(&opts.clone().image(&image).build())
|
Driver::get_metadata(opts.clone().image(&image).build())
|
||||||
.inspect_err(|e| trace!("{e:?}"))
|
.inspect_err(|e| trace!("{e:?}"))
|
||||||
.map(|_| image)
|
.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!())
|
let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:v{}", crate_version!())
|
||||||
.parse()
|
.parse()
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
Driver::get_metadata(&opts.image(&image).build())
|
Driver::get_metadata(opts.image(&image).build())
|
||||||
.inspect_err(|e| trace!("{e:?}"))
|
.inspect_err(|e| trace!("{e:?}"))
|
||||||
.map(|_| image)
|
.map(|_| image)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ use blue_build_recipe::Recipe;
|
||||||
use blue_build_utils::{
|
use blue_build_utils::{
|
||||||
constants::{ARCHIVE_SUFFIX, BB_SKIP_VALIDATION},
|
constants::{ARCHIVE_SUFFIX, BB_SKIP_VALIDATION},
|
||||||
string_vec,
|
string_vec,
|
||||||
traits::CowCollecter,
|
|
||||||
};
|
};
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
use clap::{Args, Subcommand, ValueEnum};
|
use clap::{Args, Subcommand, ValueEnum};
|
||||||
|
|
@ -189,8 +188,10 @@ impl GenerateIsoCommand {
|
||||||
format!("SECURE_BOOT_KEY_URL={}", self.secure_boot_url),
|
format!("SECURE_BOOT_KEY_URL={}", self.secure_boot_url),
|
||||||
format!("ENROLLMENT_PASSWORD={}", self.enrollment_password),
|
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![
|
let mut vols = run_volumes![
|
||||||
output_dir.display().to_string() => "/build-container-installer/build",
|
output_dir => "/build-container-installer/build",
|
||||||
"dnf-cache" => "/cache/dnf/",
|
"dnf-cache" => "/cache/dnf/",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -239,8 +240,8 @@ impl GenerateIsoCommand {
|
||||||
.call()?,
|
.call()?,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
vols.extend(run_volumes![
|
vols.extend(&run_volumes![
|
||||||
image_out_dir.display().to_string() => "/img_src/",
|
image_out_dir => "/img_src/",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,11 +251,11 @@ impl GenerateIsoCommand {
|
||||||
.image("ghcr.io/jasonn3/build-container-installer")
|
.image("ghcr.io/jasonn3/build-container-installer")
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.remove(true)
|
.remove(true)
|
||||||
.args(args.collect_cow_vec())
|
.args(&args)
|
||||||
.volumes(vols)
|
.volumes(&vols)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let status = Driver::run(&opts)?;
|
let status = Driver::run(opts)?;
|
||||||
|
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
bail!("Failed to create ISO");
|
bail!("Failed to create ISO");
|
||||||
|
|
|
||||||
|
|
@ -518,8 +518,8 @@ impl InitCommand {
|
||||||
.with_context(|| format!("Failed to delete old public file {COSIGN_PUB_PATH}"))?;
|
.with_context(|| format!("Failed to delete old public file {COSIGN_PUB_PATH}"))?;
|
||||||
|
|
||||||
Driver::generate_key_pair(
|
Driver::generate_key_pair(
|
||||||
&GenerateKeyPairOpts::builder()
|
GenerateKeyPairOpts::builder()
|
||||||
.maybe_dir(self.dir.as_ref())
|
.maybe_dir(self.dir.as_deref())
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ impl BlueBuildCommand for PruneCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
Driver::prune(
|
Driver::prune(
|
||||||
&PruneOpts::builder()
|
PruneOpts::builder()
|
||||||
.all(self.all)
|
.all(self.all)
|
||||||
.volumes(self.volumes)
|
.volumes(self.volumes)
|
||||||
.build(),
|
.build(),
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,19 @@
|
||||||
use std::{
|
use std::path::PathBuf;
|
||||||
path::{Path, PathBuf},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use blue_build_process_management::{
|
use blue_build_process_management::drivers::{
|
||||||
drivers::{Driver, DriverArgs},
|
BootDriver, BuildDriver, CiDriver, Driver, DriverArgs, PodmanDriver, RunDriver,
|
||||||
logging::CommandLogging,
|
opts::{BuildOpts, GenerateImageNameOpts, RemoveImageOpts, SwitchOpts},
|
||||||
|
types::ImageRef,
|
||||||
};
|
};
|
||||||
use blue_build_recipe::Recipe;
|
use blue_build_recipe::Recipe;
|
||||||
use blue_build_utils::{
|
use blue_build_utils::constants::BB_SKIP_VALIDATION;
|
||||||
constants::{
|
|
||||||
ARCHIVE_SUFFIX, BB_SKIP_VALIDATION, LOCAL_BUILD, OCI_ARCHIVE, OSTREE_UNVERIFIED_IMAGE,
|
|
||||||
SUDO_ASKPASS,
|
|
||||||
},
|
|
||||||
has_env_var, running_as_root,
|
|
||||||
};
|
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use comlexr::cmd;
|
use log::trace;
|
||||||
use indicatif::ProgressBar;
|
|
||||||
use log::{debug, trace};
|
|
||||||
use miette::{IntoDiagnostic, Result, bail};
|
use miette::{IntoDiagnostic, Result, bail};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
use crate::{commands::build::BuildCommand, rpm_ostree_status::RpmOstreeStatus};
|
use crate::commands::generate::GenerateCommand;
|
||||||
|
|
||||||
use super::BlueBuildCommand;
|
use super::BlueBuildCommand;
|
||||||
|
|
||||||
|
|
@ -60,238 +50,59 @@ impl BlueBuildCommand for SwitchCommand {
|
||||||
|
|
||||||
Driver::init(self.drivers);
|
Driver::init(self.drivers);
|
||||||
|
|
||||||
let status = RpmOstreeStatus::try_new()?;
|
let status = Driver::status()?;
|
||||||
trace!("{status:?}");
|
|
||||||
|
|
||||||
if status.transaction_in_progress() {
|
if status.transaction_in_progress() {
|
||||||
bail!("There is a transaction in progress. Please cancel it using `rpm-ostree cancel`");
|
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 {
|
let tempdir = if let Some(ref dir) = self.tempdir {
|
||||||
TempDir::new_in(dir).into_diagnostic()?
|
TempDir::new_in(dir).into_diagnostic()?
|
||||||
} else {
|
} else {
|
||||||
TempDir::new().into_diagnostic()?
|
TempDir::new().into_diagnostic()?
|
||||||
};
|
};
|
||||||
trace!("{tempdir:?}");
|
let containerfile = tempdir
|
||||||
|
.path()
|
||||||
|
.join(blue_build_utils::generate_containerfile_path(&self.recipe)?);
|
||||||
|
|
||||||
BuildCommand::builder()
|
GenerateCommand::builder()
|
||||||
.recipe([self.recipe.clone()])
|
.output(&containerfile)
|
||||||
.archive(tempdir.path())
|
.recipe(&self.recipe)
|
||||||
.maybe_tempdir(self.tempdir.clone())
|
|
||||||
.skip_validation(self.skip_validation)
|
|
||||||
.build()
|
.build()
|
||||||
.try_run()?;
|
.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)?;
|
if status
|
||||||
let image_file_name = format!(
|
.booted_image()
|
||||||
"{}.{ARCHIVE_SUFFIX}",
|
.is_some_and(|booted| booted == image_name)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
let command = cmd!("rpm-ostree", "upgrade", if self.reboot => "--reboot");
|
Driver::upgrade(
|
||||||
|
SwitchOpts::builder()
|
||||||
trace!("{command:?}");
|
.image(&image_name)
|
||||||
command
|
.reboot(self.reboot)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let image_ref = format!(
|
Driver::switch(
|
||||||
"{OSTREE_UNVERIFIED_IMAGE}:{OCI_ARCHIVE}:{path}",
|
SwitchOpts::builder()
|
||||||
path = archive_path.display()
|
.image(&image_name)
|
||||||
);
|
.reboot(self.reboot)
|
||||||
|
.build(),
|
||||||
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",
|
|
||||||
)
|
|
||||||
.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
|
|
||||||
}
|
|
||||||
.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);
|
shadow_rs::shadow!(shadow);
|
||||||
|
|
||||||
pub mod commands;
|
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"] }
|
askama = { version = "0.14", features = ["serde_json"] }
|
||||||
blue-build-recipe = { version = "=0.9.22", path = "../recipe" }
|
blue-build-recipe = { version = "=0.9.22", path = "../recipe" }
|
||||||
blue-build-utils = { version = "=0.9.22", path = "../utils" }
|
blue-build-utils = { version = "=0.9.22", path = "../utils" }
|
||||||
|
oci-distribution.workspace = true
|
||||||
|
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -9,28 +9,29 @@ use bon::Builder;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use colored::control::ShouldColorize;
|
use colored::control::ShouldColorize;
|
||||||
use log::{debug, error, trace, warn};
|
use log::{debug, error, trace, warn};
|
||||||
|
use oci_distribution::Reference;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use askama::Template;
|
pub use askama::Template;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Template, Builder)]
|
#[derive(Debug, Clone, Template, Builder)]
|
||||||
#[template(path = "Containerfile.j2", escape = "none", whitespace = "minimize")]
|
#[template(path = "Containerfile.j2", escape = "none", whitespace = "minimize")]
|
||||||
#[builder(on(Cow<'_, str>, into))]
|
|
||||||
pub struct ContainerFileTemplate<'a> {
|
pub struct ContainerFileTemplate<'a> {
|
||||||
#[builder(into)]
|
#[builder(into)]
|
||||||
recipe: &'a Recipe<'a>,
|
recipe: &'a Recipe<'a>,
|
||||||
|
recipe_path: &'a Path,
|
||||||
#[builder(into)]
|
|
||||||
recipe_path: Cow<'a, Path>,
|
|
||||||
|
|
||||||
#[builder(into)]
|
#[builder(into)]
|
||||||
build_id: Uuid,
|
build_id: Uuid,
|
||||||
os_version: u64,
|
os_version: u64,
|
||||||
registry: Cow<'a, str>,
|
registry: &'a str,
|
||||||
build_scripts_image: Cow<'a, str>,
|
build_scripts_image: &'a Reference,
|
||||||
repo: Cow<'a, str>,
|
repo: &'a str,
|
||||||
base_digest: Cow<'a, str>,
|
base_digest: &'a str,
|
||||||
nushell_version: Option<&'a MaybeVersion>,
|
nushell_version: Option<&'a MaybeVersion>,
|
||||||
|
|
||||||
|
#[builder(default)]
|
||||||
|
build_features: &'a [String],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerFileTemplate<'_> {
|
impl ContainerFileTemplate<'_> {
|
||||||
|
|
@ -47,6 +48,15 @@ impl ContainerFileTemplate<'_> {
|
||||||
Some(MaybeVersion::Version(version)) => version.to_string(),
|
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)]
|
#[derive(Debug, Clone, Template, Builder)]
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ FROM {{ recipe.base_image }}@{{ base_digest }} AS {{ main_stage }}
|
||||||
|
|
||||||
ARG RECIPE={{ recipe_path.display() }}
|
ARG RECIPE={{ recipe_path.display() }}
|
||||||
ARG IMAGE_REGISTRY={{ registry }}
|
ARG IMAGE_REGISTRY={{ registry }}
|
||||||
|
ARG BB_BUILD_FEATURES="{{ get_features() }}"
|
||||||
|
|
||||||
{%- if self::config_dir_exists() && !self::files_dir_exists() %}
|
{%- if self::config_dir_exists() && !self::files_dir_exists() %}
|
||||||
ARG CONFIG_DIRECTORY="/tmp/config"
|
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 OSTREE_UNVERIFIED_IMAGE: &str = "ostree-unverified-image";
|
||||||
pub const SKOPEO_IMAGE: &str = "quay.io/skopeo/stable:latest";
|
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 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_SHELL: &str = "<unknown shell>";
|
||||||
pub const UNKNOWN_VERSION: &str = "<unknown version>";
|
pub const UNKNOWN_VERSION: &str = "<unknown version>";
|
||||||
pub const UNKNOWN_TERMINAL: &str = "<unknown terminal>";
|
pub const UNKNOWN_TERMINAL: &str = "<unknown terminal>";
|
||||||
|
|
@ -110,3 +111,4 @@ pub const STAGE_SCHEMA: &str = concat!(JSON_SCHEMA, "/stage-v1.json");
|
||||||
// Messages
|
// Messages
|
||||||
pub const BUG_REPORT_WARNING_MESSAGE: &str =
|
pub const BUG_REPORT_WARNING_MESSAGE: &str =
|
||||||
"Please copy the above report and open an issue manually.";
|
"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::{
|
use std::{
|
||||||
collections::HashSet,
|
|
||||||
fs,
|
fs,
|
||||||
hash::{DefaultHasher, Hash, Hasher},
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
ops::Not,
|
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)]
|
#[allow(private_bounds)]
|
||||||
pub trait SecretArgs: private::Private {
|
pub trait SecretArgs: private::Private {
|
||||||
|
|
@ -138,7 +137,7 @@ pub trait SecretArgs: private::Private {
|
||||||
fn ssh(&self) -> bool;
|
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>> {
|
fn args(&self, temp_dir: &TempDir) -> Result<Vec<String>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -173,7 +172,7 @@ impl<H: std::hash::BuildHasher> SecretArgs for HashSet<&Secret, H> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ssh(&self) -> bool {
|
fn ssh(&self) -> bool {
|
||||||
self.contains(&Secret::Ssh)
|
self.contains(&&Secret::Ssh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue