From 8ce83ba7ff74439d79450f7c84844afc7a7e5736 Mon Sep 17 00:00:00 2001 From: Gerald Pinder Date: Mon, 12 Aug 2024 23:52:07 -0400 Subject: [PATCH] refactor: Create SigningDriver and CiDriver (#197) This also includes a new `login` command. The signing and CI logic is now using the Driver trait system along with a new experimental sigstore signing driver. New static macros have also been created to make implementation management easier for `Command` usage and `Driver` trait implementation calls. --------- Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com> --- .cargo/config_fast_builds.toml | 34 - .github/workflows/build-pr.yml | 46 +- .github/workflows/build.yml | 44 +- .gitignore | 1 + .rusty-hook.toml | 2 +- Cargo.lock | 3036 ++++++++++++++++-- Cargo.toml | 38 +- Earthfile | 19 +- integration-tests/Earthfile | 37 +- justfile | 11 +- process/Cargo.toml | 50 + process/drivers.rs | 408 +++ {src => process}/drivers/buildah_driver.rs | 115 +- process/drivers/cosign_driver.rs | 241 ++ {src => process}/drivers/docker_driver.rs | 302 +- process/drivers/functions.rs | 52 + process/drivers/github_driver.rs | 272 ++ process/drivers/github_driver/event.rs | 44 + process/drivers/gitlab_driver.rs | 293 ++ {src => process/drivers}/image_metadata.rs | 0 process/drivers/local_driver.rs | 43 + {src => process}/drivers/opts.rs | 2 + {src => process}/drivers/opts/build.rs | 2 +- {src => process}/drivers/opts/inspect.rs | 0 process/drivers/opts/run.rs | 82 + process/drivers/opts/signing.rs | 115 + {src => process}/drivers/podman_driver.rs | 229 +- process/drivers/sigstore_driver.rs | 288 ++ {src => process}/drivers/skopeo_driver.rs | 17 +- process/drivers/traits.rs | 335 ++ process/drivers/types.rs | 169 + {utils/src => process}/logging.rs | 0 process/process.rs | 57 + {utils/src => process}/signal_handler.rs | 35 +- recipe/Cargo.toml | 1 - recipe/src/recipe.rs | 119 +- src/bin/bluebuild.rs | 11 +- src/commands.rs | 29 +- src/commands/build.rs | 506 +-- src/commands/generate.rs | 50 +- src/commands/local.rs | 27 +- src/commands/login.rs | 105 + src/commands/switch.rs | 55 +- src/credentials.rs | 129 - src/drivers.rs | 474 --- src/drivers/opts/run.rs | 45 - src/drivers/types.rs | 30 - src/lib.rs | 3 - src/rpm_ostree_status.rs | 6 +- template/Cargo.toml | 3 - template/src/lib.rs | 54 +- template/templates/Containerfile.j2 | 2 - template/templates/init/README.j2 | 45 + test-files/github-events/branch.json | 10 + test-files/github-events/default-branch.json | 10 + test-files/github-events/pr-branch.json | 15 + test-files/keys/cosign.key | 11 + test-files/keys/cosign.pub | 4 + utils/Cargo.toml | 28 +- utils/src/constants.rs | 7 + utils/src/credentials.rs | 177 + utils/src/lib.rs | 26 +- utils/src/macros.rs | 150 + 63 files changed, 6468 insertions(+), 2083 deletions(-) delete mode 100644 .cargo/config_fast_builds.toml create mode 100644 process/Cargo.toml create mode 100644 process/drivers.rs rename {src => process}/drivers/buildah_driver.rs (55%) create mode 100644 process/drivers/cosign_driver.rs rename {src => process}/drivers/docker_driver.rs (53%) create mode 100644 process/drivers/functions.rs create mode 100644 process/drivers/github_driver.rs create mode 100644 process/drivers/github_driver/event.rs create mode 100644 process/drivers/gitlab_driver.rs rename {src => process/drivers}/image_metadata.rs (100%) create mode 100644 process/drivers/local_driver.rs rename {src => process}/drivers/opts.rs (93%) rename {src => process}/drivers/opts/build.rs (98%) rename {src => process}/drivers/opts/inspect.rs (100%) create mode 100644 process/drivers/opts/run.rs create mode 100644 process/drivers/opts/signing.rs rename {src => process}/drivers/podman_driver.rs (53%) create mode 100644 process/drivers/sigstore_driver.rs rename {src => process}/drivers/skopeo_driver.rs (76%) create mode 100644 process/drivers/traits.rs create mode 100644 process/drivers/types.rs rename {utils/src => process}/logging.rs (100%) create mode 100644 process/process.rs rename {utils/src => process}/signal_handler.rs (88%) create mode 100644 src/commands/login.rs delete mode 100644 src/credentials.rs delete mode 100644 src/drivers.rs delete mode 100644 src/drivers/opts/run.rs delete mode 100644 src/drivers/types.rs create mode 100644 template/templates/init/README.j2 create mode 100644 test-files/github-events/branch.json create mode 100644 test-files/github-events/default-branch.json create mode 100644 test-files/github-events/pr-branch.json create mode 100644 test-files/keys/cosign.key create mode 100644 test-files/keys/cosign.pub create mode 100644 utils/src/credentials.rs create mode 100644 utils/src/macros.rs diff --git a/.cargo/config_fast_builds.toml b/.cargo/config_fast_builds.toml deleted file mode 100644 index 0d7bec0..0000000 --- a/.cargo/config_fast_builds.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Add the contents of this file to `config.toml` to enable "fast build" configuration. Please read the notes below. - -# NOTE: For maximum performance, build using a nightly compiler -# If you are using rust stable, remove the "-Zshare-generics=y" below. - -[target.x86_64-unknown-linux-gnu] -linker = "clang" -rustflags = [ - "-Clink-arg=-fuse-ld=lld", # Use LLD Linker - # "-Zshare-generics=y", # (Nightly) Make the current crate share its generic instantiations -] - -# NOTE: you must install [Mach-O LLD Port](https://lld.llvm.org/MachO/index.html) on mac. you can easily do this by installing llvm which includes lld with the "brew" package manager: -# `brew install llvm` -[target.x86_64-apple-darwin] -rustflags = [ - "-Clink-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld", # Use LLD Linker - # "-Zshare-generics=y", # (Nightly) Make the current crate share its generic instantiations -] - -[target.aarch64-apple-darwin] -rustflags = [ - "-Clink-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld", # Use LLD Linker - # "-Zshare-generics=y", # (Nightly) Make the current crate share its generic instantiations -] - -[target.x86_64-pc-windows-msvc] -linker = "rust-lld.exe" # Use LLD Linker -# rustflags = ["-Zshare-generics=n"] - -# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only' -# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains. -#[profile.dev] -#debug = 1 diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index c8dae30..26207dd 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -13,6 +13,42 @@ env: RUST_LOG_STYLE: always jobs: + test: + timeout-minutes: 20 + runs-on: ubuntu-latest + + steps: + - uses: earthly/actions-setup@v1 + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Run test + id: build + run: | + earthly --ci +test + + lint: + timeout-minutes: 20 + runs-on: ubuntu-latest + + steps: + - uses: earthly/actions-setup@v1 + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Run lint + id: build + run: | + earthly --ci +lint + arm64-prebuild: timeout-minutes: 60 runs-on: ubuntu-latest @@ -220,7 +256,7 @@ jobs: cd integration-tests/test-repo bluebuild template -vv | tee Containerfile grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 - bluebuild build --push -vv recipes/recipe.yml recipes/recipe-39.yml + bluebuild build --retry-push -B docker -I docker -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml docker-build-external-login: timeout-minutes: 60 @@ -275,7 +311,7 @@ jobs: cd integration-tests/test-repo bluebuild template -vv | tee Containerfile grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 - bluebuild build --push -vv recipes/recipe.yml recipes/recipe-39.yml + bluebuild build --retry-push -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml podman-build: timeout-minutes: 60 @@ -327,10 +363,10 @@ jobs: cd integration-tests/test-repo bluebuild template -vv | tee Containerfile grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 - bluebuild build -B podman --push -vv recipes/recipe.yml recipes/recipe-39.yml + bluebuild build --retry-push -B podman -I podman -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml buildah-build: - timeout-minutes: 60 + timeout-minutes: 15 runs-on: ubuntu-latest permissions: contents: read @@ -379,4 +415,4 @@ jobs: cd integration-tests/test-repo bluebuild template -vv | tee Containerfile grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 - bluebuild build -B buildah --push -vv recipes/recipe.yml recipes/recipe-39.yml + bluebuild build --retry-push -B buildah -I podman -S sigstore --squash --push -vv recipes/recipe.yml recipes/recipe-39.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9982fb..00b4319 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,42 @@ env: RUST_LOG_STYLE: always jobs: + test: + timeout-minutes: 20 + runs-on: ubuntu-latest + + steps: + - uses: earthly/actions-setup@v1 + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Run build + id: build + run: | + earthly --ci +test + + lint: + timeout-minutes: 20 + runs-on: ubuntu-latest + + steps: + - uses: earthly/actions-setup@v1 + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Run build + id: build + run: | + earthly --ci +test + arm64-prebuild: timeout-minutes: 60 runs-on: ubuntu-latest @@ -218,7 +254,7 @@ jobs: cd integration-tests/test-repo bluebuild template -vv | tee Containerfile grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 - bluebuild build --push -vv recipes/recipe.yml recipes/recipe-39.yml + bluebuild build --retry-push -B docker -I docker -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml docker-build-external-login: timeout-minutes: 60 @@ -273,7 +309,7 @@ jobs: cd integration-tests/test-repo bluebuild template -vv | tee Containerfile grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 - bluebuild build --push -vv recipes/recipe.yml recipes/recipe-39.yml + bluebuild build --retry-push -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml podman-build: timeout-minutes: 60 @@ -325,7 +361,7 @@ jobs: cd integration-tests/test-repo bluebuild template -vv | tee Containerfile grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 - bluebuild build -B podman --push -vv recipes/recipe.yml recipes/recipe-39.yml + bluebuild build --retry-push -B podman -I podman -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml buildah-build: timeout-minutes: 60 @@ -377,4 +413,4 @@ jobs: cd integration-tests/test-repo bluebuild template -vv | tee Containerfile grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1 - bluebuild build -B buildah --push -vv recipes/recipe.yml recipes/recipe-39.yml + bluebuild build --retry-push -B buildah -I podman -S sigstore --squash --push -vv recipes/recipe.yml recipes/recipe-39.yml diff --git a/.gitignore b/.gitignore index f73b671..39039fa 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ result* .direnv/ cosign.key +!test-files/keys/cosign.key # Local testing for bluebuild recipe files /config/* diff --git a/.rusty-hook.toml b/.rusty-hook.toml index cea1625..29b25ec 100644 --- a/.rusty-hook.toml +++ b/.rusty-hook.toml @@ -1,5 +1,5 @@ [hooks] -pre-commit = "cargo fmt --check && cargo test && cargo test --all-features && cargo clippy -- -D warnings && cargo clippy --all-features -- -D warnings" +pre-push = "cargo fmt --check && cargo test --workspace && cargo test --workspace --all-features && cargo clippy -- -D warnings && cargo clippy --all-features -- -D warnings" [logging] verbose = true diff --git a/Cargo.lock b/Cargo.lock index 45f4150..8f4aeaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,38 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -30,6 +62,21 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -47,9 +94,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -62,33 +109,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -106,13 +153,35 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -147,6 +216,18 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -159,6 +240,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "basic-toml" version = "0.1.9" @@ -185,9 +272,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake2" @@ -207,32 +294,45 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + [[package]] name = "blue-build" version = "0.8.12" dependencies = [ + "blue-build-process-management", "blue-build-recipe", "blue-build-template", "blue-build-utils", - "chrono", "clap", "clap-verbosity-flag", "clap_complete", - "clap_complete_nushell", "colored", "fuzzy-matcher", - "indexmap 2.2.6", "indicatif", - "lenient_semver", "log", "miette", - "once_cell", "open", "os_info", "rayon", "requestty", "rusty-hook", - "semver", "serde", "serde_json", "serde_yaml 0.9.34+deprecated", @@ -241,7 +341,42 @@ dependencies = [ "typed-builder", "urlencoding", "users", +] + +[[package]] +name = "blue-build-process-management" +version = "0.8.12" +dependencies = [ + "anyhow", + "blue-build-recipe", + "blue-build-utils", + "chrono", + "clap", + "colored", + "expect-exit", + "indexmap 2.3.0", + "indicatif", + "indicatif-log-bridge", + "lenient_semver", + "log", + "log4rs", + "miette", + "nix", + "nu-ansi-term", + "once_cell", + "os_pipe", + "rand 0.8.5", + "semver", + "serde", + "serde_json", + "signal-hook", + "sigstore", + "tempdir", + "tokio", + "typed-builder", + "users", "uuid", + "zeroize", ] [[package]] @@ -249,9 +384,8 @@ name = "blue-build-recipe" version = "0.8.12" dependencies = [ "blue-build-utils", - "chrono", "colored", - "indexmap 2.2.6", + "indexmap 2.3.0", "log", "miette", "serde", @@ -268,9 +402,6 @@ dependencies = [ "blue-build-utils", "log", "rinja", - "serde", - "serde_json", - "serde_yaml 0.9.34+deprecated", "typed-builder", "uuid", ] @@ -279,36 +410,36 @@ dependencies = [ name = "blue-build-utils" version = "0.8.12" dependencies = [ - "anyhow", "atty", "base64 0.22.1", "blake2", "chrono", "clap", - "colored", "directories", + "docker_credential", "format_serde_error", - "indicatif", - "indicatif-log-bridge", "log", - "log4rs", "miette", - "nix", - "nu-ansi-term", - "once_cell", - "os_pipe", "process_control", - "rand 0.8.5", + "rstest", "serde", "serde_json", "serde_yaml 0.9.34+deprecated", - "signal-hook", "syntect", - "tempdir", "typed-builder", "which", ] +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -316,16 +447,78 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "cc" -version = "1.0.98" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cached" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8466736fe5dbcaf8b8ee24f9bbefe43c884dc3e9ff7178da70f55bffca1133c" +dependencies = [ + "ahash 0.8.11", + "async-trait", + "cached_proc_macro", + "cached_proc_macro_types", + "futures", + "hashbrown 0.14.5", + "instant", + "once_cell", + "thiserror", + "tokio", +] + +[[package]] +name = "cached_proc_macro" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575f32e012222055211b70f5b0601f951f84523410a0e65c81f2744a6042450d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" dependencies = [ "jobserver", "libc", - "once_cell", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -348,8 +541,9 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -362,10 +556,21 @@ dependencies = [ ] [[package]] -name = "clap" -version = "4.5.4" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" dependencies = [ "clap_builder", "clap_derive", @@ -373,9 +578,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb9b20c0dd58e4c2e991c8d203bbeb76c11304d1011659686b5b644bc29aa478" +checksum = "63d19864d6b68464c59f7162c9914a0b569ddc2926b4a2d71afe62a9738eff53" dependencies = [ "clap", "log", @@ -383,9 +588,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -397,46 +602,36 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.2" +version = "4.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" +checksum = "1d11bff0290e9a266fc9b4ce6fa96c2bf2ca3f9724c41c10202ac1daf7a087f8" dependencies = [ "clap", ] -[[package]] -name = "clap_complete_nushell" -version = "4.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0e48e026ce7df2040239117d25e4e79714907420c70294a5ce4b6bbe6a7b6" -dependencies = [ - "clap", - "clap_complete", -] - [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" @@ -448,6 +643,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "console" version = "0.15.8" @@ -461,6 +666,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const_fn" version = "0.4.10" @@ -487,12 +698,31 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -536,7 +766,7 @@ dependencies = [ "bitflags 1.3.2", "crossterm_winapi", "libc", - "mio", + "mio 0.8.11", "parking_lot", "signal-hook", "signal-hook-mio", @@ -552,6 +782,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -559,9 +801,117 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.72", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "decoded-char" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5440d1dc8ea7cae44cda3c64568db29bfa2434aba51ae66a50c00488841a65a3" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "deranged" version = "0.3.11" @@ -569,6 +919,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -595,6 +946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -621,10 +973,87 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.12.0" +name = "docker_credential" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] [[package]] name = "encode_unicode" @@ -632,6 +1061,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "envmnt" version = "0.8.4" @@ -658,6 +1096,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "expect-exit" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a35d7c9abd6c97c27ac44c4295fec4f51ffd2dcbf6fd45fc1cd39d11ad9df" + [[package]] name = "fastrand" version = "2.1.0" @@ -665,10 +1109,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] -name = "flate2" -version = "1.0.30" +name = "ff" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flagset" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" + +[[package]] +name = "flate2" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" dependencies = [ "crc32fast", "miniz_oxide", @@ -714,6 +1186,101 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -731,6 +1298,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -749,8 +1317,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -765,18 +1335,70 @@ version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", "libgit2-sys", "log", "url", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.3.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -784,7 +1406,8 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.11", + "allocator-api2", ] [[package]] @@ -802,6 +1425,36 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.9" @@ -811,6 +1464,83 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-auth" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643c9bbf6a4ea8a656d6b4cd53d34f79e3f841ad5203c1a55fb7d761923bc255" +dependencies = [ + "memchr", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humansize" version = "2.1.3" @@ -826,6 +1556,101 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.23.12", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots 0.26.3", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -849,6 +1674,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -867,13 +1698,14 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -905,6 +1737,16 @@ dependencies = [ "log", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -914,6 +1756,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "is-docker" version = "0.2.0" @@ -947,9 +1795,27 @@ checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +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 = "itoa" @@ -958,10 +1824,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "jobserver" -version = "0.1.31" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -976,10 +1864,59 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "json-number" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "4c54d19ae7e6fc83aafa649707655a9a0ac956a0f62793bde4cfd193b0693fdf" +dependencies = [ + "lexical", + "ryu-js", + "serde", + "smallvec", +] + +[[package]] +name = "json-syntax" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "044a68aba3f96d712f492b72be25e10f96201eaaca3207a7d6e68d6d5105fda9" +dependencies = [ + "decoded-char", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "json-number", + "locspan", + "locspan-derive", + "ryu-js", + "serde", + "smallstr", + "smallvec", + "utf8-decode", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lenient_semver" @@ -1010,6 +1947,79 @@ dependencies = [ "semver", ] +[[package]] +name = "lexical" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7aefb36fd43fef7003334742cbf77b243fcd36418a1d1bdd480d613a67968f6" +dependencies = [ + "lexical-core", +] + +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" +dependencies = [ + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +dependencies = [ + "lexical-util", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.155" @@ -1040,7 +2050,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", ] @@ -1056,12 +2066,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "line-wrap" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1085,10 +2089,28 @@ dependencies = [ ] [[package]] -name = "log" -version = "0.4.21" +name = "locspan" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "33890449fcfac88e94352092944bf321f55e5deb4e289a6f51c87c55731200a0" + +[[package]] +name = "locspan-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88991223b049a3d29ca1f60c05639581336a0f3ee4bf8a659dddecc11c4961a" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "serde", ] @@ -1129,9 +2151,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miette" @@ -1161,7 +2183,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -1172,9 +2194,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -1188,9 +2210,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -1207,6 +2229,30 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nias" version = "0.5.0" @@ -1219,7 +2265,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", @@ -1237,11 +2283,28 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.0" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", ] [[package]] @@ -1250,6 +2313,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1257,6 +2340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1281,14 +2365,104 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] -name = "object" -version = "0.36.1" +name = "oauth2" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom", + "http 0.2.12", + "rand 0.8.5", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror", + "url", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "object" +version = "0.36.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] +[[package]] +name = "oci-distribution" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95a2c51531af0cb93761f66094044ca6ea879320bccd35ab747ff3fcab3f422" +dependencies = [ + "bytes", + "chrono", + "futures-util", + "http 1.1.0", + "http-auth", + "jwt", + "lazy_static", + "olpc-cjson", + "regex", + "reqwest 0.12.5", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tracing", + "unicase", +] + +[[package]] +name = "olpc-cjson" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d637c9c15b639ccff597da8f4fa968300651ad2f1e968aefc3b4927a6fb2027a" +dependencies = [ + "serde", + "serde_json", + "unicode-normalization", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1301,7 +2475,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa7085055bbe9c8edbd982048dbcf8181794d4a81cb04a11931673e63cc18dc6" dependencies = [ - "ahash", + "ahash 0.8.11", "hashbrown 0.14.5", "parking_lot", "stable_deref_trait", @@ -1330,16 +2504,54 @@ dependencies = [ ] [[package]] -name = "open" -version = "5.1.3" +name = "opaque-debug" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb49fbd5616580e9974662cb96a3463da4476e649a7e4b258df0de065db0657" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "open" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" dependencies = [ "is-wsl", "libc", "pathdiff", ] +[[package]] +name = "openidconnect" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" +dependencies = [ + "base64 0.13.1", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http 0.2.12", + "itertools 0.10.5", + "log", + "oauth2", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde-value", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror", + "url", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1368,12 +2580,12 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.1.5" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1382,6 +2594,30 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1400,9 +2636,20 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall 0.5.3", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", ] [[package]] @@ -1411,12 +2658,121 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.3.0", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "pkcs5", + "rand_core 0.6.4", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -1425,23 +2781,33 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plist" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.21.7", - "indexmap 2.2.6", - "line-wrap", + "base64 0.22.1", + "indexmap 2.3.0", "quick-xml", "serde", "time", ] [[package]] -name = "portable-atomic" -version = "1.6.0" +name = "poly1305" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "powerfmt" @@ -1451,39 +2817,222 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.72", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "process_control" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d18334c4a4b2770ee894e63cf466d5a9ea449cf29e321101b0b135a747afb6f" +checksum = "52a6e5a3a16f96120ae4c65bffa1be6825d51abc3c564d82d737c463d63f12cf" dependencies = [ "libc", "signal-hook", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.72", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "prost-reflect" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057237efdb71cf4b3f9396302a3d6599a92fa94063ba537b66130980ea9909f3" +dependencies = [ + "base64 0.21.7", + "once_cell", + "prost", + "prost-reflect-derive", + "prost-types", + "serde", + "serde-value", +] + +[[package]] +name = "prost-reflect-build" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d0aa0c82e0fc36214c77b4dabe00750b3c41be45055baf2631cbbb7769b8ca" +dependencies = [ + "prost-build", + "prost-reflect", +] + +[[package]] +name = "prost-reflect-derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172da1212c02be2c94901440cb27183cd92bff00ebacca5c323bf7520b8f9c04" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", ] [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.12", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls 0.23.12", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -1591,11 +3140,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -1616,10 +3165,39 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.8.3" +name = "regex" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "remove_dir_all" @@ -1671,6 +3249,119 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 0.25.4", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls 0.27.2", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.12", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tokio-rustls 0.26.0", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 0.26.3", + "winreg 0.52.0", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rinja" version = "0.3.0" @@ -1699,7 +3390,7 @@ dependencies = [ "quote", "rinja_parser", "serde", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -1712,25 +3403,161 @@ dependencies = [ "nom", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.72", + "unicode-ident", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.6", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -1755,6 +3582,21 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "ryu-js" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1770,6 +3612,42 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.23" @@ -1781,9 +3659,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.205" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" dependencies = [ "serde_derive", ] @@ -1800,26 +3678,99 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.205" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", "itoa", "ryu", "serde", ] +[[package]] +name = "serde_with" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.3.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "serde_yaml" version = "0.8.26" @@ -1838,13 +3789,35 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "itoa", "ryu", "serde", "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shadow-rs" version = "0.26.1" @@ -1877,12 +3850,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 0.8.11", "signal-hook", ] @@ -1895,6 +3868,121 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "sigstore" +version = "0.9.0" +source = "git+https://github.com/gmpinder/sigstore-rs.git?rev=3a804bff#3a804bff5805dde3140c17b4fc9272572447472d" +dependencies = [ + "async-trait", + "base64 0.22.1", + "cached", + "cfg-if", + "chrono", + "const-oid", + "crypto_secretbox", + "digest", + "ecdsa", + "ed25519", + "ed25519-dalek", + "elliptic-curve", + "futures", + "futures-util", + "getrandom", + "hex", + "json-syntax", + "lazy_static", + "oci-distribution", + "olpc-cjson", + "openidconnect", + "p256", + "p384", + "pem", + "pkcs1", + "pkcs8", + "rand 0.8.5", + "regex", + "reqwest 0.12.5", + "ring", + "rsa", + "rustls-webpki 0.102.6", + "scrypt", + "serde", + "serde_json", + "serde_repr", + "serde_with", + "sha2", + "signature", + "sigstore_protobuf_specs", + "thiserror", + "tls_codec", + "tokio", + "tokio-util", + "tough", + "tracing", + "url", + "webbrowser", + "x509-cert", + "zeroize", +] + +[[package]] +name = "sigstore-protobuf-specs-derive" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80baa401f274093f7bb27d7a69d6139cbc11f1b97624e9a61a9b3ea32c776a35" +dependencies = [ + "quote", + "syn 2.0.72", +] + +[[package]] +name = "sigstore_protobuf_specs" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2e92220d708bd04b8e50aab37f7039580fa00aec4ca045fe1fed43264cad1b" +dependencies = [ + "anyhow", + "glob", + "prost", + "prost-build", + "prost-reflect", + "prost-reflect-build", + "prost-types", + "serde", + "serde_json", + "sigstore-protobuf-specs-derive", + "which", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b1aefdf380735ff8ded0b15f31aab05daf1f70216c01c02a12926badd1df9d" +dependencies = [ + "serde", + "smallvec", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -1907,12 +3995,67 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "snafu" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b835cb902660db3415a672d862905e791e54d306c6e8189168c7f3d9ae1c79d" +dependencies = [ + "futures-core", + "pin-project", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -1921,9 +4064,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "supports-color" @@ -1959,15 +4102,27 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "syntect" version = "5.2.0" @@ -1990,6 +4145,27 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -2002,14 +4178,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2058,29 +4235,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] name = "thread-id" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" dependencies = [ "libc", "winapi", @@ -2131,9 +4308,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2144,6 +4321,89 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "tokio" +version = "1.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio 1.0.1", + "parking_lot", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.12", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.11" @@ -2153,6 +4413,105 @@ dependencies = [ "serde", ] +[[package]] +name = "tough" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d7a87d51ca5a113542e1b9f5ee2b14b6864bf7f34d103740086fa9c3d57d3b" +dependencies = [ + "async-recursion", + "async-trait", + "bytes", + "chrono", + "dyn-clone", + "futures", + "futures-core", + "globset", + "hex", + "log", + "olpc-cjson", + "pem", + "percent-encoding", + "reqwest 0.11.27", + "ring", + "serde", + "serde_json", + "serde_plain", + "snafu", + "tempfile", + "tokio", + "tokio-util", + "typed-path", + "untrusted", + "url", + "walkdir", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typed-builder" version = "0.18.2" @@ -2170,9 +4529,15 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] +[[package]] +name = "typed-path" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668404597c2c687647f6f8934f97c280fd500db28557f52b07c56b92d3dc500a" + [[package]] name = "typemap-ors" version = "1.0.0" @@ -2261,9 +4626,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -2271,6 +4636,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "unsafe-any-ors" version = "1.0.0" @@ -2287,14 +4662,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] -name = "url" -version = "2.5.0" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2314,16 +4696,22 @@ dependencies = [ ] [[package]] -name = "utf8parse" -version = "0.2.1" +name = "utf8-decode" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "ca61eb27fa339aa08826a29f03e87b99b4d8f0fc2255306fd266bb1b6a9de498" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", ] @@ -2336,9 +4724,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -2350,6 +4738,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2377,10 +4774,22 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -2399,7 +4808,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2411,10 +4820,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] -name = "which" -version = "6.0.1" +name = "wasm-streams" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e" +dependencies = [ + "block2", + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" dependencies = [ "either", "home", @@ -2440,11 +4905,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2459,7 +4924,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -2477,7 +4951,31 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2497,20 +4995,26 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2519,9 +5023,15 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -2531,9 +5041,15 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -2543,15 +5059,21 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -2561,9 +5083,15 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -2573,9 +5101,15 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -2585,9 +5119,15 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -2597,9 +5137,29 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] [[package]] name = "winsafe" @@ -2613,6 +5173,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956" +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "sha1", + "signature", + "spki", + "tls_codec", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -2628,6 +5202,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -2639,5 +5214,26 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "serde", + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", ] diff --git a/Cargo.toml b/Cargo.toml index 705c798..e22cdc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["utils", "recipe", "template"] +members = ["utils", "recipe", "template", "process"] [workspace.package] description = "A CLI tool built for creating Containerfile templates for ostree based atomic distros" @@ -13,30 +13,29 @@ version = "0.8.12" chrono = "0.4" clap = "4" colored = "2" -format_serde_error = "0.3" indexmap = { version = "2", features = ["serde"] } indicatif = { version = "0.17", features = ["improved_unicode"] } -indicatif-log-bridge = "0.2" log = "0.4" miette = "7" -once_cell = "1" +rstest = "0.18" serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yaml = "0.9" tempdir = "0.3" typed-builder = "0.18" +users = "0.11" uuid = { version = "1", features = ["v4"] } [workspace.lints.rust] unsafe_code = "forbid" [workspace.lints.clippy] -correctness = "warn" -suspicious = "warn" -perf = "warn" -style = "warn" -nursery = "warn" -pedantic = "warn" +correctness = "deny" +suspicious = "deny" +perf = "deny" +style = "deny" +nursery = "deny" +pedantic = "deny" module_name_repetitions = { level = "allow", priority = 1 } [package] @@ -59,34 +58,28 @@ pre-release-replacements = [ blue-build-recipe = { version = "=0.8.12", path = "./recipe" } blue-build-template = { version = "=0.8.12", path = "./template" } blue-build-utils = { version = "=0.8.12", path = "./utils" } +blue-build-process-management = { version = "=0.8.12", path = "./process" } clap-verbosity-flag = "2" clap_complete = "4" -clap_complete_nushell = "4" fuzzy-matcher = "0.3" -lenient_semver = "0.4" open = "5" os_info = "3" rayon = { version = "1.10.0", optional = true } requestty = { version = "0.5", features = ["macros", "termion"] } -semver = { version = "1", features = ["serde"] } shadow-rs = "0.26" urlencoding = "2" -users = "0.11" -chrono.workspace = true clap = { workspace = true, features = ["derive", "cargo", "unicode", "env"] } colored.workspace = true -indexmap.workspace = true indicatif.workspace = true log.workspace = true miette = { workspace = true, features = ["fancy"] } -once_cell.workspace = true serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true tempdir.workspace = true typed-builder.workspace = true -uuid.workspace = true +users.workspace = true [features] default = [] @@ -94,6 +87,8 @@ stages = ["blue-build-recipe/stages"] copy = ["blue-build-recipe/copy"] multi-recipe = ["rayon", "indicatif/rayon"] switch = [] +sigstore = ["blue-build-process-management/sigstore"] +login = [] [dev-dependencies] rusty-hook = "0.11" @@ -107,5 +102,10 @@ workspace = true [profile.release] lto = true codegen-units = 1 -strip = true +strip = "none" debug = false +panic = "abort" + +[patch.crates-io] +# sigstore = { path = "../../sigstore-rs/" } +sigstore = { git = "https://github.com/gmpinder/sigstore-rs.git", rev = "3a804bff" } diff --git a/Earthfile b/Earthfile index dbe82da..02b2fc8 100644 --- a/Earthfile +++ b/Earthfile @@ -16,7 +16,6 @@ build: WAIT BUILD --platform=linux/amd64 --platform=linux/arm64 +build-scripts END - BUILD +run-checks BUILD --platform=linux/amd64 --platform=linux/arm64 +build-images run-checks: @@ -34,15 +33,19 @@ prebuild: lint: FROM +common - DO rust+CARGO --args="clippy -- -D warnings" - DO rust+CARGO --args="clippy --all-features -- -D warnings" - DO rust+CARGO --args="clippy --no-default-features -- -D warnings" + RUN cargo fmt --check + DO rust+CARGO --args="clippy" + DO rust+CARGO --args="clippy --all-features" + DO rust+CARGO --args="clippy --no-default-features" test: FROM +common - DO rust+CARGO --args="test -- --show-output" - DO rust+CARGO --args="test --all-features -- --show-output" - DO rust+CARGO --args="test --no-default-features -- --show-output" + COPY --dir test-files/ integration-tests/ /app + COPY +cosign/cosign /usr/bin/cosign + + DO rust+CARGO --args="test --workspace -- --show-output" + DO rust+CARGO --args="test --workspace --all-features -- --show-output" + DO rust+CARGO --args="test --workspace --no-default-features -- --show-output" install: FROM +common @@ -64,7 +67,7 @@ common: FROM --platform=native ghcr.io/blue-build/earthly-lib/cargo-builder WORKDIR /app - COPY --keep-ts --dir src/ template/ recipe/ utils/ /app + COPY --keep-ts --dir src/ template/ recipe/ utils/ process/ /app COPY --keep-ts Cargo.* /app COPY --keep-ts *.md /app COPY --keep-ts LICENSE /app diff --git a/integration-tests/Earthfile b/integration-tests/Earthfile index 339b274..38055a7 100644 --- a/integration-tests/Earthfile +++ b/integration-tests/Earthfile @@ -1,6 +1,8 @@ VERSION 0.8 PROJECT blue-build/cli +IMPORT github.com/earthly/lib/utils/dind AS dind + all: BUILD +test-image BUILD +test-legacy-image @@ -31,37 +33,51 @@ build-template: template-containerfile: FROM +test-base - RUN bluebuild -vv generate recipes/recipe.yml | tee Containerfile + RUN bluebuild -v generate recipes/recipe.yml | tee Containerfile SAVE ARTIFACT /test template-legacy-containerfile: FROM +legacy-base - RUN bluebuild -vv template config/recipe.yml | tee Containerfile + RUN bluebuild -v template config/recipe.yml | tee Containerfile SAVE ARTIFACT /test build: FROM +test-base - RUN bluebuild -vv build recipes/recipe.yml + RUN bluebuild -v build recipes/recipe.yml + +build-full: + FROM +test-base --MOCK="false" + + DO dind+INSTALL + + ENV BB_USERNAME=gmpinder + ENV BB_REGISTRY=ghcr.io + ENV BB_REGISTRY_NAMESPACE=blue-build + + WITH DOCKER + RUN --secret BB_PASSWORD=github/registry bluebuild build --push -S sigstore -vv recipes/recipe.yml + END + rebase: FROM +legacy-base - RUN bluebuild -vv rebase config/recipe.yml + RUN bluebuild -v rebase config/recipe.yml upgrade: FROM +legacy-base RUN mkdir -p /etc/bluebuild && touch $BB_TEST_LOCAL_IMAGE - RUN bluebuild -vv upgrade config/recipe.yml + RUN bluebuild -v upgrade config/recipe.yml switch: FROM +test-base RUN mkdir -p /etc/bluebuild && touch $BB_TEST_LOCAL_IMAGE - RUN bluebuild -vv switch recipes/recipe.yml + RUN bluebuild -v switch recipes/recipe.yml legacy-base: FROM ../+blue-build-cli-alpine @@ -84,7 +100,10 @@ test-base: ENV BB_TEST_LOCAL_IMAGE=/etc/bluebuild/cli_test.tar.gz ENV CLICOLOR_FORCE=1 - COPY ./mock-scripts/ /usr/bin/ + ARG MOCK="true" + IF [ "$MOCK" = "true" ] + COPY ./mock-scripts/ /usr/bin/ + END WORKDIR /test COPY ./test-repo /test @@ -94,7 +113,9 @@ test-base: GEN_KEYPAIR: FUNCTION # Setup a cosign key pair - RUN echo -n "\n\n" | cosign generate-key-pair + ENV COSIGN_PASSWORD="" + ENV COSIGN_YES="true" + RUN cosign generate-key-pair ENV COSIGN_PRIVATE_KEY=$(cat cosign.key) RUN rm cosign.key diff --git a/justfile b/justfile index e1002b3..b92ba67 100644 --- a/justfile +++ b/justfile @@ -35,11 +35,11 @@ test-all-features: # Run clippy lint: - cargo clippy -- -D warnings + cargo clippy # Run clippy for all features lint-all-features: - cargo clippy --all-features -- -D warnings + cargo clippy --all-features # Watch the files and run cargo check on changes watch: @@ -63,14 +63,17 @@ watch-test-all-features: # Run lint anytime a file is changed watch-lint: - cargo watch -c -x 'clippy -- -D warnings' + cargo watch -c -x 'clippy' # Run all feature lint anytime a file is changed watch-lint-all-features: - cargo watch -c -x 'clippy --all-features -- -D warnings' + cargo watch -c -x 'clippy --all-features' # Installs cargo tools that help with development tools: + rustup toolchain install stable + rustup override set stable + rustup component add --toolchain stable rust-analyzer clippy rustfmt cargo install cargo-watch # Run cargo release and push the tag separately diff --git a/process/Cargo.toml b/process/Cargo.toml new file mode 100644 index 0000000..b671106 --- /dev/null +++ b/process/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "blue-build-process-management" +description.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true +categories.workspace = true +version.workspace = true + +[lib] +path = "process.rs" + +[dependencies] +anyhow = "1" +blue-build-recipe = { version = "=0.8.12", path = "../recipe" } +blue-build-utils = { version = "=0.8.12", path = "../utils" } +expect-exit = "0.5" +indicatif-log-bridge = "0.2" +lenient_semver = "0.4" +log4rs = { version = "1", features = ["background_rotation"] } +nu-ansi-term = { version = "0.50", features = ["gnu_legacy"] } +nix = { version = "0.29", features = ["signal"] } +once_cell = "1" +os_pipe = { version = "1", features = ["io_safety"] } +rand = "0.8" +semver = { version = "1", features = ["serde"] } +signal-hook = { version = "0.3", features = ["extended-siginfo"] } +sigstore = { version = "0.9", features = ["full-rustls-tls", "cached-client", "sigstore-trust-root", "sign"], default-features = false } +tokio = { version = "1.39.2", features = ["rt", "rt-multi-thread"], optional = true } +zeroize = { version = "1", features = ["aarch64", "derive", "serde"] } + +chrono.workspace = true +clap = { workspace = true, features = ["derive", "env"] } +colored.workspace = true +indicatif.workspace = true +indexmap.workspace = true +log.workspace = true +miette.workspace = true +serde.workspace = true +serde_json.workspace = true +tempdir.workspace = true +typed-builder.workspace = true +users.workspace = true +uuid.workspace = true + +[lints] +workspace = true + +[features] +sigstore = ["dep:tokio"] diff --git a/process/drivers.rs b/process/drivers.rs new file mode 100644 index 0000000..ab3064f --- /dev/null +++ b/process/drivers.rs @@ -0,0 +1,408 @@ +//! This module is responsible for managing various strategies +//! to perform actions throughout the program. This hides all +//! the implementation details from the command logic and allows +//! for caching certain long execution tasks like inspecting the +//! labels for an image. + +use std::{ + collections::{hash_map::Entry, HashMap}, + fmt::Debug, + process::{ExitStatus, Output}, + sync::{Mutex, RwLock}, +}; + +use blue_build_recipe::Recipe; +use blue_build_utils::constants::IMAGE_VERSION_LABEL; +use clap::Args; +use log::{debug, info, trace}; +use miette::{miette, Result}; +use once_cell::sync::Lazy; +#[cfg(feature = "sigstore")] +use sigstore_driver::SigstoreDriver; +use typed_builder::TypedBuilder; +use uuid::Uuid; + +use self::{ + buildah_driver::BuildahDriver, + cosign_driver::CosignDriver, + docker_driver::DockerDriver, + github_driver::GithubDriver, + gitlab_driver::GitlabDriver, + image_metadata::ImageMetadata, + local_driver::LocalDriver, + opts::{ + BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateKeyPairOpts, GetMetadataOpts, + PushOpts, RunOpts, SignOpts, TagOpts, VerifyOpts, + }, + podman_driver::PodmanDriver, + skopeo_driver::SkopeoDriver, + types::{ + BuildDriverType, CiDriverType, DetermineDriver, InspectDriverType, RunDriverType, + SigningDriverType, + }, +}; + +pub use traits::*; + +mod buildah_driver; +mod cosign_driver; +mod docker_driver; +mod functions; +mod github_driver; +mod gitlab_driver; +pub mod image_metadata; +mod local_driver; +pub mod opts; +mod podman_driver; +#[cfg(feature = "sigstore")] +mod sigstore_driver; +mod skopeo_driver; +mod traits; +pub mod types; + +static INIT: Lazy> = Lazy::new(|| Mutex::new(false)); +static SELECTED_BUILD_DRIVER: Lazy>> = + Lazy::new(|| RwLock::new(None)); +static SELECTED_INSPECT_DRIVER: Lazy>> = + Lazy::new(|| RwLock::new(None)); +static SELECTED_RUN_DRIVER: Lazy>> = Lazy::new(|| RwLock::new(None)); +static SELECTED_SIGNING_DRIVER: Lazy>> = + Lazy::new(|| RwLock::new(None)); +static SELECTED_CI_DRIVER: Lazy>> = Lazy::new(|| RwLock::new(None)); + +/// UUID used to mark the current builds +static BUILD_ID: Lazy = Lazy::new(Uuid::new_v4); + +/// The cached os versions +static OS_VERSION: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); + +/// Args for selecting the various drivers to use for runtime. +/// +/// If the args are left uninitialized, the program will determine +/// the best one available. +#[derive(Default, Clone, Copy, Debug, TypedBuilder, Args)] +pub struct DriverArgs { + /// Select which driver to use to build + /// your image. + #[builder(default)] + #[arg(short = 'B', long)] + build_driver: Option, + + /// Select which driver to use to inspect + /// images. + #[builder(default)] + #[arg(short = 'I', long)] + inspect_driver: Option, + + /// Select which driver to use to sign + /// images. + #[builder(default)] + #[arg(short = 'S', long)] + signing_driver: Option, + + /// Select which driver to use to run + /// containers. + #[builder(default)] + #[arg(short = 'R', long)] + run_driver: Option, +} + +macro_rules! impl_driver_type { + ($cache:ident) => {{ + let lock = $cache.read().expect("Should read"); + lock.expect("Driver should have initialized build driver") + }}; +} + +macro_rules! impl_driver_init { + (@) => { }; + ($init:ident; $($tail:tt)*) => { + { + let mut initialized = $init.lock().expect("Must lock INIT"); + + if !*initialized { + impl_driver_init!(@ $($tail)*); + + *initialized = true; + } + } + }; + (@ default => $cache:ident; $($tail:tt)*) => { + { + let mut driver = $cache.write().expect("Should lock"); + + impl_driver_init!(@ $($tail)*); + + *driver = Some(driver.determine_driver()); + ::log::trace!("Driver set {driver:?}"); + drop(driver); + } + }; + (@ $driver:expr => $cache:ident; $($tail:tt)*) => { + { + let mut driver = $cache.write().expect("Should lock"); + + impl_driver_init!(@ $($tail)*); + + *driver = Some($driver.determine_driver()); + ::log::trace!("Driver set {driver:?}"); + drop(driver); + } + }; +} + +pub struct Driver; + +impl Driver { + /// Initializes the Strategy with user provided credentials. + /// + /// If you want to take advantage of a user's credentials, + /// you will want to run init before trying to use any of + /// the strategies. + /// + /// # Panics + /// Will panic if it is unable to initialize drivers. + pub fn init(mut args: DriverArgs) { + trace!("Driver::init()"); + + impl_driver_init! { + INIT; + args.build_driver => SELECTED_BUILD_DRIVER; + args.inspect_driver => SELECTED_INSPECT_DRIVER; + args.run_driver => SELECTED_RUN_DRIVER; + args.signing_driver => SELECTED_SIGNING_DRIVER; + default => SELECTED_CI_DRIVER; + } + } + + /// Gets the current build's UUID + #[must_use] + pub fn get_build_id() -> Uuid { + trace!("Driver::get_build_id()"); + *BUILD_ID + } + + /// Retrieve the `os_version` for an image. + /// + /// This gets cached for faster resolution if it's required + /// in another part of the program. + /// + /// # Errors + /// Will error if the image doesn't have OS version info + /// or we are unable to lock a mutex. + /// + /// # Panics + /// Panics if the mutex fails to lock. + pub fn get_os_version(recipe: &Recipe) -> Result { + #[cfg(test)] + { + use miette::IntoDiagnostic; + + if std::env::var(crate::test::BB_UNIT_TEST_MOCK_GET_OS_VERSION).is_ok() { + return crate::test::create_test_recipe() + .image_version + .parse() + .into_diagnostic(); + } + } + + trace!("Driver::get_os_version({recipe:#?})"); + let image = format!("{}:{}", &recipe.base_image, &recipe.image_version); + + let mut os_version_lock = OS_VERSION.lock().expect("Should lock"); + + let entry = os_version_lock.get(&image); + + let os_version = match entry { + None => { + info!("Retrieving OS version from {image}. This might take a bit"); + let inspect_opts = GetMetadataOpts::builder() + .image(&*recipe.base_image) + .tag(&*recipe.image_version) + .build(); + let inspection = Self::get_metadata(&inspect_opts)?; + + let os_version = inspection.get_version().ok_or_else(|| { + miette!( + help = format!("Please check with the image author about using '{IMAGE_VERSION_LABEL}' to report the os version."), + "Unable to get the OS version from the labels" + ) + })?; + trace!("os_version: {os_version}"); + + os_version + } + Some(os_version) => { + debug!("Found cached {os_version} for {image}"); + *os_version + } + }; + + if let Entry::Vacant(entry) = os_version_lock.entry(image.clone()) { + trace!("Caching version {os_version} for {image}"); + entry.insert(os_version); + } + drop(os_version_lock); + Ok(os_version) + } + + fn get_build_driver() -> BuildDriverType { + impl_driver_type!(SELECTED_BUILD_DRIVER) + } + + fn get_inspect_driver() -> InspectDriverType { + impl_driver_type!(SELECTED_INSPECT_DRIVER) + } + + fn get_signing_driver() -> SigningDriverType { + impl_driver_type!(SELECTED_SIGNING_DRIVER) + } + + fn get_run_driver() -> RunDriverType { + impl_driver_type!(SELECTED_RUN_DRIVER) + } + + fn get_ci_driver() -> CiDriverType { + impl_driver_type!(SELECTED_CI_DRIVER) + } +} + +macro_rules! impl_build_driver { + ($func:ident($($args:expr),*)) => { + match Self::get_build_driver() { + BuildDriverType::Buildah => BuildahDriver::$func($($args,)*), + BuildDriverType::Podman => PodmanDriver::$func($($args,)*), + BuildDriverType::Docker => DockerDriver::$func($($args,)*), + } + }; +} + +impl BuildDriver for Driver { + fn build(opts: &BuildOpts) -> Result<()> { + impl_build_driver!(build(opts)) + } + + fn tag(opts: &TagOpts) -> Result<()> { + impl_build_driver!(tag(opts)) + } + + fn push(opts: &PushOpts) -> Result<()> { + impl_build_driver!(push(opts)) + } + + fn login() -> Result<()> { + impl_build_driver!(login()) + } + + fn build_tag_push(opts: &BuildTagPushOpts) -> Result<()> { + impl_build_driver!(build_tag_push(opts)) + } +} + +macro_rules! impl_signing_driver { + ($func:ident($($args:expr),*)) => { + match Self::get_signing_driver() { + SigningDriverType::Cosign => CosignDriver::$func($($args,)*), + + #[cfg(feature = "sigstore")] + SigningDriverType::Sigstore => SigstoreDriver::$func($($args,)*), + } + }; +} + +impl SigningDriver for Driver { + fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> { + impl_signing_driver!(generate_key_pair(opts)) + } + + fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()> { + impl_signing_driver!(check_signing_files(opts)) + } + + fn sign(opts: &SignOpts) -> Result<()> { + impl_signing_driver!(sign(opts)) + } + + fn verify(opts: &VerifyOpts) -> Result<()> { + impl_signing_driver!(verify(opts)) + } + + fn signing_login() -> Result<()> { + impl_signing_driver!(signing_login()) + } +} + +macro_rules! impl_inspect_driver { + ($func:ident($($args:expr),*)) => { + match Self::get_inspect_driver() { + InspectDriverType::Skopeo => SkopeoDriver::$func($($args,)*), + InspectDriverType::Podman => PodmanDriver::$func($($args,)*), + InspectDriverType::Docker => DockerDriver::$func($($args,)*), + } + }; +} + +impl InspectDriver for Driver { + fn get_metadata(opts: &GetMetadataOpts) -> Result { + impl_inspect_driver!(get_metadata(opts)) + } +} + +macro_rules! impl_run_driver { + ($func:ident($($args:expr),*)) => { + match Self::get_run_driver() { + RunDriverType::Docker => DockerDriver::$func($($args,)*), + RunDriverType::Podman => PodmanDriver::$func($($args,)*), + } + }; +} + +impl RunDriver for Driver { + fn run(opts: &RunOpts) -> std::io::Result { + impl_run_driver!(run(opts)) + } + + fn run_output(opts: &RunOpts) -> std::io::Result { + impl_run_driver!(run_output(opts)) + } +} + +macro_rules! impl_ci_driver { + ($func:ident($($args:expr),*)) => { + match Self::get_ci_driver() { + CiDriverType::Local => LocalDriver::$func($($args)*), + CiDriverType::Gitlab => GitlabDriver::$func($($args)*), + CiDriverType::Github => GithubDriver::$func($($args)*), + } + }; +} + +impl CiDriver for Driver { + fn on_default_branch() -> bool { + impl_ci_driver!(on_default_branch()) + } + + fn keyless_cert_identity() -> Result { + impl_ci_driver!(keyless_cert_identity()) + } + + fn oidc_provider() -> Result { + impl_ci_driver!(oidc_provider()) + } + + fn generate_tags(recipe: &Recipe) -> Result> { + impl_ci_driver!(generate_tags(recipe)) + } + + fn get_repo_url() -> Result { + impl_ci_driver!(get_repo_url()) + } + + fn get_registry() -> Result { + impl_ci_driver!(get_registry()) + } + + fn generate_image_name(recipe: &Recipe) -> Result { + impl_ci_driver!(generate_image_name(recipe)) + } +} diff --git a/src/drivers/buildah_driver.rs b/process/drivers/buildah_driver.rs similarity index 55% rename from src/drivers/buildah_driver.rs rename to process/drivers/buildah_driver.rs index a500427..773ebda 100644 --- a/src/drivers/buildah_driver.rs +++ b/process/drivers/buildah_driver.rs @@ -1,12 +1,12 @@ -use std::process::Command; +use std::{io::Write, process::Stdio}; -use blue_build_utils::logging::CommandLogging; -use log::{error, info, trace}; -use miette::{bail, IntoDiagnostic, Result}; +use blue_build_utils::{cmd, credentials::Credentials}; +use log::{debug, error, info, trace}; +use miette::{bail, miette, IntoDiagnostic, Result}; use semver::Version; use serde::Deserialize; -use crate::credentials::{self, Credentials}; +use crate::logging::CommandLogging; use super::{ opts::{BuildOpts, PushOpts, TagOpts}, @@ -30,9 +30,7 @@ impl DriverVersion for BuildahDriver { trace!("BuildahDriver::version()"); trace!("buildah version --json"); - let output = Command::new("buildah") - .arg("version") - .arg("--json") + let output = cmd!("buildah", "version", "--json") .output() .into_diagnostic()?; @@ -46,24 +44,21 @@ impl DriverVersion for BuildahDriver { } impl BuildDriver for BuildahDriver { - fn build(&self, opts: &BuildOpts) -> Result<()> { + fn build(opts: &BuildOpts) -> Result<()> { trace!("BuildahDriver::build({opts:#?})"); - trace!( - "buildah build --pull=true --layers={} -f {} -t {}", - !opts.squash, - opts.containerfile.display(), - opts.image, + let command = cmd!( + "buildah", + "build", + "--pull=true", + format!("--layers={}", !opts.squash), + "-f", + &*opts.containerfile, + "-t", + &*opts.image, ); - let mut command = Command::new("buildah"); - command - .arg("build") - .arg("--pull=true") - .arg(format!("--layers={}", !opts.squash)) - .arg("-f") - .arg(opts.containerfile.as_ref()) - .arg("-t") - .arg(opts.image.as_ref()); + + trace!("{command:?}"); let status = command .status_image_ref_progress(&opts.image, "Building Image") .into_diagnostic()?; @@ -76,18 +71,13 @@ impl BuildDriver for BuildahDriver { Ok(()) } - fn tag(&self, opts: &TagOpts) -> Result<()> { + fn tag(opts: &TagOpts) -> Result<()> { trace!("BuildahDriver::tag({opts:#?})"); - trace!("buildah tag {} {}", opts.src_image, opts.dest_image); - let status = Command::new("buildah") - .arg("tag") - .arg(opts.src_image.as_ref()) - .arg(opts.dest_image.as_ref()) - .status() - .into_diagnostic()?; + let mut command = cmd!("buildah", "tag", &*opts.src_image, &*opts.dest_image,); - if status.success() { + trace!("{command:?}"); + if command.status().into_diagnostic()?.success() { info!("Successfully tagged {}!", opts.dest_image); } else { bail!("Failed to tag image {}", opts.dest_image); @@ -95,18 +85,20 @@ impl BuildDriver for BuildahDriver { Ok(()) } - fn push(&self, opts: &PushOpts) -> Result<()> { + fn push(opts: &PushOpts) -> Result<()> { trace!("BuildahDriver::push({opts:#?})"); - trace!("buildah push {}", opts.image); - let mut command = Command::new("buildah"); - command - .arg("push") - .arg(format!( + let command = cmd!( + "buildah", + "push", + format!( "--compression-format={}", opts.compression_type.unwrap_or_default() - )) - .arg(opts.image.as_ref()); + ), + &*opts.image, + ); + + trace!("{command:?}"); let status = command .status_image_ref_progress(&opts.image, "Pushing Image") .into_diagnostic()?; @@ -119,30 +111,47 @@ impl BuildDriver for BuildahDriver { Ok(()) } - fn login(&self) -> Result<()> { + fn login() -> Result<()> { trace!("BuildahDriver::login()"); if let Some(Credentials { registry, username, password, - }) = credentials::get() + }) = Credentials::get() { - trace!("buildah login -u {username} -p [MASKED] {registry}"); - let output = Command::new("buildah") - .arg("login") - .arg("-u") - .arg(username) - .arg("-p") - .arg(password) - .arg(registry) - .output() - .into_diagnostic()?; + let mut command = cmd!( + "buildah", + "login", + "-u", + username, + "--password-stdin", + registry + ); + command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + trace!("{command:?}"); + let mut child = command.spawn().into_diagnostic()?; + + write!( + child + .stdin + .as_mut() + .ok_or_else(|| miette!("Unable to open pipe to stdin"))?, + "{password}" + ) + .into_diagnostic()?; + + let output = child.wait_with_output().into_diagnostic()?; if !output.status.success() { let err_out = String::from_utf8_lossy(&output.stderr); - bail!("Failed to login for buildah: {err_out}"); + bail!("Failed to login for buildah:\n{}", err_out.trim()); } + debug!("Logged into {registry}"); } Ok(()) } diff --git a/process/drivers/cosign_driver.rs b/process/drivers/cosign_driver.rs new file mode 100644 index 0000000..70f22ef --- /dev/null +++ b/process/drivers/cosign_driver.rs @@ -0,0 +1,241 @@ +use std::{fmt::Debug, fs, io::Write, path::Path, process::Stdio}; + +use blue_build_utils::{ + cmd, + constants::{COSIGN_PASSWORD, COSIGN_PUB_PATH, COSIGN_YES}, + credentials::Credentials, +}; +use log::{debug, trace}; +use miette::{bail, miette, Context, IntoDiagnostic, Result}; + +use crate::drivers::opts::VerifyType; + +use super::{ + functions::get_private_key, + opts::{CheckKeyPairOpts, GenerateKeyPairOpts, SignOpts, VerifyOpts}, + SigningDriver, +}; + +#[derive(Debug)] +pub struct CosignDriver; + +impl SigningDriver for CosignDriver { + fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> { + let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir); + + let mut command = cmd!( + "cosign", + "generate-key-pair", + COSIGN_PASSWORD => "", + COSIGN_YES => "true", + ); + command.current_dir(path); + + let status = command.status().into_diagnostic()?; + + if !status.success() { + bail!("Failed to generate cosign key-pair!"); + } + + Ok(()) + } + + 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)?; + + let mut command = cmd!( + "cosign", + "public-key", + format!("--key={priv_key}"), + COSIGN_PASSWORD => "", + COSIGN_YES => "true", + ); + + trace!("{command:?}"); + let output = command.output().into_diagnostic()?; + + if !output.status.success() { + bail!( + "Failed to run cosign public-key: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + let calculated_pub_key = String::from_utf8(output.stdout).into_diagnostic()?; + let found_pub_key = fs::read_to_string(path.join(COSIGN_PUB_PATH)) + .into_diagnostic() + .with_context(|| format!("Failed to read {COSIGN_PUB_PATH}"))?; + trace!("calculated_pub_key={calculated_pub_key},found_pub_key={found_pub_key}"); + + if calculated_pub_key.trim() == found_pub_key.trim() { + debug!("Cosign files match, continuing build"); + Ok(()) + } else { + bail!("Public key '{COSIGN_PUB_PATH}' does not match private key") + } + } + + fn signing_login() -> Result<()> { + trace!("CosignDriver::signing_login()"); + + if let Some(Credentials { + registry, + username, + password, + }) = Credentials::get() + { + let mut command = cmd!( + "cosign", + "login", + "-u", + username, + "--password-stdin", + registry, + stdin = Stdio::piped(), + stdout = Stdio::piped(), + stderr = Stdio::piped(), + ); + + trace!("{command:?}"); + let mut child = command.spawn().into_diagnostic()?; + + write!( + child + .stdin + .as_mut() + .ok_or_else(|| miette!("Unable to open pipe to stdin"))?, + "{password}" + ) + .into_diagnostic()?; + + let output = child.wait_with_output().into_diagnostic()?; + + if !output.status.success() { + let err_out = String::from_utf8_lossy(&output.stderr); + bail!("Failed to login for cosign:\n{}", err_out.trim()); + } + debug!("Logged into {registry}"); + } + Ok(()) + } + + fn sign(opts: &SignOpts) -> Result<()> { + let image_digest: &str = opts.image.as_ref(); + let mut command = cmd!( + "cosign", + "sign", + if let Some(ref key) = opts.key => format!("--key={key}"), + "--recursive", + image_digest, + COSIGN_PASSWORD => "", + COSIGN_YES => "true", + ); + + trace!("{command:?}"); + if !command.status().into_diagnostic()?.success() { + bail!("Failed to sign {image_digest}"); + } + + Ok(()) + } + + fn verify(opts: &VerifyOpts) -> Result<()> { + let image_name_tag: &str = opts.image.as_ref(); + let mut command = cmd!( + "cosign", + "verify", + |c| { + match &opts.verify_type { + VerifyType::File(path) => cmd!(c, format!("--key={}", path.display())), + VerifyType::Keyless { issuer, identity } => cmd!( + c, + "--certificate-identity-regexp", + identity as &str, + "--certificate-oidc-issuer", + issuer as &str, + ), + }; + }, + image_name_tag + ); + + trace!("{command:?}"); + if !command.status().into_diagnostic()?.success() { + bail!("Failed to verify {image_name_tag}"); + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::{fs, path::Path}; + + use blue_build_utils::constants::{COSIGN_PRIV_PATH, COSIGN_PUB_PATH}; + use tempdir::TempDir; + + use crate::drivers::{ + opts::{CheckKeyPairOpts, GenerateKeyPairOpts}, + SigningDriver, + }; + + use super::CosignDriver; + + #[test] + fn generate_key_pair() { + let tempdir = TempDir::new("keypair").unwrap(); + + let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build(); + + CosignDriver::generate_key_pair(&gen_opts).unwrap(); + + eprintln!( + "Private key:\n{}", + fs::read_to_string(tempdir.path().join(COSIGN_PRIV_PATH)).unwrap() + ); + eprintln!( + "Public key:\n{}", + 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(); + } + + #[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(); + } + + #[test] + #[cfg(feature = "sigstore")] + fn compatibility() { + use crate::drivers::sigstore_driver::SigstoreDriver; + + let tempdir = TempDir::new("keypair").unwrap(); + + let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build(); + + CosignDriver::generate_key_pair(&gen_opts).unwrap(); + + eprintln!( + "Private key:\n{}", + fs::read_to_string(tempdir.path().join(COSIGN_PRIV_PATH)).unwrap() + ); + eprintln!( + "Public key:\n{}", + 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(); + } +} diff --git a/src/drivers/docker_driver.rs b/process/drivers/docker_driver.rs similarity index 53% rename from src/drivers/docker_driver.rs rename to process/drivers/docker_driver.rs index e4c7e66..9bfe987 100644 --- a/src/drivers/docker_driver.rs +++ b/process/drivers/docker_driver.rs @@ -1,18 +1,20 @@ use std::{ env, + io::Write, path::Path, - process::{Command, ExitStatus}, + process::{Command, ExitStatus, Stdio}, sync::Mutex, time::Duration, }; use blue_build_utils::{ + cmd, constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST, SKOPEO_IMAGE}, - logging::{CommandLogging, Logger}, - signal_handler::{add_cid, remove_cid, ContainerId}, + credentials::Credentials, + string_vec, }; use indicatif::{ProgressBar, ProgressStyle}; -use log::{info, trace, warn}; +use log::{debug, info, trace, warn}; use miette::{bail, IntoDiagnostic, Result}; use once_cell::sync::Lazy; use semver::Version; @@ -20,11 +22,12 @@ use serde::Deserialize; use tempdir::TempDir; use crate::{ - credentials::Credentials, drivers::types::RunDriverType, image_metadata::ImageMetadata, + drivers::image_metadata::ImageMetadata, + logging::{CommandLogging, Logger}, + signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime}, }; use super::{ - credentials, opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts}, BuildDriver, DriverVersion, InspectDriver, RunDriver, }; @@ -58,10 +61,7 @@ impl DockerDriver { } trace!("docker buildx ls --format={}", "{{.Name}}"); - let ls_out = Command::new("docker") - .arg("buildx") - .arg("ls") - .arg("--format={{.Name}}") + let ls_out = cmd!("docker", "buildx", "ls", "--format={{.Name}}") .output() .into_diagnostic()?; @@ -75,14 +75,16 @@ impl DockerDriver { if !ls_out.lines().any(|line| line == "bluebuild") { trace!("docker buildx create --bootstrap --driver=docker-container --name=bluebuild"); - let create_out = Command::new("docker") - .arg("buildx") - .arg("create") - .arg("--bootstrap") - .arg("--driver=docker-container") - .arg("--name=bluebuild") - .output() - .into_diagnostic()?; + let create_out = cmd!( + "docker", + "buildx", + "create", + "--bootstrap", + "--driver=docker-container", + "--name=bluebuild", + ) + .output() + .into_diagnostic()?; if !create_out.status.success() { bail!("{}", String::from_utf8_lossy(&create_out.stderr)); @@ -101,10 +103,7 @@ impl DriverVersion for DockerDriver { const VERSION_REQ: &'static str = ">=23"; fn version() -> Result { - let output = Command::new("docker") - .arg("version") - .arg("-f") - .arg("json") + let output = cmd!("docker", "version", "-f", "json") .output() .into_diagnostic()?; @@ -116,7 +115,7 @@ impl DriverVersion for DockerDriver { } impl BuildDriver for DockerDriver { - fn build(&self, opts: &BuildOpts) -> Result<()> { + fn build(opts: &BuildOpts) -> Result<()> { trace!("DockerDriver::build({opts:#?})"); if opts.squash { @@ -124,15 +123,17 @@ impl BuildDriver for DockerDriver { } trace!("docker build -t {} -f {CONTAINER_FILE} .", opts.image); - let status = Command::new("docker") - .arg("build") - .arg("-t") - .arg(opts.image.as_ref()) - .arg("-f") - .arg(opts.containerfile.as_ref()) - .arg(".") - .status() - .into_diagnostic()?; + let status = cmd!( + "docker", + "build", + "-t", + &*opts.image, + "-f", + &*opts.containerfile, + ".", + ) + .status() + .into_diagnostic()?; if status.success() { info!("Successfully built {}", opts.image); @@ -142,14 +143,11 @@ impl BuildDriver for DockerDriver { Ok(()) } - fn tag(&self, opts: &TagOpts) -> Result<()> { + fn tag(opts: &TagOpts) -> Result<()> { trace!("DockerDriver::tag({opts:#?})"); trace!("docker tag {} {}", opts.src_image, opts.dest_image); - let status = Command::new("docker") - .arg("tag") - .arg(opts.src_image.as_ref()) - .arg(opts.dest_image.as_ref()) + let status = cmd!("docker", "tag", &*opts.src_image, &*opts.dest_image,) .status() .into_diagnostic()?; @@ -161,13 +159,11 @@ impl BuildDriver for DockerDriver { Ok(()) } - fn push(&self, opts: &PushOpts) -> Result<()> { + fn push(opts: &PushOpts) -> Result<()> { trace!("DockerDriver::push({opts:#?})"); trace!("docker push {}", opts.image); - let status = Command::new("docker") - .arg("push") - .arg(opts.image.as_ref()) + let status = cmd!("docker", "push", &*opts.image) .status() .into_diagnostic()?; @@ -179,119 +175,117 @@ impl BuildDriver for DockerDriver { Ok(()) } - fn login(&self) -> Result<()> { + fn login() -> Result<()> { trace!("DockerDriver::login()"); if let Some(Credentials { registry, username, password, - }) = credentials::get() + }) = Credentials::get() { - trace!("docker login -u {username} -p [MASKED] {registry}"); - let output = Command::new("docker") - .arg("login") - .arg("-u") - .arg(username) - .arg("-p") - .arg(password) - .arg(registry) - .output() - .into_diagnostic()?; + let mut command = cmd!( + "docker", + "login", + "-u", + username, + "--password-stdin", + registry, + stdin = Stdio::piped(), + stdout = Stdio::piped(), + stderr = Stdio::piped(), + ); + + trace!("{command:?}"); + let mut child = command.spawn().into_diagnostic()?; + + write!(child.stdin.as_mut().unwrap(), "{password}").into_diagnostic()?; + + let output = child.wait_with_output().into_diagnostic()?; if !output.status.success() { let err_out = String::from_utf8_lossy(&output.stderr); - bail!("Failed to login for docker: {err_out}"); + bail!("Failed to login for docker:\n{}", err_out.trim()); } + debug!("Logged into {registry}"); } + Ok(()) } - fn build_tag_push(&self, opts: &BuildTagPushOpts) -> Result<()> { + fn build_tag_push(opts: &BuildTagPushOpts) -> Result<()> { trace!("DockerDriver::build_tag_push({opts:#?})"); if opts.squash { warn!("Squash is deprecated for docker so this build will not squash"); } - trace!("docker buildx"); - let mut command = Command::new("docker"); - command.arg("buildx"); - - if !env::var(DOCKER_HOST).is_ok_and(|dh| !dh.is_empty()) { - Self::setup()?; - - trace!("--builder=bluebuild"); - command.arg("--builder=bluebuild"); - } - - trace!( - "build --progress=plain --pull -f {}", - opts.containerfile.display() + let mut command = cmd!( + "docker", + "buildx", + |command|? { + if !env::var(DOCKER_HOST).is_ok_and(|dh| !dh.is_empty()) { + Self::setup()?; + cmd!(command, "--builder=bluebuild"); + } + }, + "build", + "--pull", + "-f", + &*opts.containerfile, + // https://github.com/moby/buildkit?tab=readme-ov-file#github-actions-cache-experimental + if env::var(BB_BUILDKIT_CACHE_GHA) + .map_or_else(|_| false, |e| e == "true") => [ + "--cache-from", + "type=gha", + "--cache-to", + "type=gha", + ], ); - command - .arg("build") - .arg("--pull") - .arg("-f") - .arg(opts.containerfile.as_ref()); - - // https://github.com/moby/buildkit?tab=readme-ov-file#github-actions-cache-experimental - if env::var(BB_BUILDKIT_CACHE_GHA).map_or_else(|_| false, |e| e == "true") { - trace!("--cache-from type=gha --cache-to type=gha"); - command - .arg("--cache-from") - .arg("type=gha") - .arg("--cache-to") - .arg("type=gha"); - } let mut final_image = String::new(); - match (opts.image.as_ref(), opts.archive_path.as_ref()) { + match (opts.image.as_deref(), opts.archive_path.as_deref()) { (Some(image), None) => { if opts.tags.is_empty() { final_image.push_str(image); - - trace!("-t {image}"); - command.arg("-t").arg(image.as_ref()); + cmd!(command, "-t", image); } else { - final_image - .push_str(format!("{image}:{}", opts.tags.first().unwrap_or(&"")).as_str()); + final_image.push_str( + format!("{image}:{}", opts.tags.first().map_or("", String::as_str)) + .as_str(), + ); opts.tags.iter().for_each(|tag| { - let full_image = format!("{image}:{tag}"); - - trace!("-t {full_image}"); - command.arg("-t").arg(full_image); + cmd!(command, "-t", format!("{image}:{tag}")); }); } if opts.push { - trace!("--output type=image,name={image},push=true,compression={},oci-mediatypes=true", opts.compression); - command.arg("--output").arg(format!( - "type=image,name={image},push=true,compression={},oci-mediatypes=true", - opts.compression - )); + cmd!( + command, + "--output", + format!( + "type=image,name={image},push=true,compression={},oci-mediatypes=true", + opts.compression + ) + ); } else { - trace!("--load"); - command.arg("--load"); + cmd!(command, "--load"); } } (None, Some(archive_path)) => { final_image.push_str(archive_path); - trace!("--output type=oci,dest={archive_path}"); - command - .arg("--output") - .arg(format!("type=oci,dest={archive_path}")); + cmd!(command, "--output", format!("type=oci,dest={archive_path}")); } (Some(_), Some(_)) => bail!("Cannot use both image and archive path"), (None, None) => bail!("Need either the image or archive path set"), } - trace!("."); - command.arg("."); + cmd!(command, "."); + trace!("{command:?}"); if command .status_image_ref_progress(&final_image, "Building Image") .into_diagnostic()? @@ -310,7 +304,7 @@ impl BuildDriver for DockerDriver { } impl InspectDriver for DockerDriver { - fn get_metadata(&self, opts: &GetMetadataOpts) -> Result { + fn get_metadata(opts: &GetMetadataOpts) -> Result { trace!("DockerDriver::get_labels({opts:#?})"); let url = opts.tag.as_ref().map_or_else( @@ -325,14 +319,14 @@ impl InspectDriver for DockerDriver { ); progress.enable_steady_tick(Duration::from_millis(100)); - let output = self - .run_output( - &RunOpts::builder() - .image(SKOPEO_IMAGE) - .args(&["inspect".to_string(), url.clone()]) - .build(), - ) - .into_diagnostic()?; + let output = Self::run_output( + &RunOpts::builder() + .image(SKOPEO_IMAGE) + .args(string_vec!["inspect", url.clone()]) + .remove(true) + .build(), + ) + .into_diagnostic()?; progress.finish(); Logger::multi_progress().remove(&progress); @@ -348,29 +342,25 @@ impl InspectDriver for DockerDriver { } impl RunDriver for DockerDriver { - fn run(&self, opts: &RunOpts) -> std::io::Result { - trace!("DockerDriver::run({opts:#?})"); - + fn run(opts: &RunOpts) -> std::io::Result { let cid_path = TempDir::new("docker")?; let cid_file = cid_path.path().join("cid"); - let cid = ContainerId::new(&cid_file, RunDriverType::Docker, false); + let cid = ContainerId::new(&cid_file, ContainerRuntime::Docker, false); add_cid(&cid); let status = docker_run(opts, &cid_file) - .status_image_ref_progress(opts.image.as_ref(), "Running container")?; + .status_image_ref_progress(&*opts.image, "Running container")?; remove_cid(&cid); Ok(status) } - fn run_output(&self, opts: &RunOpts) -> std::io::Result { - trace!("DockerDriver::run({opts:#?})"); - + fn run_output(opts: &RunOpts) -> std::io::Result { let cid_path = TempDir::new("docker")?; let cid_file = cid_path.path().join("cid"); - let cid = ContainerId::new(&cid_file, RunDriverType::Docker, false); + let cid = ContainerId::new(&cid_file, ContainerRuntime::Docker, false); add_cid(&cid); @@ -383,41 +373,29 @@ impl RunDriver for DockerDriver { } fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command { - let mut command = Command::new("docker"); - - command - .arg("run") - .arg(format!("--cidfile={}", cid_file.display())); - - if opts.privileged { - command.arg("--privileged"); - } - - if opts.remove { - command.arg("--rm"); - } - - if opts.pull { - command.arg("--pull=always"); - } - - opts.volumes.iter().for_each(|volume| { - command.arg("--volume"); - command.arg(format!( - "{}:{}", - volume.path_or_vol_name, volume.container_path, - )); - }); - - opts.env_vars.iter().for_each(|env| { - command.arg("--env"); - command.arg(format!("{}={}", env.key, env.value)); - }); - - command.arg(opts.image.as_ref()); - - command.args(opts.args.iter()); - - trace!("{command:?}"); - command + cmd!( + "docker", + "run", + format!("--cidfile={}", cid_file.display()), + if opts.privileged => "--privileged", + if opts.remove => "--rm", + if opts.pull => "--pull=always", + for volume in opts.volumes => [ + "--volume", + format!("{}:{}", volume.path_or_vol_name, volume.container_path), + ], + for env in opts.env_vars => [ + "--env", + format!("{}={}", env.key, env.value), + ], + |command| { + match (opts.uid, opts.gid) { + (Some(uid), None) => cmd!(command, "-u", format!("{uid}")), + (Some(uid), Some(gid)) => cmd!(command, "-u", format!("{}:{}", uid, gid)), + _ => {} + } + }, + &*opts.image, + for opts.args, + ) } diff --git a/process/drivers/functions.rs b/process/drivers/functions.rs new file mode 100644 index 0000000..e569ba2 --- /dev/null +++ b/process/drivers/functions.rs @@ -0,0 +1,52 @@ +use std::{env, path::Path}; + +use blue_build_utils::{ + constants::{BB_PRIVATE_KEY, COSIGN_PRIVATE_KEY, COSIGN_PRIV_PATH, COSIGN_PUB_PATH}, + string, +}; +use miette::{bail, Result}; + +use super::opts::PrivateKey; + +pub(super) fn get_private_key

(path: P) -> Result +where + P: AsRef, +{ + let path = path.as_ref(); + + Ok( + match ( + path.join(COSIGN_PUB_PATH).exists(), + env::var(BB_PRIVATE_KEY).ok(), + env::var(COSIGN_PRIVATE_KEY).ok(), + path.join(COSIGN_PRIV_PATH), + ) { + (true, Some(private_key), _, _) if !private_key.is_empty() => { + PrivateKey::Env(string!(BB_PRIVATE_KEY)) + } + (true, _, Some(cosign_priv_key), _) if !cosign_priv_key.is_empty() => { + PrivateKey::Env(string!(COSIGN_PRIVATE_KEY)) + } + (true, _, _, cosign_priv_key_path) if cosign_priv_key_path.exists() => { + PrivateKey::Path(cosign_priv_key_path) + } + _ => { + bail!( + help = format!( + "{}{}{}{}{}{}", + format_args!("Make sure you have a `{COSIGN_PUB_PATH}`\n"), + format_args!( + "in the root of your repo and have either {COSIGN_PRIVATE_KEY}\n" + ), + format_args!("set in your env variables or a `{COSIGN_PRIV_PATH}`\n"), + "file in the root of your repo.\n\n", + "See https://blue-build.org/how-to/cosign/ for more information.\n\n", + "If you don't want to sign your image, use the `--no-sign` flag.", + ), + "{}", + "Unable to find private/public key pair", + ) + } + }, + ) +} diff --git a/process/drivers/github_driver.rs b/process/drivers/github_driver.rs new file mode 100644 index 0000000..16301db --- /dev/null +++ b/process/drivers/github_driver.rs @@ -0,0 +1,272 @@ +use blue_build_utils::{ + constants::{ + GITHUB_EVENT_NAME, GITHUB_REF_NAME, GITHUB_SHA, GITHUB_TOKEN_ISSUER_URL, + GITHUB_WORKFLOW_REF, PR_EVENT_NUMBER, + }, + get_env_var, +}; +use event::Event; +use log::trace; + +use super::{CiDriver, Driver}; + +mod event; + +pub struct GithubDriver; + +impl CiDriver for GithubDriver { + fn on_default_branch() -> bool { + Event::try_new().map_or_else( + |_| false, + |event| match (event.commit_ref, event.head) { + (Some(commit_ref), _) => { + commit_ref.trim_start_matches("refs/heads/") == event.repository.default_branch + } + (_, Some(head)) => event.repository.default_branch == head.commit_ref, + _ => false, + }, + ) + } + + fn keyless_cert_identity() -> miette::Result { + get_env_var(GITHUB_WORKFLOW_REF) + } + + fn oidc_provider() -> miette::Result { + Ok(GITHUB_TOKEN_ISSUER_URL.to_string()) + } + + fn generate_tags(recipe: &blue_build_recipe::Recipe) -> miette::Result> { + let mut tags: Vec = Vec::new(); + let os_version = Driver::get_os_version(recipe)?; + let github_event_name = get_env_var(GITHUB_EVENT_NAME)?; + + if github_event_name == "pull_request" { + trace!("Running in a PR"); + + let github_event_number = get_env_var(PR_EVENT_NUMBER)?; + + tags.push(format!("pr-{github_event_number}-{os_version}")); + } else if Self::on_default_branch() { + tags.push(os_version.to_string()); + + let timestamp = blue_build_utils::get_tag_timestamp(); + tags.push(format!("{timestamp}-{os_version}")); + + if let Some(ref alt_tags) = recipe.alt_tags { + tags.extend(alt_tags.iter().map(ToString::to_string)); + } else { + tags.push("latest".into()); + tags.push(timestamp); + } + } else { + let github_ref_name = get_env_var(GITHUB_REF_NAME)?; + + tags.push(format!("br-{github_ref_name}-{os_version}")); + } + + let mut short_sha = get_env_var(GITHUB_SHA)?; + short_sha.truncate(7); + + tags.push(format!("{short_sha}-{os_version}")); + + Ok(tags) + } + + fn get_repo_url() -> miette::Result { + Ok(Event::try_new()?.repository.html_url) + } + + fn get_registry() -> miette::Result { + Ok(format!( + "ghcr.io/{}", + Event::try_new()?.repository.owner.login + )) + } +} + +#[cfg(test)] +mod test { + use std::env; + + use blue_build_utils::constants::{ + GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER, + }; + + use crate::{ + drivers::CiDriver, + test::{create_test_recipe, BB_UNIT_TEST_MOCK_GET_OS_VERSION, ENV_LOCK}, + }; + + use super::GithubDriver; + + fn setup_default_branch() { + setup(); + env::set_var( + GITHUB_EVENT_PATH, + "../test-files/github-events/default-branch.json", + ); + env::set_var(GITHUB_REF_NAME, "main"); + } + + fn setup_pr_branch() { + setup(); + env::set_var( + GITHUB_EVENT_PATH, + "../test-files/github-events/pr-branch.json", + ); + env::set_var(GITHUB_EVENT_NAME, "pull_request"); + env::set_var(GITHUB_REF_NAME, "test"); + env::set_var(PR_EVENT_NUMBER, "12"); + } + + fn setup_branch() { + setup(); + env::set_var(GITHUB_EVENT_PATH, "../test-files/github-events/branch.json"); + env::set_var(GITHUB_REF_NAME, "test"); + } + + fn setup() { + env::set_var(GITHUB_EVENT_NAME, "push"); + env::set_var(GITHUB_SHA, "1234567890"); + env::set_var(BB_UNIT_TEST_MOCK_GET_OS_VERSION, ""); + } + + fn teardown() { + env::remove_var(GITHUB_EVENT_NAME); + env::remove_var(GITHUB_EVENT_PATH); + env::remove_var(GITHUB_REF_NAME); + env::remove_var(PR_EVENT_NUMBER); + env::remove_var(GITHUB_SHA); + env::remove_var(BB_UNIT_TEST_MOCK_GET_OS_VERSION); + } + + #[test] + fn get_registry() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_default_branch(); + + let registry = GithubDriver::get_registry().unwrap(); + + assert_eq!(registry, "ghcr.io/test-owner"); + teardown(); + } + + #[test] + fn on_default_branch_true() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_default_branch(); + + assert!(GithubDriver::on_default_branch()); + teardown(); + } + + #[test] + fn on_default_branch_false() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_pr_branch(); + + assert!(!GithubDriver::on_default_branch()); + teardown(); + } + + #[test] + fn get_repo_url() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_branch(); + + let url = GithubDriver::get_repo_url().unwrap(); + + assert_eq!(url, "https://example.com/"); + teardown(); + } + + #[test] + fn generate_tags_default_branch() { + let _env = ENV_LOCK.lock().unwrap(); + let timestamp = blue_build_utils::get_tag_timestamp(); + + setup_default_branch(); + + let mut tags = GithubDriver::generate_tags(&create_test_recipe()).unwrap(); + tags.sort(); + + let mut expected_tags = vec![ + format!("{timestamp}-40"), + "latest".to_string(), + timestamp, + "1234567-40".to_string(), + "40".to_string(), + ]; + expected_tags.sort(); + + assert_eq!(tags, expected_tags); + + teardown(); + } + + #[test] + fn generate_tags_default_branch_alt_tags() { + let _env = ENV_LOCK.lock().unwrap(); + let timestamp = blue_build_utils::get_tag_timestamp(); + + setup_default_branch(); + + let mut recipe = create_test_recipe(); + + recipe.alt_tags = Some(vec!["test-tag1".into(), "test-tag2".into()]); + + let mut tags = GithubDriver::generate_tags(&recipe).unwrap(); + tags.sort(); + + let mut expected_tags = vec![ + format!("{timestamp}-40"), + "1234567-40".to_string(), + "40".to_string(), + ]; + expected_tags.extend(recipe.alt_tags.unwrap().iter().map(ToString::to_string)); + expected_tags.sort(); + + assert_eq!(tags, expected_tags); + + teardown(); + } + + #[test] + fn generate_tags_pr_branch() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_pr_branch(); + + let mut tags = GithubDriver::generate_tags(&create_test_recipe()).unwrap(); + tags.sort(); + + let mut expected_tags = vec!["pr-12-40".to_string(), "1234567-40".to_string()]; + expected_tags.sort(); + + assert_eq!(tags, expected_tags); + + teardown(); + } + + #[test] + fn generate_tags_branch() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_branch(); + + let mut tags = GithubDriver::generate_tags(&create_test_recipe()).unwrap(); + tags.sort(); + + let mut expected_tags = vec!["1234567-40".to_string(), "br-test-40".to_string()]; + expected_tags.sort(); + + assert_eq!(tags, expected_tags); + + teardown(); + } +} diff --git a/process/drivers/github_driver/event.rs b/process/drivers/github_driver/event.rs new file mode 100644 index 0000000..fa07d55 --- /dev/null +++ b/process/drivers/github_driver/event.rs @@ -0,0 +1,44 @@ +use std::{fs, path::PathBuf}; + +use blue_build_utils::{constants::GITHUB_EVENT_PATH, get_env_var}; +use miette::{IntoDiagnostic, Result}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, Clone)] +pub(super) struct Event { + pub repository: EventRepository, + // pub base: Option, + pub head: Option, + + #[serde(alias = "ref")] + pub commit_ref: Option, +} + +impl Event { + pub fn try_new() -> Result { + get_env_var(GITHUB_EVENT_PATH) + .map(PathBuf::from) + .and_then(|event_path| { + serde_json::from_str::(&fs::read_to_string(event_path).into_diagnostic()?) + .into_diagnostic() + }) + } +} + +#[derive(Debug, Deserialize, Clone)] +pub(super) struct EventRepository { + pub default_branch: String, + pub owner: EventRepositoryOwner, + pub html_url: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub(super) struct EventRepositoryOwner { + pub login: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub(super) struct EventRefInfo { + #[serde(alias = "ref")] + pub commit_ref: String, +} diff --git a/process/drivers/gitlab_driver.rs b/process/drivers/gitlab_driver.rs new file mode 100644 index 0000000..8d05f6c --- /dev/null +++ b/process/drivers/gitlab_driver.rs @@ -0,0 +1,293 @@ +use std::env; + +use blue_build_utils::{ + constants::{ + CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, + CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_PROJECT_URL, CI_REGISTRY, + CI_SERVER_HOST, CI_SERVER_PROTOCOL, + }, + get_env_var, +}; +use log::{debug, trace}; + +use crate::drivers::Driver; + +use super::CiDriver; + +pub struct GitlabDriver; + +impl CiDriver for GitlabDriver { + fn on_default_branch() -> bool { + env::var(CI_DEFAULT_BRANCH).is_ok_and(|default_branch| { + env::var(CI_COMMIT_REF_NAME).is_ok_and(|branch| default_branch == branch) + }) + } + + fn keyless_cert_identity() -> miette::Result { + Ok(format!( + "{}//.gitlab-ci.yml@refs/heads/{}", + get_env_var(CI_PROJECT_URL)?, + get_env_var(CI_DEFAULT_BRANCH)?, + )) + } + + fn oidc_provider() -> miette::Result { + Ok(format!( + "{}://{}", + get_env_var(CI_SERVER_PROTOCOL)?, + get_env_var(CI_SERVER_HOST)?, + )) + } + + fn generate_tags(recipe: &blue_build_recipe::Recipe) -> miette::Result> { + let mut tags: Vec = Vec::new(); + let os_version = Driver::get_os_version(recipe)?; + + if Self::on_default_branch() { + debug!("Running on the default branch"); + + tags.push(os_version.to_string()); + + let timestamp = blue_build_utils::get_tag_timestamp(); + tags.push(format!("{timestamp}-{os_version}")); + + if let Some(ref alt_tags) = recipe.alt_tags { + tags.extend(alt_tags.iter().map(ToString::to_string)); + } else { + tags.push("latest".into()); + tags.push(timestamp); + } + } else if let Ok(mr_iid) = env::var(CI_MERGE_REQUEST_IID) { + trace!("{CI_MERGE_REQUEST_IID}={mr_iid}"); + + let pipeline_source = get_env_var(CI_PIPELINE_SOURCE)?; + trace!("{CI_PIPELINE_SOURCE}={pipeline_source}"); + + if pipeline_source == "merge_request_event" { + debug!("Running in a MR"); + tags.push(format!("mr-{mr_iid}-{os_version}")); + } + } else { + let commit_branch = get_env_var(CI_COMMIT_REF_NAME)?; + trace!("{CI_COMMIT_REF_NAME}={commit_branch}"); + + debug!("Running on branch {commit_branch}"); + tags.push(format!("br-{commit_branch}-{os_version}")); + } + + let commit_sha = get_env_var(CI_COMMIT_SHORT_SHA)?; + trace!("{CI_COMMIT_SHORT_SHA}={commit_sha}"); + + tags.push(format!("{commit_sha}-{os_version}")); + Ok(tags) + } + + fn get_repo_url() -> miette::Result { + Ok(format!( + "{}://{}/{}/{}", + get_env_var(CI_SERVER_PROTOCOL)?, + get_env_var(CI_SERVER_HOST)?, + get_env_var(CI_PROJECT_NAMESPACE)?, + get_env_var(CI_PROJECT_NAME)?, + )) + } + + fn get_registry() -> miette::Result { + Ok(format!( + "{}/{}/{}", + get_env_var(CI_REGISTRY)?, + get_env_var(CI_PROJECT_NAMESPACE)?, + get_env_var(CI_PROJECT_NAME)?, + ) + .to_lowercase()) + } +} + +#[cfg(test)] +mod test { + use std::env; + + use blue_build_utils::constants::{ + CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, + CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CI_SERVER_HOST, + CI_SERVER_PROTOCOL, + }; + + use crate::{ + drivers::CiDriver, + test::{create_test_recipe, BB_UNIT_TEST_MOCK_GET_OS_VERSION, ENV_LOCK}, + }; + + use super::GitlabDriver; + + fn setup_default_branch() { + setup(); + env::set_var(CI_COMMIT_REF_NAME, "main"); + } + + fn setup_mr_branch() { + setup(); + env::set_var(CI_MERGE_REQUEST_IID, "12"); + env::set_var(CI_PIPELINE_SOURCE, "merge_request_event"); + env::set_var(CI_COMMIT_REF_NAME, "test"); + } + + fn setup_branch() { + setup(); + env::set_var(CI_COMMIT_REF_NAME, "test"); + } + + fn setup() { + env::set_var(CI_DEFAULT_BRANCH, "main"); + env::set_var(CI_COMMIT_SHORT_SHA, "1234567"); + env::set_var(CI_REGISTRY, "registry.example.com"); + env::set_var(CI_PROJECT_NAMESPACE, "test-project"); + env::set_var(CI_PROJECT_NAME, "test"); + env::set_var(CI_SERVER_PROTOCOL, "https"); + env::set_var(CI_SERVER_HOST, "gitlab.example.com"); + env::set_var(BB_UNIT_TEST_MOCK_GET_OS_VERSION, ""); + } + + fn teardown() { + env::remove_var(CI_COMMIT_REF_NAME); + env::remove_var(CI_MERGE_REQUEST_IID); + env::remove_var(CI_PIPELINE_SOURCE); + env::remove_var(CI_DEFAULT_BRANCH); + env::remove_var(CI_COMMIT_SHORT_SHA); + env::remove_var(CI_REGISTRY); + env::remove_var(CI_PROJECT_NAMESPACE); + env::remove_var(CI_PROJECT_NAME); + env::remove_var(CI_SERVER_PROTOCOL); + env::remove_var(CI_SERVER_HOST); + env::remove_var(BB_UNIT_TEST_MOCK_GET_OS_VERSION); + } + + #[test] + fn get_registry() { + let _env = ENV_LOCK.lock().unwrap(); + + setup(); + + let registry = GitlabDriver::get_registry().unwrap(); + + assert_eq!(registry, "registry.example.com/test-project/test"); + teardown(); + } + + #[test] + fn on_default_branch_true() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_default_branch(); + + assert!(GitlabDriver::on_default_branch()); + teardown(); + } + + #[test] + fn on_default_branch_false() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_branch(); + + assert!(!GitlabDriver::on_default_branch()); + teardown(); + } + + #[test] + fn get_repo_url() { + let _env = ENV_LOCK.lock().unwrap(); + + setup(); + + let url = GitlabDriver::get_repo_url().unwrap(); + + assert_eq!(url, "https://gitlab.example.com/test-project/test"); + teardown(); + } + + #[test] + fn generate_tags_default_branch() { + let _env = ENV_LOCK.lock().unwrap(); + let timestamp = blue_build_utils::get_tag_timestamp(); + + setup_default_branch(); + + let mut tags = GitlabDriver::generate_tags(&create_test_recipe()).unwrap(); + tags.sort(); + + let mut expected_tags = vec![ + format!("{timestamp}-40"), + "latest".to_string(), + timestamp, + "1234567-40".to_string(), + "40".to_string(), + ]; + expected_tags.sort(); + + assert_eq!(tags, expected_tags); + + teardown(); + } + + #[test] + fn generate_tags_default_branch_alt_tags() { + let _env = ENV_LOCK.lock().unwrap(); + let timestamp = blue_build_utils::get_tag_timestamp(); + + setup_default_branch(); + + let mut recipe = create_test_recipe(); + + recipe.alt_tags = Some(vec!["test-tag1".into(), "test-tag2".into()]); + + let mut tags = GitlabDriver::generate_tags(&recipe).unwrap(); + tags.sort(); + + let mut expected_tags = vec![ + format!("{timestamp}-40"), + "1234567-40".to_string(), + "40".to_string(), + ]; + expected_tags.extend(recipe.alt_tags.unwrap().iter().map(ToString::to_string)); + expected_tags.sort(); + + assert_eq!(tags, expected_tags); + + teardown(); + } + + #[test] + fn generate_tags_mr_branch() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_mr_branch(); + + let mut tags = GitlabDriver::generate_tags(&create_test_recipe()).unwrap(); + tags.sort(); + + let mut expected_tags = vec!["mr-12-40".to_string(), "1234567-40".to_string()]; + expected_tags.sort(); + + assert_eq!(tags, expected_tags); + + teardown(); + } + + #[test] + fn generate_tags_branch() { + let _env = ENV_LOCK.lock().unwrap(); + + setup_branch(); + + let mut tags = GitlabDriver::generate_tags(&create_test_recipe()).unwrap(); + tags.sort(); + + let mut expected_tags = vec!["1234567-40".to_string(), "br-test-40".to_string()]; + expected_tags.sort(); + + assert_eq!(tags, expected_tags); + + teardown(); + } +} diff --git a/src/image_metadata.rs b/process/drivers/image_metadata.rs similarity index 100% rename from src/image_metadata.rs rename to process/drivers/image_metadata.rs diff --git a/process/drivers/local_driver.rs b/process/drivers/local_driver.rs new file mode 100644 index 0000000..625f69a --- /dev/null +++ b/process/drivers/local_driver.rs @@ -0,0 +1,43 @@ +use log::trace; +use miette::bail; + +use super::{CiDriver, Driver}; + +pub struct LocalDriver; + +impl CiDriver for LocalDriver { + fn on_default_branch() -> bool { + trace!("LocalDriver::on_default_branch()"); + false + } + + fn keyless_cert_identity() -> miette::Result { + trace!("LocalDriver::keyless_cert_identity()"); + bail!("Keyless not supported"); + } + + fn oidc_provider() -> miette::Result { + trace!("LocalDriver::oidc_provider()"); + bail!("Keyless not supported"); + } + + fn generate_tags(recipe: &blue_build_recipe::Recipe) -> miette::Result> { + trace!("LocalDriver::generate_tags({recipe:?})"); + Ok(vec![format!("local-{}", Driver::get_os_version(recipe)?)]) + } + + fn generate_image_name(recipe: &blue_build_recipe::Recipe) -> miette::Result { + trace!("LocalDriver::generate_image_name({recipe:?})"); + Ok(recipe.name.trim().to_lowercase()) + } + + fn get_repo_url() -> miette::Result { + trace!("LocalDriver::get_repo_url()"); + Ok(String::new()) + } + + fn get_registry() -> miette::Result { + trace!("LocalDriver::get_registry()"); + Ok(String::new()) + } +} diff --git a/src/drivers/opts.rs b/process/drivers/opts.rs similarity index 93% rename from src/drivers/opts.rs rename to process/drivers/opts.rs index 97632c5..824c01d 100644 --- a/src/drivers/opts.rs +++ b/process/drivers/opts.rs @@ -3,10 +3,12 @@ use clap::ValueEnum; pub use build::*; pub use inspect::*; pub use run::*; +pub use signing::*; mod build; mod inspect; mod run; +mod signing; #[derive(Debug, Copy, Clone, Default, ValueEnum)] pub enum CompressionType { diff --git a/src/drivers/opts/build.rs b/process/drivers/opts/build.rs similarity index 98% rename from src/drivers/opts/build.rs rename to process/drivers/opts/build.rs index 5cd6549..77d4d19 100644 --- a/src/drivers/opts/build.rs +++ b/process/drivers/opts/build.rs @@ -57,7 +57,7 @@ pub struct BuildTagPushOpts<'a> { /// The list of tags for the image being built. #[builder(default, setter(into))] - pub tags: Cow<'a, [&'a str]>, + pub tags: Cow<'a, [String]>, /// Enable pushing the image. #[builder(default)] diff --git a/src/drivers/opts/inspect.rs b/process/drivers/opts/inspect.rs similarity index 100% rename from src/drivers/opts/inspect.rs rename to process/drivers/opts/inspect.rs diff --git a/process/drivers/opts/run.rs b/process/drivers/opts/run.rs new file mode 100644 index 0000000..ab2a2fb --- /dev/null +++ b/process/drivers/opts/run.rs @@ -0,0 +1,82 @@ +use std::borrow::Cow; + +use typed_builder::TypedBuilder; + +#[derive(Debug, Clone, TypedBuilder)] +pub struct RunOpts<'scope> { + #[builder(setter(into))] + pub image: Cow<'scope, str>, + + #[builder(default, setter(into))] + pub args: Cow<'scope, [String]>, + + #[builder(default, setter(into))] + pub env_vars: Cow<'scope, [RunOptsEnv<'scope>]>, + + #[builder(default, setter(into))] + pub volumes: Cow<'scope, [RunOptsVolume<'scope>]>, + + #[builder(default, setter(strip_option))] + pub uid: Option, + + #[builder(default, setter(strip_option))] + pub gid: Option, + + #[builder(default, setter(into))] + pub workdir: Cow<'scope, str>, + + #[builder(default)] + pub privileged: bool, + + #[builder(default)] + pub pull: bool, + + #[builder(default)] + pub remove: bool, +} + +#[derive(Debug, Clone, TypedBuilder)] +pub struct RunOptsVolume<'scope> { + #[builder(setter(into))] + pub path_or_vol_name: Cow<'scope, str>, + + #[builder(setter(into))] + pub container_path: Cow<'scope, str>, +} + +#[macro_export] +macro_rules! run_volumes { + ($($host:expr => $container:expr),+ $(,)?) => { + { + [ + $($crate::drivers::opts::RunOptsVolume::builder() + .path_or_vol_name($host) + .container_path($container) + .build(),)* + ] + } + }; +} + +#[derive(Debug, Clone, TypedBuilder)] +pub struct RunOptsEnv<'scope> { + #[builder(setter(into))] + pub key: Cow<'scope, str>, + + #[builder(setter(into))] + pub value: Cow<'scope, str>, +} + +#[macro_export] +macro_rules! run_envs { + ($($key:expr => $value:expr),+ $(,)?) => { + { + [ + $($crate::drivers::opts::RunOptsEnv::builder() + .key($key) + .value($value) + .build(),)* + ] + } + }; +} diff --git a/process/drivers/opts/signing.rs b/process/drivers/opts/signing.rs new file mode 100644 index 0000000..77acc47 --- /dev/null +++ b/process/drivers/opts/signing.rs @@ -0,0 +1,115 @@ +use std::{ + borrow::Cow, + env, fs, + path::{Path, PathBuf}, +}; + +use miette::{IntoDiagnostic, Result}; +use typed_builder::TypedBuilder; +use zeroize::{Zeroize, Zeroizing}; + +pub enum PrivateKey { + Env(String), + Path(PathBuf), +} + +pub trait PrivateKeyContents +where + T: Zeroize, +{ + /// Gets's the contents of the `PrivateKey`. + /// + /// # Errors + /// Will error if the file or the environment couldn't be read. + fn contents(&self) -> Result>; +} + +impl PrivateKeyContents> for PrivateKey { + fn contents(&self) -> Result>> { + let key: Zeroizing = self.contents()?; + Ok(Zeroizing::new(key.as_bytes().to_vec())) + } +} + +impl PrivateKeyContents for PrivateKey { + fn contents(&self) -> Result> { + Ok(Zeroizing::new(match *self { + Self::Env(ref env) => env::var(env).into_diagnostic()?, + Self::Path(ref path) => fs::read_to_string(path).into_diagnostic()?, + })) + } +} + +impl std::fmt::Display for PrivateKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + match *self { + Self::Env(ref env) => format!("env://{env}"), + Self::Path(ref path) => format!("{}", path.display()), + } + .as_str(), + ) + } +} + +#[derive(Debug, Clone, TypedBuilder)] +pub struct GenerateKeyPairOpts<'scope> { + #[builder(setter(into, strip_option))] + pub dir: Option>, +} + +#[derive(Debug, Clone, TypedBuilder)] +pub struct CheckKeyPairOpts<'scope> { + #[builder(setter(into, strip_option))] + pub dir: Option>, +} + +#[derive(Debug, Clone, TypedBuilder)] +pub struct SignOpts<'scope> { + #[builder(setter(into))] + pub image: Cow<'scope, str>, + + #[builder(default, setter(into, strip_option))] + pub key: Option>, + + #[builder(default, setter(into, strip_option))] + pub dir: Option>, +} + +#[derive(Debug, Clone)] +pub enum VerifyType<'scope> { + File(Cow<'scope, Path>), + Keyless { + issuer: Cow<'scope, str>, + identity: Cow<'scope, str>, + }, +} + +#[derive(Debug, Clone, TypedBuilder)] +pub struct VerifyOpts<'scope> { + #[builder(setter(into))] + pub image: Cow<'scope, str>, + pub verify_type: VerifyType<'scope>, +} + +#[derive(Debug, Clone, TypedBuilder)] +pub struct SignVerifyOpts<'scope> { + #[builder(setter(into))] + pub image: Cow<'scope, str>, + + #[builder(default, setter(into, strip_option))] + pub tag: Option>, + + #[builder(default, setter(into, strip_option))] + pub dir: Option>, + + /// Enable retry logic for pushing. + #[builder(default)] + pub retry_push: bool, + + /// Number of times to retry pushing. + /// + /// Defaults to 1. + #[builder(default = 1)] + pub retry_count: u8, +} diff --git a/src/drivers/podman_driver.rs b/process/drivers/podman_driver.rs similarity index 53% rename from src/drivers/podman_driver.rs rename to process/drivers/podman_driver.rs index c0a0519..d8eb5ab 100644 --- a/src/drivers/podman_driver.rs +++ b/process/drivers/podman_driver.rs @@ -1,28 +1,26 @@ use std::{ + io::Write, path::Path, - process::{Command, ExitStatus}, + process::{Command, ExitStatus, Stdio}, time::Duration, }; -use blue_build_utils::{ - constants::SKOPEO_IMAGE, - logging::{CommandLogging, Logger}, - signal_handler::{add_cid, remove_cid, ContainerId}, -}; +use blue_build_utils::{cmd, constants::SKOPEO_IMAGE, credentials::Credentials, string_vec}; use colored::Colorize; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info, trace, warn}; -use miette::{bail, IntoDiagnostic, Result}; +use miette::{bail, miette, IntoDiagnostic, Result}; use semver::Version; use serde::Deserialize; use tempdir::TempDir; use crate::{ - credentials::Credentials, drivers::types::RunDriverType, image_metadata::ImageMetadata, + drivers::image_metadata::ImageMetadata, + logging::{CommandLogging, Logger}, + signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime}, }; use super::{ - credentials, opts::{BuildOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts}, BuildDriver, DriverVersion, InspectDriver, RunDriver, }; @@ -51,10 +49,7 @@ impl DriverVersion for PodmanDriver { trace!("PodmanDriver::version()"); trace!("podman version -f json"); - let output = Command::new("podman") - .arg("version") - .arg("-f") - .arg("json") + let output = cmd!("podman", "version", "-f", "json") .output() .into_diagnostic()?; @@ -68,25 +63,22 @@ impl DriverVersion for PodmanDriver { } impl BuildDriver for PodmanDriver { - fn build(&self, opts: &BuildOpts) -> Result<()> { + fn build(opts: &BuildOpts) -> Result<()> { trace!("PodmanDriver::build({opts:#?})"); - trace!( - "podman build --pull=true --layers={} -f {} -t {} .", - !opts.squash, - opts.containerfile.display(), - opts.image, + let command = cmd!( + "podman", + "build", + "--pull=true", + format!("--layers={}", !opts.squash), + "-f", + &*opts.containerfile, + "-t", + &*opts.image, + ".", ); - let mut command = Command::new("podman"); - command - .arg("build") - .arg("--pull=true") - .arg(format!("--layers={}", !opts.squash)) - .arg("-f") - .arg(opts.containerfile.as_ref()) - .arg("-t") - .arg(opts.image.as_ref()) - .arg("."); + + trace!("{command:?}"); let status = command .status_image_ref_progress(&opts.image, "Building Image") .into_diagnostic()?; @@ -99,16 +91,13 @@ impl BuildDriver for PodmanDriver { Ok(()) } - fn tag(&self, opts: &TagOpts) -> Result<()> { + fn tag(opts: &TagOpts) -> Result<()> { trace!("PodmanDriver::tag({opts:#?})"); - trace!("podman tag {} {}", opts.src_image, opts.dest_image); - let status = Command::new("podman") - .arg("tag") - .arg(opts.src_image.as_ref()) - .arg(opts.dest_image.as_ref()) - .status() - .into_diagnostic()?; + let mut command = cmd!("podman", "tag", &*opts.src_image, &*opts.dest_image,); + + trace!("{command:?}"); + let status = command.status().into_diagnostic()?; if status.success() { info!("Successfully tagged {}!", opts.dest_image); @@ -118,18 +107,20 @@ impl BuildDriver for PodmanDriver { Ok(()) } - fn push(&self, opts: &PushOpts) -> Result<()> { + fn push(opts: &PushOpts) -> Result<()> { trace!("PodmanDriver::push({opts:#?})"); - trace!("podman push {}", opts.image); - let mut command = Command::new("podman"); - command - .arg("push") - .arg(format!( + let command = cmd!( + "podman", + "push", + format!( "--compression-format={}", opts.compression_type.unwrap_or_default() - )) - .arg(opts.image.as_ref()); + ), + &*opts.image, + ); + + trace!("{command:?}"); let status = command .status_image_ref_progress(&opts.image, "Pushing Image") .into_diagnostic()?; @@ -142,40 +133,57 @@ impl BuildDriver for PodmanDriver { Ok(()) } - fn login(&self) -> Result<()> { + fn login() -> Result<()> { trace!("PodmanDriver::login()"); if let Some(Credentials { registry, username, password, - }) = credentials::get() + }) = Credentials::get() { - trace!("podman login -u {username} -p [MASKED] {registry}"); - let output = Command::new("podman") - .arg("login") - .arg("-u") - .arg(username) - .arg("-p") - .arg(password) - .arg(registry) - .output() - .into_diagnostic()?; + let mut command = cmd!( + "podman", + "login", + "-u", + username, + "--password-stdin", + registry + ); + command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + trace!("{command:?}"); + let mut child = command.spawn().into_diagnostic()?; + + write!( + child + .stdin + .as_mut() + .ok_or_else(|| miette!("Unable to open pipe to stdin"))?, + "{password}" + ) + .into_diagnostic()?; + + let output = child.wait_with_output().into_diagnostic()?; if !output.status.success() { let err_out = String::from_utf8_lossy(&output.stderr); - bail!("Failed to login for podman: {err_out}"); + bail!("Failed to login for podman:\n{}", err_out.trim()); } + debug!("Logged into {registry}"); } Ok(()) } } impl InspectDriver for PodmanDriver { - fn get_metadata(&self, opts: &GetMetadataOpts) -> Result { + fn get_metadata(opts: &GetMetadataOpts) -> Result { trace!("PodmanDriver::get_metadata({opts:#?})"); - let url = opts.tag.as_ref().map_or_else( + let url = opts.tag.as_deref().map_or_else( || format!("docker://{}", opts.image), |tag| format!("docker://{}:{tag}", opts.image), ); @@ -187,14 +195,14 @@ impl InspectDriver for PodmanDriver { ); progress.enable_steady_tick(Duration::from_millis(100)); - let output = self - .run_output( - &RunOpts::builder() - .image(SKOPEO_IMAGE) - .args(&["inspect".to_string(), url.clone()]) - .build(), - ) - .into_diagnostic()?; + let output = Self::run_output( + &RunOpts::builder() + .image(SKOPEO_IMAGE) + .args(string_vec!["inspect", url.clone()]) + .remove(true) + .build(), + ) + .into_diagnostic()?; progress.finish(); Logger::multi_progress().remove(&progress); @@ -209,31 +217,31 @@ impl InspectDriver for PodmanDriver { } impl RunDriver for PodmanDriver { - fn run(&self, opts: &RunOpts) -> std::io::Result { + fn run(opts: &RunOpts) -> std::io::Result { trace!("PodmanDriver::run({opts:#?})"); let cid_path = TempDir::new("podman")?; let cid_file = cid_path.path().join("cid"); - let cid = ContainerId::new(&cid_file, RunDriverType::Podman, opts.privileged); + let cid = ContainerId::new(&cid_file, ContainerRuntime::Podman, opts.privileged); add_cid(&cid); let status = podman_run(opts, &cid_file) - .status_image_ref_progress(opts.image.as_ref(), "Running container")?; + .status_image_ref_progress(&*opts.image, "Running container")?; remove_cid(&cid); Ok(status) } - fn run_output(&self, opts: &RunOpts) -> std::io::Result { + fn run_output(opts: &RunOpts) -> std::io::Result { trace!("PodmanDriver::run_output({opts:#?})"); let cid_path = TempDir::new("podman")?; let cid_file = cid_path.path().join("cid"); - let cid = ContainerId::new(&cid_file, RunDriverType::Podman, opts.privileged); + let cid = ContainerId::new(&cid_file, ContainerRuntime::Podman, opts.privileged); add_cid(&cid); @@ -246,52 +254,31 @@ impl RunDriver for PodmanDriver { } fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command { - let mut command = if opts.privileged { - warn!( - "Running 'podman' in privileged mode requires '{}'", - "sudo".bold().red() - ); - Command::new("sudo") - } else { - Command::new("podman") - }; - - if opts.privileged { - command.arg("podman"); - } - - command - .arg("run") - .arg(format!("--cidfile={}", cid_file.display())); - - if opts.privileged { - command.arg("--privileged"); - } - - if opts.remove { - command.arg("--rm"); - } - - if opts.pull { - command.arg("--pull=always"); - } - - opts.volumes.iter().for_each(|volume| { - command.arg("--volume"); - command.arg(format!( - "{}:{}", - volume.path_or_vol_name, volume.container_path, - )); - }); - - opts.env_vars.iter().for_each(|env| { - command.arg("--env"); - command.arg(format!("{}={}", env.key, env.value)); - }); - - command.arg(opts.image.as_ref()); - command.args(opts.args.iter()); - - trace!("{command:?}"); - command + cmd!( + if opts.privileged { + warn!( + "Running 'podman' in privileged mode requires '{}'", + "sudo".bold().red() + ); + "sudo" + } else { + "podman" + }, + if opts.privileged => "podman", + "run", + format!("--cidfile={}", cid_file.display()), + if opts.privileged => "--privileged", + if opts.remove => "--rm", + if opts.pull => "--pull=always", + for volume in opts.volumes => [ + "--volume", + format!("{}:{}", volume.path_or_vol_name, volume.container_path), + ], + for env in opts.env_vars => [ + "--env", + format!("{}={}", env.key, env.value), + ], + &*opts.image, + for opts.args, + ) } diff --git a/process/drivers/sigstore_driver.rs b/process/drivers/sigstore_driver.rs new file mode 100644 index 0000000..65ba986 --- /dev/null +++ b/process/drivers/sigstore_driver.rs @@ -0,0 +1,288 @@ +use std::{fs, path::Path}; + +use crate::{ + drivers::opts::{PrivateKeyContents, VerifyType}, + RT, +}; + +use super::{ + functions::get_private_key, + opts::{CheckKeyPairOpts, GenerateKeyPairOpts, SignOpts, VerifyOpts}, + SigningDriver, +}; +use blue_build_utils::{ + constants::{COSIGN_PRIV_PATH, COSIGN_PUB_PATH}, + credentials::Credentials, +}; +use log::{debug, trace}; +use miette::{bail, miette, Context, IntoDiagnostic}; +use sigstore::{ + cosign::{ + constraint::PrivateKeySigner, + verification_constraint::{PublicKeyVerifier, VerificationConstraintVec}, + ClientBuilder, Constraint, CosignCapabilities, SignatureLayer, + }, + crypto::{signing_key::SigStoreKeyPair, SigningScheme}, + errors::SigstoreVerifyConstraintsError, + registry::{Auth, OciReference}, +}; +use zeroize::Zeroizing; + +pub struct SigstoreDriver; + +impl SigningDriver for SigstoreDriver { + 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); + + if priv_key_path.exists() { + bail!("Private key file already exists at {COSIGN_PRIV_PATH}"); + } else if pub_key_path.exists() { + bail!("Public key file already exists at {COSIGN_PUB_PATH}"); + } + + let signer = SigningScheme::default() + .create_signer() + .into_diagnostic() + .context("Failed to create signer")?; + + let keypair = signer + .to_sigstore_keypair() + .into_diagnostic() + .context("Failed to create key pair")?; + + let priv_key = keypair + .private_key_to_encrypted_pem(b"") + .into_diagnostic() + .context("Failed to create encrypted private key")?; + let pub_key = keypair.public_key_to_pem().into_diagnostic()?; + + fs::write(priv_key_path, priv_key) + .into_diagnostic() + .with_context(|| format!("Failed to write {COSIGN_PRIV_PATH}"))?; + fs::write(pub_key_path, pub_key) + .into_diagnostic() + .with_context(|| format!("Failed to write {COSIGN_PUB_PATH}"))?; + + Ok(()) + } + + 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); + let pub_path = path.join(COSIGN_PUB_PATH); + + let pub_key = fs::read_to_string(&pub_path) + .into_diagnostic() + .with_context(|| format!("Failed to open public key file {}", pub_path.display()))?; + debug!("Retrieved public key from {COSIGN_PUB_PATH}"); + trace!("{pub_key}"); + + let key: Zeroizing = get_private_key(path) + .context("Failed to get private key")? + .contents()?; + debug!("Retrieved private key"); + + let keypair = SigStoreKeyPair::from_encrypted_pem(key.as_bytes(), b"") + .into_diagnostic() + .context("Failed to generate key pair from private key")?; + let gen_pub = keypair + .public_key_to_pem() + .into_diagnostic() + .context("Failed to generate public key from private key")?; + debug!("Generated public key from private key"); + trace!("{gen_pub}"); + + if pub_key.trim() == gen_pub.trim() { + debug!("Public and private key matches"); + Ok(()) + } else { + bail!("Private and public keys do not match.") + } + } + + fn sign(opts: &SignOpts) -> miette::Result<()> { + trace!("SigstoreDriver::sign({opts:?})"); + + let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir); + let mut client = ClientBuilder::default().build().into_diagnostic()?; + + let image_digest: &str = opts.image.as_ref(); + let image_digest: OciReference = image_digest.parse().into_diagnostic()?; + trace!("{image_digest:?}"); + + let signing_scheme = SigningScheme::default(); + let key: Zeroizing> = get_private_key(path)?.contents()?; + debug!("Retrieved private key"); + + let signer = PrivateKeySigner::new_with_signer( + SigStoreKeyPair::from_encrypted_pem(&key, b"") + .into_diagnostic()? + .to_sigstore_signer(&signing_scheme) + .into_diagnostic()?, + ); + debug!("Created signer"); + + let Credentials { + registry: _, + username, + password, + } = Credentials::get().ok_or_else(|| miette!("Credentials are required for signing"))?; + let auth = Auth::Basic(username.clone(), password.clone()); + debug!("Credentials retrieved"); + + let (cosign_signature_image, source_image_digest) = RT + .block_on(client.triangulate(&image_digest, &auth)) + .into_diagnostic() + .with_context(|| format!("Failed to triangulate image {image_digest}"))?; + debug!("Triangulating image"); + trace!("{cosign_signature_image}, {source_image_digest}"); + + let mut signature_layer = + SignatureLayer::new_unsigned(&image_digest, &source_image_digest).into_diagnostic()?; + signer + .add_constraint(&mut signature_layer) + .into_diagnostic()?; + debug!("Created signing layer"); + + debug!("Pushing signature"); + RT.block_on(client.push_signature( + None, + &auth, + &cosign_signature_image, + vec![signature_layer], + )) + .into_diagnostic() + .with_context(|| { + format!("Failed to push signature {cosign_signature_image} for image {image_digest}") + })?; + debug!("Successfully pushed signature"); + + Ok(()) + } + + fn verify(opts: &VerifyOpts) -> miette::Result<()> { + let mut client = ClientBuilder::default().build().into_diagnostic()?; + + let image_digest: &str = opts.image.as_ref(); + let image_digest: OciReference = image_digest.parse().into_diagnostic()?; + trace!("{image_digest:?}"); + + let signing_scheme = SigningScheme::default(); + + let pub_key = fs::read_to_string(match &opts.verify_type { + VerifyType::File(path) => path, + VerifyType::Keyless { .. } => { + todo!("Keyless currently not supported for sigstore driver") + } + }) + .into_diagnostic() + .with_context(|| format!("Failed to open public key file {COSIGN_PUB_PATH}"))?; + debug!("Retrieved public key from {COSIGN_PUB_PATH}"); + trace!("{pub_key}"); + + let verifier = + PublicKeyVerifier::new(pub_key.as_bytes(), &signing_scheme).into_diagnostic()?; + let verification_constraints: VerificationConstraintVec = vec![Box::new(verifier)]; + + let auth = Auth::Anonymous; + let (cosign_signature_image, source_image_digest) = RT + .block_on(client.triangulate(&image_digest, &auth)) + .into_diagnostic() + .with_context(|| format!("Failed to triangulate image {image_digest}"))?; + debug!("Triangulating image"); + trace!("{cosign_signature_image}, {source_image_digest}"); + + let trusted_layers = RT + .block_on(client.trusted_signature_layers( + &auth, + &source_image_digest, + &cosign_signature_image, + )) + .into_diagnostic()?; + + sigstore::cosign::verify_constraints(&trusted_layers, verification_constraints.iter()) + .map_err( + |SigstoreVerifyConstraintsError { + unsatisfied_constraints, + }| { + miette!("Failed to verify for constraints: {unsatisfied_constraints:?}") + }, + ) + } + + fn signing_login() -> miette::Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::{fs, path::Path}; + + use blue_build_utils::constants::{COSIGN_PRIV_PATH, COSIGN_PUB_PATH}; + use tempdir::TempDir; + + use crate::drivers::{ + cosign_driver::CosignDriver, + opts::{CheckKeyPairOpts, GenerateKeyPairOpts}, + SigningDriver, + }; + + use super::SigstoreDriver; + + #[test] + fn generate_key_pair() { + let tempdir = TempDir::new("keypair").unwrap(); + + let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build(); + + SigstoreDriver::generate_key_pair(&gen_opts).unwrap(); + + eprintln!( + "Private key:\n{}", + fs::read_to_string(tempdir.path().join(COSIGN_PRIV_PATH)).unwrap() + ); + eprintln!( + "Public key:\n{}", + 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(); + } + + #[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(); + } + + #[test] + fn compatibility() { + let tempdir = TempDir::new("keypair").unwrap(); + + let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build(); + + SigstoreDriver::generate_key_pair(&gen_opts).unwrap(); + + eprintln!( + "Private key:\n{}", + fs::read_to_string(tempdir.path().join(COSIGN_PRIV_PATH)).unwrap() + ); + eprintln!( + "Public key:\n{}", + 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(); + } +} diff --git a/src/drivers/skopeo_driver.rs b/process/drivers/skopeo_driver.rs similarity index 76% rename from src/drivers/skopeo_driver.rs rename to process/drivers/skopeo_driver.rs index 9da563c..5e7377b 100644 --- a/src/drivers/skopeo_driver.rs +++ b/process/drivers/skopeo_driver.rs @@ -1,22 +1,19 @@ -use std::{ - process::{Command, Stdio}, - time::Duration, -}; +use std::{process::Stdio, time::Duration}; -use blue_build_utils::logging::Logger; +use blue_build_utils::cmd; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, trace}; use miette::{bail, IntoDiagnostic, Result}; -use crate::image_metadata::ImageMetadata; +use crate::logging::Logger; -use super::{opts::GetMetadataOpts, InspectDriver}; +use super::{image_metadata::ImageMetadata, opts::GetMetadataOpts, InspectDriver}; #[derive(Debug)] pub struct SkopeoDriver; impl InspectDriver for SkopeoDriver { - fn get_metadata(&self, opts: &GetMetadataOpts) -> Result { + fn get_metadata(opts: &GetMetadataOpts) -> Result { trace!("SkopeoDriver::get_metadata({opts:#?})"); let url = opts.tag.as_ref().map_or_else( @@ -32,9 +29,7 @@ impl InspectDriver for SkopeoDriver { progress.enable_steady_tick(Duration::from_millis(100)); trace!("skopeo inspect {url}"); - let output = Command::new("skopeo") - .arg("inspect") - .arg(&url) + let output = cmd!("skopeo", "inspect", &url) .stderr(Stdio::inherit()) .output() .into_diagnostic()?; diff --git a/process/drivers/traits.rs b/process/drivers/traits.rs new file mode 100644 index 0000000..fcacc99 --- /dev/null +++ b/process/drivers/traits.rs @@ -0,0 +1,335 @@ +use std::{ + path::PathBuf, + process::{ExitStatus, Output}, +}; + +use blue_build_recipe::Recipe; +use blue_build_utils::{constants::COSIGN_PUB_PATH, retry}; +use log::{debug, info, trace}; +use miette::{bail, miette, Result}; +use semver::{Version, VersionReq}; + +use crate::drivers::{functions::get_private_key, types::CiDriverType, Driver}; + +use super::{ + image_metadata::ImageMetadata, + opts::{ + BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateKeyPairOpts, GetMetadataOpts, + PushOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts, VerifyOpts, VerifyType, + }, +}; + +/// Trait for retrieving version of a driver. +pub trait DriverVersion { + /// The version req string slice that follows + /// the semver standard . + const VERSION_REQ: &'static str; + + /// Returns the version of the driver. + /// + /// # Errors + /// Will error if it can't retrieve the version. + fn version() -> Result; + + #[must_use] + fn is_supported_version() -> bool { + Self::version().is_ok_and(|version| { + VersionReq::parse(Self::VERSION_REQ).is_ok_and(|req| req.matches(&version)) + }) + } +} + +/// Allows agnostic building, tagging +/// pushing, and login. +pub trait BuildDriver { + /// Runs the build logic for the driver. + /// + /// # Errors + /// Will error if the build fails. + fn build(opts: &BuildOpts) -> Result<()>; + + /// Runs the tag logic for the driver. + /// + /// # Errors + /// Will error if the tagging fails. + fn tag(opts: &TagOpts) -> Result<()>; + + /// Runs the push logic for the driver + /// + /// # Errors + /// Will error if the push fails. + fn push(opts: &PushOpts) -> Result<()>; + + /// Runs the login logic for the driver. + /// + /// # Errors + /// Will error if login fails. + fn login() -> 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<()> { + trace!("BuildDriver::build_tag_push({opts:#?})"); + + let full_image = match (opts.archive_path.as_ref(), opts.image.as_ref()) { + (Some(archive_path), None) => { + format!("oci-archive:{archive_path}") + } + (None, Some(image)) => opts + .tags + .first() + .map_or_else(|| image.to_string(), |tag| format!("{image}:{tag}")), + (Some(_), Some(_)) => bail!("Cannot use both image and archive path"), + (None, None) => bail!("Need either the image or archive path set"), + }; + + let build_opts = BuildOpts::builder() + .image(&full_image) + .containerfile(opts.containerfile.as_ref()) + .squash(opts.squash) + .build(); + + info!("Building image {full_image}"); + Self::build(&build_opts)?; + + if !opts.tags.is_empty() && opts.archive_path.is_none() { + let image = opts + .image + .as_ref() + .ok_or_else(|| miette!("Image is required in order to tag"))?; + debug!("Tagging all images"); + + for tag in opts.tags.as_ref() { + debug!("Tagging {} with {tag}", &full_image); + + let tag_opts = TagOpts::builder() + .src_image(&full_image) + .dest_image(format!("{image}:{tag}")) + .build(); + + Self::tag(&tag_opts)?; + + if opts.push { + let retry_count = if opts.retry_push { opts.retry_count } else { 0 }; + + debug!("Pushing all images"); + // Push images with retries (1s delay between retries) + blue_build_utils::retry(retry_count, 5, || { + let tag_image = format!("{image}:{tag}"); + + debug!("Pushing image {tag_image}"); + + let push_opts = PushOpts::builder() + .image(&tag_image) + .compression_type(opts.compression) + .build(); + + Self::push(&push_opts) + })?; + } + } + } + + Ok(()) + } +} + +/// Allows agnostic inspection of images. +pub trait InspectDriver { + /// Gets the metadata on an image tag. + /// + /// # Errors + /// Will error if it is unable to get the labels. + fn get_metadata(opts: &GetMetadataOpts) -> Result; +} + +pub trait RunDriver: Sync + Send { + /// Run a container to perform an action. + /// + /// # Errors + /// Will error if there is an issue running the container. + fn run(opts: &RunOpts) -> std::io::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) -> std::io::Result; +} + +pub trait SigningDriver { + /// Generate a new private/public key pair. + /// + /// # Errors + /// Will error if a key-pair couldn't be generated. + 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<()>; + + /// Signs the image digest. + /// + /// # Errors + /// Will error if signing fails. + fn sign(opts: &SignOpts) -> Result<()>; + + /// Verifies the image. + /// + /// The image can be verified either with `VerifyType::File` containing + /// the public key contents, or with `VerifyType::Keyless` containing + /// information about the `issuer` and `identity`. + /// + /// # Errors + /// Will error if the image fails to be verified. + 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<()> { + trace!("sign_and_verify({opts:?})"); + + let path = opts + .dir + .as_ref() + .map_or_else(|| PathBuf::from("."), |d| d.to_path_buf()); + + let image_name: &str = opts.image.as_ref(); + let inspect_opts = GetMetadataOpts::builder().image(image_name); + + let inspect_opts = if let Some(ref tag) = opts.tag { + inspect_opts.tag(tag.as_ref() as &str).build() + } else { + inspect_opts.build() + }; + + let image_digest = Driver::get_metadata(&inspect_opts)?.digest; + let image_name_tag = opts + .tag + .as_ref() + .map_or_else(|| image_name.to_owned(), |t| format!("{image_name}:{t}")); + let image_digest = format!("{image_name}@{image_digest}"); + + 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(&image_name_tag) + .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(&image_name_tag) + .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 retry_count = if opts.retry_push { opts.retry_count } else { 0 }; + + retry(retry_count, 5, || { + Self::sign(&sign_opts)?; + Self::verify(&verify_opts) + })?; + + Ok(()) + } + + /// Runs the login logic for the signing driver. + /// + /// # Errors + /// Will error if login fails. + fn signing_login() -> Result<()>; +} + +/// Allows agnostic retrieval of CI-based information. +pub trait CiDriver { + /// Determines if we're on the main branch of + /// a repository. + fn on_default_branch() -> bool; + + /// Retrieve the certificate identity for + /// keyless signing. + /// + /// # Errors + /// Will error if the environment variables aren't set. + fn keyless_cert_identity() -> Result; + + /// Retrieve the OIDC Provider for keyless signing. + /// + /// # Errors + /// Will error if the environment variables aren't set. + fn oidc_provider() -> Result; + + /// Generate a list of tags based on the OS version. + /// + /// ## CI + /// The tags are generated based on the CI system that + /// is detected. The general format for the default branch is: + /// - `${os_version}` + /// - `${timestamp}-${os_version}` + /// + /// On a branch: + /// - `br-${branch_name}-${os_version}` + /// + /// In a PR(GitHub)/MR(GitLab) + /// - `pr-${pr_event_number}-${os_version}`/`mr-${mr_iid}-${os_version}` + /// + /// In all above cases the short git sha is also added: + /// - `${commit_sha}-${os_version}` + /// + /// When `alt_tags` are not present, the following tags are added: + /// - `latest` + /// - `${timestamp}` + /// + /// ## Locally + /// When ran locally, only a local tag is created: + /// - `local-${os_version}` + /// + /// # Errors + /// Will error if the environment variables aren't set. + fn generate_tags(recipe: &Recipe) -> Result>; + + /// Generates the image name based on CI. + /// + /// # Errors + /// Will error if the environment variables aren't set. + fn generate_image_name(recipe: &Recipe) -> Result { + Ok(format!( + "{}/{}", + Self::get_registry()?, + recipe.name.trim().to_lowercase() + )) + } + + /// Get the URL for the repository. + /// + /// # Errors + /// Will error if the environment variables aren't set. + fn get_repo_url() -> Result; + + /// Get the registry ref for the image. + /// + /// # Errors + /// Will error if the environment variables aren't set. + fn get_registry() -> Result; +} diff --git a/process/drivers/types.rs b/process/drivers/types.rs new file mode 100644 index 0000000..45b177c --- /dev/null +++ b/process/drivers/types.rs @@ -0,0 +1,169 @@ +use std::env; + +use blue_build_utils::constants::{GITHUB_ACTIONS, GITLAB_CI}; +use clap::ValueEnum; +use log::trace; + +use crate::drivers::{ + buildah_driver::BuildahDriver, docker_driver::DockerDriver, podman_driver::PodmanDriver, + DriverVersion, +}; + +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() => { + 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 {}, ", 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, + #[cfg(feature = "sigstore")] + Sigstore, +} + +impl DetermineDriver for Option { + fn determine_driver(&mut self) -> SigningDriverType { + trace!("SigningDriverType::determine_signing_driver()"); + + #[cfg(feature = "sigstore")] + { + *self.get_or_insert( + blue_build_utils::check_command_exists("cosign") + .map_or(SigningDriverType::Sigstore, |()| SigningDriverType::Cosign), + ) + } + + #[cfg(not(feature = "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 (env::var(GITLAB_CI).ok(), env::var(GITHUB_ACTIONS).ok()) { + (Some(_gitlab_ci), None) => CiDriverType::Gitlab, + (None, Some(_github_actions)) => CiDriverType::Github, + _ => CiDriverType::Local, + }, + ) + } +} diff --git a/utils/src/logging.rs b/process/logging.rs similarity index 100% rename from utils/src/logging.rs rename to process/logging.rs diff --git a/process/process.rs b/process/process.rs new file mode 100644 index 0000000..628b4bd --- /dev/null +++ b/process/process.rs @@ -0,0 +1,57 @@ +//! This module is responsible for managing processes spawned +//! by this tool. It contains drivers for running, building, inspecting, and signing +//! images that interface with tools like docker or podman. + +#[cfg(feature = "sigstore")] +use once_cell::sync::Lazy; +#[cfg(feature = "sigstore")] +use tokio::runtime::Runtime; + +pub mod drivers; +pub mod logging; +pub mod signal_handler; + +#[cfg(feature = "sigstore")] +pub(crate) static RT: Lazy = Lazy::new(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() +}); + +#[cfg(test)] +pub(crate) mod test { + use std::sync::Mutex; + + use blue_build_recipe::{Module, ModuleExt, Recipe}; + use indexmap::IndexMap; + use once_cell::sync::Lazy; + + pub const BB_UNIT_TEST_MOCK_GET_OS_VERSION: &str = "BB_UNIT_TEST_MOCK_GET_OS_VERSION"; + + /// This mutex is used for tests that require the reading of + /// environment variables. Env vars are an inheritly unsafe + /// as they can be changed and affect other threads functionality. + /// + /// For tests that require setting env vars, they need to lock this + /// mutex before making changes to the env. Any changes made to the env + /// MUST be undone in the same test before dropping the lock. Failure to + /// do so will cause unpredictable behavior with other tests. + pub static ENV_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); + + pub fn create_test_recipe() -> Recipe<'static> { + Recipe::builder() + .name("test") + .description("This is a test") + .base_image("base-image") + .image_version("40") + .modules_ext( + ModuleExt::builder() + .modules(vec![Module::builder().build()]) + .build(), + ) + .stages_ext(None) + .extra(IndexMap::new()) + .build() + } +} diff --git a/utils/src/signal_handler.rs b/process/signal_handler.rs similarity index 88% rename from utils/src/signal_handler.rs rename to process/signal_handler.rs index 4e6a218..17b0a6d 100644 --- a/utils/src/signal_handler.rs +++ b/process/signal_handler.rs @@ -1,11 +1,11 @@ use std::{ fs, path::PathBuf, - process::{self, Command}, sync::{atomic::AtomicBool, Arc, Mutex}, thread, }; +use blue_build_utils::cmd; use log::{debug, error, trace, warn}; use nix::{ libc::{SIGABRT, SIGCONT, SIGHUP, SIGTSTP}, @@ -26,21 +26,34 @@ use crate::logging::Logger; pub struct ContainerId { cid_path: PathBuf, requires_sudo: bool, - crt: String, + container_runtime: ContainerRuntime, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ContainerRuntime { + Podman, + Docker, +} + +impl std::fmt::Display for ContainerRuntime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match *self { + Self::Podman => "podman", + Self::Docker => "docker", + }) + } } impl ContainerId { - pub fn new(cid_path: P, container_runtime: S, requires_sudo: bool) -> Self + pub fn new

(cid_path: P, container_runtime: ContainerRuntime, requires_sudo: bool) -> Self where P: Into, - S: Into, { let cid_path = cid_path.into(); - let crt = container_runtime.into(); Self { cid_path, requires_sudo, - crt, + container_runtime, } } } @@ -97,13 +110,9 @@ where debug!("Killing container {id}"); let status = if cid.requires_sudo { - Command::new("sudo") - .arg(&cid.crt) - .arg("stop") - .arg(id) - .status() + cmd!("sudo", cid.container_runtime.to_string(), "stop", id).status() } else { - Command::new(&cid.crt).arg("stop").arg(id).status() + cmd!(cid.container_runtime.to_string(), "stop", id).status() }; if let Err(e) = status { @@ -113,7 +122,7 @@ where }); drop(cid_list); - process::exit(1); + expect_exit::exit_unwind(1); } SIGTSTP => { if has_terminal { diff --git a/recipe/Cargo.toml b/recipe/Cargo.toml index 0b8cb92..b500417 100644 --- a/recipe/Cargo.toml +++ b/recipe/Cargo.toml @@ -11,7 +11,6 @@ license.workspace = true [dependencies] blue-build-utils = { version = "=0.8.12", path = "../utils" } -chrono.workspace = true colored.workspace = true log.workspace = true miette.workspace = true diff --git a/recipe/src/recipe.rs b/recipe/src/recipe.rs index 8516897..74a94d7 100644 --- a/recipe/src/recipe.rs +++ b/recipe/src/recipe.rs @@ -1,12 +1,7 @@ -use std::{borrow::Cow, env, fs, path::Path}; +use std::{borrow::Cow, fs, path::Path}; -use blue_build_utils::constants::{ - CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, - CI_PIPELINE_SOURCE, GITHUB_EVENT_NAME, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER, -}; -use chrono::Local; use indexmap::IndexMap; -use log::{debug, trace, warn}; +use log::{debug, trace}; use miette::{Context, IntoDiagnostic, Result}; use serde::{Deserialize, Serialize}; use serde_yaml::Value; @@ -83,116 +78,6 @@ pub struct Recipe<'a> { } impl<'a> Recipe<'a> { - /// Generate a list of tags based on the OS version. - /// - /// ## CI - /// The tags are generated based on the CI system that - /// is detected. The general format for the default branch is: - /// - `${os_version}` - /// - `${timestamp}-${os_version}` - /// - /// On a branch: - /// - `br-${branch_name}-${os_version}` - /// - /// In a PR(GitHub)/MR(GitLab) - /// - `pr-${pr_event_number}-${os_version}`/`mr-${mr_iid}-${os_version}` - /// - /// In all above cases the short git sha is also added: - /// - `${commit_sha}-${os_version}` - /// - /// When `alt_tags` are not present, the following tags are added: - /// - `latest` - /// - `${timestamp}` - /// - /// ## Locally - /// When ran locally, only a local tag is created: - /// - `local-${os_version}` - #[must_use] - pub fn generate_tags(&self, os_version: u64) -> Vec { - trace!("Recipe::generate_tags()"); - trace!("Generating image tags for {}", &self.name); - - let mut tags: Vec = Vec::new(); - let timestamp = Local::now().format("%Y%m%d").to_string(); - - if let (Ok(commit_branch), Ok(default_branch), Ok(commit_sha), Ok(pipeline_source)) = ( - env::var(CI_COMMIT_REF_NAME), - env::var(CI_DEFAULT_BRANCH), - env::var(CI_COMMIT_SHORT_SHA), - env::var(CI_PIPELINE_SOURCE), - ) { - trace!("CI_COMMIT_REF_NAME={commit_branch}, CI_DEFAULT_BRANCH={default_branch},CI_COMMIT_SHORT_SHA={commit_sha}, CI_PIPELINE_SOURCE={pipeline_source}"); - warn!("Detected running in Gitlab, pulling information from CI variables"); - - if let Ok(mr_iid) = env::var(CI_MERGE_REQUEST_IID) { - trace!("CI_MERGE_REQUEST_IID={mr_iid}"); - if pipeline_source == "merge_request_event" { - debug!("Running in a MR"); - tags.push(format!("mr-{mr_iid}-{os_version}")); - } - } - - if default_branch == commit_branch { - debug!("Running on the default branch"); - tags.push(os_version.to_string()); - tags.push(format!("{timestamp}-{os_version}")); - - if let Some(alt_tags) = self.alt_tags.as_ref() { - tags.extend(alt_tags.iter().map(ToString::to_string)); - } else { - tags.push("latest".into()); - tags.push(timestamp); - } - } else { - debug!("Running on branch {commit_branch}"); - tags.push(format!("br-{commit_branch}-{os_version}")); - } - - tags.push(format!("{commit_sha}-{os_version}")); - } else if let ( - Ok(github_event_name), - Ok(github_event_number), - Ok(github_sha), - Ok(github_ref_name), - ) = ( - env::var(GITHUB_EVENT_NAME), - env::var(PR_EVENT_NUMBER), - env::var(GITHUB_SHA), - env::var(GITHUB_REF_NAME), - ) { - trace!("GITHUB_EVENT_NAME={github_event_name},PR_EVENT_NUMBER={github_event_number},GITHUB_SHA={github_sha},GITHUB_REF_NAME={github_ref_name}"); - warn!("Detected running in Github, pulling information from GITHUB variables"); - - let mut short_sha = github_sha; - short_sha.truncate(7); - - if github_event_name == "pull_request" { - debug!("Running in a PR"); - tags.push(format!("pr-{github_event_number}-{os_version}")); - } else if github_ref_name == "live" || github_ref_name == "main" { - tags.push(os_version.to_string()); - tags.push(format!("{timestamp}-{os_version}")); - - if let Some(alt_tags) = self.alt_tags.as_ref() { - tags.extend(alt_tags.iter().map(ToString::to_string)); - } else { - tags.push("latest".into()); - tags.push(timestamp); - } - } else { - tags.push(format!("br-{github_ref_name}-{os_version}")); - } - tags.push(format!("{short_sha}-{os_version}")); - } else { - warn!("Running locally"); - tags.push(format!("local-{os_version}")); - } - debug!("Finished generating tags!"); - debug!("Tags: {tags:#?}"); - - tags.into_iter().map(|t| t.replace('/', "_")).collect() - } - /// Parse a recipe file /// /// # Errors diff --git a/src/bin/bluebuild.rs b/src/bin/bluebuild.rs index 7a194d1..1c37dd3 100644 --- a/src/bin/bluebuild.rs +++ b/src/bin/bluebuild.rs @@ -1,5 +1,5 @@ use blue_build::commands::{BlueBuildArgs, BlueBuildCommand, CommandArgs}; -use blue_build_utils::{logging::Logger, signal_handler}; +use blue_build_process_management::{logging::Logger, signal_handler}; use clap::Parser; use log::LevelFilter; @@ -8,7 +8,11 @@ fn main() { Logger::new() .filter_level(args.verbosity.log_level_filter()) - .filter_modules([("hyper::proto", LevelFilter::Info)]) + .filter_modules([ + ("hyper::proto", LevelFilter::Off), + ("hyper_util", LevelFilter::Off), + ("oci_distribution", LevelFilter::Off), + ]) .log_out_dir(args.log_out.clone()) .init(); log::trace!("Parsed arguments: {args:#?}"); @@ -32,6 +36,9 @@ fn main() { #[cfg(not(feature = "switch"))] CommandArgs::Upgrade(mut command) => command.run(), + #[cfg(feature = "login")] + CommandArgs::Login(mut command) => command.run(), + CommandArgs::BugReport(mut command) => command.run(), CommandArgs::Completions(mut command) => command.run(), diff --git a/src/commands.rs b/src/commands.rs index fddee2e..327fddb 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,19 +2,17 @@ use std::path::PathBuf; use log::error; -use clap::{command, crate_authors, Args, Parser, Subcommand}; +use clap::{command, crate_authors, Parser, Subcommand}; use clap_verbosity_flag::{InfoLevel, Verbosity}; -use typed_builder::TypedBuilder; -use crate::{ - drivers::types::{BuildDriverType, InspectDriverType}, - shadow, -}; +use crate::shadow; pub mod bug_report; pub mod build; pub mod completions; pub mod generate; +#[cfg(feature = "login")] +pub mod login; // #[cfg(feature = "init")] // pub mod init; #[cfg(not(feature = "switch"))] @@ -107,6 +105,10 @@ pub enum CommandArgs { #[cfg(feature = "switch")] Switch(switch::SwitchCommand), + /// Login to all services used for building. + #[cfg(feature = "login")] + Login(login::LoginCommand), + // /// Initialize a new Ublue Starting Point repo // #[cfg(feature = "init")] // Init(init::InitCommand), @@ -120,21 +122,6 @@ pub enum CommandArgs { Completions(completions::CompletionsCommand), } -#[derive(Default, Clone, Copy, Debug, TypedBuilder, Args)] -pub struct DriverArgs { - /// Select which driver to use to build - /// your image. - #[builder(default)] - #[arg(short = 'B', long)] - build_driver: Option, - - /// Select which driver to use to inspect - /// images. - #[builder(default)] - #[arg(short = 'I', long)] - inspect_driver: Option, -} - #[cfg(test)] mod test { use clap::CommandFactory; diff --git a/src/commands/build.rs b/src/commands/build.rs index fb206b0..89055f9 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,20 +1,19 @@ use std::{ - env, fs, + fs, path::{Path, PathBuf}, - process::Command, }; +use blue_build_process_management::drivers::{ + opts::{BuildTagPushOpts, CheckKeyPairOpts, CompressionType}, + BuildDriver, CiDriver, Driver, DriverArgs, SigningDriver, +}; use blue_build_recipe::Recipe; use blue_build_utils::{ constants::{ - ARCHIVE_SUFFIX, BB_PASSWORD, BB_REGISTRY, BB_REGISTRY_NAMESPACE, BB_USERNAME, - BUILD_ID_LABEL, CI_DEFAULT_BRANCH, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_PROJECT_URL, - CI_REGISTRY, CI_SERVER_HOST, CI_SERVER_PROTOCOL, CONFIG_PATH, CONTAINER_FILE, - COSIGN_PRIVATE_KEY, COSIGN_PRIV_PATH, COSIGN_PUB_PATH, GITHUB_REPOSITORY_OWNER, - GITHUB_TOKEN, GITHUB_TOKEN_ISSUER_URL, GITHUB_WORKFLOW_REF, GITIGNORE_PATH, - LABELED_ERROR_MESSAGE, NO_LABEL_ERROR_MESSAGE, RECIPE_FILE, RECIPE_PATH, SIGSTORE_ID_TOKEN, + ARCHIVE_SUFFIX, BB_REGISTRY_NAMESPACE, BUILD_ID_LABEL, CONFIG_PATH, CONTAINER_FILE, + GITIGNORE_PATH, LABELED_ERROR_MESSAGE, NO_LABEL_ERROR_MESSAGE, RECIPE_FILE, RECIPE_PATH, }, - generate_containerfile_path, + credentials::{Credentials, CredentialsArgs}, }; use clap::Args; use colored::Colorize; @@ -22,16 +21,9 @@ use log::{debug, info, trace, warn}; use miette::{bail, Context, IntoDiagnostic, Result}; use typed_builder::TypedBuilder; -use crate::{ - commands::generate::GenerateCommand, - credentials::{self, Credentials}, - drivers::{ - opts::{BuildTagPushOpts, CompressionType, GetMetadataOpts}, - Driver, - }, -}; +use crate::commands::generate::GenerateCommand; -use super::{BlueBuildCommand, DriverArgs}; +use super::BlueBuildCommand; #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Args, TypedBuilder)] @@ -88,11 +80,6 @@ pub struct BuildCommand { #[builder(default, setter(into, strip_option))] archive: Option, - /// The registry's domain name. - #[arg(long, env = BB_REGISTRY)] - #[builder(default, setter(into, strip_option))] - registry: Option, - /// The url path to your base /// project images. #[arg(long, env = BB_REGISTRY_NAMESPACE)] @@ -100,18 +87,6 @@ pub struct BuildCommand { #[arg(visible_alias("registry-path"))] registry_namespace: Option, - /// The username to login to the - /// container registry. - #[arg(short = 'U', long, env = BB_USERNAME, hide_env_values = true)] - #[builder(default, setter(into, strip_option))] - username: Option, - - /// The password to login to the - /// container registry. - #[arg(short = 'P', long, env = BB_PASSWORD, hide_env_values = true)] - #[builder(default, setter(into, strip_option))] - password: Option, - /// Do not sign the image on push. #[arg(long)] #[builder(default)] @@ -128,6 +103,10 @@ pub struct BuildCommand { #[builder(default)] squash: bool, + #[clap(flatten)] + #[builder(default)] + credentials: CredentialsArgs, + #[clap(flatten)] #[builder(default)] drivers: DriverArgs, @@ -138,14 +117,9 @@ impl BlueBuildCommand for BuildCommand { fn try_run(&mut self) -> Result<()> { trace!("BuildCommand::try_run()"); - Driver::builder() - .username(self.username.as_ref()) - .password(self.password.as_ref()) - .registry(self.registry.as_ref()) - .build_driver(self.drivers.build_driver) - .inspect_driver(self.drivers.inspect_driver) - .build() - .init(); + Driver::init(self.drivers); + + Credentials::init(self.credentials.clone()); self.update_gitignore()?; @@ -155,8 +129,9 @@ impl BlueBuildCommand for BuildCommand { if self.push { blue_build_utils::check_command_exists("cosign")?; - self.check_cosign_files()?; - Self::login()?; + Driver::check_signing_files(&CheckKeyPairOpts::builder().dir(Path::new(".")).build())?; + Driver::login()?; + Driver::signing_login()?; } #[cfg(feature = "multi-recipe")] @@ -180,7 +155,11 @@ impl BlueBuildCommand for BuildCommand { recipe_paths.par_iter().try_for_each(|recipe| { GenerateCommand::builder() - .output(generate_containerfile_path(recipe)?) + .output(if recipe_paths.len() > 1 { + blue_build_utils::generate_containerfile_path(recipe)? + } else { + PathBuf::from(CONTAINER_FILE) + }) .recipe(recipe) .drivers(self.drivers) .build() @@ -204,7 +183,7 @@ impl BlueBuildCommand for BuildCommand { }); GenerateCommand::builder() - .output(generate_containerfile_path(&recipe_path)?) + .output(CONTAINER_FILE) .recipe(&recipe_path) .drivers(self.drivers) .build() @@ -218,16 +197,21 @@ impl BlueBuildCommand for BuildCommand { impl BuildCommand { #[cfg(feature = "multi-recipe")] fn start(&self, recipe_paths: &[PathBuf]) -> Result<()> { + use blue_build_process_management::drivers::opts::SignVerifyOpts; use rayon::prelude::*; + trace!("BuildCommand::build_image()"); recipe_paths .par_iter() .try_for_each(|recipe_path| -> Result<()> { let recipe = Recipe::parse(recipe_path)?; - let os_version = Driver::get_os_version(&recipe)?; - let containerfile = generate_containerfile_path(recipe_path)?; - let tags = recipe.generate_tags(os_version); + let containerfile = if recipe_paths.len() > 1 { + blue_build_utils::generate_containerfile_path(recipe_path)? + } else { + PathBuf::from(CONTAINER_FILE) + }; + let tags = Driver::generate_tags(&recipe)?; let image_name = self.generate_full_image_name(&recipe)?; let opts = if let Some(archive_dir) = self.archive.as_ref() { @@ -244,7 +228,7 @@ impl BuildCommand { BuildTagPushOpts::builder() .image(&image_name) .containerfile(&containerfile) - .tags(tags.iter().map(String::as_str).collect::>()) + .tags(&tags) .push(self.push) .retry_push(self.retry_push) .retry_count(self.retry_count) @@ -253,10 +237,19 @@ impl BuildCommand { .build() }; - Driver::get_build_driver().build_tag_push(&opts)?; + Driver::build_tag_push(&opts)?; if self.push && !self.no_sign { - sign_images(&image_name, tags.first().map(String::as_str))?; + let opts = SignVerifyOpts::builder() + .image(&image_name) + .retry_push(self.retry_push) + .retry_count(self.retry_count); + let opts = if let Some(tag) = tags.first() { + opts.tag(tag).build() + } else { + opts.build() + }; + Driver::sign_and_verify(&opts)?; } Ok(()) @@ -268,12 +261,13 @@ impl BuildCommand { #[cfg(not(feature = "multi-recipe"))] fn start(&self, recipe_path: &Path) -> Result<()> { + use blue_build_process_management::drivers::opts::SignVerifyOpts; + trace!("BuildCommand::start()"); let recipe = Recipe::parse(recipe_path)?; - let os_version = Driver::get_os_version(&recipe)?; - let containerfile = generate_containerfile_path(recipe_path)?; - let tags = recipe.generate_tags(os_version); + let containerfile = PathBuf::from(CONTAINER_FILE); + let tags = Driver::generate_tags(&recipe)?; let image_name = self.generate_full_image_name(&recipe)?; let opts = if let Some(archive_dir) = self.archive.as_ref() { @@ -290,7 +284,7 @@ impl BuildCommand { BuildTagPushOpts::builder() .image(&image_name) .containerfile(&containerfile) - .tags(tags.iter().map(String::as_str).collect::>()) + .tags(&tags) .push(self.push) .retry_push(self.retry_push) .retry_count(self.retry_count) @@ -299,105 +293,45 @@ impl BuildCommand { .build() }; - Driver::get_build_driver().build_tag_push(&opts)?; + Driver::build_tag_push(&opts)?; if self.push && !self.no_sign { - sign_images(&image_name, tags.first().map(String::as_str))?; + let opts = SignVerifyOpts::builder() + .image(&image_name) + .retry_push(self.retry_push) + .retry_count(self.retry_count); + let opts = if let Some(tag) = tags.first() { + opts.tag(tag).build() + } else { + opts.build() + }; + Driver::sign_and_verify(&opts)?; } info!("Build complete!"); Ok(()) } - fn login() -> Result<()> { - trace!("BuildCommand::login()"); - info!("Attempting to login to the registry"); - - if let Some(Credentials { - registry, - username, - password, - }) = credentials::get() - { - info!("Logging into the registry, {registry}"); - Driver::get_build_driver().login()?; - - trace!("cosign login -u {username} -p [MASKED] {registry}"); - let login_output = Command::new("cosign") - .arg("login") - .arg("-u") - .arg(username) - .arg("-p") - .arg(password) - .arg(registry) - .output() - .into_diagnostic()?; - - if !login_output.status.success() { - let err_output = String::from_utf8_lossy(&login_output.stderr); - bail!("Failed to login for cosign: {err_output}"); - } - info!("Login success at {registry}"); - } - - Ok(()) - } - /// # Errors /// /// Will return `Err` if the image name cannot be generated. - pub fn generate_full_image_name(&self, recipe: &Recipe) -> Result { + fn generate_full_image_name(&self, recipe: &Recipe) -> Result { trace!("BuildCommand::generate_full_image_name({recipe:#?})"); info!("Generating full image name"); - let image_name = match ( - env::var(CI_REGISTRY).ok().map(|s| s.to_lowercase()), - env::var(CI_PROJECT_NAMESPACE) - .ok() - .map(|s| s.to_lowercase()), - env::var(CI_PROJECT_NAME).ok().map(|s| s.to_lowercase()), - env::var(GITHUB_REPOSITORY_OWNER) - .ok() - .map(|s| s.to_lowercase()), - self.registry.as_ref().map(|s| s.to_lowercase()), + let image_name = if let (Some(registry), Some(registry_path)) = ( + self.credentials.registry.as_ref().map(|r| r.to_lowercase()), self.registry_namespace.as_ref().map(|s| s.to_lowercase()), ) { - (_, _, _, _, Some(registry), Some(registry_path)) => { - trace!("registry={registry}, registry_path={registry_path}"); - format!( - "{}/{}/{}", - registry.trim().trim_matches('/'), - registry_path.trim().trim_matches('/'), - recipe.name.trim(), - ) - } - ( - Some(ci_registry), - Some(ci_project_namespace), - Some(ci_project_name), - None, - None, - None, - ) => { - trace!("CI_REGISTRY={ci_registry}, CI_PROJECT_NAMESPACE={ci_project_namespace}, CI_PROJECT_NAME={ci_project_name}"); - warn!("Generating Gitlab Registry image"); - format!( - "{ci_registry}/{ci_project_namespace}/{ci_project_name}/{}", - recipe.name.trim().to_lowercase() - ) - } - (None, None, None, Some(github_repository_owner), None, None) => { - trace!("GITHUB_REPOSITORY_OWNER={github_repository_owner}"); - warn!("Generating Github Registry image"); - format!("ghcr.io/{github_repository_owner}/{}", &recipe.name) - } - _ => { - trace!("Nothing to indicate an image name with a registry"); - if self.push { - bail!("Need '--registry' and '--registry-namespace' in order to push image"); - } - recipe.name.trim().to_lowercase() - } + trace!("registry={registry}, registry_path={registry_path}"); + format!( + "{}/{}/{}", + registry.trim().trim_matches('/'), + registry_path.trim().trim_matches('/'), + recipe.name.trim(), + ) + } else { + Driver::generate_image_name(recipe)? }; debug!("Using image name '{image_name}'"); @@ -478,296 +412,4 @@ impl BuildCommand { Ok(()) } - /// Checks the cosign private/public key pair to ensure they match. - /// - /// # Errors - /// Will error if it's unable to verify key pairs. - fn check_cosign_files(&self) -> Result<()> { - trace!("check_for_cosign_files()"); - - if self.no_sign { - Ok(()) - } else { - env::set_var("COSIGN_PASSWORD", ""); - env::set_var("COSIGN_YES", "true"); - - match ( - env::var(COSIGN_PRIVATE_KEY).ok(), - Path::new(COSIGN_PRIV_PATH), - ) { - (Some(cosign_priv_key), _) - if !cosign_priv_key.is_empty() && Path::new(COSIGN_PUB_PATH).exists() => - { - trace!("cosign public-key --key env://COSIGN_PRIVATE_KEY"); - let output = Command::new("cosign") - .arg("public-key") - .arg("--key=env://COSIGN_PRIVATE_KEY") - .output() - .into_diagnostic()?; - - if !output.status.success() { - bail!( - "Failed to run cosign public-key: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - let calculated_pub_key = String::from_utf8(output.stdout).into_diagnostic()?; - let found_pub_key = fs::read_to_string(COSIGN_PUB_PATH) - .into_diagnostic() - .with_context(|| format!("Failed to read {COSIGN_PUB_PATH}"))?; - trace!("calculated_pub_key={calculated_pub_key},found_pub_key={found_pub_key}"); - - if calculated_pub_key.trim() == found_pub_key.trim() { - debug!("Cosign files match, continuing build"); - Ok(()) - } else { - bail!("Public key '{COSIGN_PUB_PATH}' does not match private key") - } - } - (None, cosign_priv_key_path) if cosign_priv_key_path.exists() => { - trace!("cosign public-key --key {COSIGN_PRIV_PATH}"); - let output = Command::new("cosign") - .arg("public-key") - .arg(format!("--key={COSIGN_PRIV_PATH}")) - .output() - .into_diagnostic()?; - - if !output.status.success() { - bail!( - "Failed to run cosign public-key: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - let calculated_pub_key = String::from_utf8(output.stdout).into_diagnostic()?; - let found_pub_key = fs::read_to_string(COSIGN_PUB_PATH) - .into_diagnostic() - .with_context(|| format!("Failed to read {COSIGN_PUB_PATH}"))?; - trace!("calculated_pub_key={calculated_pub_key},found_pub_key={found_pub_key}"); - - if calculated_pub_key.trim() == found_pub_key.trim() { - debug!("Cosign files match, continuing build"); - Ok(()) - } else { - bail!("Public key '{COSIGN_PUB_PATH}' does not match private key") - } - } - _ => { - bail!("Unable to find private/public key pair.\n\nMake sure you have a `{COSIGN_PUB_PATH}` in the root of your repo and have either {COSIGN_PRIVATE_KEY} set in your env variables or a `{COSIGN_PRIV_PATH}` file in the root of your repo.\n\nSee https://blue-build.org/how-to/cosign/ for more information.\n\nIf you don't want to sign your image, use the `--no-sign` flag."); - } - } - } - } -} - -// ======================================================== // -// ========================= Helpers ====================== // -// ======================================================== // - -#[allow(clippy::too_many_lines)] -fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> { - trace!("BuildCommand::sign_images({image_name}, {tag:?})"); - - env::set_var("COSIGN_PASSWORD", ""); - env::set_var("COSIGN_YES", "true"); - - let inspect_opts = GetMetadataOpts::builder().image(image_name); - - let inspect_opts = if let Some(tag) = tag { - inspect_opts.tag(tag).build() - } else { - inspect_opts.build() - }; - - let image_digest = Driver::get_inspection_driver() - .get_metadata(&inspect_opts)? - .digest; - let image_name_digest = format!("{image_name}@{image_digest}"); - let image_name_tag = tag.map_or_else(|| image_name.to_owned(), |t| format!("{image_name}:{t}")); - - match ( - // GitLab specific vars - env::var(CI_DEFAULT_BRANCH), - env::var(CI_PROJECT_URL), - env::var(CI_SERVER_PROTOCOL), - env::var(CI_SERVER_HOST), - env::var(SIGSTORE_ID_TOKEN), - // GitHub specific vars - env::var(GITHUB_TOKEN), - env::var(GITHUB_WORKFLOW_REF), - // Cosign public/private key pair - env::var(COSIGN_PRIVATE_KEY), - Path::new(COSIGN_PRIV_PATH), - ) { - // Cosign public/private key pair - (_, _, _, _, _, _, _, Ok(cosign_private_key), _) - if !cosign_private_key.is_empty() && Path::new(COSIGN_PUB_PATH).exists() => - { - sign_priv_public_pair_env(&image_name_digest, &image_name_tag)?; - } - (_, _, _, _, _, _, _, _, cosign_priv_key_path) if cosign_priv_key_path.exists() => { - sign_priv_public_pair_file(&image_name_digest, &image_name_tag)?; - } - // Gitlab keyless - ( - Ok(ci_default_branch), - Ok(ci_project_url), - Ok(ci_server_protocol), - Ok(ci_server_host), - Ok(_), - _, - _, - _, - _, - ) => { - trace!("CI_PROJECT_URL={ci_project_url}, CI_DEFAULT_BRANCH={ci_default_branch}, CI_SERVER_PROTOCOL={ci_server_protocol}, CI_SERVER_HOST={ci_server_host}"); - - info!("Signing image: {image_name_digest}"); - - trace!("cosign sign {image_name_digest}"); - - if Command::new("cosign") - .arg("sign") - .arg("--recursive") - .arg(&image_name_digest) - .status() - .into_diagnostic()? - .success() - { - info!("Successfully signed image!"); - } else { - bail!("Failed to sign image: {image_name_digest}"); - } - - let cert_ident = - format!("{ci_project_url}//.gitlab-ci.yml@refs/heads/{ci_default_branch}"); - - let cert_oidc = format!("{ci_server_protocol}://{ci_server_host}"); - - trace!("cosign verify --certificate-identity {cert_ident} --certificate-oidc-issuer {cert_oidc} {image_name_tag}"); - - if !Command::new("cosign") - .arg("verify") - .arg("--certificate-identity") - .arg(&cert_ident) - .arg("--certificate-oidc-issuer") - .arg(&cert_oidc) - .arg(&image_name_tag) - .status() - .into_diagnostic()? - .success() - { - bail!("Failed to verify image!"); - } - } - // GitHub keyless - (_, _, _, _, _, Ok(_), Ok(github_worflow_ref), _, _) => { - trace!("GITHUB_WORKFLOW_REF={github_worflow_ref}"); - - info!("Signing image {image_name_digest}"); - - trace!("cosign sign {image_name_digest}"); - if Command::new("cosign") - .arg("sign") - .arg("--recursive") - .arg(&image_name_digest) - .status() - .into_diagnostic()? - .success() - { - info!("Successfully signed image!"); - } else { - bail!("Failed to sign image: {image_name_digest}"); - } - - trace!("cosign verify --certificate-identity-regexp {github_worflow_ref} --certificate-oidc-issuer {GITHUB_TOKEN_ISSUER_URL} {image_name_tag}"); - if !Command::new("cosign") - .arg("verify") - .arg("--certificate-identity-regexp") - .arg(&github_worflow_ref) - .arg("--certificate-oidc-issuer") - .arg(GITHUB_TOKEN_ISSUER_URL) - .arg(&image_name_tag) - .status() - .into_diagnostic()? - .success() - { - bail!("Failed to verify image!"); - } - } - _ => warn!("Not running in CI with cosign variables, not signing"), - } - - Ok(()) -} - -fn sign_priv_public_pair_env(image_digest: &str, image_name_tag: &str) -> Result<()> { - info!("Signing image: {image_digest}"); - - trace!("cosign sign --key=env://{COSIGN_PRIVATE_KEY} {image_digest}"); - - if Command::new("cosign") - .arg("sign") - .arg("--key=env://COSIGN_PRIVATE_KEY") - .arg("--recursive") - .arg(image_digest) - .status() - .into_diagnostic()? - .success() - { - info!("Successfully signed image!"); - } else { - bail!("Failed to sign image: {image_digest}"); - } - - trace!("cosign verify --key {COSIGN_PUB_PATH} {image_name_tag}"); - - if !Command::new("cosign") - .arg("verify") - .arg(format!("--key={COSIGN_PUB_PATH}")) - .arg(image_name_tag) - .status() - .into_diagnostic()? - .success() - { - bail!("Failed to verify image!"); - } - - Ok(()) -} - -fn sign_priv_public_pair_file(image_digest: &str, image_name_tag: &str) -> Result<()> { - info!("Signing image: {image_digest}"); - - trace!("cosign sign --key={COSIGN_PRIV_PATH} {image_digest}"); - - if Command::new("cosign") - .arg("sign") - .arg(format!("--key={COSIGN_PRIV_PATH}")) - .arg("--recursive") - .arg(image_digest) - .status() - .into_diagnostic()? - .success() - { - info!("Successfully signed image!"); - } else { - bail!("Failed to sign image: {image_digest}"); - } - - trace!("cosign verify --key {COSIGN_PUB_PATH} {image_name_tag}"); - - if !Command::new("cosign") - .arg("verify") - .arg(format!("--key={COSIGN_PUB_PATH}")) - .arg(image_name_tag) - .status() - .into_diagnostic()? - .success() - { - bail!("Failed to verify image!"); - } - - Ok(()) } diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 1f3170c..b6fec42 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -3,13 +3,11 @@ use std::{ path::{Path, PathBuf}, }; +use blue_build_process_management::drivers::{CiDriver, Driver, DriverArgs}; use blue_build_recipe::Recipe; use blue_build_template::{ContainerFileTemplate, Template}; use blue_build_utils::{ - constants::{ - CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CONFIG_PATH, GITHUB_REPOSITORY_OWNER, - RECIPE_FILE, RECIPE_PATH, - }, + constants::{CONFIG_PATH, RECIPE_FILE, RECIPE_PATH}, syntax_highlighting::{self, DefaultThemes}, }; use clap::{crate_version, Args}; @@ -17,9 +15,9 @@ use log::{debug, info, trace, warn}; use miette::{IntoDiagnostic, Result}; use typed_builder::TypedBuilder; -use crate::{drivers::Driver, shadow}; +use crate::shadow; -use super::{BlueBuildCommand, DriverArgs}; +use super::BlueBuildCommand; #[derive(Debug, Clone, Args, TypedBuilder)] pub struct GenerateCommand { @@ -73,11 +71,7 @@ pub struct GenerateCommand { impl BlueBuildCommand for GenerateCommand { fn try_run(&mut self) -> Result<()> { - Driver::builder() - .build_driver(self.drivers.build_driver) - .inspect_driver(self.drivers.inspect_driver) - .build() - .init(); + Driver::init(self.drivers); self.template_file() } @@ -119,7 +113,8 @@ impl GenerateCommand { .build_id(Driver::get_build_id()) .recipe(&recipe_de) .recipe_path(recipe_path.as_path()) - .registry(self.get_registry()) + .registry(Driver::get_registry()?) + .repo(Driver::get_repo_url()?) .exports_tag({ #[allow(clippy::const_is_empty)] if shadow::COMMIT_HASH.is_empty() { @@ -146,37 +141,6 @@ impl GenerateCommand { Ok(()) } - - fn get_registry(&self) -> String { - match ( - self.registry.as_ref(), - self.registry_namespace.as_ref(), - Self::get_github_repo_owner(), - Self::get_gitlab_registry_path(), - ) { - (Some(r), Some(rn), _, _) => format!("{r}/{rn}"), - (Some(r), None, _, _) => r.to_string(), - (None, None, Some(gh_repo_owner), None) => format!("ghcr.io/{gh_repo_owner}"), - (None, None, None, Some(gl_reg_path)) => gl_reg_path, - _ => "localhost".to_string(), - } - } - - fn get_github_repo_owner() -> Option { - Some(env::var(GITHUB_REPOSITORY_OWNER).ok()?.to_lowercase()) - } - - fn get_gitlab_registry_path() -> Option { - Some( - format!( - "{}/{}/{}", - env::var(CI_REGISTRY).ok()?, - env::var(CI_PROJECT_NAMESPACE).ok()?, - env::var(CI_PROJECT_NAME).ok()?, - ) - .to_lowercase(), - ) - } } // ======================================================== // diff --git a/src/commands/local.rs b/src/commands/local.rs index 6f225e7..4617404 100644 --- a/src/commands/local.rs +++ b/src/commands/local.rs @@ -1,11 +1,14 @@ use std::{ fs, path::{Path, PathBuf}, - process::Command, }; +use blue_build_process_management::drivers::DriverArgs; use blue_build_recipe::Recipe; -use blue_build_utils::constants::{ARCHIVE_SUFFIX, LOCAL_BUILD}; +use blue_build_utils::{ + cmd, + constants::{ARCHIVE_SUFFIX, LOCAL_BUILD}, +}; use clap::Args; use log::{debug, info, trace}; use miette::{bail, IntoDiagnostic, Result}; @@ -14,7 +17,7 @@ use users::{Users, UsersCache}; use crate::commands::build::BuildCommand; -use super::{BlueBuildCommand, DriverArgs}; +use super::BlueBuildCommand; #[derive(Default, Clone, Debug, TypedBuilder, Args)] pub struct LocalCommonArgs { @@ -81,19 +84,14 @@ impl BlueBuildCommand for UpgradeCommand { info!("Upgrading image {image_name} and rebooting"); trace!("rpm-ostree upgrade --reboot"); - Command::new("rpm-ostree") - .arg("upgrade") - .arg("--reboot") + cmd!("rpm-ostree", "upgrade", "--reboot") .status() .into_diagnostic()? } else { info!("Upgrading image {image_name}"); trace!("rpm-ostree upgrade"); - Command::new("rpm-ostree") - .arg("upgrade") - .status() - .into_diagnostic()? + cmd!("rpm-ostree", "upgrade").status().into_diagnostic()? }; if status.success() { @@ -146,19 +144,14 @@ impl BlueBuildCommand for RebaseCommand { info!("Rebasing image {image_name} and rebooting"); trace!("rpm-ostree rebase --reboot {rebase_url}"); - Command::new("rpm-ostree") - .arg("rebase") - .arg("--reboot") - .arg(rebase_url) + cmd!("rpm-ostree", "rebase", "--reboot", rebase_url) .status() .into_diagnostic()? } else { info!("Rebasing image {image_name}"); trace!("rpm-ostree rebase {rebase_url}"); - Command::new("rpm-ostree") - .arg("rebase") - .arg(rebase_url) + cmd!("rpm-ostree", "rebase", rebase_url) .status() .into_diagnostic()? }; diff --git a/src/commands/login.rs b/src/commands/login.rs new file mode 100644 index 0000000..2f163df --- /dev/null +++ b/src/commands/login.rs @@ -0,0 +1,105 @@ +use std::io::{self, Read}; + +use blue_build_process_management::drivers::{BuildDriver, Driver, DriverArgs, SigningDriver}; +use blue_build_utils::credentials::{Credentials, CredentialsArgs}; +use clap::Args; +use miette::{bail, IntoDiagnostic, Result}; +use requestty::questions; +use typed_builder::TypedBuilder; + +use super::BlueBuildCommand; + +#[derive(Debug, Clone, Args, TypedBuilder)] +pub struct LoginCommand { + /// The server to login to. + server: String, + + /// The password to login with. + /// + /// Cannont be used with `--password-stdin`. + #[arg(group = "pass", long, short)] + password: Option, + + /// Read password from stdin, + /// + /// Cannot be used with `--password/-p` + #[arg(group = "pass", long)] + password_stdin: bool, + + /// The username to login with + #[arg(long, short)] + username: Option, + + #[clap(flatten)] + #[builder(default)] + drivers: DriverArgs, +} + +impl BlueBuildCommand for LoginCommand { + fn try_run(&mut self) -> miette::Result<()> { + Driver::init(self.drivers); + + Credentials::init( + CredentialsArgs::builder() + .registry(&self.server) + .username(self.get_username()?) + .password(self.get_password()?) + .build(), + ); + + Driver::login()?; + Driver::signing_login()?; + + Ok(()) + } +} + +impl LoginCommand { + fn get_username(&self) -> Result { + Ok(if let Some(ref username) = self.username { + username.clone() + } else if !self.password_stdin { + let questions = questions! [ inline + Input { + name: "username", + }, + ]; + + requestty::prompt(questions) + .into_diagnostic()? + .get("username") + .unwrap() + .as_string() + .unwrap() + .to_string() + } else { + bail!("Cannot prompt for username when using `--password-stdin`"); + }) + } + + fn get_password(&self) -> Result { + Ok(if let Some(ref password) = self.password { + password.clone() + } else if self.password_stdin { + let mut password = String::new(); + io::stdin() + .read_to_string(&mut password) + .into_diagnostic()?; + password + } else { + let questions = questions! [ inline + Password { + name: "password", + } + ]; + + requestty::prompt(questions) + .into_diagnostic()? + .get("password") + .unwrap() + .as_string() + .unwrap() + .to_string() + }) + } +} diff --git a/src/commands/switch.rs b/src/commands/switch.rs index c5a38c1..56b6f6b 100644 --- a/src/commands/switch.rs +++ b/src/commands/switch.rs @@ -1,13 +1,16 @@ use std::{ path::{Path, PathBuf}, - process::Command, time::Duration, }; +use blue_build_process_management::{ + drivers::{Driver, DriverArgs}, + logging::CommandLogging, +}; use blue_build_recipe::Recipe; use blue_build_utils::{ + cmd, constants::{ARCHIVE_SUFFIX, LOCAL_BUILD, OCI_ARCHIVE, OSTREE_UNVERIFIED_IMAGE}, - logging::CommandLogging, }; use clap::Args; use colored::Colorize; @@ -17,9 +20,9 @@ use miette::{bail, IntoDiagnostic, Result}; use tempdir::TempDir; use typed_builder::TypedBuilder; -use crate::{commands::build::BuildCommand, drivers::Driver, rpm_ostree_status::RpmOstreeStatus}; +use crate::{commands::build::BuildCommand, rpm_ostree_status::RpmOstreeStatus}; -use super::{BlueBuildCommand, DriverArgs}; +use super::BlueBuildCommand; #[derive(Default, Clone, Debug, TypedBuilder, Args)] pub struct SwitchCommand { @@ -51,11 +54,7 @@ impl BlueBuildCommand for SwitchCommand { fn try_run(&mut self) -> Result<()> { trace!("SwitchCommand::try_run()"); - Driver::builder() - .build_driver(self.drivers.build_driver) - .inspect_driver(self.drivers.inspect_driver) - .build() - .init(); + Driver::init(self.drivers); let status = RpmOstreeStatus::try_new()?; trace!("{status:?}"); @@ -110,17 +109,13 @@ impl SwitchCommand { let status = if status.is_booted_on_archive(archive_path) || status.is_staged_on_archive(archive_path) { - let mut command = Command::new("rpm-ostree"); - command.arg("upgrade"); + let mut command = cmd!("rpm-ostree", "upgrade"); if self.reboot { - command.arg("--reboot"); + cmd!(command, "--reboot"); } - trace!( - "rpm-ostree upgrade {}", - self.reboot.then_some("--reboot").unwrap_or_default() - ); + trace!("{command:?}"); command } else { let image_ref = format!( @@ -128,17 +123,13 @@ impl SwitchCommand { path = archive_path.display() ); - let mut command = Command::new("rpm-ostree"); - command.arg("rebase").arg(&image_ref); + let mut command = cmd!("rpm-ostree", "rebase", &image_ref); if self.reboot { - command.arg("--reboot"); + cmd!(command, "--reboot"); } - trace!( - "rpm-ostree rebase{} {image_ref}", - self.reboot.then_some(" --reboot").unwrap_or_default() - ); + trace!("{command:?}"); command } .status_image_ref_progress( @@ -165,11 +156,7 @@ impl SwitchCommand { progress.set_message(format!("Moving image archive to {}...", to.display())); trace!("sudo mv {} {}", from.display(), to.display()); - let status = Command::new("sudo") - .arg("mv") - .args([from, to]) - .status() - .into_diagnostic()?; + let status = cmd!("sudo", "mv", from, to).status().into_diagnostic()?; progress.finish_and_clear(); @@ -194,8 +181,7 @@ impl SwitchCommand { trace!("sudo ls {LOCAL_BUILD}"); let output = String::from_utf8( - Command::new("sudo") - .args(["ls", LOCAL_BUILD]) + cmd!("sudo", "ls", LOCAL_BUILD) .output() .into_diagnostic()? .stdout, @@ -218,11 +204,7 @@ impl SwitchCommand { progress.set_message("Removing old image archive files..."); trace!("sudo rm -f {files}"); - let status = Command::new("sudo") - .args(["rm", "-f"]) - .arg(files) - .status() - .into_diagnostic()?; + let status = cmd!("sudo", "rm", "-f", files).status().into_diagnostic()?; progress.finish_and_clear(); @@ -236,8 +218,7 @@ impl SwitchCommand { local_build_path.display() ); - let status = Command::new("sudo") - .args(["mkdir", "-p", LOCAL_BUILD]) + let status = cmd!("sudo", "mkdir", "-p", LOCAL_BUILD) .status() .into_diagnostic()?; diff --git a/src/credentials.rs b/src/credentials.rs deleted file mode 100644 index 0376c59..0000000 --- a/src/credentials.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::{env, sync::Mutex}; - -use blue_build_utils::constants::{ - CI_REGISTRY, CI_REGISTRY_PASSWORD, CI_REGISTRY_USER, GITHUB_ACTIONS, GITHUB_ACTOR, GITHUB_TOKEN, -}; -use log::trace; -use once_cell::sync::Lazy; -use typed_builder::TypedBuilder; - -/// Stored user creds. -/// -/// This is a special handoff static ref that is consumed -/// by the `ENV_CREDENTIALS` static ref. This can be set -/// at the beginning of a command for future calls for -/// creds to source from. -static USER_CREDS: Mutex = Mutex::new(UserCreds { - username: None, - password: None, - registry: None, -}); - -/// The credentials for logging into image registries. -#[derive(Debug, Default, Clone, TypedBuilder)] -pub struct Credentials { - pub registry: String, - pub username: String, - pub password: String, -} - -struct UserCreds { - pub username: Option, - pub password: Option, - pub registry: Option, -} - -/// Stores the global env credentials. -/// -/// This on load will determine the credentials based off of -/// `USER_CREDS` and env vars from CI systems. Once this is called -/// the value is stored and cannot change. -/// -/// If you have user -/// provided credentials, make sure you update `USER_CREDS` -/// before trying to access this reference. -static ENV_CREDENTIALS: Lazy> = Lazy::new(|| { - let (username, password, registry) = { - USER_CREDS.lock().map_or((None, None, None), |creds| { - ( - creds.username.as_ref().map(std::borrow::ToOwned::to_owned), - creds.password.as_ref().map(std::borrow::ToOwned::to_owned), - creds.registry.as_ref().map(std::borrow::ToOwned::to_owned), - ) - }) - }; - - let registry = match ( - registry, - env::var(CI_REGISTRY).ok(), - env::var(GITHUB_ACTIONS).ok(), - ) { - (Some(registry), _, _) if !registry.is_empty() => registry, - (None, Some(ci_registry), None) if !ci_registry.is_empty() => ci_registry, - (None, None, Some(_)) => "ghcr.io".to_string(), - _ => return None, - }; - trace!("Registry: {registry}"); - - let username = match ( - username, - env::var(CI_REGISTRY_USER).ok(), - env::var(GITHUB_ACTOR).ok(), - ) { - (Some(username), _, _) if !username.is_empty() => username, - (None, Some(ci_registry_user), None) if !ci_registry_user.is_empty() => ci_registry_user, - (None, None, Some(github_actor)) if !github_actor.is_empty() => github_actor, - _ => return None, - }; - trace!("Username: {username}"); - - let password = match ( - password, - env::var(CI_REGISTRY_PASSWORD).ok(), - env::var(GITHUB_TOKEN).ok(), - ) { - (Some(password), _, _) if !password.is_empty() => password, - (None, Some(ci_registry_password), None) if !ci_registry_password.is_empty() => { - ci_registry_password - } - (None, None, Some(registry_token)) if !registry_token.is_empty() => registry_token, - _ => return None, - }; - - Some( - Credentials::builder() - .registry(registry) - .username(username) - .password(password) - .build(), - ) -}); - -/// Set the users credentials for -/// the current set of actions. -/// -/// Be sure to call this before trying to use -/// any strategy that requires credentials as -/// the environment credentials are lazy allocated. -/// -/// # Panics -/// Will panic if it can't lock the mutex. -pub fn set_user_creds( - username: Option<&String>, - password: Option<&String>, - registry: Option<&String>, -) { - trace!("credentials::set({username:?}, password, {registry:?})"); - let mut creds_lock = USER_CREDS.lock().expect("Must lock USER_CREDS"); - creds_lock.username = username.map(ToOwned::to_owned); - creds_lock.password = password.map(ToOwned::to_owned); - creds_lock.registry = registry.map(ToOwned::to_owned); - drop(creds_lock); - let _ = ENV_CREDENTIALS.as_ref(); -} - -/// Get the credentials for the current set of actions. -pub fn get() -> Option<&'static Credentials> { - trace!("credentials::get()"); - ENV_CREDENTIALS.as_ref() -} diff --git a/src/drivers.rs b/src/drivers.rs deleted file mode 100644 index 468e8f5..0000000 --- a/src/drivers.rs +++ /dev/null @@ -1,474 +0,0 @@ -//! This module is responsible for managing various strategies -//! to perform actions throughout the program. This hides all -//! the implementation details from the command logic and allows -//! for caching certain long execution tasks like inspecting the -//! labels for an image. - -use std::{ - collections::{hash_map::Entry, HashMap}, - process::{ExitStatus, Output}, - sync::{Arc, Mutex}, -}; - -use blue_build_recipe::Recipe; -use blue_build_utils::constants::IMAGE_VERSION_LABEL; -use log::{debug, info, trace}; -use miette::{bail, miette, Result}; -use once_cell::sync::Lazy; -use semver::{Version, VersionReq}; -use typed_builder::TypedBuilder; -use uuid::Uuid; - -use crate::{credentials, image_metadata::ImageMetadata}; - -use self::{ - buildah_driver::BuildahDriver, - docker_driver::DockerDriver, - opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts}, - podman_driver::PodmanDriver, - skopeo_driver::SkopeoDriver, - types::{BuildDriverType, InspectDriverType, RunDriverType}, -}; - -mod buildah_driver; -mod docker_driver; -pub mod opts; -mod podman_driver; -mod skopeo_driver; -pub mod types; - -static INIT: Lazy> = Lazy::new(|| Mutex::new(false)); -static SELECTED_BUILD_DRIVER: Lazy>> = Lazy::new(|| Mutex::new(None)); -static SELECTED_INSPECT_DRIVER: Lazy>> = - Lazy::new(|| Mutex::new(None)); -static SELECTED_RUN_DRIVER: Lazy>> = Lazy::new(|| Mutex::new(None)); - -/// Stores the build driver. -/// -/// This will, on load, find the best way to build in the -/// current environment. Once that strategy is determined, -/// it will be available for any part of the program to call -/// on to perform builds. -/// -/// # Panics -/// -/// This will cause a panic if a build strategy could -/// not be determined. -static BUILD_DRIVER: Lazy> = Lazy::new(|| { - let driver = SELECTED_BUILD_DRIVER.lock().unwrap(); - driver.map_or_else( - || panic!("Driver needs to be initialized"), - |driver| -> Arc { - match driver { - BuildDriverType::Buildah => Arc::new(BuildahDriver), - BuildDriverType::Podman => Arc::new(PodmanDriver), - BuildDriverType::Docker => Arc::new(DockerDriver), - } - }, - ) -}); - -/// Stores the inspection driver. -/// -/// This will, on load, find the best way to inspect images in the -/// current environment. Once that strategy is determined, -/// it will be available for any part of the program to call -/// on to perform inspections. -/// -/// # Panics -/// -/// This will cause a panic if a inspect strategy could -/// not be determined. -static INSPECT_DRIVER: Lazy> = Lazy::new(|| { - let driver = SELECTED_INSPECT_DRIVER.lock().unwrap(); - driver.map_or_else( - || panic!("Driver needs to be initialized"), - |driver| -> Arc { - match driver { - InspectDriverType::Skopeo => Arc::new(SkopeoDriver), - InspectDriverType::Podman => Arc::new(PodmanDriver), - InspectDriverType::Docker => Arc::new(DockerDriver), - } - }, - ) -}); - -/// Stores the run driver. -/// -/// This will, on load, find the best way to run containers in the -/// current environment. Once that strategy is determined, -/// it will be available for any part of the program to call -/// on to perform inspections. -/// -/// # Panics -/// -/// This will cause a panic if a run strategy could -/// not be determined. -static RUN_DRIVER: Lazy> = Lazy::new(|| { - let driver = SELECTED_RUN_DRIVER.lock().unwrap(); - driver.map_or_else( - || panic!("Driver needs to be initialized"), - |driver| -> Arc { - match driver { - RunDriverType::Podman => Arc::new(PodmanDriver), - RunDriverType::Docker => Arc::new(DockerDriver), - } - }, - ) -}); - -/// UUID used to mark the current builds -static BUILD_ID: Lazy = Lazy::new(Uuid::new_v4); - -/// The cached os versions -static OS_VERSION: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); - -/// Trait for retrieving version of a driver. -pub trait DriverVersion { - /// The version req string slice that follows - /// the semver standard . - const VERSION_REQ: &'static str; - - /// Returns the version of the driver. - /// - /// # Errors - /// Will error if it can't retrieve the version. - fn version() -> Result; - - #[must_use] - fn is_supported_version() -> bool { - Self::version().is_ok_and(|version| { - VersionReq::parse(Self::VERSION_REQ).is_ok_and(|req| req.matches(&version)) - }) - } -} - -/// Allows agnostic building, tagging -/// pushing, and login. -pub trait BuildDriver: Sync + Send { - /// Runs the build logic for the strategy. - /// - /// # Errors - /// Will error if the build fails. - fn build(&self, opts: &BuildOpts) -> Result<()>; - - /// Runs the tag logic for the strategy. - /// - /// # Errors - /// Will error if the tagging fails. - fn tag(&self, opts: &TagOpts) -> Result<()>; - - /// Runs the push logic for the strategy - /// - /// # Errors - /// Will error if the push fails. - fn push(&self, opts: &PushOpts) -> Result<()>; - - /// Runs the login logic for the strategy. - /// - /// # Errors - /// Will error if login fails. - fn login(&self) -> Result<()>; - - /// Runs the logic for building, tagging, and pushing an image. - /// - /// # Errors - /// Will error if building, tagging, or pusing fails. - fn build_tag_push(&self, opts: &BuildTagPushOpts) -> Result<()> { - trace!("BuildDriver::build_tag_push({opts:#?})"); - - let full_image = match (opts.archive_path.as_ref(), opts.image.as_ref()) { - (Some(archive_path), None) => { - format!("oci-archive:{archive_path}") - } - (None, Some(image)) => opts - .tags - .first() - .map_or_else(|| image.to_string(), |tag| format!("{image}:{tag}")), - (Some(_), Some(_)) => bail!("Cannot use both image and archive path"), - (None, None) => bail!("Need either the image or archive path set"), - }; - - let build_opts = BuildOpts::builder() - .image(&full_image) - .containerfile(opts.containerfile.as_ref()) - .squash(opts.squash) - .build(); - - info!("Building image {full_image}"); - self.build(&build_opts)?; - - if !opts.tags.is_empty() && opts.archive_path.is_none() { - let image = opts - .image - .as_ref() - .ok_or_else(|| miette!("Image is required in order to tag"))?; - debug!("Tagging all images"); - - for tag in opts.tags.as_ref() { - debug!("Tagging {} with {tag}", &full_image); - - let tag_opts = TagOpts::builder() - .src_image(&full_image) - .dest_image(format!("{image}:{tag}")) - .build(); - - self.tag(&tag_opts)?; - - if opts.push { - let retry_count = if opts.retry_push { opts.retry_count } else { 0 }; - - debug!("Pushing all images"); - // Push images with retries (1s delay between retries) - blue_build_utils::retry(retry_count, 10, || { - let tag_image = format!("{image}:{tag}"); - - debug!("Pushing image {tag_image}"); - - let push_opts = PushOpts::builder() - .image(&tag_image) - .compression_type(opts.compression) - .build(); - - self.push(&push_opts) - })?; - } - } - } - - Ok(()) - } -} - -pub trait RunDriver: Sync + Send { - /// Run a container to perform an action. - /// - /// # Errors - /// Will error if there is an issue running the container. - fn run(&self, opts: &RunOpts) -> std::io::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(&self, opts: &RunOpts) -> std::io::Result; -} - -/// Allows agnostic inspection of images. -pub trait InspectDriver: Sync + Send { - /// Gets the metadata on an image tag. - /// - /// # Errors - /// Will error if it is unable to get the labels. - fn get_metadata(&self, opts: &GetMetadataOpts) -> Result; -} - -#[derive(Debug, TypedBuilder)] -pub struct Driver<'a> { - #[builder(default)] - username: Option<&'a String>, - - #[builder(default)] - password: Option<&'a String>, - - #[builder(default)] - registry: Option<&'a String>, - - #[builder(default)] - build_driver: Option, - - #[builder(default)] - inspect_driver: Option, - - #[builder(default)] - run_driver: Option, -} - -impl Driver<'_> { - /// Initializes the Strategy with user provided credentials. - /// - /// If you want to take advantage of a user's credentials, - /// you will want to run init before trying to use any of - /// the strategies. - /// - /// # Panics - /// Will panic if it is unable to initialize drivers. - pub fn init(self) { - trace!("Driver::init()"); - let mut initialized = INIT.lock().expect("Must lock INIT"); - - if !*initialized { - credentials::set_user_creds(self.username, self.password, self.registry); - - let mut build_driver = SELECTED_BUILD_DRIVER.lock().expect("Must lock BuildDriver"); - let mut inspect_driver = SELECTED_INSPECT_DRIVER - .lock() - .expect("Must lock InspectDriver"); - let mut run_driver = SELECTED_RUN_DRIVER.lock().expect("Must lock RunDriver"); - - *build_driver = Some( - self.build_driver - .map_or_else(Self::determine_build_driver, |driver| driver), - ); - trace!("Build driver set to {:?}", *build_driver); - drop(build_driver); - let _ = Self::get_build_driver(); - - *inspect_driver = Some( - self.inspect_driver - .map_or_else(Self::determine_inspect_driver, |driver| driver), - ); - trace!("Inspect driver set to {:?}", *inspect_driver); - drop(inspect_driver); - let _ = Self::get_inspection_driver(); - - *run_driver = Some( - self.run_driver - .map_or_else(Self::determine_run_driver, |driver| driver), - ); - drop(run_driver); - let _ = Self::get_run_driver(); - - *initialized = true; - } - } - - /// Gets the current build's UUID - #[must_use] - pub fn get_build_id() -> Uuid { - trace!("Driver::get_build_id()"); - *BUILD_ID - } - - /// Gets the current run's build strategy - pub fn get_build_driver() -> Arc { - trace!("Driver::get_build_driver()"); - BUILD_DRIVER.clone() - } - - /// Gets the current run's inspectioin strategy - pub fn get_inspection_driver() -> Arc { - trace!("Driver::get_inspection_driver()"); - INSPECT_DRIVER.clone() - } - - pub fn get_run_driver() -> Arc { - trace!("Driver::get_run_driver()"); - RUN_DRIVER.clone() - } - - /// Retrieve the `os_version` for an image. - /// - /// This gets cached for faster resolution if it's required - /// in another part of the program. - /// - /// # Errors - /// Will error if the image doesn't have OS version info - /// or we are unable to lock a mutex. - /// - /// # Panics - /// Panics if the mutex fails to lock. - pub fn get_os_version(recipe: &Recipe) -> Result { - trace!("Driver::get_os_version({recipe:#?})"); - let image = format!("{}:{}", &recipe.base_image, &recipe.image_version); - - let mut os_version_lock = OS_VERSION.lock().expect("Should lock"); - - let entry = os_version_lock.get(&image); - - let os_version = match entry { - None => { - info!("Retrieving OS version from {image}. This might take a bit"); - let inspect_opts = GetMetadataOpts::builder() - .image(recipe.base_image.as_ref()) - .tag(recipe.image_version.as_ref()) - .build(); - let inspection = INSPECT_DRIVER.get_metadata(&inspect_opts)?; - - let os_version = inspection.get_version().ok_or_else(|| { - miette!( - help = format!("Please check with the image author about using '{IMAGE_VERSION_LABEL}' to report the os version."), - "Unable to get the OS version from the labels" - ) - })?; - trace!("os_version: {os_version}"); - - os_version - } - Some(os_version) => { - debug!("Found cached {os_version} for {image}"); - *os_version - } - }; - - if let Entry::Vacant(entry) = os_version_lock.entry(image.clone()) { - trace!("Caching version {os_version} for {image}"); - entry.insert(os_version); - } - drop(os_version_lock); - Ok(os_version) - } - - fn determine_inspect_driver() -> InspectDriverType { - trace!("Driver::determine_inspect_driver()"); - - 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"), - } - } - - fn determine_build_driver() -> BuildDriverType { - trace!("Driver::determine_build_driver()"); - - 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() => { - 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, need either docker version {}, podman version {}, or buildah version {} to continue", - DockerDriver::VERSION_REQ, - PodmanDriver::VERSION_REQ, - BuildahDriver::VERSION_REQ, - ), - } - } - - fn determine_run_driver() -> RunDriverType { - trace!("Driver::determine_run_driver()"); - - 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 - ), - ), - } - } -} diff --git a/src/drivers/opts/run.rs b/src/drivers/opts/run.rs deleted file mode 100644 index d381336..0000000 --- a/src/drivers/opts/run.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::borrow::Cow; - -use typed_builder::TypedBuilder; - -#[derive(Debug, Clone, TypedBuilder)] -pub struct RunOpts<'a> { - #[builder(default, setter(into))] - pub image: Cow<'a, str>, - - #[builder(default, setter(into))] - pub args: Cow<'a, [String]>, - - #[builder(default, setter(into))] - pub env_vars: Cow<'a, [RunOptsEnv<'a>]>, - - #[builder(default, setter(into))] - pub volumes: Cow<'a, [RunOptsVolume<'a>]>, - - #[builder(default)] - pub privileged: bool, - - #[builder(default)] - pub pull: bool, - - #[builder(default)] - pub remove: bool, -} - -#[derive(Debug, Clone, TypedBuilder)] -pub struct RunOptsVolume<'a> { - #[builder(setter(into))] - pub path_or_vol_name: Cow<'a, str>, - - #[builder(setter(into))] - pub container_path: Cow<'a, str>, -} - -#[derive(Debug, Clone, TypedBuilder)] -pub struct RunOptsEnv<'a> { - #[builder(setter(into))] - pub key: Cow<'a, str>, - - #[builder(setter(into))] - pub value: Cow<'a, str>, -} diff --git a/src/drivers/types.rs b/src/drivers/types.rs deleted file mode 100644 index 25eee8c..0000000 --- a/src/drivers/types.rs +++ /dev/null @@ -1,30 +0,0 @@ -use clap::ValueEnum; - -#[derive(Debug, Clone, Copy, ValueEnum)] -pub enum InspectDriverType { - Skopeo, - Podman, - Docker, -} - -#[derive(Debug, Clone, Copy, ValueEnum)] -pub enum BuildDriverType { - Buildah, - Podman, - Docker, -} - -#[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(), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index f5f546f..0676261 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,4 @@ shadow_rs::shadow!(shadow); pub mod commands; -pub mod credentials; -pub mod drivers; -pub mod image_metadata; pub mod rpm_ostree_status; diff --git a/src/rpm_ostree_status.rs b/src/rpm_ostree_status.rs index c961a95..115ffe0 100644 --- a/src/rpm_ostree_status.rs +++ b/src/rpm_ostree_status.rs @@ -1,5 +1,6 @@ -use std::{borrow::Cow, path::Path, process::Command}; +use std::{borrow::Cow, path::Path}; +use blue_build_utils::cmd; use log::trace; use miette::{bail, IntoDiagnostic, Result}; use serde::Deserialize; @@ -27,8 +28,7 @@ impl<'a> RpmOstreeStatus<'a> { blue_build_utils::check_command_exists("rpm-ostree")?; trace!("rpm-ostree status --json"); - let output = Command::new("rpm-ostree") - .args(["status", "--json"]) + let output = cmd!("rpm-ostree", "status", "--json") .output() .into_diagnostic()?; diff --git a/template/Cargo.toml b/template/Cargo.toml index b49749b..c8f8a77 100644 --- a/template/Cargo.toml +++ b/template/Cargo.toml @@ -14,9 +14,6 @@ blue-build-recipe = { version = "=0.8.12", path = "../recipe" } blue-build-utils = { version = "=0.8.12", path = "../utils" } log.workspace = true -serde.workspace = true -serde_yaml.workspace = true -serde_json.workspace = true typed-builder.workspace = true uuid.workspace = true diff --git a/template/src/lib.rs b/template/src/lib.rs index f7bc0fe..06ff7f9 100644 --- a/template/src/lib.rs +++ b/template/src/lib.rs @@ -1,10 +1,8 @@ -use std::{borrow::Cow, env, fs, path::Path, process}; +use std::{borrow::Cow, fs, path::Path, process}; use blue_build_recipe::Recipe; use blue_build_utils::constants::{ - CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_SERVER_HOST, CI_SERVER_PROTOCOL, CONFIG_PATH, - CONTAINERFILES_PATH, CONTAINER_FILE, COSIGN_PUB_PATH, FILES_PATH, GITHUB_RESPOSITORY, - GITHUB_SERVER_URL, + CONFIG_PATH, CONTAINERFILES_PATH, CONTAINER_FILE, COSIGN_PUB_PATH, FILES_PATH, }; use log::{debug, error, trace, warn}; use typed_builder::TypedBuilder; @@ -30,6 +28,9 @@ pub struct ContainerFileTemplate<'a> { #[builder(setter(into))] exports_tag: Cow<'a, str>, + + #[builder(setter(into))] + repo: Cow<'a, str>, } #[derive(Debug, Clone, Template, TypedBuilder)] @@ -78,6 +79,19 @@ pub struct GithubIssueTemplate<'a> { terminal_version: Cow<'a, str>, } +#[derive(Debug, Clone, Template, TypedBuilder)] +#[template(path = "init/README.j2", escape = "md")] +pub struct InitReadmeTemplate<'a> { + #[builder(setter(into))] + repo_name: Cow<'a, str>, + + #[builder(setter(into))] + registry: Cow<'a, str>, + + #[builder(setter(into))] + image_name: Cow<'a, str>, +} + fn has_cosign_file() -> bool { trace!("has_cosign_file()"); std::env::current_dir() @@ -110,38 +124,6 @@ fn print_containerfile(containerfile: &str) -> String { file } -fn get_repo_url() -> Option { - Some( - match ( - // GitHub vars - env::var(GITHUB_SERVER_URL), - env::var(GITHUB_RESPOSITORY), - // GitLab vars - env::var(CI_SERVER_PROTOCOL), - env::var(CI_SERVER_HOST), - env::var(CI_PROJECT_NAMESPACE), - env::var(CI_PROJECT_NAME), - ) { - (Ok(github_server), Ok(github_repo), _, _, _, _) => { - format!("{github_server}/{github_repo}") - } - ( - _, - _, - Ok(ci_server_protocol), - Ok(ci_server_host), - Ok(ci_project_namespace), - Ok(ci_project_name), - ) => { - format!( - "{ci_server_protocol}://{ci_server_host}/{ci_project_namespace}/{ci_project_name}" - ) - } - _ => return None, - }, - ) -} - fn modules_exists() -> bool { let mod_path = Path::new("modules"); mod_path.exists() && mod_path.is_dir() diff --git a/template/templates/Containerfile.j2 b/template/templates/Containerfile.j2 index cd17bd3..5640625 100644 --- a/template/templates/Containerfile.j2 +++ b/template/templates/Containerfile.j2 @@ -38,7 +38,5 @@ RUN rm -fr /tmp/* /var/* && ostree container commit LABEL {{ blue_build_utils::constants::BUILD_ID_LABEL }}="{{ build_id }}" LABEL org.opencontainers.image.title="{{ recipe.name }}" LABEL org.opencontainers.image.description="{{ recipe.description }}" -{%- if let Some(repo) = self::get_repo_url() %} LABEL org.opencontainers.image.source="{{ repo }}" -{%- endif %} LABEL io.artifacthub.package.readme-url=https://raw.githubusercontent.com/blue-build/cli/main/README.md diff --git a/template/templates/init/README.j2 b/template/templates/init/README.j2 new file mode 100644 index 0000000..37e0eaa --- /dev/null +++ b/template/templates/init/README.j2 @@ -0,0 +1,45 @@ +# {{ repo_name }} Image Repo + +See the [BlueBuild docs](https://blue-build.org/how-to/setup/) for quick setup instructions for setting up your own repository based on this template. + +After setup, it is recommended you update this README to describe your custom image. + +## Installation + +> **Warning** +> [This is an experimental feature](https://www.fedoraproject.org/wiki/Changes/OstreeNativeContainerStable), try at your own discretion. + +To rebase an existing atomic Fedora installation to the latest build: + +- First rebase to the unsigned image, to get the proper signing keys and policies installed: + ``` + rpm-ostree rebase ostree-unverified-registry:{{ registry }}/{{ repo_name }}/{{ image_name }}:latest + ``` +- Reboot to complete the rebase: + ``` + systemctl reboot + ``` +- Then rebase to the signed image, like so: + ``` + rpm-ostree rebase ostree-image-signed:docker://{{ registry }}/{{ repo_name }}/{{ image_name }}:latest + ``` +- Reboot again to complete the installation + ``` + systemctl reboot + ``` + +The `latest` tag will automatically point to the latest build. That build will still always use the Fedora version specified in `recipe.yml`, so you won't get accidentally updated to the next major version. + +## ISO + +If build on Fedora Atomic, you can generate an offline ISO with the instructions available [here](https://blue-build.org/learn/universal-blue/#fresh-install-from-an-iso). These ISOs cannot unfortunately be distributed on GitHub for free due to large sizes, so for public projects something else has to be used for hosting. + +## Verification + +These images are signed with [Sigstore](https://www.sigstore.dev/)'s [cosign](https://github.com/sigstore/cosign). You can verify the signature by downloading the `cosign.pub` file from this repo and running the following command: + +```bash +cosign verify --key cosign.pub {{ registry }}/{{ repo_name }}/{{ image_name }} +``` + +Cloned from https://github.com/blue-build/template diff --git a/test-files/github-events/branch.json b/test-files/github-events/branch.json new file mode 100644 index 0000000..99e28cb --- /dev/null +++ b/test-files/github-events/branch.json @@ -0,0 +1,10 @@ +{ + "ref": "refs/heads/test-branch", + "repository": { + "default_branch": "main", + "owner": { + "login": "test-owner" + }, + "html_url": "https://example.com/" + } +} \ No newline at end of file diff --git a/test-files/github-events/default-branch.json b/test-files/github-events/default-branch.json new file mode 100644 index 0000000..5b8b9eb --- /dev/null +++ b/test-files/github-events/default-branch.json @@ -0,0 +1,10 @@ +{ + "ref": "refs/heads/main", + "repository": { + "default_branch": "main", + "owner": { + "login": "test-owner" + }, + "html_url": "https://example.com/" + } +} \ No newline at end of file diff --git a/test-files/github-events/pr-branch.json b/test-files/github-events/pr-branch.json new file mode 100644 index 0000000..1362ba0 --- /dev/null +++ b/test-files/github-events/pr-branch.json @@ -0,0 +1,15 @@ +{ + "head": { + "ref": "test-branch" + }, + "base": { + "ref": "main" + }, + "repository": { + "default_branch": "main", + "owner": { + "login": "test-owner" + }, + "html_url": "https://example.com/" + } +} \ No newline at end of file diff --git a/test-files/keys/cosign.key b/test-files/keys/cosign.key new file mode 100644 index 0000000..c08c47d --- /dev/null +++ b/test-files/keys/cosign.key @@ -0,0 +1,11 @@ +-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjo2NTUzNiwiciI6 +OCwicCI6MX0sInNhbHQiOiIvNjdKOVZ3WThhNnJhdk9DQUxmTzFQM05HRDRYc2s2 +L005aE5iYVhDNytBPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiIvaHQ1MjlSNlhnbkFGbVV6L3U0anlRVE1lb200VDZNVCJ9LCJj +aXBoZXJ0ZXh0IjoiWkpZWWsyR1FhWmdKdEh6UzBKdFVuTWhTblFXc25HcEQzYTVC +MjN3ZVlLb2REbzJkeFVOZXhFSURwODhGUkMzalVSTTRiNTZFSEVjblZVWmFETDNj +Z2ZrTjdNZWVvMThWWVN2Wm13STdYaFJaczExOUc2eWlmaThIcVpGYmdJM21Rd052 +MEVEcDFEekw0d2ZJWjBweVAreEEvM2xOeTlteWZSZDZSM1JoR0h5SWt6NVF4eHJ2 +WjB1VHZHVExOcmdLSHVzL3NTbis1WktsL1E9PSJ9 +-----END ENCRYPTED SIGSTORE PRIVATE KEY----- diff --git a/test-files/keys/cosign.pub b/test-files/keys/cosign.pub new file mode 100644 index 0000000..6ceb656 --- /dev/null +++ b/test-files/keys/cosign.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq8TdgrRtcWVq6MXuB2uznS14EOQ9 +Ol41BztsDr0Qd8BGfYM6lOkZ+/NLteBFZ9gQsgVhVrjrSifcHmMAUOZYwg== +-----END PUBLIC KEY----- diff --git a/utils/Cargo.toml b/utils/Cargo.toml index d54cb09..594c415 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -9,38 +9,30 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1" atty = "0.2" -base64 = "0.22.1" -blake2 = "0.10.6" +base64 = "0.22" +blake2 = "0.10" directories = "5" -rand = "0.8.5" -log4rs = { version = "1.3.0", features = ["background_rotation"] } -nix = { version = "0.29.0", features = ["signal"] } -nu-ansi-term = { version = "0.50.0", features = ["gnu_legacy"] } -os_pipe = { version = "1", features = ["io_safety"] } +docker_credential = "1" +format_serde_error = "0.3" process_control = { version = "4", features = ["crossbeam-channel"] } -signal-hook = { version = "0.3.17", features = ["extended-siginfo"] } syntect = "5" which = "6" chrono.workspace = true -clap = { workspace = true, features = ["derive"] } -colored.workspace = true -format_serde_error.workspace = true -indicatif.workspace = true -indicatif-log-bridge.workspace = true +clap = { workspace = true, features = ["derive", "env"] } log.workspace = true miette.workspace = true -once_cell.workspace = true -tempdir.workspace = true serde.workspace = true -serde_yaml.workspace = true serde_json.workspace = true +serde_yaml.workspace = true typed-builder.workspace = true [build-dependencies] -syntect = "5.2.0" +syntect = "5" + +[dev-dependencies] +rstest.workspace = true [lints] workspace = true diff --git a/utils/src/constants.rs b/utils/src/constants.rs index e6a2106..55d7764 100644 --- a/utils/src/constants.rs +++ b/utils/src/constants.rs @@ -19,6 +19,7 @@ pub const IMAGE_VERSION_LABEL: &str = "org.opencontainers.image.version"; // BlueBuild vars pub const BB_BUILDKIT_CACHE_GHA: &str = "BB_BUILDKIT_CACHE_GHA"; pub const BB_PASSWORD: &str = "BB_PASSWORD"; +pub const BB_PRIVATE_KEY: &str = "BB_PRIVATE_KEY"; pub const BB_REGISTRY: &str = "BB_REGISTRY"; pub const BB_REGISTRY_NAMESPACE: &str = "BB_REGISTRY_NAMESPACE"; pub const BB_USERNAME: &str = "BB_USERNAME"; @@ -27,7 +28,9 @@ pub const BB_USERNAME: &str = "BB_USERNAME"; pub const DOCKER_HOST: &str = "DOCKER_HOST"; // Cosign vars +pub const COSIGN_PASSWORD: &str = "COSIGN_PASSWORD"; pub const COSIGN_PRIVATE_KEY: &str = "COSIGN_PRIVATE_KEY"; +pub const COSIGN_YES: &str = "COSIGN_YES"; pub const GITHUB_TOKEN_ISSUER_URL: &str = "https://token.actions.githubusercontent.com"; pub const SIGSTORE_ID_TOKEN: &str = "SIGSTORE_ID_TOKEN"; @@ -35,6 +38,7 @@ pub const SIGSTORE_ID_TOKEN: &str = "SIGSTORE_ID_TOKEN"; pub const GITHUB_ACTIONS: &str = "GITHUB_ACTIONS"; pub const GITHUB_ACTOR: &str = "GITHUB_ACTOR"; pub const GITHUB_EVENT_NAME: &str = "GITHUB_EVENT_NAME"; +pub const GITHUB_EVENT_PATH: &str = "GITHUB_EVENT_PATH"; pub const GITHUB_REF_NAME: &str = "GITHUB_REF_NAME"; pub const GITHUB_RESPOSITORY: &str = "GITHUB_REPOSITORY"; pub const GITHUB_REPOSITORY_OWNER: &str = "GITHUB_REPOSITORY_OWNER"; @@ -58,6 +62,7 @@ pub const CI_SERVER_PROTOCOL: &str = "CI_SERVER_PROTOCOL"; pub const CI_REGISTRY: &str = "CI_REGISTRY"; pub const CI_REGISTRY_PASSWORD: &str = "CI_REGISTRY_PASSWORD"; pub const CI_REGISTRY_USER: &str = "CI_REGISTRY_USER"; +pub const GITLAB_CI: &str = "GITLAB_CI"; // Terminal vars pub const TERM_PROGRAM: &str = "TERM_PROGRAM"; @@ -67,10 +72,12 @@ pub const LC_TERMINAL_VERSION: &str = "LC_TERMINAL_VERSION"; pub const XDG_RUNTIME_DIR: &str = "XDG_RUNTIME_DIR"; // Misc +pub const COSIGN_IMAGE: &str = "gcr.io/projectsigstore/cosign:latest"; pub const OCI_ARCHIVE: &str = "oci-archive"; 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 UNKNOWN_SHELL: &str = ""; pub const UNKNOWN_VERSION: &str = ""; pub const UNKNOWN_TERMINAL: &str = ""; diff --git a/utils/src/credentials.rs b/utils/src/credentials.rs new file mode 100644 index 0000000..b33ca2a --- /dev/null +++ b/utils/src/credentials.rs @@ -0,0 +1,177 @@ +use std::{ + env, + sync::{LazyLock, Mutex}, +}; + +use clap::Args; +use docker_credential::DockerCredential; +use log::trace; +use typed_builder::TypedBuilder; + +use crate::{ + constants::{ + BB_PASSWORD, BB_REGISTRY, BB_USERNAME, CI_REGISTRY, CI_REGISTRY_PASSWORD, CI_REGISTRY_USER, + GITHUB_ACTIONS, GITHUB_ACTOR, GITHUB_TOKEN, + }, + string, +}; + +static INIT: LazyLock> = LazyLock::new(|| Mutex::new(false)); + +/// Stored user creds. +/// +/// This is a special handoff static ref that is consumed +/// by the `ENV_CREDENTIALS` static ref. This can be set +/// at the beginning of a command for future calls for +/// creds to source from. +static INIT_CREDS: Mutex = Mutex::new(CredentialsArgs { + username: None, + password: None, + registry: None, +}); + +/// Stores the global env credentials. +/// +/// This on load will determine the credentials based off of +/// `USER_CREDS` and env vars from CI systems. Once this is called +/// the value is stored and cannot change. +/// +/// If you have user +/// provided credentials, make sure you update `USER_CREDS` +/// before trying to access this reference. +static ENV_CREDENTIALS: LazyLock> = LazyLock::new(|| { + let (username, password, registry) = { + INIT_CREDS.lock().map_or((None, None, None), |mut creds| { + ( + creds.username.take(), + creds.password.take(), + creds.registry.take(), + ) + }) + }; + + let registry = match ( + registry, + env::var(CI_REGISTRY).ok(), + env::var(GITHUB_ACTIONS).ok(), + ) { + (Some(registry), _, _) if !registry.is_empty() => registry, + (None, Some(ci_registry), None) if !ci_registry.is_empty() => ci_registry, + (None, None, Some(_)) => string!("ghcr.io"), + _ => return None, + }; + trace!("Registry: {registry:?}"); + + let docker_creds = docker_credential::get_credential(®istry).ok(); + let podman_creds = docker_credential::get_podman_credential(®istry).ok(); + + let username = match ( + username, + env::var(CI_REGISTRY_USER).ok(), + env::var(GITHUB_ACTOR).ok(), + &docker_creds, + &podman_creds, + ) { + (Some(username), _, _, _, _) if !username.is_empty() => username, + (_, _, _, Some(DockerCredential::UsernamePassword(username, _)), _) + | (_, _, _, _, Some(DockerCredential::UsernamePassword(username, _))) + if !username.is_empty() => + { + username.clone() + } + (None, Some(ci_registry_user), None, _, _) if !ci_registry_user.is_empty() => { + ci_registry_user + } + (None, None, Some(github_actor), _, _) if !github_actor.is_empty() => github_actor, + _ => return None, + }; + trace!("Username: {username:?}"); + + let password = match ( + password, + env::var(CI_REGISTRY_PASSWORD).ok(), + env::var(GITHUB_TOKEN).ok(), + &docker_creds, + &podman_creds, + ) { + (Some(password), _, _, _, _) if !password.is_empty() => password, + (_, _, _, Some(DockerCredential::UsernamePassword(_, password)), _) + | (_, _, _, _, Some(DockerCredential::UsernamePassword(_, password))) + if !password.is_empty() => + { + password.clone() + } + (None, Some(ci_registry_password), None, _, _) if !ci_registry_password.is_empty() => { + ci_registry_password + } + (None, None, Some(registry_token), _, _) if !registry_token.is_empty() => registry_token, + _ => return None, + }; + + Some( + Credentials::builder() + .registry(registry) + .username(username) + .password(password) + .build(), + ) +}); + +/// The credentials for logging into image registries. +#[derive(Debug, Default, Clone, TypedBuilder)] +pub struct Credentials { + pub registry: String, + pub username: String, + pub password: String, +} + +impl Credentials { + /// Set the users credentials for + /// the current set of actions. + /// + /// Be sure to call this before trying to use + /// any strategy that requires credentials as + /// the environment credentials are lazy allocated. + /// + /// # Panics + /// Will panic if it can't lock the mutex. + pub fn init(args: CredentialsArgs) { + trace!("Credentials::init()"); + let mut initialized = INIT.lock().expect("Must lock INIT"); + + if !*initialized { + let mut creds_lock = INIT_CREDS.lock().expect("Must lock USER_CREDS"); + *creds_lock = args; + drop(creds_lock); + let _ = ENV_CREDENTIALS.as_ref(); + + *initialized = true; + } + } + + /// Get the credentials for the current set of actions. + pub fn get() -> Option<&'static Self> { + trace!("credentials::get()"); + ENV_CREDENTIALS.as_ref() + } +} + +#[derive(Debug, Default, Clone, TypedBuilder, Args)] +pub struct CredentialsArgs { + /// The registry's domain name. + #[arg(long, env = BB_REGISTRY)] + #[builder(default, setter(into, strip_option))] + pub registry: Option, + + /// The username to login to the + /// container registry. + #[arg(short = 'U', long, env = BB_USERNAME, hide_env_values = true)] + #[builder(default, setter(into, strip_option))] + pub username: Option, + + /// The password to login to the + /// container registry. + #[arg(short = 'P', long, env = BB_PASSWORD, hide_env_values = true)] + #[builder(default, setter(into, strip_option))] + pub password: Option, +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 2c32d49..53377db 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,13 +1,12 @@ pub mod command_output; pub mod constants; -pub mod logging; -pub mod signal_handler; +pub mod credentials; +mod macros; pub mod syntax_highlighting; use std::{ os::unix::ffi::OsStrExt, path::{Path, PathBuf}, - process::Command, thread, time::Duration, }; @@ -17,9 +16,10 @@ use blake2::{ digest::{Update, VariableOutput}, Blake2bVar, }; +use chrono::Local; use format_serde_error::SerdeError; use log::trace; -use miette::{miette, IntoDiagnostic, Result}; +use miette::{miette, Context, IntoDiagnostic, Result}; use crate::constants::CONTAINER_FILE; @@ -33,8 +33,7 @@ pub fn check_command_exists(command: &str) -> Result<()> { trace!("check_command_exists({command})"); trace!("which {command}"); - if Command::new("which") - .arg(command) + if cmd!("which", command) .output() .into_diagnostic()? .status @@ -109,3 +108,18 @@ pub fn generate_containerfile_path>(path: T) -> Result { BASE64_URL_SAFE_NO_PAD.encode(buf) ))) } + +#[must_use] +pub fn get_tag_timestamp() -> String { + Local::now().format("%Y%m%d").to_string() +} + +/// Get's the env var wrapping it with a miette error +/// +/// # Errors +/// Will error if the env var doesn't exist. +pub fn get_env_var(key: &str) -> Result { + std::env::var(key) + .into_diagnostic() + .with_context(|| format!("Failed to get {key}'")) +} diff --git a/utils/src/macros.rs b/utils/src/macros.rs new file mode 100644 index 0000000..01a3b5b --- /dev/null +++ b/utils/src/macros.rs @@ -0,0 +1,150 @@ +/// Creates or modifies a `std::process::Command` adding args. +/// +/// # Examples +/// ``` +/// use blue_build_utils::cmd; +/// +/// const NAME: &str = "Bob"; +/// let mut command = cmd!("echo", "Hello world!"); +/// cmd!(command, "This is Joe.", format!("And this is {NAME}")); +/// command.status().unwrap(); +/// ``` +#[macro_export] +macro_rules! cmd { + ($command:expr) => { + ::std::process::Command::new($command) + }; + ($command:ident, $($tail:tt)*) => { + cmd!(@ $command, $($tail)*) + }; + ($command:expr, $($tail:tt)*) => { + { + let mut c = cmd!($command); + cmd!(@ c, $($tail)*); + c + } + }; + (@ $command:ident $(,)?) => { }; + (@ $command:ident, for $for_expr:expr $(, $($tail:tt)*)?) => { + { + for arg in $for_expr.iter() { + cmd!($command, arg); + } + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, for $iter:ident in $for_expr:expr => [ $($arg:expr),* $(,)? ] $(, $($tail:tt)*)?) => { + { + for $iter in $for_expr.iter() { + $(cmd!(@ $command, $arg);)* + } + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, for $iter:ident in $for_expr:expr => $arg:expr $(, $($tail:tt)*)?) => { + { + for $iter in $for_expr.iter() { + cmd!(@ $command, $arg); + } + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, if let $let_pat:pat = $if_expr:expr => [ $($arg:expr),* $(,)? ] $(, $($tail:tt)*)?) => { + { + if let $let_pat = $if_expr { + $(cmd!(@ $command, $arg);)* + } + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, if let $let_pat:pat = $if_expr:expr => $arg:expr $(, $($tail:tt)*)?) => { + { + if let $let_pat = $if_expr { + cmd!(@ $command, $arg); + } + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, if $if_expr:expr => [ $($arg:expr),* $(,)?] $(, $($tail:tt)*)?) => { + { + if $if_expr { + $(cmd!(@ $command, $arg);)* + } + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, if $if_expr:expr => $arg:expr $(, $($tail:tt)*)?) => { + { + if $if_expr { + cmd!(@ $command, $arg); + } + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, |$cmd_ref:ident|? $op:block $(, $($tail:tt)*)?) => { + { + let op_fn = |$cmd_ref: &mut ::std::process::Command| -> Result<()> { + $op + Ok(()) + }; + op_fn(&mut $command)?; + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, |$cmd_ref:ident| $op:block $(, $($tail:tt)*)?) => { + { + let op_fn = |$cmd_ref: &mut ::std::process::Command| $op; + op_fn(&mut $command); + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, $key:expr => $value:expr $(, $($tail:tt)*)?) => { + { + $command.env($key, $value); + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, stdin = $pipe:expr $(, $($tail:tt)*)?) => { + { + $command.stdin($pipe); + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, stdout = $pipe:expr $(, $($tail:tt)*)?) => { + { + $command.stdout($pipe); + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, stderr = $pipe:expr $(, $($tail:tt)*)?) => { + { + $command.stderr($pipe); + $(cmd!(@ $command, $($tail)*);)* + } + }; + (@ $command:ident, $arg:expr $(, $($tail:tt)*)?) => { + { + $command.arg($arg); + $(cmd!(@ $command, $($tail)*);)* + } + }; +} + +#[macro_export] +macro_rules! string { + ($str:expr) => { + String::from($str) + }; +} + +#[macro_export] +macro_rules! string_vec { + ($($string:expr),+ $(,)?) => { + { + use $crate::string; + vec![ + $(string!($string),)* + ] + } + }; +}