From 3a0be4099a3850872bf64918cbd6b230680ff6f1 Mon Sep 17 00:00:00 2001 From: Gerald Pinder Date: Sat, 9 Aug 2025 14:05:59 -0400 Subject: [PATCH] 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 --- .github/workflows/test.yml | 8 +- Cargo.lock | 445 ++++++----- Cargo.toml | 10 +- integration-tests/Earthfile | 10 +- integration-tests/mock-scripts/bootc | 41 + integration-tests/mock-scripts/podman | 4 + integration-tests/mock-scripts/rpm-ostree | 6 +- .../test-repo/files/usr/{ => bin}/test-file | 0 .../test-repo/recipes/recipe-arm64.yml | 2 +- .../test-repo/recipes/recipe-buildah.yml | 2 +- .../recipes/recipe-docker-external.yml | 2 +- .../recipes/recipe-invalid-from-file.yml | 2 +- .../recipes/recipe-invalid-module.yml | 2 +- .../recipes/recipe-invalid-stage.yml | 2 +- .../test-repo/recipes/recipe-invalid.yml | 2 +- .../test-repo/recipes/recipe-podman.yml | 2 +- .../test-repo/recipes/recipe-rechunk.yml | 2 +- .../test-repo/recipes/recipe.yml | 2 +- process/Cargo.toml | 5 + process/drivers.rs | 141 ++-- process/drivers/bootc_driver.rs | 90 +++ process/drivers/bootc_driver/status.rs | 85 ++ process/drivers/buildah_driver.rs | 12 +- process/drivers/cosign_driver.rs | 36 +- process/drivers/docker_driver.rs | 43 +- process/drivers/github_driver.rs | 10 +- process/drivers/gitlab_driver.rs | 10 +- process/drivers/local_driver.rs | 7 +- process/drivers/opts.rs | 2 + process/drivers/opts/boot.rs | 10 + process/drivers/opts/build.rs | 36 +- process/drivers/opts/ci.rs | 19 +- process/drivers/opts/inspect.rs | 2 +- process/drivers/opts/rechunk.rs | 38 +- process/drivers/opts/run.rs | 47 +- process/drivers/opts/signing.rs | 41 +- process/drivers/podman_driver.rs | 274 +++---- process/drivers/rpm_ostree_driver.rs | 88 +++ process/drivers/rpm_ostree_driver/status.rs | 173 ++++ .../rpm_ostree_driver/status/image_ref.rs | 738 ++++++++++++++++++ process/drivers/sigstore_driver.rs | 38 +- process/drivers/skopeo_driver.rs | 12 +- process/drivers/traits.rs | 335 ++++---- process/drivers/types.rs | 510 +----------- process/drivers/types/container.rs | 179 +++++ process/drivers/types/drivers.rs | 191 +++++ process/drivers/types/metadata.rs | 30 + process/drivers/types/platform.rs | 155 ++++ recipe/src/recipe.rs | 4 +- scripts/exports.sh | 29 + scripts/post_build.sh | 7 +- src/commands/build.rs | 125 ++- src/commands/generate.rs | 36 +- src/commands/generate_iso.rs | 15 +- src/commands/init.rs | 4 +- src/commands/prune.rs | 2 +- src/commands/switch.rs | 279 ++----- src/lib.rs | 1 - src/rpm_ostree_status.rs | 256 ------ template/Cargo.toml | 1 + template/src/lib.rs | 26 +- template/templates/Containerfile.j2 | 1 + utils/src/constants.rs | 2 + utils/src/macros.rs | 152 ++++ utils/src/secret.rs | 7 +- 65 files changed, 2991 insertions(+), 1857 deletions(-) create mode 100755 integration-tests/mock-scripts/bootc rename integration-tests/test-repo/files/usr/{ => bin}/test-file (100%) create mode 100644 process/drivers/bootc_driver.rs create mode 100644 process/drivers/bootc_driver/status.rs create mode 100644 process/drivers/opts/boot.rs create mode 100644 process/drivers/rpm_ostree_driver.rs create mode 100644 process/drivers/rpm_ostree_driver/status.rs create mode 100644 process/drivers/rpm_ostree_driver/status/image_ref.rs create mode 100644 process/drivers/types/container.rs create mode 100644 process/drivers/types/drivers.rs create mode 100644 process/drivers/types/metadata.rs create mode 100644 process/drivers/types/platform.rs delete mode 100644 src/rpm_ostree_status.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a37a161..5ff67ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -207,8 +207,8 @@ jobs: arm64-build: timeout-minutes: 90 - # runs-on: ubuntu-24.04-arm - runs-on: ubuntu-latest + runs-on: ubuntu-24.04-arm + # runs-on: ubuntu-latest permissions: contents: read packages: write @@ -218,8 +218,8 @@ jobs: - name: Maximize build space uses: ublue-os/remove-unwanted-software@cc0becac701cf642c8f0a6613bbdaf5dc36b259e # v9 - - name: Set up QEMU - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + # - name: Set up QEMU + # uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 diff --git a/Cargo.lock b/Cargo.lock index 7fbb869..307d39a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -188,7 +188,7 @@ dependencies = [ "rustc-hash 2.1.1", "serde", "serde_derive", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -211,7 +211,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -222,7 +222,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -238,15 +238,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.1" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -255,9 +255,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ "bindgen", "cc", @@ -347,7 +347,7 @@ dependencies = [ "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -357,7 +357,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.101", + "syn 2.0.104", "which 4.4.2", ] @@ -490,6 +490,7 @@ dependencies = [ "indexmap 2.10.0", "indicatif", "indicatif-log-bridge", + "lazy-regex", "log", "log4rs", "miette", @@ -497,6 +498,7 @@ dependencies = [ "nu-ansi-term", "oci-distribution", "os_pipe", + "pretty_assertions", "rand 0.9.2", "reqwest", "rstest", @@ -540,6 +542,7 @@ dependencies = [ "chrono", "colored", "log", + "oci-distribution", "uuid", ] @@ -598,7 +601,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -619,9 +622,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytecount" @@ -686,7 +689,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -698,7 +701,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -718,9 +721,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.26" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -744,9 +747,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -802,9 +805,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" dependencies = [ "clap_builder", "clap_derive", @@ -822,16 +825,16 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "unicase", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -862,14 +865,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmake" @@ -893,7 +896,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -924,7 +927,7 @@ checksum = "d08086e596b136bd07f79ac3a70646d5159d89676ff23f550573a26e62a98f12" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -936,7 +939,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "windows-sys 0.60.2", ] @@ -999,9 +1002,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1118,7 +1121,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1152,7 +1155,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1166,7 +1169,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1177,7 +1180,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1188,7 +1191,7 @@ checksum = "e79f8e61677d5df9167cd85265f8e5f64b215cdea3fb55eebc3e622e44c7a146" dependencies = [ "darling_core 0.21.0", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1218,7 +1221,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1260,7 +1263,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1270,7 +1273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1279,6 +1282,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -1320,7 +1329,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1342,9 +1351,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ecdsa" @@ -1372,9 +1381,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -1460,12 +1469,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1656,7 +1665,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1717,11 +1726,11 @@ dependencies = [ [[package]] name = "getopts" -version = "0.2.21" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" dependencies = [ - "unicode-width 0.1.14", + "unicode-width 0.2.1", ] [[package]] @@ -1733,7 +1742,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1753,14 +1762,14 @@ dependencies = [ [[package]] name = "getset" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1990,9 +1999,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64 0.22.1", "bytes", @@ -2006,7 +2015,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -2181,7 +2190,7 @@ dependencies = [ "portable-atomic", "rayon", "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "unit-prefix", "web-time", ] @@ -2208,9 +2217,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -2279,6 +2288,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -2409,6 +2427,29 @@ dependencies = [ "sha2", ] +[[package]] +name = "lazy-regex" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.104", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2528,15 +2569,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libgit2-sys" -version = "0.18.1+1.9.0" +version = "0.18.2+1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" +checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" dependencies = [ "cc", "libc", @@ -2551,7 +2592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -2562,9 +2603,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.1", "libc", @@ -2695,9 +2736,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miette" @@ -2726,7 +2767,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2753,9 +2794,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -2768,7 +2809,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -2779,7 +2820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -3113,9 +3154,9 @@ dependencies = [ [[package]] name = "openidconnect" -version = "4.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd50d4a5e7730e754f94d977efe61f611aadd3131f6a2b464f6e3a4167e8ef7" +checksum = "0d8c6709ba2ea764bbed26bce1adf3c10517113ddea6f2d4196e4851757ef2b2" dependencies = [ "base64 0.21.7", "chrono", @@ -3187,9 +3228,9 @@ checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "owo-colors" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" [[package]] name = "p256" @@ -3233,7 +3274,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.12", + "redox_syscall 0.5.17", "smallvec", "windows-targets 0.52.6", ] @@ -3317,7 +3358,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3378,9 +3419,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.7.1" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64 0.22.1", "indexmap 2.10.0", @@ -3431,13 +3472,23 @@ dependencies = [ ] [[package]] -name = "prettyplease" -version = "0.2.33" +name = "pretty_assertions" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3492,7 +3543,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3541,7 +3592,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.101", + "syn 2.0.104", "tempfile", ] @@ -3555,7 +3606,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3602,7 +3653,7 @@ checksum = "f4fce6b22f15cc8d8d400a2b98ad29202b33bd56c7d9ddd815bc803a807ecb65" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3613,7 +3664,7 @@ checksum = "ab076798900edeaf1499ed1c30097db86e6697c5d02660a63d72fe4ebdcfefd2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3627,9 +3678,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.32.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" dependencies = [ "memchr", ] @@ -3677,9 +3728,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", @@ -3700,9 +3751,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -3794,9 +3845,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -3809,9 +3860,9 @@ checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -3835,7 +3886,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4040,15 +4091,15 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.101", + "syn 2.0.104", "unicode-ident", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -4086,29 +4137,29 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.3", + "rustls-webpki 0.103.4", "subtle", "zeroize", ] @@ -4136,9 +4187,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "aws-lc-rs", "ring", @@ -4194,6 +4245,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -4262,7 +4337,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4304,7 +4379,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4321,15 +4396,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -4339,14 +4416,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4542,14 +4619,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80baa401f274093f7bb27d7a69d6139cbc11f1b97624e9a61a9b3ea32c776a35" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "sigstore_protobuf_specs" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23738cfc7e052e3a00a71853804b5f3da030b801e58e0fbde9b0fbf06e7cda58" +checksum = "799e5ed827a6d8d2be7fc598515d061b59d85f496d7066152822a80f3250af74" dependencies = [ "anyhow", "glob", @@ -4566,12 +4643,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallstr" @@ -4615,7 +4689,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4688,7 +4762,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4731,9 +4805,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -4757,7 +4831,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4791,7 +4865,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -4801,7 +4875,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.0.7", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -4835,7 +4909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "unicode-linebreak", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -4864,7 +4938,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4875,7 +4949,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4890,12 +4964,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -4974,7 +5047,7 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5004,7 +5077,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5133,13 +5206,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5245,9 +5318,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -5405,9 +5478,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -5440,7 +5513,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -5475,7 +5548,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5524,12 +5597,11 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5df295f8451142f1856b1bd86a606dfe9587d439bc036e319c827700dbd555e" +checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" dependencies = [ "core-foundation", - "home", "jni", "log", "ndk-context", @@ -5541,9 +5613,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -5568,7 +5640,7 @@ checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" dependencies = [ "either", "env_home", - "rustix 1.0.7", + "rustix 1.0.8", "winsafe", ] @@ -5579,7 +5651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", - "rustix 1.0.7", + "rustix 1.0.8", "winsafe", ] @@ -5605,7 +5677,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -5635,7 +5707,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5646,14 +5718,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" @@ -5715,7 +5787,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -5766,10 +5838,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -5962,9 +6035,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -6030,6 +6103,12 @@ dependencies = [ "hashlink", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" @@ -6050,28 +6129,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -6091,7 +6170,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] @@ -6113,7 +6192,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -6146,5 +6225,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] diff --git a/Cargo.toml b/Cargo.toml index 23a1797..784d8b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,13 @@ colored = "2" comlexr = "1" indexmap = { version = "2", features = ["serde"] } indicatif = { version = "0.18", features = ["improved_unicode", "rayon"] } +lazy-regex = "3" log = "0.4" miette = "7" nix = { version = "0.29" } oci-distribution = { version = "0.11", default-features = false } +pretty_assertions = "1" +regex = "1" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } rstest = "0.18" semver = "1" @@ -78,7 +81,6 @@ jsonschema = "0.30" open = "5" os_info = "3" rayon = "1" -regex = "1" requestty = { version = "0.5", features = ["macros", "termion"] } shadow-rs = { version = "1", default-features = false } thiserror = "2" @@ -94,6 +96,7 @@ indicatif.workspace = true log.workspace = true miette = { workspace = true, features = ["fancy"] } oci-distribution.workspace = true +regex.workspace = true reqwest.workspace = true semver.workspace = true serde.workspace = true @@ -109,6 +112,11 @@ users.workspace = true # Top level features default = [] +v0_10_0 = [ + "bootc" +] +bootc = ["blue-build-process-management/bootc"] + [dev-dependencies] rusty-hook = "0.11" diff --git a/integration-tests/Earthfile b/integration-tests/Earthfile index c0b33eb..514d9ea 100644 --- a/integration-tests/Earthfile +++ b/integration-tests/Earthfile @@ -43,8 +43,8 @@ build-full: switch: FROM +test-base - RUN mkdir -p /etc/bluebuild && touch $BB_TEST_LOCAL_IMAGE - RUN --no-cache bluebuild -v switch recipes/recipe.yml + RUN --no-cache bluebuild -v switch --boot-driver rpm-ostree recipes/recipe.yml + RUN --no-cache bluebuild -v switch --boot-driver bootc recipes/recipe.yml validate: FROM +test-base @@ -92,7 +92,7 @@ init: legacy-base: FROM ../+blue-build-cli --RELEASE=false - ENV BB_TEST_LOCAL_IMAGE=/etc/bluebuild/cli_test-legacy.tar.gz + ENV BB_TEST_LOCAL_IMAGE=localhost/cli/test:latest ENV CLICOLOR_FORCE=1 COPY ./mock-scripts/ /usr/bin/ @@ -103,13 +103,14 @@ legacy-base: DO ../+INSTALL --OUT_DIR="/usr/bin/" --BUILD_TARGET="x86_64-unknown-linux-musl" --TAGGED="true" DO +GEN_KEYPAIR + ENV USER=root test-base: FROM ../+blue-build-cli --RELEASE=false RUN git config --global user.email "you@example.com" && \ git config --global user.name "Your Name" - ENV BB_TEST_LOCAL_IMAGE=/etc/bluebuild/cli_test.tar.gz + ENV BB_TEST_LOCAL_IMAGE=localhost/cli/test:latest ENV CLICOLOR_FORCE=1 ARG MOCK="true" @@ -121,6 +122,7 @@ test-base: COPY ./test-repo /test DO +GEN_KEYPAIR + ENV USER=root GEN_KEYPAIR: FUNCTION diff --git a/integration-tests/mock-scripts/bootc b/integration-tests/mock-scripts/bootc new file mode 100755 index 0000000..3a968e3 --- /dev/null +++ b/integration-tests/mock-scripts/bootc @@ -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 <> = std::sync::LazyLock::new(|| Mutex::new(false)); -static SELECTED_BUILD_DRIVER: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| RwLock::new(None)); -static SELECTED_INSPECT_DRIVER: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| RwLock::new(None)); -static SELECTED_RUN_DRIVER: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| RwLock::new(None)); -static SELECTED_SIGNING_DRIVER: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| RwLock::new(None)); -static SELECTED_CI_DRIVER: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| RwLock::new(None)); +static INIT: AtomicBool = AtomicBool::new(false); +static SELECTED_BUILD_DRIVER: LazyLock>> = + LazyLock::new(|| RwLock::new(None)); +static SELECTED_INSPECT_DRIVER: LazyLock>> = + LazyLock::new(|| RwLock::new(None)); +static SELECTED_RUN_DRIVER: LazyLock>> = + LazyLock::new(|| RwLock::new(None)); +static SELECTED_SIGNING_DRIVER: LazyLock>> = + LazyLock::new(|| RwLock::new(None)); +static SELECTED_CI_DRIVER: LazyLock>> = + LazyLock::new(|| RwLock::new(None)); +static SELECTED_BOOT_DRIVER: LazyLock>> = + LazyLock::new(|| RwLock::new(None)); /// Args for selecting the various drivers to use for runtime. /// @@ -95,6 +104,9 @@ pub struct DriverArgs { /// containers. #[arg(short = 'R', long)] run_driver: Option, + + #[arg(short = 'T', long)] + boot_driver: Option, } macro_rules! impl_driver_type { @@ -108,12 +120,13 @@ macro_rules! impl_driver_init { (@) => { }; ($init:ident; $($tail:tt)*) => { { - let mut initialized = $init.lock().expect("Must lock INIT"); - - if !*initialized { + if $init.compare_exchange( + false, + true, + std::sync::atomic::Ordering::AcqRel, + std::sync::atomic::Ordering::Acquire + ).is_ok() { impl_driver_init!(@ $($tail)*); - - *initialized = true; } } }; @@ -162,6 +175,7 @@ impl Driver { args.inspect_driver => SELECTED_INSPECT_DRIVER; args.run_driver => SELECTED_RUN_DRIVER; args.signing_driver => SELECTED_SIGNING_DRIVER; + args.boot_driver => SELECTED_BOOT_DRIVER; default => SELECTED_CI_DRIVER; } } @@ -206,7 +220,7 @@ impl Driver { info!("Retrieving OS version from {oci_ref}"); let os_version = Self::get_metadata( - &GetMetadataOpts::builder() + GetMetadataOpts::builder() .image(oci_ref) .maybe_platform(platform) .build(), @@ -247,6 +261,10 @@ impl Driver { pub fn get_ci_driver() -> CiDriverType { impl_driver_type!(SELECTED_CI_DRIVER) } + + pub fn get_boot_driver() -> BootDriverType { + impl_driver_type!(SELECTED_BOOT_DRIVER) + } } #[cached( @@ -278,9 +296,9 @@ fn get_version_run_image(oci_ref: &Reference) -> Result { }; let output = Driver::run_output( - &RunOpts::builder() - .image(oci_ref.to_string()) - .args(bon::vec![ + RunOpts::builder() + .image(&oci_ref.to_string()) + .args(&bon::vec![ "/bin/bash", "-c", r#"awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release"#, @@ -291,7 +309,7 @@ fn get_version_run_image(oci_ref: &Reference) -> Result { )?; if should_remove { - Driver::remove_image(&RemoveImageOpts::builder().image(oci_ref).build())?; + Driver::remove_image(RemoveImageOpts::builder().image(oci_ref).build())?; } progress.finish_and_clear(); @@ -314,15 +332,15 @@ macro_rules! impl_build_driver { } impl BuildDriver for Driver { - fn build(opts: &BuildOpts) -> Result<()> { + fn build(opts: BuildOpts) -> Result<()> { impl_build_driver!(build(opts)) } - fn tag(opts: &TagOpts) -> Result<()> { + fn tag(opts: TagOpts) -> Result<()> { impl_build_driver!(tag(opts)) } - fn push(opts: &PushOpts) -> Result<()> { + fn push(opts: PushOpts) -> Result<()> { impl_build_driver!(push(opts)) } @@ -330,11 +348,11 @@ impl BuildDriver for Driver { impl_build_driver!(login()) } - fn prune(opts: &opts::PruneOpts) -> Result<()> { + fn prune(opts: PruneOpts) -> Result<()> { impl_build_driver!(prune(opts)) } - fn build_tag_push(opts: &BuildTagPushOpts) -> Result> { + fn build_tag_push(opts: BuildTagPushOpts) -> Result> { impl_build_driver!(build_tag_push(opts)) } } @@ -349,19 +367,19 @@ macro_rules! impl_signing_driver { } impl SigningDriver for Driver { - fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> { + fn generate_key_pair(opts: GenerateKeyPairOpts) -> Result<()> { impl_signing_driver!(generate_key_pair(opts)) } - fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()> { + fn check_signing_files(opts: CheckKeyPairOpts) -> Result<()> { impl_signing_driver!(check_signing_files(opts)) } - fn sign(opts: &SignOpts) -> Result<()> { + fn sign(opts: SignOpts) -> Result<()> { impl_signing_driver!(sign(opts)) } - fn verify(opts: &VerifyOpts) -> Result<()> { + fn verify(opts: VerifyOpts) -> Result<()> { impl_signing_driver!(verify(opts)) } @@ -381,7 +399,7 @@ macro_rules! impl_inspect_driver { } impl InspectDriver for Driver { - fn get_metadata(opts: &GetMetadataOpts) -> Result { + fn get_metadata(opts: GetMetadataOpts) -> Result { impl_inspect_driver!(get_metadata(opts)) } } @@ -396,23 +414,23 @@ macro_rules! impl_run_driver { } impl RunDriver for Driver { - fn run(opts: &RunOpts) -> Result { + fn run(opts: RunOpts) -> Result { impl_run_driver!(run(opts)) } - fn run_output(opts: &RunOpts) -> Result { + fn run_output(opts: RunOpts) -> Result { impl_run_driver!(run_output(opts)) } - fn create_container(opts: &CreateContainerOpts) -> Result { + fn create_container(opts: CreateContainerOpts) -> Result { impl_run_driver!(create_container(opts)) } - fn remove_container(opts: &RemoveContainerOpts) -> Result<()> { + fn remove_container(opts: RemoveContainerOpts) -> Result<()> { impl_run_driver!(remove_container(opts)) } - fn remove_image(opts: &RemoveImageOpts) -> Result<()> { + fn remove_image(opts: RemoveImageOpts) -> Result<()> { impl_run_driver!(remove_image(opts)) } @@ -444,7 +462,7 @@ impl CiDriver for Driver { impl_ci_driver!(oidc_provider()) } - fn generate_tags(opts: &GenerateTagsOpts) -> Result> { + fn generate_tags(opts: GenerateTagsOpts) -> Result> { impl_ci_driver!(generate_tags(opts)) } @@ -469,27 +487,52 @@ impl CiDriver for Driver { } impl ContainerMountDriver for Driver { - fn mount_container(opts: &opts::ContainerOpts) -> Result { + fn mount_container(opts: ContainerOpts) -> Result { PodmanDriver::mount_container(opts) } - fn unmount_container(opts: &opts::ContainerOpts) -> Result<()> { + fn unmount_container(opts: ContainerOpts) -> Result<()> { PodmanDriver::unmount_container(opts) } - fn remove_volume(opts: &opts::VolumeOpts) -> Result<()> { + fn remove_volume(opts: VolumeOpts) -> Result<()> { PodmanDriver::remove_volume(opts) } } impl OciCopy for Driver { - fn copy_oci_dir(opts: &opts::CopyOciDirOpts) -> Result<()> { + fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()> { SkopeoDriver::copy_oci_dir(opts) } } impl RechunkDriver for Driver { - fn rechunk(opts: &opts::RechunkOpts) -> Result> { + fn rechunk(opts: RechunkOpts) -> Result> { 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> { + impl_boot_driver!(status()) + } + + fn switch(opts: SwitchOpts) -> Result<()> { + impl_boot_driver!(switch(opts)) + } + + fn upgrade(opts: SwitchOpts) -> Result<()> { + impl_boot_driver!(upgrade(opts)) + } +} diff --git a/process/drivers/bootc_driver.rs b/process/drivers/bootc_driver.rs new file mode 100644 index 0000000..5a9739c --- /dev/null +++ b/process/drivers/bootc_driver.rs @@ -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> { + 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::(&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(()) + } +} diff --git a/process/drivers/bootc_driver/status.rs b/process/drivers/bootc_driver/status.rs new file mode 100644 index 0000000..8ab7e11 --- /dev/null +++ b/process/drivers/bootc_driver/status.rs @@ -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, + 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> { + 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> { + 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, + } + } +} diff --git a/process/drivers/buildah_driver.rs b/process/drivers/buildah_driver.rs index 9fc8fe9..d887d9e 100644 --- a/process/drivers/buildah_driver.rs +++ b/process/drivers/buildah_driver.rs @@ -12,7 +12,7 @@ use crate::logging::CommandLogging; use super::{ BuildDriver, DriverVersion, - opts::{BuildOpts, PushOpts, TagOpts}, + opts::{BuildOpts, PruneOpts, PushOpts, TagOpts}, }; #[derive(Debug, Deserialize)] @@ -48,7 +48,7 @@ impl DriverVersion for BuildahDriver { } impl BuildDriver for BuildahDriver { - fn build(opts: &BuildOpts) -> Result<()> { + fn build(opts: BuildOpts) -> Result<()> { trace!("BuildahDriver::build({opts:#?})"); let temp_dir = TempDir::new() @@ -83,7 +83,7 @@ impl BuildDriver for BuildahDriver { ), ], "-f", - &*opts.containerfile, + opts.containerfile, "-t", opts.image.to_string(), ); @@ -101,7 +101,7 @@ impl BuildDriver for BuildahDriver { Ok(()) } - fn tag(opts: &TagOpts) -> Result<()> { + fn tag(opts: TagOpts) -> Result<()> { trace!("BuildahDriver::tag({opts:#?})"); let dest_image_str = opts.dest_image.to_string(); @@ -122,7 +122,7 @@ impl BuildDriver for BuildahDriver { Ok(()) } - fn push(opts: &PushOpts) -> Result<()> { + fn push(opts: PushOpts) -> Result<()> { trace!("BuildahDriver::push({opts:#?})"); let image_str = opts.image.to_string(); @@ -195,7 +195,7 @@ impl BuildDriver for BuildahDriver { Ok(()) } - fn prune(opts: &super::opts::PruneOpts) -> Result<()> { + fn prune(opts: PruneOpts) -> Result<()> { trace!("PodmanDriver::prune({opts:?})"); let status = cmd!( diff --git a/process/drivers/cosign_driver.rs b/process/drivers/cosign_driver.rs index ec37732..d40fccd 100644 --- a/process/drivers/cosign_driver.rs +++ b/process/drivers/cosign_driver.rs @@ -21,7 +21,7 @@ use super::{ pub struct CosignDriver; impl SigningDriver for CosignDriver { - fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> { + fn generate_key_pair(opts: GenerateKeyPairOpts) -> Result<()> { let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir); let status = { @@ -47,7 +47,7 @@ impl SigningDriver for CosignDriver { Ok(()) } - fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()> { + fn check_signing_files(opts: CheckKeyPairOpts) -> Result<()> { let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir); let priv_key = get_private_key(path)?; @@ -124,7 +124,7 @@ impl SigningDriver for CosignDriver { Ok(()) } - fn sign(opts: &SignOpts) -> Result<()> { + fn sign(opts: SignOpts) -> Result<()> { if opts.image.digest().is_none() { bail!( "Image ref {} is not a digest ref", @@ -140,7 +140,7 @@ impl SigningDriver for CosignDriver { }; "cosign", "sign", - if let Some(ref key) = opts.key => format!("--key={key}"), + if let Some(key) = opts.key => format!("--key={key}"), "--recursive", opts.image.to_string(), ); @@ -157,7 +157,7 @@ impl SigningDriver for CosignDriver { Ok(()) } - fn verify(opts: &VerifyOpts) -> Result<()> { + fn verify(opts: VerifyOpts) -> Result<()> { let status = { let c = cmd!( "cosign", @@ -205,9 +205,8 @@ mod test { fn generate_key_pair() { let tempdir = TempDir::new().unwrap(); - let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build(); - - CosignDriver::generate_key_pair(&gen_opts).unwrap(); + CosignDriver::generate_key_pair(GenerateKeyPairOpts::builder().dir(tempdir.path()).build()) + .unwrap(); eprintln!( "Private key:\n{}", @@ -218,18 +217,15 @@ mod test { fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap() ); - let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build(); - - CosignDriver::check_signing_files(&check_opts).unwrap(); + CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(tempdir.path()).build()) + .unwrap(); } #[test] fn check_key_pairs() { let path = Path::new("../test-files/keys"); - let opts = CheckKeyPairOpts::builder().dir(path).build(); - - CosignDriver::check_signing_files(&opts).unwrap(); + CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(path).build()).unwrap(); } #[test] @@ -238,9 +234,8 @@ mod test { let tempdir = TempDir::new().unwrap(); - let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build(); - - CosignDriver::generate_key_pair(&gen_opts).unwrap(); + CosignDriver::generate_key_pair(GenerateKeyPairOpts::builder().dir(tempdir.path()).build()) + .unwrap(); eprintln!( "Private key:\n{}", @@ -251,8 +246,9 @@ mod test { fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap() ); - let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build(); - - SigstoreDriver::check_signing_files(&check_opts).unwrap(); + SigstoreDriver::check_signing_files( + CheckKeyPairOpts::builder().dir(tempdir.path()).build(), + ) + .unwrap(); } } diff --git a/process/drivers/docker_driver.rs b/process/drivers/docker_driver.rs index cdfff40..05785ab 100644 --- a/process/drivers/docker_driver.rs +++ b/process/drivers/docker_driver.rs @@ -36,7 +36,7 @@ use crate::{ signal_handler::{ContainerRuntime, ContainerSignalId, add_cid, remove_cid}, }; -use super::opts::{CreateContainerOpts, RemoveContainerOpts, RemoveImageOpts}; +use super::opts::{CreateContainerOpts, PruneOpts, RemoveContainerOpts, RemoveImageOpts}; #[derive(Debug, Deserialize)] struct VerisonJsonClient { @@ -192,7 +192,7 @@ impl DriverVersion for DockerDriver { } impl BuildDriver for DockerDriver { - fn build(opts: &BuildOpts) -> Result<()> { + fn build(opts: BuildOpts) -> Result<()> { trace!("DockerDriver::build({opts:#?})"); let temp_dir = TempDir::new() @@ -232,7 +232,7 @@ impl BuildDriver for DockerDriver { repository = cache_to.repository(), ), ], - &*opts.containerfile, + opts.containerfile, ".", ); trace!("{c:?}"); @@ -249,7 +249,7 @@ impl BuildDriver for DockerDriver { Ok(()) } - fn tag(opts: &TagOpts) -> Result<()> { + fn tag(opts: TagOpts) -> Result<()> { trace!("DockerDriver::tag({opts:#?})"); let dest_image_str = opts.dest_image.to_string(); @@ -270,7 +270,7 @@ impl BuildDriver for DockerDriver { Ok(()) } - fn push(opts: &PushOpts) -> Result<()> { + fn push(opts: PushOpts) -> Result<()> { trace!("DockerDriver::push({opts:#?})"); let image_str = opts.image.to_string(); @@ -328,7 +328,7 @@ impl BuildDriver for DockerDriver { Ok(()) } - fn prune(opts: &super::opts::PruneOpts) -> Result<()> { + fn prune(opts: PruneOpts) -> Result<()> { trace!("DockerDriver::prune({opts:?})"); let (system, buildx) = std::thread::scope( @@ -385,7 +385,7 @@ impl BuildDriver for DockerDriver { Ok(()) } - fn build_tag_push(opts: &BuildTagPushOpts) -> Result> { + fn build_tag_push(opts: BuildTagPushOpts) -> Result> { trace!("DockerDriver::build_tag_push({opts:#?})"); let temp_dir = TempDir::new() @@ -420,7 +420,7 @@ impl BuildDriver for DockerDriver { } fn build_tag_push_cmd( - opts: &BuildTagPushOpts<'_>, + opts: BuildTagPushOpts<'_>, first_image: &str, temp_dir: &TempDir, ) -> Result { @@ -461,7 +461,7 @@ fn build_tag_push_cmd( platform.to_string(), ], "-f", - &*opts.containerfile, + opts.containerfile, if let Some(cache_from) = opts.cache_from.as_ref() => [ "--cache-from", format!( @@ -479,7 +479,7 @@ fn build_tag_push_cmd( Ok(c) } -fn get_final_images(opts: &BuildTagPushOpts<'_>) -> Vec { +fn get_final_images(opts: BuildTagPushOpts<'_>) -> Vec { match &opts.image { ImageRef::Remote(image) => { if opts.tags.is_empty() { @@ -495,11 +495,14 @@ fn get_final_images(opts: &BuildTagPushOpts<'_>) -> Vec { ImageRef::LocalTar(archive_path) => { string_vec![archive_path.display().to_string()] } + ImageRef::Other(other) => { + string_vec![&**other] + } } } impl InspectDriver for DockerDriver { - fn get_metadata(opts: &GetMetadataOpts) -> Result { + fn get_metadata(opts: GetMetadataOpts) -> Result { get_metadata_cache(opts) } } @@ -510,7 +513,7 @@ impl InspectDriver for DockerDriver { convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#, sync_writes = "by_key" )] -fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { +fn get_metadata_cache(opts: GetMetadataOpts) -> Result { trace!("DockerDriver::get_metadata({opts:#?})"); let image_str = opts.image.to_string(); @@ -547,7 +550,7 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { } impl RunDriver for DockerDriver { - fn run(opts: &RunOpts) -> Result { + fn run(opts: RunOpts) -> Result { trace!("DockerDriver::run({opts:#?})"); let cid_path = TempDir::new().into_diagnostic()?; @@ -557,7 +560,7 @@ impl RunDriver for DockerDriver { add_cid(&cid); let status = docker_run(opts, &cid_file) - .build_status(&*opts.image, "Running container") + .build_status(opts.image, "Running container") .into_diagnostic()?; remove_cid(&cid); @@ -565,7 +568,7 @@ impl RunDriver for DockerDriver { Ok(status) } - fn run_output(opts: &RunOpts) -> Result { + fn run_output(opts: RunOpts) -> Result { trace!("DockerDriver::run({opts:#?})"); let cid_path = TempDir::new().into_diagnostic()?; @@ -581,7 +584,7 @@ impl RunDriver for DockerDriver { Ok(output) } - fn create_container(opts: &CreateContainerOpts) -> Result { + fn create_container(opts: CreateContainerOpts) -> Result { trace!("DockerDriver::create_container({opts:?})"); let output = { @@ -601,7 +604,7 @@ impl RunDriver for DockerDriver { )) } - fn remove_container(opts: &RemoveContainerOpts) -> Result<()> { + fn remove_container(opts: RemoveContainerOpts) -> Result<()> { trace!("DockerDriver::remove_container({opts:?})"); let output = { @@ -619,7 +622,7 @@ impl RunDriver for DockerDriver { Ok(()) } - fn remove_image(opts: &RemoveImageOpts) -> Result<()> { + fn remove_image(opts: RemoveImageOpts) -> Result<()> { trace!("DockerDriver::remove_image({opts:?})"); let output = { @@ -675,7 +678,7 @@ impl RunDriver for DockerDriver { } } -fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command { +fn docker_run(opts: RunOpts, cid_file: &Path) -> Command { let command = cmd!( "docker", "run", @@ -693,7 +696,7 @@ fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command { "--env", format!("{key}={value}"), ], - &*opts.image, + opts.image, for arg in opts.args.iter() => &**arg, ); trace!("{command:?}"); diff --git a/process/drivers/github_driver.rs b/process/drivers/github_driver.rs index 9a1508e..3035fba 100644 --- a/process/drivers/github_driver.rs +++ b/process/drivers/github_driver.rs @@ -36,7 +36,7 @@ impl CiDriver for GithubDriver { Ok(GITHUB_TOKEN_ISSUER_URL.to_string()) } - fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result> { + fn generate_tags(opts: GenerateTagsOpts) -> miette::Result> { const PR_EVENT: &str = "pull_request"; let timestamp = blue_build_utils::get_tag_timestamp(); let os_version = Driver::get_os_version() @@ -142,8 +142,6 @@ impl CiDriver for GithubDriver { #[cfg(test)] mod test { - use std::borrow::Cow; - use blue_build_utils::{ constants::{ GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER, @@ -286,7 +284,7 @@ mod test { )] fn generate_tags( #[case] setup: impl FnOnce(), - #[case] alt_tags: Option>>, + #[case] alt_tags: Option>, #[case] mut expected: Vec, ) { setup(); @@ -294,9 +292,9 @@ mod test { let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap(); let mut tags = GithubDriver::generate_tags( - &GenerateTagsOpts::builder() + GenerateTagsOpts::builder() .oci_ref(&oci_ref) - .maybe_alt_tags(alt_tags) + .maybe_alt_tags(alt_tags.as_deref()) .platform(Platform::LinuxAmd64) .build(), ) diff --git a/process/drivers/gitlab_driver.rs b/process/drivers/gitlab_driver.rs index d0fb9e2..fdee806 100644 --- a/process/drivers/gitlab_driver.rs +++ b/process/drivers/gitlab_driver.rs @@ -45,7 +45,7 @@ impl CiDriver for GitlabDriver { )) } - fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result> { + fn generate_tags(opts: GenerateTagsOpts) -> miette::Result> { const MR_EVENT: &str = "merge_request_event"; let os_version = Driver::get_os_version() .oci_ref(opts.oci_ref) @@ -151,8 +151,6 @@ impl CiDriver for GitlabDriver { #[cfg(test)] mod test { - use std::borrow::Cow; - use blue_build_utils::{ constants::{ CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, @@ -293,7 +291,7 @@ mod test { )] fn generate_tags( #[case] setup: impl FnOnce(), - #[case] alt_tags: Option>>, + #[case] alt_tags: Option>, #[case] mut expected: Vec, ) { setup(); @@ -301,9 +299,9 @@ mod test { let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap(); let mut tags = GitlabDriver::generate_tags( - &GenerateTagsOpts::builder() + GenerateTagsOpts::builder() .oci_ref(&oci_ref) - .maybe_alt_tags(alt_tags) + .maybe_alt_tags(alt_tags.as_deref()) .platform(crate::drivers::types::Platform::LinuxAmd64) .build(), ) diff --git a/process/drivers/local_driver.rs b/process/drivers/local_driver.rs index 273783a..0777d7e 100644 --- a/process/drivers/local_driver.rs +++ b/process/drivers/local_driver.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use blue_build_utils::string_vec; use comlexr::cmd; use log::trace; +use miette::bail; use super::{CiDriver, Driver, opts::GenerateTagsOpts}; @@ -15,14 +16,14 @@ impl CiDriver for LocalDriver { } fn keyless_cert_identity() -> miette::Result { - unimplemented!() + bail!("Unimplemented for local") } fn oidc_provider() -> miette::Result { - unimplemented!() + bail!("Unimplemented for local") } - fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result> { + fn generate_tags(opts: GenerateTagsOpts) -> miette::Result> { trace!("LocalDriver::generate_tags({opts:?})"); let os_version = Driver::get_os_version() .oci_ref(opts.oci_ref) diff --git a/process/drivers/opts.rs b/process/drivers/opts.rs index a8a17de..86c1554 100644 --- a/process/drivers/opts.rs +++ b/process/drivers/opts.rs @@ -1,5 +1,6 @@ use clap::ValueEnum; +pub use boot::*; pub use build::*; pub use ci::*; pub use inspect::*; @@ -7,6 +8,7 @@ pub use rechunk::*; pub use run::*; pub use signing::*; +mod boot; mod build; mod ci; mod inspect; diff --git a/process/drivers/opts/boot.rs b/process/drivers/opts/boot.rs new file mode 100644 index 0000000..c2dd168 --- /dev/null +++ b/process/drivers/opts/boot.rs @@ -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, +} diff --git a/process/drivers/opts/build.rs b/process/drivers/opts/build.rs index 2abb958..e057dd8 100644 --- a/process/drivers/opts/build.rs +++ b/process/drivers/opts/build.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashSet, path::Path}; +use std::path::Path; use blue_build_utils::secret::Secret; use bon::Builder; @@ -9,16 +9,14 @@ use crate::drivers::types::{ImageRef, Platform}; use super::CompressionType; /// Options for building -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct BuildOpts<'scope> { - #[builder(into)] - pub image: ImageRef<'scope>, + pub image: &'scope ImageRef<'scope>, #[builder(default)] pub squash: bool, - #[builder(into)] - pub containerfile: Cow<'scope, Path>, + pub containerfile: &'scope Path, pub platform: Option, @@ -27,18 +25,14 @@ pub struct BuildOpts<'scope> { #[builder(default)] pub privileged: bool, - - #[builder(into)] pub cache_from: Option<&'scope Reference>, - - #[builder(into)] pub cache_to: Option<&'scope Reference>, #[builder(default)] - pub secrets: HashSet<&'scope Secret>, + pub secrets: &'scope [&'scope Secret], } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct TagOpts<'scope> { pub src_image: &'scope Reference, pub dest_image: &'scope Reference, @@ -47,7 +41,7 @@ pub struct TagOpts<'scope> { pub privileged: bool, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct PushOpts<'scope> { pub image: &'scope Reference, pub compression_type: Option, @@ -56,7 +50,7 @@ pub struct PushOpts<'scope> { pub privileged: bool, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct PruneOpts { pub all: bool, pub volumes: bool, @@ -64,19 +58,17 @@ pub struct PruneOpts { /// Options for building, tagging, and pusing images. #[allow(clippy::struct_excessive_bools)] -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct BuildTagPushOpts<'scope> { /// The base image name. - #[builder(into)] - pub image: ImageRef<'scope>, + pub image: &'scope ImageRef<'scope>, /// The path to the Containerfile to build. - #[builder(into)] - pub containerfile: Cow<'scope, Path>, + pub containerfile: &'scope Path, /// The list of tags for the image being built. - #[builder(default, into)] - pub tags: Vec>, + #[builder(default)] + pub tags: &'scope [String], /// Enable pushing the image. #[builder(default)] @@ -115,5 +107,5 @@ pub struct BuildTagPushOpts<'scope> { /// Secrets to mount #[builder(default)] - pub secrets: HashSet<&'scope Secret>, + pub secrets: &'scope [&'scope Secret], } diff --git a/process/drivers/opts/ci.rs b/process/drivers/opts/ci.rs index 12dcdf3..c8eb578 100644 --- a/process/drivers/opts/ci.rs +++ b/process/drivers/opts/ci.rs @@ -1,28 +1,21 @@ -use std::borrow::Cow; - use bon::Builder; use oci_distribution::Reference; use crate::drivers::types::Platform; -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct GenerateTagsOpts<'scope> { pub oci_ref: &'scope Reference, #[builder(into)] - pub alt_tags: Option>>, + pub alt_tags: Option<&'scope [String]>, pub platform: Option, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct GenerateImageNameOpts<'scope> { - #[builder(into)] - pub name: Cow<'scope, str>, - - #[builder(into)] - pub registry: Option>, - - #[builder(into)] - pub registry_namespace: Option>, + pub name: &'scope str, + pub registry: Option<&'scope str>, + pub registry_namespace: Option<&'scope str>, } diff --git a/process/drivers/opts/inspect.rs b/process/drivers/opts/inspect.rs index 1f34ab9..77c9ca8 100644 --- a/process/drivers/opts/inspect.rs +++ b/process/drivers/opts/inspect.rs @@ -3,7 +3,7 @@ use oci_distribution::Reference; use crate::drivers::types::Platform; -#[derive(Debug, Clone, Builder, Hash)] +#[derive(Debug, Clone, Copy, Builder, Hash)] #[builder(derive(Clone))] pub struct GetMetadataOpts<'scope> { #[builder(into)] diff --git a/process/drivers/opts/rechunk.rs b/process/drivers/opts/rechunk.rs index 1b2c5ed..fef5258 100644 --- a/process/drivers/opts/rechunk.rs +++ b/process/drivers/opts/rechunk.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashSet, path::Path}; +use std::path::Path; use blue_build_utils::secret::Secret; use bon::Builder; @@ -8,25 +8,22 @@ use crate::drivers::types::{ContainerId, OciDir, Platform}; use super::CompressionType; -#[derive(Debug, Clone, Builder)] -#[builder(on(Cow<'_, str>, into))] +#[derive(Debug, Clone, Copy, Builder)] pub struct RechunkOpts<'scope> { - pub image: Cow<'scope, str>, - - #[builder(into)] - pub containerfile: Cow<'scope, Path>, + pub image: &'scope str, + pub containerfile: &'scope Path, pub platform: Option, - pub version: Cow<'scope, str>, - pub name: Cow<'scope, str>, - pub description: Cow<'scope, str>, - pub base_digest: Cow<'scope, str>, - pub base_image: Cow<'scope, str>, - pub repo: Cow<'scope, str>, + pub version: &'scope str, + pub name: &'scope str, + pub description: &'scope str, + pub base_digest: &'scope str, + pub base_image: &'scope str, + pub repo: &'scope str, /// The list of tags for the image being built. - #[builder(default, into)] - pub tags: Vec>, + #[builder(default)] + pub tags: &'scope [String], /// Enable pushing the image. #[builder(default)] @@ -57,10 +54,10 @@ pub struct RechunkOpts<'scope> { pub cache_to: Option<&'scope Reference>, #[builder(default)] - pub secrets: HashSet<&'scope Secret>, + pub secrets: &'scope [&'scope Secret], } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct ContainerOpts<'scope> { pub container_id: &'scope ContainerId, @@ -68,16 +65,15 @@ pub struct ContainerOpts<'scope> { pub privileged: bool, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct VolumeOpts<'scope> { - #[builder(into)] - pub volume_id: Cow<'scope, str>, + pub volume_id: &'scope str, #[builder(default)] pub privileged: bool, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct CopyOciDirOpts<'scope> { pub oci_dir: &'scope OciDir, pub registry: &'scope Reference, diff --git a/process/drivers/opts/run.rs b/process/drivers/opts/run.rs index 6ce37dc..e786824 100644 --- a/process/drivers/opts/run.rs +++ b/process/drivers/opts/run.rs @@ -1,26 +1,21 @@ -use std::borrow::Cow; - use bon::Builder; use oci_distribution::Reference; use crate::drivers::types::ContainerId; -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct RunOpts<'scope> { - #[builder(into)] - pub image: Cow<'scope, str>, + pub image: &'scope str, - #[builder(default, into)] - pub args: Vec>, + #[builder(default)] + pub args: &'scope [String], - #[builder(default, into)] - pub env_vars: Vec>, + #[builder(default)] + pub env_vars: &'scope [RunOptsEnv<'scope>], - #[builder(default, into)] - pub volumes: Vec>, - - #[builder(into)] - pub user: Option>, + #[builder(default)] + pub volumes: &'scope [RunOptsVolume<'scope>], + pub user: Option<&'scope str>, #[builder(default)] pub privileged: bool, @@ -32,13 +27,10 @@ pub struct RunOpts<'scope> { pub remove: bool, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct RunOptsVolume<'scope> { - #[builder(into)] - pub path_or_vol_name: Cow<'scope, str>, - - #[builder(into)] - pub container_path: Cow<'scope, str>, + pub path_or_vol_name: &'scope str, + pub container_path: &'scope str, } #[macro_export] @@ -55,13 +47,10 @@ macro_rules! run_volumes { }; } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct RunOptsEnv<'scope> { - #[builder(into)] - pub key: Cow<'scope, str>, - - #[builder(into)] - pub value: Cow<'scope, str>, + pub key: &'scope str, + pub value: &'scope str, } #[macro_export] @@ -78,7 +67,7 @@ macro_rules! run_envs { }; } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct CreateContainerOpts<'scope> { pub image: &'scope Reference, @@ -86,7 +75,7 @@ pub struct CreateContainerOpts<'scope> { pub privileged: bool, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct RemoveContainerOpts<'scope> { pub container_id: &'scope ContainerId, @@ -94,7 +83,7 @@ pub struct RemoveContainerOpts<'scope> { pub privileged: bool, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct RemoveImageOpts<'scope> { pub image: &'scope Reference, diff --git a/process/drivers/opts/signing.rs b/process/drivers/opts/signing.rs index 44a859a..bc62553 100644 --- a/process/drivers/opts/signing.rs +++ b/process/drivers/opts/signing.rs @@ -1,5 +1,4 @@ use std::{ - borrow::Cow, fs, path::{Path, PathBuf}, }; @@ -12,6 +11,7 @@ use zeroize::{Zeroize, Zeroizing}; use crate::drivers::types::Platform; +#[derive(Debug)] pub enum PrivateKey { Env(String), Path(PathBuf), @@ -56,53 +56,42 @@ impl PrivateKeyContents for PrivateKey { } } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct GenerateKeyPairOpts<'scope> { - #[builder(into)] - pub dir: Option>, + pub dir: Option<&'scope Path>, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct CheckKeyPairOpts<'scope> { - #[builder(into)] - pub dir: Option>, + pub dir: Option<&'scope Path>, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct SignOpts<'scope> { - #[builder(into)] pub image: &'scope Reference, - - #[builder(into)] - pub key: Option>, - - #[builder(into)] - pub dir: Option>, + pub key: Option<&'scope PrivateKey>, + pub dir: Option<&'scope Path>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum VerifyType<'scope> { - File(Cow<'scope, Path>), + File(&'scope Path), Keyless { - issuer: Cow<'scope, str>, - identity: Cow<'scope, str>, + issuer: &'scope str, + identity: &'scope str, }, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct VerifyOpts<'scope> { - #[builder(into)] pub image: &'scope Reference, pub verify_type: VerifyType<'scope>, } -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Copy, Builder)] pub struct SignVerifyOpts<'scope> { - #[builder(into)] pub image: &'scope Reference, - - #[builder(into)] - pub dir: Option>, + pub dir: Option<&'scope Path>, /// Enable retry logic for pushing. #[builder(default)] diff --git a/process/drivers/podman_driver.rs b/process/drivers/podman_driver.rs index 6918176..b8b12dc 100644 --- a/process/drivers/podman_driver.rs +++ b/process/drivers/podman_driver.rs @@ -1,13 +1,14 @@ use std::{ collections::HashMap, + ops::Not, path::Path, process::{Command, ExitStatus}, time::Duration, }; use blue_build_utils::{ - constants::SUDO_ASKPASS, credentials::Credentials, has_env_var, running_as_root, - secret::SecretArgs, semver::Version, + constants::USER, credentials::Credentials, get_env_var, secret::SecretArgs, semver::Version, + sudo_cmd, }; use cached::proc_macro::cached; use colored::Colorize; @@ -21,7 +22,10 @@ use tempfile::TempDir; use super::{ ContainerMountDriver, RechunkDriver, - opts::{CreateContainerOpts, RemoveContainerOpts, RemoveImageOpts}, + opts::{ + ContainerOpts, CreateContainerOpts, PruneOpts, RemoveContainerOpts, RemoveImageOpts, + VolumeOpts, + }, types::{ContainerId, MountId}, }; use crate::{ @@ -108,6 +112,42 @@ struct PodmanVersionJson { #[derive(Debug)] pub struct PodmanDriver; +impl PodmanDriver { + /// Copy an image from the user container + /// store to the root container store for + /// booting off of. + /// + /// # Errors + /// Will error if the image can't be copied. + pub fn copy_image_to_root_store(image: &Reference) -> Result<()> { + let image = image.whole(); + let status = { + let c = sudo_cmd!( + prompt = SUDO_PROMPT, + "podman", + "image", + "scp", + format!("{}@localhost::{image}", get_env_var(USER)?), + "root@localhost::" + ); + trace!("{c:?}"); + c + } + .build_status(&image, "Copying image to root container store") + // .status() + .into_diagnostic()?; + + if status.success().not() { + bail!( + "Failed to copy image {} to root container store", + image.bold() + ); + } + + Ok(()) + } +} + impl DriverVersion for PodmanDriver { // First podman version to use buildah v1.24 // https://github.com/containers/podman/blob/main/RELEASE_NOTES.md#400 @@ -134,29 +174,17 @@ impl DriverVersion for PodmanDriver { } impl BuildDriver for PodmanDriver { - fn build(opts: &BuildOpts) -> Result<()> { + fn build(opts: BuildOpts) -> Result<()> { trace!("PodmanDriver::build({opts:#?})"); let temp_dir = TempDir::new() .into_diagnostic() .wrap_err("Failed to create temporary directory for secrets")?; - let use_sudo = opts.privileged && !running_as_root(); - let command = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => [ - "--preserve-env", - "podman", - ], + let command = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "build", if let Some(platform) = opts.platform => [ "--platform", @@ -182,7 +210,7 @@ impl BuildDriver for PodmanDriver { if opts.host_network => "--net=host", format!("--layers={}", !opts.squash), "-f", - &*opts.containerfile, + opts.containerfile, "-t", opts.image.to_string(), for opts.secrets.args(&temp_dir)?, @@ -203,24 +231,15 @@ impl BuildDriver for PodmanDriver { Ok(()) } - fn tag(opts: &TagOpts) -> Result<()> { + fn tag(opts: TagOpts) -> Result<()> { trace!("PodmanDriver::tag({opts:#?})"); let dest_image_str = opts.dest_image.to_string(); - let use_sudo = opts.privileged && !running_as_root(); - let mut command = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let mut command = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "tag", opts.src_image.to_string(), &dest_image_str @@ -237,24 +256,15 @@ impl BuildDriver for PodmanDriver { Ok(()) } - fn push(opts: &PushOpts) -> Result<()> { + fn push(opts: PushOpts) -> Result<()> { trace!("PodmanDriver::push({opts:#?})"); let image_str = opts.image.to_string(); - let use_sudo = opts.privileged && !running_as_root(); - let command = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let command = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "push", format!( "--compression-format={}", @@ -312,7 +322,7 @@ impl BuildDriver for PodmanDriver { Ok(()) } - fn prune(opts: &super::opts::PruneOpts) -> Result<()> { + fn prune(opts: PruneOpts) -> Result<()> { trace!("PodmanDriver::prune({opts:?})"); let status = { @@ -339,7 +349,7 @@ impl BuildDriver for PodmanDriver { } impl InspectDriver for PodmanDriver { - fn get_metadata(opts: &GetMetadataOpts) -> Result { + fn get_metadata(opts: GetMetadataOpts) -> Result { get_metadata_cache(opts) } } @@ -350,7 +360,7 @@ impl InspectDriver for PodmanDriver { convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#, sync_writes = "by_key" )] -fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { +fn get_metadata_cache(opts: GetMetadataOpts) -> Result { trace!("PodmanDriver::get_metadata({opts:#?})"); let image_str = opts.image.to_string(); @@ -409,21 +419,12 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { } impl ContainerMountDriver for PodmanDriver { - fn mount_container(opts: &super::opts::ContainerOpts) -> Result { - let use_sudo = opts.privileged && !running_as_root(); + fn mount_container(opts: ContainerOpts) -> Result { let output = { - let c = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let c = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "mount", opts.container_id, ); @@ -442,21 +443,12 @@ impl ContainerMountDriver for PodmanDriver { )) } - fn unmount_container(opts: &super::opts::ContainerOpts) -> Result<()> { - let use_sudo = opts.privileged && !running_as_root(); + fn unmount_container(opts: ContainerOpts) -> Result<()> { let output = { - let c = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let c = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "unmount", opts.container_id ); @@ -473,24 +465,15 @@ impl ContainerMountDriver for PodmanDriver { Ok(()) } - fn remove_volume(opts: &super::opts::VolumeOpts) -> Result<()> { - let use_sudo = opts.privileged && !running_as_root(); + fn remove_volume(opts: VolumeOpts) -> Result<()> { let output = { - let c = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let c = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "volume", "rm", - &*opts.volume_id + opts.volume_id ); trace!("{c:?}"); c @@ -509,7 +492,7 @@ impl ContainerMountDriver for PodmanDriver { impl RechunkDriver for PodmanDriver {} impl RunDriver for PodmanDriver { - fn run(opts: &RunOpts) -> Result { + fn run(opts: RunOpts) -> Result { trace!("PodmanDriver::run({opts:#?})"); let cid_path = TempDir::new().into_diagnostic()?; @@ -520,7 +503,7 @@ impl RunDriver for PodmanDriver { add_cid(&cid); let status = podman_run(opts, &cid_file) - .build_status(&*opts.image, "Running container") + .build_status(opts.image, "Running container") .into_diagnostic()?; remove_cid(&cid); @@ -528,7 +511,7 @@ impl RunDriver for PodmanDriver { Ok(status) } - fn run_output(opts: &RunOpts) -> Result { + fn run_output(opts: RunOpts) -> Result { trace!("PodmanDriver::run_output({opts:#?})"); let cid_path = TempDir::new().into_diagnostic()?; @@ -545,23 +528,14 @@ impl RunDriver for PodmanDriver { Ok(output) } - fn create_container(opts: &CreateContainerOpts) -> Result { + fn create_container(opts: CreateContainerOpts) -> Result { trace!("PodmanDriver::create_container({opts:?})"); - let use_sudo = opts.privileged && !running_as_root(); let output = { - let c = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let c = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "create", opts.image.to_string(), "bash" @@ -581,23 +555,14 @@ impl RunDriver for PodmanDriver { )) } - fn remove_container(opts: &RemoveContainerOpts) -> Result<()> { + fn remove_container(opts: RemoveContainerOpts) -> Result<()> { trace!("PodmanDriver::remove_container({opts:?})"); - let use_sudo = opts.privileged && !running_as_root(); let output = { - let c = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let c = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "rm", opts.container_id, ); @@ -614,23 +579,14 @@ impl RunDriver for PodmanDriver { Ok(()) } - fn remove_image(opts: &RemoveImageOpts) -> Result<()> { + fn remove_image(opts: RemoveImageOpts) -> Result<()> { trace!("PodmanDriver::remove_image({opts:?})"); - let use_sudo = opts.privileged && !running_as_root(); let output = { - let c = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let c = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "rmi", opts.image.to_string() ); @@ -656,20 +612,11 @@ impl RunDriver for PodmanDriver { trace!("PodmanDriver::list_images({privileged})"); - let use_sudo = privileged && !running_as_root(); let output = { - let c = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", + let c = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = privileged, + "podman", "images", "--format", "json" @@ -698,20 +645,11 @@ impl RunDriver for PodmanDriver { } } -fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command { - let use_sudo = opts.privileged && !running_as_root(); - let command = cmd!( - if use_sudo { - "sudo" - } else { - "podman" - }, - if use_sudo && has_env_var(SUDO_ASKPASS) => [ - "-A", - "-p", - SUDO_PROMPT, - ], - if use_sudo => "podman", +fn podman_run(opts: RunOpts, cid_file: &Path) -> Command { + let command = sudo_cmd!( + prompt = SUDO_PROMPT, + sudo_check = opts.privileged, + "podman", "run", format!("--cidfile={}", cid_file.display()), if opts.privileged => [ @@ -729,7 +667,7 @@ fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command { "--env", format!("{key}={value}"), ], - &*opts.image, + opts.image, for arg in opts.args.iter() => &**arg, ); trace!("{command:?}"); diff --git a/process/drivers/rpm_ostree_driver.rs b/process/drivers/rpm_ostree_driver.rs new file mode 100644 index 0000000..3d58bcc --- /dev/null +++ b/process/drivers/rpm_ostree_driver.rs @@ -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> { + 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::(&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(()) + } +} diff --git a/process/drivers/rpm_ostree_driver/status.rs b/process/drivers/rpm_ostree_driver/status.rs new file mode 100644 index 0000000..0ff71eb --- /dev/null +++ b/process/drivers/rpm_ostree_driver/status.rs @@ -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, + transactions: Option>, +} + +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> { + (&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> { + (&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()); + } +} diff --git a/process/drivers/rpm_ostree_driver/status/image_ref.rs b/process/drivers/rpm_ostree_driver/status/image_ref.rs new file mode 100644 index 0000000..c312256 --- /dev/null +++ b/process/drivers/rpm_ostree_driver/status/image_ref.rs @@ -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 { + 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 { + regex_switch!( + s, + r"ostree-unverified-image:(?.*)" => { + Self::UnverifiedImage(reference.try_into()?) + } + r"ostree-unverified-registry:(?.*)" => { + Self::UnverifiedRegistry(reference.try_into().into_diagnostic()?) + } + r"ostree-remote-image:(?[^:]+):registry:(?.*)" => { + Self::RemoteImage { + remote: remote.into(), + reference: reference.try_into().into_diagnostic()?, + } + } + r"ostree-remote-registry:(?[^:]+):(?.*)" => { + Self::RemoteRegistry { + remote: remote.into(), + reference: reference.try_into().into_diagnostic()?, + } + } + r"ostree-image-signed:(?.*)" => { + 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, + }, + DockerDaemon(DockerDaemon), + Dir(PathBuf), + Oci { + path: PathBuf, + ref_index: Option, + }, + OciArchive { + path: PathBuf, + reference: Option, + }, + ContainersStorage { + storage_specifier: Option, + reference: Reference, + }, + Ostree { + reference: Reference, + repo_path: Option, + }, + 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 { + regex_switch!( + s, + r"registry:(?.*)" => { + Self::Registry(reference.try_into().into_diagnostic()?) + } + r"docker://(?.*)" => { + Self::Docker(reference.try_into().into_diagnostic()?) + } + r"docker-archive:(?[^:]+)(?::(?.*))?" => { + 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:(?.*)" => { + Self::DockerDaemon(reference.try_into()?) + } + r"dir:(?.*)" => { + Self::Dir(path.into()) + } + r"oci:(?[^:]+)(?::(?.*))?" => { + 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:(?[^:]+)(?::(?.*))?" => { + let reference = if reference.is_empty().not() { + Some(reference.try_into().into_diagnostic()?) + } else { + None + }; + Self::OciArchive { path: path.into(), reference } + } + r"containers-storage:(?:\[(?.*)\])?(?.*)" => { + 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:(?[^@]+)(?:@(?.*))?" => { + 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:(?.*)" => { + 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 { + Ok(match (Reference::try_from(s), s.parse::()) { + (_, 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 { + 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 { + 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, + root: PathBuf, + run_root: Option, + options: Option, +} + +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 { + regex_if!( + r"(?:(?[\w-]+)@)?(?[\w\/-]+)(?:\+(?[\w\/-]+))?(?:\:(?[\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, + #[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()); + } +} diff --git a/process/drivers/sigstore_driver.rs b/process/drivers/sigstore_driver.rs index 49a0e71..d5248e2 100644 --- a/process/drivers/sigstore_driver.rs +++ b/process/drivers/sigstore_driver.rs @@ -33,7 +33,7 @@ use zeroize::Zeroizing; pub struct SigstoreDriver; impl SigningDriver for SigstoreDriver { - fn generate_key_pair(opts: &GenerateKeyPairOpts) -> miette::Result<()> { + fn generate_key_pair(opts: GenerateKeyPairOpts) -> miette::Result<()> { let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir); let priv_key_path = path.join(COSIGN_PRIV_PATH); let pub_key_path = path.join(COSIGN_PUB_PATH); @@ -70,7 +70,7 @@ impl SigningDriver for SigstoreDriver { Ok(()) } - fn check_signing_files(opts: &CheckKeyPairOpts) -> miette::Result<()> { + fn check_signing_files(opts: CheckKeyPairOpts) -> miette::Result<()> { trace!("SigstoreDriver::check_signing_files({opts:?})"); let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir); @@ -105,7 +105,7 @@ impl SigningDriver for SigstoreDriver { } } - fn sign(opts: &SignOpts) -> miette::Result<()> { + fn sign(opts: SignOpts) -> miette::Result<()> { trace!("SigstoreDriver::sign({opts:?})"); if opts.image.digest().is_none() { @@ -176,7 +176,7 @@ impl SigningDriver for SigstoreDriver { Ok(()) } - fn verify(opts: &VerifyOpts) -> miette::Result<()> { + fn verify(opts: VerifyOpts) -> miette::Result<()> { let mut client = ClientBuilder::default().build().into_diagnostic()?; let image_digest: OciReference = opts.image.to_string().parse().into_diagnostic()?; @@ -253,9 +253,10 @@ mod test { fn generate_key_pair() { let tempdir = TempDir::new().unwrap(); - let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build(); - - SigstoreDriver::generate_key_pair(&gen_opts).unwrap(); + SigstoreDriver::generate_key_pair( + GenerateKeyPairOpts::builder().dir(tempdir.path()).build(), + ) + .unwrap(); eprintln!( "Private key:\n{}", @@ -266,27 +267,27 @@ mod test { fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap() ); - let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build(); - - SigstoreDriver::check_signing_files(&check_opts).unwrap(); + SigstoreDriver::check_signing_files( + CheckKeyPairOpts::builder().dir(tempdir.path()).build(), + ) + .unwrap(); } #[test] fn check_key_pairs() { let path = Path::new("../test-files/keys"); - let opts = CheckKeyPairOpts::builder().dir(path).build(); - - SigstoreDriver::check_signing_files(&opts).unwrap(); + SigstoreDriver::check_signing_files(CheckKeyPairOpts::builder().dir(path).build()).unwrap(); } #[test] fn compatibility() { let tempdir = TempDir::new().unwrap(); - let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build(); - - SigstoreDriver::generate_key_pair(&gen_opts).unwrap(); + SigstoreDriver::generate_key_pair( + GenerateKeyPairOpts::builder().dir(tempdir.path()).build(), + ) + .unwrap(); eprintln!( "Private key:\n{}", @@ -297,8 +298,7 @@ mod test { fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap() ); - let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build(); - - CosignDriver::check_signing_files(&check_opts).unwrap(); + CosignDriver::check_signing_files(CheckKeyPairOpts::builder().dir(tempdir.path()).build()) + .unwrap(); } } diff --git a/process/drivers/skopeo_driver.rs b/process/drivers/skopeo_driver.rs index 3496858..08e494a 100644 --- a/process/drivers/skopeo_driver.rs +++ b/process/drivers/skopeo_driver.rs @@ -9,13 +9,17 @@ use miette::{IntoDiagnostic, Result, bail}; use crate::{drivers::types::Platform, logging::Logger}; -use super::{InspectDriver, opts::GetMetadataOpts, types::ImageMetadata}; +use super::{ + InspectDriver, + opts::{CopyOciDirOpts, GetMetadataOpts}, + types::ImageMetadata, +}; #[derive(Debug)] pub struct SkopeoDriver; impl InspectDriver for SkopeoDriver { - fn get_metadata(opts: &GetMetadataOpts) -> Result { + fn get_metadata(opts: GetMetadataOpts) -> Result { get_metadata_cache(opts) } } @@ -26,7 +30,7 @@ impl InspectDriver for SkopeoDriver { convert = r#"{ format!("{}-{:?}", opts.image, opts.platform)}"#, sync_writes = "by_key" )] -fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { +fn get_metadata_cache(opts: GetMetadataOpts) -> Result { trace!("SkopeoDriver::get_metadata({opts:#?})"); let image_str = opts.image.to_string(); @@ -68,7 +72,7 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { } impl super::OciCopy for SkopeoDriver { - fn copy_oci_dir(opts: &super::opts::CopyOciDirOpts) -> Result<()> { + fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()> { use crate::logging::CommandLogging; let use_sudo = opts.privileged && !blue_build_utils::running_as_root(); diff --git a/process/drivers/traits.rs b/process/drivers/traits.rs index fda27b4..9288767 100644 --- a/process/drivers/traits.rs +++ b/process/drivers/traits.rs @@ -17,22 +17,16 @@ use crate::drivers::{ }; use super::{ - buildah_driver::BuildahDriver, - cosign_driver::CosignDriver, - docker_driver::DockerDriver, - github_driver::GithubDriver, - gitlab_driver::GitlabDriver, - local_driver::LocalDriver, opts::{ - BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, CreateContainerOpts, GenerateImageNameOpts, - GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts, RechunkOpts, - RemoveContainerOpts, RemoveImageOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts, - VerifyOpts, VerifyType, + BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, ContainerOpts, CopyOciDirOpts, + CreateContainerOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts, + GetMetadataOpts, PushOpts, RechunkOpts, RemoveContainerOpts, RemoveImageOpts, RunOpts, + SignOpts, SignVerifyOpts, SwitchOpts, TagOpts, VerifyOpts, VerifyType, VolumeOpts, + }, + types::{ + BootDriverType, BuildDriverType, ContainerId, ImageMetadata, InspectDriverType, MountId, + RunDriverType, SigningDriverType, }, - podman_driver::PodmanDriver, - sigstore_driver::SigstoreDriver, - skopeo_driver::SkopeoDriver, - types::{ContainerId, ImageMetadata, MountId}, }; trait PrivateDriver {} @@ -46,19 +40,37 @@ macro_rules! impl_private_driver { } impl_private_driver!( - Driver, - DockerDriver, - PodmanDriver, - BuildahDriver, - GithubDriver, - GitlabDriver, - LocalDriver, - CosignDriver, - SkopeoDriver, - CiDriverType, - SigstoreDriver, + super::Driver, + super::docker_driver::DockerDriver, + super::podman_driver::PodmanDriver, + super::buildah_driver::BuildahDriver, + super::github_driver::GithubDriver, + super::gitlab_driver::GitlabDriver, + super::local_driver::LocalDriver, + super::cosign_driver::CosignDriver, + super::skopeo_driver::SkopeoDriver, + super::sigstore_driver::SigstoreDriver, + super::rpm_ostree_driver::RpmOstreeDriver, + super::rpm_ostree_driver::Status, + Option, + Option, + Option, + Option, + Option, + Option, ); +#[cfg(feature = "bootc")] +impl_private_driver!( + super::bootc_driver::BootcDriver, + super::bootc_driver::BootcStatus +); + +#[allow(private_bounds)] +pub trait DetermineDriver: PrivateDriver { + fn determine_driver(&mut self) -> T; +} + /// Trait for retrieving version of a driver. #[allow(private_bounds)] pub trait DriverVersion: PrivateDriver { @@ -88,19 +100,19 @@ pub trait BuildDriver: PrivateDriver { /// /// # Errors /// Will error if the build fails. - fn build(opts: &BuildOpts) -> Result<()>; + fn build(opts: BuildOpts) -> Result<()>; /// Runs the tag logic for the driver. /// /// # Errors /// Will error if the tagging fails. - fn tag(opts: &TagOpts) -> Result<()>; + fn tag(opts: TagOpts) -> Result<()>; /// Runs the push logic for the driver /// /// # Errors /// Will error if the push fails. - fn push(opts: &PushOpts) -> Result<()>; + fn push(opts: PushOpts) -> Result<()>; /// Runs the login logic for the driver. /// @@ -112,27 +124,27 @@ pub trait BuildDriver: PrivateDriver { /// /// # Errors /// Will error if the driver fails to prune. - fn prune(opts: &super::opts::PruneOpts) -> Result<()>; + fn prune(opts: super::opts::PruneOpts) -> Result<()>; /// Runs the logic for building, tagging, and pushing an image. /// /// # Errors /// Will error if building, tagging, or pusing fails. - fn build_tag_push(opts: &BuildTagPushOpts) -> Result> { + fn build_tag_push(opts: BuildTagPushOpts) -> Result> { trace!("BuildDriver::build_tag_push({opts:#?})"); let build_opts = BuildOpts::builder() - .image(&opts.image) + .image(opts.image) .containerfile(opts.containerfile.as_ref()) .maybe_platform(opts.platform) .squash(opts.squash) .maybe_cache_from(opts.cache_from) .maybe_cache_to(opts.cache_to) - .secrets(opts.secrets.clone()) + .secrets(opts.secrets) .build(); info!("Building image {}", opts.image); - Self::build(&build_opts)?; + Self::build(build_opts)?; let image_list: Vec = match &opts.image { ImageRef::Remote(image) if !opts.tags.is_empty() => { @@ -140,7 +152,7 @@ pub trait BuildDriver: PrivateDriver { let mut image_list = Vec::with_capacity(opts.tags.len()); - for tag in &opts.tags { + for tag in opts.tags { debug!("Tagging {} with {tag}", &image); let tagged_image = Reference::with_tag( image.registry().into(), @@ -153,7 +165,7 @@ pub trait BuildDriver: PrivateDriver { .dest_image(&tagged_image) .build(); - Self::tag(&tag_opts)?; + Self::tag(tag_opts)?; image_list.push(tagged_image.to_string()); if opts.push { @@ -169,7 +181,7 @@ pub trait BuildDriver: PrivateDriver { .compression_type(opts.compression) .build(); - Self::push(&push_opts) + Self::push(push_opts) })?; } } @@ -177,7 +189,7 @@ pub trait BuildDriver: PrivateDriver { image_list } _ => { - string_vec![&opts.image] + string_vec![opts.image] } }; @@ -192,7 +204,7 @@ pub trait InspectDriver: PrivateDriver { /// /// # Errors /// Will error if it is unable to get the labels. - fn get_metadata(opts: &GetMetadataOpts) -> Result; + fn get_metadata(opts: GetMetadataOpts) -> Result; } /// Allows agnostic running of containers. @@ -202,31 +214,31 @@ pub trait RunDriver: PrivateDriver { /// /// # Errors /// Will error if there is an issue running the container. - fn run(opts: &RunOpts) -> Result; + fn run(opts: RunOpts) -> Result; /// Run a container to perform an action and capturing output. /// /// # Errors /// Will error if there is an issue running the container. - fn run_output(opts: &RunOpts) -> Result; + fn run_output(opts: RunOpts) -> Result; /// Creates container /// /// # Errors /// Will error if the container create command fails. - fn create_container(opts: &CreateContainerOpts) -> Result; + fn create_container(opts: CreateContainerOpts) -> Result; /// Removes a container /// /// # Errors /// Will error if the container remove command fails. - fn remove_container(opts: &RemoveContainerOpts) -> Result<()>; + fn remove_container(opts: RemoveContainerOpts) -> Result<()>; /// Removes an image /// /// # Errors /// Will error if the image remove command fails. - fn remove_image(opts: &RemoveImageOpts) -> Result<()>; + fn remove_image(opts: RemoveImageOpts) -> Result<()>; /// List all images in the local image registry. /// @@ -241,23 +253,23 @@ pub(super) trait ContainerMountDriver: PrivateDriver { /// /// # Errors /// Will error if the container mount command fails. - fn mount_container(opts: &super::opts::ContainerOpts) -> Result; + fn mount_container(opts: ContainerOpts) -> Result; /// Unmount the container /// /// # Errors /// Will error if the container unmount command fails. - fn unmount_container(opts: &super::opts::ContainerOpts) -> Result<()>; + fn unmount_container(opts: ContainerOpts) -> Result<()>; /// Remove a volume /// /// # Errors /// Will error if the volume remove command fails. - fn remove_volume(opts: &super::opts::VolumeOpts) -> Result<()>; + fn remove_volume(opts: VolumeOpts) -> Result<()>; } pub(super) trait OciCopy { - fn copy_oci_dir(opts: &super::opts::CopyOciDirOpts) -> Result<()>; + fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()>; } #[allow(private_bounds)] @@ -268,7 +280,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver { /// /// # Errors /// Will error if the rechunk process fails. - fn rechunk(opts: &RechunkOpts) -> Result> { + fn rechunk(opts: RechunkOpts) -> Result> { let ostree_cache_id = &uuid::Uuid::new_v4().to_string(); let raw_image = &Reference::try_from(format!("localhost/{ostree_cache_id}/raw-rechunk")).unwrap(); @@ -283,25 +295,25 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver { Self::login()?; Self::build( - &BuildOpts::builder() - .image(raw_image) - .containerfile(&*opts.containerfile) + BuildOpts::builder() + .image(&ImageRef::from(raw_image)) + .containerfile(opts.containerfile) .maybe_platform(opts.platform) .privileged(true) .squash(true) .host_network(true) - .secrets(opts.secrets.clone()) + .secrets(opts.secrets) .build(), )?; let container = &Self::create_container( - &CreateContainerOpts::builder() + CreateContainerOpts::builder() .image(raw_image) .privileged(true) .build(), )?; let mount = &Self::mount_container( - &super::opts::ContainerOpts::builder() + super::opts::ContainerOpts::builder() .container_id(container) .privileged(true) .build(), @@ -324,7 +336,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver { if opts.push { let oci_dir = &super::types::OciDir::try_from(temp_dir.path().join(ostree_cache_id))?; - for tag in &opts.tags { + for tag in opts.tags { let tagged_image = Reference::with_tag( full_image.registry().to_string(), full_image.repository().to_string(), @@ -335,7 +347,7 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver { debug!("Pushing image {tagged_image}"); Driver::copy_oci_dir( - &super::opts::CopyOciDirOpts::builder() + super::opts::CopyOciDirOpts::builder() .oci_dir(oci_dir) .registry(&tagged_image) .privileged(true) @@ -357,39 +369,39 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver { mount: &MountId, container: &ContainerId, raw_image: &Reference, - opts: &RechunkOpts<'_>, + opts: RechunkOpts<'_>, ) -> Result<(), miette::Error> { let status = Self::run( - &RunOpts::builder() + RunOpts::builder() .image(Self::RECHUNK_IMAGE) .remove(true) .user("0:0") .privileged(true) - .volumes(crate::run_volumes! { + .volumes(&crate::run_volumes! { mount => "/var/tree", }) - .env_vars(crate::run_envs! { + .env_vars(&crate::run_envs! { "TREE" => "/var/tree", }) - .args(bon::vec!["/sources/rechunk/1_prune.sh"]) + .args(&bon::vec!["/sources/rechunk/1_prune.sh"]) .build(), )?; if !status.success() { Self::unmount_container( - &super::opts::ContainerOpts::builder() + super::opts::ContainerOpts::builder() .container_id(container) .privileged(true) .build(), )?; Self::remove_container( - &RemoveContainerOpts::builder() + RemoveContainerOpts::builder() .container_id(container) .privileged(true) .build(), )?; Self::remove_image( - &RemoveImageOpts::builder() + RemoveImageOpts::builder() .image(raw_image) .privileged(true) .build(), @@ -409,40 +421,40 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver { ostree_cache_id: &str, container: &ContainerId, raw_image: &Reference, - opts: &RechunkOpts<'_>, + opts: RechunkOpts<'_>, ) -> Result<()> { let status = Self::run( - &RunOpts::builder() + RunOpts::builder() .image(Self::RECHUNK_IMAGE) .remove(true) .user("0:0") .privileged(true) - .volumes(crate::run_volumes! { + .volumes(&crate::run_volumes! { mount => "/var/tree", ostree_cache_id => "/var/ostree", }) - .env_vars(crate::run_envs! { + .env_vars(&crate::run_envs! { "TREE" => "/var/tree", "REPO" => "/var/ostree/repo", "RESET_TIMESTAMP" => "1", }) - .args(bon::vec!["/sources/rechunk/2_create.sh"]) + .args(&bon::vec!["/sources/rechunk/2_create.sh"]) .build(), )?; Self::unmount_container( - &super::opts::ContainerOpts::builder() + super::opts::ContainerOpts::builder() .container_id(container) .privileged(true) .build(), )?; Self::remove_container( - &RemoveContainerOpts::builder() + RemoveContainerOpts::builder() .container_id(container) .privileged(true) .build(), )?; Self::remove_image( - &RemoveImageOpts::builder() + RemoveImageOpts::builder() .image(raw_image) .privileged(true) .build(), @@ -463,45 +475,51 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver { ostree_cache_id: &str, temp_dir_str: &str, current_dir: &str, - opts: &RechunkOpts<'_>, + opts: RechunkOpts<'_>, ) -> 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=", + "io.artifacthub.package.readme-url=https://raw.githubusercontent.com/blue-build/cli/main/README.md", + ); let status = Self::run( - &RunOpts::builder() - .image(Self::RECHUNK_IMAGE) - .remove(true) - .user("0:0") - .privileged(true) - .volumes(crate::run_volumes! { - ostree_cache_id => "/var/ostree", - temp_dir_str => "/workspace", - current_dir => "/var/git" - }) - .env_vars(crate::run_envs! { - "REPO" => "/var/ostree/repo", - "PREV_REF" => &*opts.image, - "OUT_NAME" => ostree_cache_id, - "CLEAR_PLAN" => if opts.clear_plan { "true" } else { "" }, - "VERSION" => format!("{}", opts.version), - "OUT_REF" => format!("oci:{ostree_cache_id}"), - "GIT_DIR" => "/var/git", - "LABELS" => format!( - "{}\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=", - "io.artifacthub.package.readme-url=https://raw.githubusercontent.com/blue-build/cli/main/README.md", - ) - }) - .args(bon::vec!["/sources/rechunk/3_chunk.sh"]) - .build(), + RunOpts::builder() + .image(Self::RECHUNK_IMAGE) + .remove(true) + .user("0:0") + .privileged(true) + .volumes(&crate::run_volumes! { + ostree_cache_id => "/var/ostree", + temp_dir_str => "/workspace", + current_dir => "/var/git" + }) + .env_vars(&crate::run_envs! { + "REPO" => "/var/ostree/repo", + "PREV_REF" => opts.image, + "OUT_NAME" => ostree_cache_id, + "CLEAR_PLAN" => if opts.clear_plan { "true" } else { "" }, + "VERSION" => opts.version, + "OUT_REF" => &out_ref, + "GIT_DIR" => "/var/git", + "LABELS" => &labels, + }) + .args(&bon::vec!["/sources/rechunk/3_chunk.sh"]) + .build(), )?; Self::remove_volume( - &super::opts::VolumeOpts::builder() + super::opts::VolumeOpts::builder() .volume_id(ostree_cache_id) .privileged(true) .build(), @@ -522,20 +540,20 @@ pub trait SigningDriver: PrivateDriver { /// /// # Errors /// Will error if a key-pair couldn't be generated. - fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()>; + fn generate_key_pair(opts: GenerateKeyPairOpts) -> Result<()>; /// Checks the signing key files to ensure /// they match. /// /// # Errors /// Will error if the files cannot be verified. - fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()>; + fn check_signing_files(opts: CheckKeyPairOpts) -> Result<()>; /// Signs the image digest. /// /// # Errors /// Will error if signing fails. - fn sign(opts: &SignOpts) -> Result<()>; + fn sign(opts: SignOpts) -> Result<()>; /// Verifies the image. /// @@ -545,22 +563,23 @@ pub trait SigningDriver: PrivateDriver { /// /// # Errors /// Will error if the image fails to be verified. - fn verify(opts: &VerifyOpts) -> Result<()>; + fn verify(opts: VerifyOpts) -> Result<()>; /// Sign an image given the image name and tag. /// /// # Errors /// Will error if the image fails to be signed. - fn sign_and_verify(opts: &SignVerifyOpts) -> Result<()> { + fn sign_and_verify(opts: SignVerifyOpts) -> Result<()> { trace!("sign_and_verify({opts:?})"); let path = opts .dir .as_ref() .map_or_else(|| PathBuf::from("."), |d| d.to_path_buf()); + let cosign_file_path = path.join(COSIGN_PUB_PATH); let image_digest = Driver::get_metadata( - &GetMetadataOpts::builder() + GetMetadataOpts::builder() .image(opts.image) .maybe_platform(opts.platform) .build(), @@ -573,39 +592,40 @@ pub trait SigningDriver: PrivateDriver { ) .parse() .into_diagnostic()?; + let issuer = Driver::oidc_provider(); + let identity = Driver::keyless_cert_identity(); + let priv_key = get_private_key(&path); - let (sign_opts, verify_opts) = match (Driver::get_ci_driver(), get_private_key(&path)) { - // Cosign public/private key pair - (_, Ok(priv_key)) => ( - SignOpts::builder() - .image(&image_digest) - .dir(&path) - .key(priv_key.to_string()) - .build(), - VerifyOpts::builder() - .image(opts.image) - .verify_type(VerifyType::File(path.join(COSIGN_PUB_PATH).into())) - .build(), - ), - // Gitlab keyless - (CiDriverType::Github | CiDriverType::Gitlab, _) => ( - SignOpts::builder().dir(&path).image(&image_digest).build(), - VerifyOpts::builder() - .image(opts.image) - .verify_type(VerifyType::Keyless { - issuer: Driver::oidc_provider()?.into(), - identity: Driver::keyless_cert_identity()?.into(), - }) - .build(), - ), - _ => bail!("Failed to get information for signing the image"), - }; + let (sign_opts, verify_opts) = + match (Driver::get_ci_driver(), &priv_key, &issuer, &identity) { + // Cosign public/private key pair + (_, Ok(priv_key), _, _) => ( + SignOpts::builder() + .image(&image_digest) + .dir(&path) + .key(priv_key) + .build(), + VerifyOpts::builder() + .image(opts.image) + .verify_type(VerifyType::File(&cosign_file_path)) + .build(), + ), + // Gitlab keyless + (CiDriverType::Github | CiDriverType::Gitlab, _, Ok(issuer), Ok(identity)) => ( + SignOpts::builder().dir(&path).image(&image_digest).build(), + VerifyOpts::builder() + .image(opts.image) + .verify_type(VerifyType::Keyless { issuer, identity }) + .build(), + ), + _ => bail!("Failed to get information for signing the image"), + }; let retry_count = if opts.retry_push { opts.retry_count } else { 0 }; retry(retry_count, 5, || { - Self::sign(&sign_opts)?; - Self::verify(&verify_opts) + Self::sign(sign_opts)?; + Self::verify(verify_opts) })?; Ok(()) @@ -665,7 +685,7 @@ pub trait CiDriver: PrivateDriver { /// /// # Errors /// Will error if the environment variables aren't set. - fn generate_tags(opts: &GenerateTagsOpts) -> Result>; + fn generate_tags(opts: GenerateTagsOpts) -> Result>; /// Generates the image name based on CI. /// @@ -722,3 +742,36 @@ pub trait CiDriver: PrivateDriver { fn default_ci_file_path() -> PathBuf; } + +#[allow(private_bounds)] +pub trait BootDriver: PrivateDriver { + /// Get the status of the current booted image. + /// + /// # Errors + /// Will error if we fail to get the status. + fn status() -> Result>; + + /// 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>; + + /// Gets the staged image. + fn staged_image(&self) -> Option>; +} diff --git a/process/drivers/types.rs b/process/drivers/types.rs index d457484..b55a7b0 100644 --- a/process/drivers/types.rs +++ b/process/drivers/types.rs @@ -1,501 +1,9 @@ -use std::{ - borrow::Cow, - collections::HashMap, - path::{Path, PathBuf}, -}; - -use blue_build_utils::{ - constants::{GITHUB_ACTIONS, GITLAB_CI, IMAGE_VERSION_LABEL}, - get_env_var, - semver::Version, - string, -}; -use clap::ValueEnum; -use log::{trace, warn}; -use oci_distribution::Reference; -use serde::Deserialize; -use serde_json::Value; - -use crate::drivers::{ - DriverVersion, buildah_driver::BuildahDriver, docker_driver::DockerDriver, - podman_driver::PodmanDriver, -}; - -mod private { - pub trait Private {} -} - -pub(super) trait DetermineDriver { - fn determine_driver(&mut self) -> T; -} - -#[derive(Debug, Clone, Copy, ValueEnum)] -pub enum InspectDriverType { - Skopeo, - Podman, - Docker, -} - -impl DetermineDriver for Option { - 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 for Option { - 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 for Option { - 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 for String { - fn from(value: RunDriverType) -> Self { - match value { - RunDriverType::Podman => "podman".to_string(), - RunDriverType::Docker => "docker".to_string(), - } - } -} - -impl DetermineDriver for Option { - 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 for Option { - 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 {} - -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 { - 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, - pub digest: String, -} - -impl ImageMetadata { - #[must_use] - pub fn get_version(&self) -> Option { - Some( - self.labels - .get(IMAGE_VERSION_LABEL) - .map(ToOwned::to_owned) - .and_then(|v| { - serde_json::from_value::(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 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 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 for OciDir { - fn as_ref(&self) -> &std::ffi::OsStr { - self.0.as_ref() - } -} - -impl TryFrom for OciDir { - type Error = miette::Report; - - fn try_from(value: std::path::PathBuf) -> Result { - 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 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 for ImageRef<'_> { - fn from(value: PathBuf) -> Self { - Self::LocalTar(Cow::Owned(value)) - } -} - -impl From> for String { - fn from(value: ImageRef<'_>) -> Self { - Self::from(&value) - } -} - -impl From<&ImageRef<'_>> for String { - fn from(value: &ImageRef<'_>) -> Self { - format!("{value}") - } -} - -impl std::fmt::Display for ImageRef<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Remote(remote) => remote.whole(), - Self::LocalTar(path) => format!("oci-archive:{}", path.display()), - } - ) - } -} +mod container; +mod drivers; +mod metadata; +mod platform; + +pub use container::*; +pub use drivers::*; +pub use metadata::*; +pub use platform::*; diff --git a/process/drivers/types/container.rs b/process/drivers/types/container.rs new file mode 100644 index 0000000..03a1046 --- /dev/null +++ b/process/drivers/types/container.rs @@ -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 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 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 for OciDir { + fn as_ref(&self) -> &std::ffi::OsStr { + self.0.as_ref() + } +} + +impl TryFrom for OciDir { + type Error = miette::Report; + + fn try_from(value: std::path::PathBuf) -> Result { + 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 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 for ImageRef<'_> { + fn from(value: PathBuf) -> Self { + Self::LocalTar(Cow::Owned(value)) + } +} + +impl From> 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 for ImageRef<'_> { + fn eq(&self, other: &Reference) -> bool { + match self { + Self::Remote(remote) => &**remote == other, + _ => false, + } + } +} diff --git a/process/drivers/types/drivers.rs b/process/drivers/types/drivers.rs new file mode 100644 index 0000000..a212ede --- /dev/null +++ b/process/drivers/types/drivers.rs @@ -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 for Option { + 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 for Option { + 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 for Option { + 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 for String { + fn from(value: RunDriverType) -> Self { + match value { + RunDriverType::Podman => "podman".to_string(), + RunDriverType::Docker => "docker".to_string(), + } + } +} + +impl DetermineDriver for Option { + 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 for Option { + 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 for Option { + 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, + }, + ) + } +} diff --git a/process/drivers/types/metadata.rs b/process/drivers/types/metadata.rs new file mode 100644 index 0000000..5b82f58 --- /dev/null +++ b/process/drivers/types/metadata.rs @@ -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, + pub digest: String, +} + +impl ImageMetadata { + #[must_use] + pub fn get_version(&self) -> Option { + Some( + self.labels + .get(IMAGE_VERSION_LABEL) + .map(ToOwned::to_owned) + .and_then(|v| { + serde_json::from_value::(v) + .inspect_err(|e| warn!("Failed to parse version:\n{e}")) + .ok() + })? + .major, + ) + } +} diff --git a/process/drivers/types/platform.rs b/process/drivers/types/platform.rs new file mode 100644 index 0000000..7394b1d --- /dev/null +++ b/process/drivers/types/platform.rs @@ -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 {} + +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 { + 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, + ) + } +} diff --git a/recipe/src/recipe.rs b/recipe/src/recipe.rs index 50c66d6..675761b 100644 --- a/recipe/src/recipe.rs +++ b/recipe/src/recipe.rs @@ -138,7 +138,7 @@ impl Recipe<'_> { } #[must_use] - pub fn get_secrets(&self) -> HashSet<&Secret> { + pub fn get_secrets(&self) -> Vec<&Secret> { self.modules_ext .modules .iter() @@ -154,6 +154,8 @@ impl Recipe<'_> { .filter_map(|module| Some(&module.required_fields.as_ref()?.secrets)) .flatten(), ) + .collect::>() + .into_iter() .collect() } } diff --git a/scripts/exports.sh b/scripts/exports.sh index a233224..695913a 100644 --- a/scripts/exports.sh +++ b/scripts/exports.sh @@ -44,6 +44,35 @@ color_string() { fi } +feature_enabled() { + # Ensure the function is called with exactly one argument + if [ "$#" -ne 1 ]; then + echo "Usage: feature_enabled " >&2 + return 1 + fi + + local feature="$1" + local -a features + + # Split BB_BUILD_FEATURES by commas and read into an array + IFS=, + read -r -a features <<< "$BB_BUILD_FEATURES" + + # Loop through the array and check for a match + for f in "${features[@]}"; do + # Trim leading and trailing whitespace + local trimmed_f="${f## }" + trimmed_f="${trimmed_f%% }" + + if [[ "$trimmed_f" == "$feature" ]]; then + return 0 + fi + done + + # Feature not found + return 1 +} + # Parse OS version and export it export OS_VERSION="$(awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release)" export OS_ARCH="$(uname -m)" diff --git a/scripts/post_build.sh b/scripts/post_build.sh index 8acb091..a3f468d 100644 --- a/scripts/post_build.sh +++ b/scripts/post_build.sh @@ -1,9 +1,10 @@ #!/usr/bin/env bash set -euo pipefail +. /scripts/exports.sh rm -rf /tmp/* /var/* -# if command -v bootc > /dev/null; then -# bootc container lint -# fi +if feature_enabled "bootc" && command -v bootc > /dev/null; then + bootc container lint +fi diff --git a/src/commands/build.rs b/src/commands/build.rs index 41463bf..7b69c78 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -7,20 +7,18 @@ use blue_build_process_management::{ BuildTagPushOpts, CheckKeyPairOpts, CompressionType, GenerateImageNameOpts, GenerateTagsOpts, SignVerifyOpts, }, - types::Platform, + types::{ImageRef, Platform}, }, logging::{color_str, gen_random_ansi_color}, }; use blue_build_recipe::Recipe; use blue_build_utils::{ constants::{ - ARCHIVE_SUFFIX, BB_REGISTRY_NAMESPACE, BB_SKIP_VALIDATION, CONFIG_PATH, CONTAINER_FILE, - RECIPE_FILE, RECIPE_PATH, + ARCHIVE_SUFFIX, BB_REGISTRY_NAMESPACE, BB_SKIP_VALIDATION, CONFIG_PATH, RECIPE_FILE, + RECIPE_PATH, }, - cowstr, credentials::{Credentials, CredentialsArgs}, string, - traits::CowCollecter, }; use bon::Builder; use clap::Args; @@ -163,7 +161,7 @@ impl BlueBuildCommand for BuildCommand { if self.push { blue_build_utils::check_command_exists("cosign")?; - Driver::check_signing_files(&CheckKeyPairOpts::builder().dir(Path::new(".")).build())?; + Driver::check_signing_files(CheckKeyPairOpts::builder().dir(Path::new(".")).build())?; Driver::login()?; Driver::signing_login()?; } @@ -191,11 +189,11 @@ impl BlueBuildCommand for BuildCommand { recipe_paths.par_iter().try_for_each(|recipe| { GenerateCommand::builder() - .output(tempdir.path().join(if recipe_paths.len() > 1 { - blue_build_utils::generate_containerfile_path(recipe)? - } else { - PathBuf::from(CONTAINER_FILE) - })) + .output( + tempdir + .path() + .join(blue_build_utils::generate_containerfile_path(recipe)?), + ) .skip_validation(self.skip_validation) .maybe_platform(self.platform) .recipe(recipe) @@ -217,12 +215,10 @@ impl BuildCommand { let images = recipe_paths .par_iter() .try_fold(Vec::new, |mut images, recipe_path| -> Result> { - let containerfile = temp_dir.join(if recipe_paths.len() > 1 { - blue_build_utils::generate_containerfile_path(recipe_path)? - } else { - PathBuf::from(CONTAINER_FILE) - }); - images.extend(self.build(recipe_path, &containerfile)?); + images.extend(self.build( + recipe_path, + &temp_dir.join(blue_build_utils::generate_containerfile_path(recipe_path)?), + )?); Ok(images) }) .try_reduce(Vec::new, |mut init, image_names| { @@ -245,9 +241,9 @@ impl BuildCommand { fn build(&self, recipe_path: &Path, containerfile: &Path) -> Result> { let recipe = Recipe::parse(recipe_path)?; let tags = Driver::generate_tags( - &GenerateTagsOpts::builder() + GenerateTagsOpts::builder() .oci_ref(&recipe.base_image_ref()?) - .maybe_alt_tags(recipe.alt_tags.as_ref().map(CowCollecter::collect_cow_vec)) + .maybe_alt_tags(recipe.alt_tags.as_deref()) .maybe_platform(self.platform) .build(), )?; @@ -276,45 +272,44 @@ impl BuildCommand { &image_name, 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 { - Driver::build_tag_push(&self.archive.as_ref().map_or_else( - || { - BuildTagPushOpts::builder() - .image(&image) - .containerfile(containerfile) - .maybe_platform(self.platform) - .tags(tags.collect_cow_vec()) - .push(self.push) - .retry_push(self.retry_push) - .retry_count(self.retry_count) - .compression(self.compression_format) - .squash(self.squash) - .maybe_cache_from(cache_image.as_ref()) - .maybe_cache_to(cache_image.as_ref()) - .secrets(recipe.get_secrets()) - .build() - }, - |archive_dir| { - BuildTagPushOpts::builder() - .containerfile(containerfile) - .maybe_platform(self.platform) - .image(PathBuf::from(format!( - "{}/{}.{ARCHIVE_SUFFIX}", - archive_dir.to_string_lossy().trim_end_matches('/'), - recipe.name.to_lowercase().replace('/', "_"), - ))) - .squash(self.squash) - .maybe_cache_from(cache_image.as_ref()) - .maybe_cache_to(cache_image.as_ref()) - .secrets(recipe.get_secrets()) - .build() - }, - ))? + Driver::build_tag_push( + BuildTagPushOpts::builder() + .image(&ImageRef::from(&image)) + .containerfile(containerfile) + .maybe_platform(self.platform) + .tags(&tags) + .push(self.push) + .retry_push(self.retry_push) + .retry_count(self.retry_count) + .compression(self.compression_format) + .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 { Driver::sign_and_verify( - &SignVerifyOpts::builder() + SignVerifyOpts::builder() .image(&image) .retry_push(self.retry_push) .retry_count(self.retry_count) @@ -342,13 +337,13 @@ impl BuildCommand { .parse() .into_diagnostic()?; Driver::rechunk( - &RechunkOpts::builder() + RechunkOpts::builder() .image(image_name) .containerfile(containerfile) .maybe_platform(self.platform) - .tags(tags.collect_cow_vec()) + .tags(tags) .push(self.push) - .version(format!( + .version(&format!( "{version}.", version = Driver::get_os_version() .oci_ref(&recipe.base_image_ref()?) @@ -359,23 +354,23 @@ impl BuildCommand { .retry_count(self.retry_count) .compression(self.compression_format) .base_digest( - Driver::get_metadata( - &GetMetadataOpts::builder() + &Driver::get_metadata( + GetMetadataOpts::builder() .image(&base_image) .maybe_platform(self.platform) .build(), )? .digest, ) - .repo(Driver::get_repo_url()?) - .name(&*recipe.name) - .description(&*recipe.description) - .base_image(format!("{}:{}", &recipe.base_image, &recipe.image_version)) + .repo(&Driver::get_repo_url()?) + .name(&recipe.name) + .description(&recipe.description) + .base_image(&format!("{}:{}", &recipe.base_image, &recipe.image_version)) .maybe_tempdir(self.tempdir.as_deref()) .clear_plan(self.rechunk_clear_plan) .maybe_cache_from(cache_image) .maybe_cache_to(cache_image) - .secrets(recipe.get_secrets()) + .secrets(&recipe.get_secrets()) .build(), ) } @@ -384,8 +379,8 @@ impl BuildCommand { let image_name = Driver::generate_image_name( GenerateImageNameOpts::builder() .name(recipe.name.trim()) - .maybe_registry(self.credentials.registry.as_ref().map(|r| cowstr!(r))) - .maybe_registry_namespace(self.registry_namespace.as_ref().map(|r| cowstr!(r))) + .maybe_registry(self.credentials.registry.as_deref()) + .maybe_registry_namespace(self.registry_namespace.as_deref()) .build(), )?; diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 3276aa9..0f5c60b 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -142,6 +142,19 @@ impl GenerateCommand { let base_image: Reference = format!("{}:{}", &recipe.base_image, &recipe.image_version) .parse() .into_diagnostic()?; + let base_digest = &Driver::get_metadata( + GetMetadataOpts::builder() + .image(&base_image) + .maybe_platform(self.platform) + .build(), + )? + .digest; + let build_scripts_image = &determine_scripts_tag(self.platform)?; + let repo = &Driver::get_repo_url()?; + let build_features = &[ + #[cfg(feature = "bootc")] + "bootc".into(), + ]; let template = ContainerFileTemplate::builder() .os_version( @@ -153,19 +166,12 @@ impl GenerateCommand { .build_id(Driver::get_build_id()) .recipe(&recipe) .recipe_path(recipe_path.as_path()) - .registry(registry) - .repo(Driver::get_repo_url()?) - .build_scripts_image(determine_scripts_tag(self.platform)?.to_string()) - .base_digest( - Driver::get_metadata( - &GetMetadataOpts::builder() - .image(&base_image) - .maybe_platform(self.platform) - .build(), - )? - .digest, - ) + .registry(®istry) + .repo(repo) + .build_scripts_image(build_scripts_image) + .base_digest(base_digest) .maybe_nushell_version(recipe.nushell_version.as_ref()) + .build_features(build_features) .build(); let output_str = template.render().into_diagnostic()?; @@ -197,7 +203,7 @@ fn determine_scripts_tag(platform: Option) -> Result { .parse() .into_diagnostic() .and_then(|image| { - Driver::get_metadata(&opts.clone().image(&image).build()) + Driver::get_metadata(opts.clone().image(&image).build()) .inspect_err(|e| trace!("{e:?}")) .map(|_| image) }) @@ -205,7 +211,7 @@ fn determine_scripts_tag(platform: Option) -> Result { let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:{}", shadow::BRANCH) .parse() .into_diagnostic()?; - Driver::get_metadata(&opts.clone().image(&image).build()) + Driver::get_metadata(opts.clone().image(&image).build()) .inspect_err(|e| trace!("{e:?}")) .map(|_| image) }) @@ -213,7 +219,7 @@ fn determine_scripts_tag(platform: Option) -> Result { let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:v{}", crate_version!()) .parse() .into_diagnostic()?; - Driver::get_metadata(&opts.image(&image).build()) + Driver::get_metadata(opts.image(&image).build()) .inspect_err(|e| trace!("{e:?}")) .map(|_| image) }) diff --git a/src/commands/generate_iso.rs b/src/commands/generate_iso.rs index 1cb0734..c8743eb 100644 --- a/src/commands/generate_iso.rs +++ b/src/commands/generate_iso.rs @@ -7,7 +7,6 @@ use blue_build_recipe::Recipe; use blue_build_utils::{ constants::{ARCHIVE_SUFFIX, BB_SKIP_VALIDATION}, string_vec, - traits::CowCollecter, }; use bon::Builder; use clap::{Args, Subcommand, ValueEnum}; @@ -189,8 +188,10 @@ impl GenerateIsoCommand { format!("SECURE_BOOT_KEY_URL={}", self.secure_boot_url), format!("ENROLLMENT_PASSWORD={}", self.enrollment_password), ]; + let image_out_dir = &image_out_dir.display().to_string(); + let output_dir = &output_dir.display().to_string(); let mut vols = run_volumes![ - output_dir.display().to_string() => "/build-container-installer/build", + output_dir => "/build-container-installer/build", "dnf-cache" => "/cache/dnf/", ]; @@ -239,8 +240,8 @@ impl GenerateIsoCommand { .call()?, ), ]); - vols.extend(run_volumes![ - image_out_dir.display().to_string() => "/img_src/", + vols.extend(&run_volumes![ + image_out_dir => "/img_src/", ]); } } @@ -250,11 +251,11 @@ impl GenerateIsoCommand { .image("ghcr.io/jasonn3/build-container-installer") .privileged(true) .remove(true) - .args(args.collect_cow_vec()) - .volumes(vols) + .args(&args) + .volumes(&vols) .build(); - let status = Driver::run(&opts)?; + let status = Driver::run(opts)?; if !status.success() { bail!("Failed to create ISO"); diff --git a/src/commands/init.rs b/src/commands/init.rs index 0292cac..818447d 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -518,8 +518,8 @@ impl InitCommand { .with_context(|| format!("Failed to delete old public file {COSIGN_PUB_PATH}"))?; Driver::generate_key_pair( - &GenerateKeyPairOpts::builder() - .maybe_dir(self.dir.as_ref()) + GenerateKeyPairOpts::builder() + .maybe_dir(self.dir.as_deref()) .build(), ) } diff --git a/src/commands/prune.rs b/src/commands/prune.rs index da0f188..a0d53da 100644 --- a/src/commands/prune.rs +++ b/src/commands/prune.rs @@ -73,7 +73,7 @@ impl BlueBuildCommand for PruneCommand { } Driver::prune( - &PruneOpts::builder() + PruneOpts::builder() .all(self.all) .volumes(self.volumes) .build(), diff --git a/src/commands/switch.rs b/src/commands/switch.rs index c3b13dd..83dcdf9 100644 --- a/src/commands/switch.rs +++ b/src/commands/switch.rs @@ -1,29 +1,19 @@ -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; +use std::path::PathBuf; -use blue_build_process_management::{ - drivers::{Driver, DriverArgs}, - logging::CommandLogging, +use blue_build_process_management::drivers::{ + BootDriver, BuildDriver, CiDriver, Driver, DriverArgs, PodmanDriver, RunDriver, + opts::{BuildOpts, GenerateImageNameOpts, RemoveImageOpts, SwitchOpts}, + types::ImageRef, }; use blue_build_recipe::Recipe; -use blue_build_utils::{ - constants::{ - ARCHIVE_SUFFIX, BB_SKIP_VALIDATION, LOCAL_BUILD, OCI_ARCHIVE, OSTREE_UNVERIFIED_IMAGE, - SUDO_ASKPASS, - }, - has_env_var, running_as_root, -}; +use blue_build_utils::constants::BB_SKIP_VALIDATION; use bon::Builder; use clap::Args; -use comlexr::cmd; -use indicatif::ProgressBar; -use log::{debug, trace}; +use log::trace; use miette::{IntoDiagnostic, Result, bail}; use tempfile::TempDir; -use crate::{commands::build::BuildCommand, rpm_ostree_status::RpmOstreeStatus}; +use crate::commands::generate::GenerateCommand; use super::BlueBuildCommand; @@ -60,238 +50,59 @@ impl BlueBuildCommand for SwitchCommand { Driver::init(self.drivers); - let status = RpmOstreeStatus::try_new()?; - trace!("{status:?}"); + let status = Driver::status()?; if status.transaction_in_progress() { bail!("There is a transaction in progress. Please cancel it using `rpm-ostree cancel`"); } + let recipe = Recipe::parse(&self.recipe)?; + let image_name = Driver::generate_image_name( + GenerateImageNameOpts::builder() + .name(recipe.name.trim()) + .build(), + )?; let tempdir = if let Some(ref dir) = self.tempdir { TempDir::new_in(dir).into_diagnostic()? } else { TempDir::new().into_diagnostic()? }; - trace!("{tempdir:?}"); + let containerfile = tempdir + .path() + .join(blue_build_utils::generate_containerfile_path(&self.recipe)?); - BuildCommand::builder() - .recipe([self.recipe.clone()]) - .archive(tempdir.path()) - .maybe_tempdir(self.tempdir.clone()) - .skip_validation(self.skip_validation) + GenerateCommand::builder() + .output(&containerfile) + .recipe(&self.recipe) .build() .try_run()?; + PodmanDriver::build( + BuildOpts::builder() + .image(&ImageRef::from(&image_name)) + .containerfile(&containerfile) + .secrets(&recipe.get_secrets()) + .build(), + )?; + PodmanDriver::copy_image_to_root_store(&image_name)?; + PodmanDriver::remove_image(RemoveImageOpts::builder().image(&image_name).build())?; - let recipe = Recipe::parse(&self.recipe)?; - let image_file_name = format!( - "{}.{ARCHIVE_SUFFIX}", - recipe.name.to_lowercase().replace('/', "_") - ); - let temp_file_path = tempdir.path().join(&image_file_name); - let archive_path = Path::new(LOCAL_BUILD).join(&image_file_name); - - Self::clean_local_build_dir()?; - Self::move_archive(&temp_file_path, &archive_path)?; - - // We drop the tempdir ahead of time so that the directory - // can be cleaned out. - drop(tempdir); - - self.switch(&archive_path, &status) - } -} - -impl SwitchCommand { - fn switch(&self, archive_path: &Path, status: &RpmOstreeStatus<'_>) -> Result<()> { - trace!( - "SwitchCommand::switch({}, {status:#?})", - archive_path.display() - ); - - let status = if status.is_booted_on_archive(archive_path) - || status.is_staged_on_archive(archive_path) + if status + .booted_image() + .is_some_and(|booted| booted == image_name) { - let command = cmd!("rpm-ostree", "upgrade", if self.reboot => "--reboot"); - - trace!("{command:?}"); - command + Driver::upgrade( + SwitchOpts::builder() + .image(&image_name) + .reboot(self.reboot) + .build(), + ) } else { - let image_ref = format!( - "{OSTREE_UNVERIFIED_IMAGE}:{OCI_ARCHIVE}:{path}", - path = archive_path.display() - ); - - let command = cmd!( - "rpm-ostree", - "rebase", - &image_ref, - if self.reboot => "--reboot", - ); - - trace!("{command:?}"); - command + Driver::switch( + SwitchOpts::builder() + .image(&image_name) + .reboot(self.reboot) + .build(), + ) } - .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::>(); - - 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(()) } } diff --git a/src/lib.rs b/src/lib.rs index e6d0042..f12e4e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,3 @@ shadow_rs::shadow!(shadow); pub mod commands; -pub mod rpm_ostree_status; diff --git a/src/rpm_ostree_status.rs b/src/rpm_ostree_status.rs deleted file mode 100644 index 163b5ed..0000000 --- a/src/rpm_ostree_status.rs +++ /dev/null @@ -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]>>, -} - -#[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 { - 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 { - 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 { - Some( - self.deployments - .iter() - .find(|deployment| deployment.staged)? - .container_image_reference - .to_string(), - ) - } - - #[must_use] - pub fn is_booted_on_archive

(&self, archive_path: P) -> bool - where - P: AsRef, - { - 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

(&self, archive_path: P) -> bool - where - P: AsRef, - { - 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}")) - )); - } -} diff --git a/template/Cargo.toml b/template/Cargo.toml index 5e0a6c3..b65ad40 100644 --- a/template/Cargo.toml +++ b/template/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true askama = { version = "0.14", features = ["serde_json"] } blue-build-recipe = { version = "=0.9.22", path = "../recipe" } blue-build-utils = { version = "=0.9.22", path = "../utils" } +oci-distribution.workspace = true chrono.workspace = true log.workspace = true diff --git a/template/src/lib.rs b/template/src/lib.rs index 473cc76..b9e3e19 100644 --- a/template/src/lib.rs +++ b/template/src/lib.rs @@ -9,28 +9,29 @@ use bon::Builder; use chrono::Utc; use colored::control::ShouldColorize; use log::{debug, error, trace, warn}; +use oci_distribution::Reference; use uuid::Uuid; pub use askama::Template; #[derive(Debug, Clone, Template, Builder)] #[template(path = "Containerfile.j2", escape = "none", whitespace = "minimize")] -#[builder(on(Cow<'_, str>, into))] pub struct ContainerFileTemplate<'a> { #[builder(into)] recipe: &'a Recipe<'a>, - - #[builder(into)] - recipe_path: Cow<'a, Path>, + recipe_path: &'a Path, #[builder(into)] build_id: Uuid, os_version: u64, - registry: Cow<'a, str>, - build_scripts_image: Cow<'a, str>, - repo: Cow<'a, str>, - base_digest: Cow<'a, str>, + registry: &'a str, + build_scripts_image: &'a Reference, + repo: &'a str, + base_digest: &'a str, nushell_version: Option<&'a MaybeVersion>, + + #[builder(default)] + build_features: &'a [String], } impl ContainerFileTemplate<'_> { @@ -47,6 +48,15 @@ impl ContainerFileTemplate<'_> { Some(MaybeVersion::Version(version)) => version.to_string(), } } + + #[must_use] + fn get_features(&self) -> String { + self.build_features + .iter() + .map(|feat| feat.trim()) + .collect::>() + .join(",") + } } #[derive(Debug, Clone, Template, Builder)] diff --git a/template/templates/Containerfile.j2 b/template/templates/Containerfile.j2 index 2e7d3d5..7b84571 100644 --- a/template/templates/Containerfile.j2 +++ b/template/templates/Containerfile.j2 @@ -7,6 +7,7 @@ FROM {{ recipe.base_image }}@{{ base_digest }} AS {{ main_stage }} ARG RECIPE={{ recipe_path.display() }} ARG IMAGE_REGISTRY={{ registry }} +ARG BB_BUILD_FEATURES="{{ get_features() }}" {%- if self::config_dir_exists() && !self::files_dir_exists() %} ARG CONFIG_DIRECTORY="/tmp/config" diff --git a/utils/src/constants.rs b/utils/src/constants.rs index 834134a..ec27948 100644 --- a/utils/src/constants.rs +++ b/utils/src/constants.rs @@ -88,6 +88,7 @@ pub const OSTREE_IMAGE_SIGNED: &str = "ostree-image-signed"; pub const OSTREE_UNVERIFIED_IMAGE: &str = "ostree-unverified-image"; pub const SKOPEO_IMAGE: &str = "quay.io/skopeo/stable:latest"; pub const TEMPLATE_REPO_URL: &str = "https://github.com/blue-build/template.git"; +pub const USER: &str = "USER"; pub const UNKNOWN_SHELL: &str = ""; pub const UNKNOWN_VERSION: &str = ""; pub const UNKNOWN_TERMINAL: &str = ""; @@ -110,3 +111,4 @@ pub const STAGE_SCHEMA: &str = concat!(JSON_SCHEMA, "/stage-v1.json"); // Messages pub const BUG_REPORT_WARNING_MESSAGE: &str = "Please copy the above report and open an issue manually."; +pub const SUDO_PROMPT: &str = "Bluebuild requires your password for sudo operation"; diff --git a/utils/src/macros.rs b/utils/src/macros.rs index 672d43d..f327a5b 100644 --- a/utils/src/macros.rs +++ b/utils/src/macros.rs @@ -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 { + value.parse() + } + } + + impl TryFrom<&String> for $typ { + type Error = miette::Error; + + fn try_from(value: &String) -> Result { + Self::try_from(value.as_str()) + } + } + + impl TryFrom for $typ { + type Error = miette::Error; + + fn try_from(value: String) -> Result { + Self::try_from(value.as_str()) + } + } + + impl<'de> serde::de::Deserialize<'de> for $typ { + fn deserialize(deserializer: D) -> Result + 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)* + ) + } + }; +} diff --git a/utils/src/secret.rs b/utils/src/secret.rs index 7690be7..b4d2c86 100644 --- a/utils/src/secret.rs +++ b/utils/src/secret.rs @@ -1,5 +1,4 @@ use std::{ - collections::HashSet, fs, hash::{DefaultHasher, Hash, Hasher}, ops::Not, @@ -121,7 +120,7 @@ impl SecretMounts for Vec { } } -impl private::Private for HashSet<&Secret, H> {} +impl private::Private for &[&Secret] {} #[allow(private_bounds)] pub trait SecretArgs: private::Private { @@ -138,7 +137,7 @@ pub trait SecretArgs: private::Private { fn ssh(&self) -> bool; } -impl SecretArgs for HashSet<&Secret, H> { +impl SecretArgs for &[&Secret] { fn args(&self, temp_dir: &TempDir) -> Result> { Ok(self .iter() @@ -173,7 +172,7 @@ impl SecretArgs for HashSet<&Secret, H> { } fn ssh(&self) -> bool { - self.contains(&Secret::Ssh) + self.contains(&&Secret::Ssh) } }