From f581371b0b49e25dc410c64ebe217b3ce0cf0afc Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Wed, 11 Oct 2023 11:46:46 -0300 Subject: [PATCH] Upgrade `libcnb.rs` to `0.15.0` (#143) * Upgrade `libcnb.rs` to `0.15.0` The following general improvements were added for all commands: - Added `Display` implementations for errors shared by several commands (i.e.; `FindReleasableBuildpacksError`, `SetActionOutputError`, `GetWorkingDirectoryError`, `ReadBuildpackDescriptor`) ### `generate_buildpack_matrix` This command has been updated to remove hardcoded target output directories and instead accept a package directory (`--package-dir`). The other (optional) arguments are to line up with the arguments used by `cargo libcnb package` with sensible defaults. The `_buildpacks_release.yml` workflow has also been updated to pass through the `--package-dir` argument which is set as the default value `./packaged`. ### `generate_changelog` Slight modifications to support `libcnb` changes and refactorings detailed above. ### `prepare_release` Slight modifications to support `libcnb` changes and refactorings detailed above. ### `update_builder` Slight modifications to support `libcnb` changes and refactorings detailed above. * pass --package-dir to libcnb package command * rename directory args package-dir and workspace-dir * switching get workspace dir helper to resolve path * Replaced error `Display` impls with `thiserror` * Refactor resolve functions * Refactor resolve functions * Use `map_or` * Apply suggestions from code review * Remove --release arg Since: - it was unused - the implementation had a bug - this tooling focuses on publishing release builds, so we don't really need to support non-release builds (and can always add them later if really needed) * Remove --working-dir arg Since it's unused. The variable names were normalised back to `current_dir`, to make its value clearer, and also since it reduces the size of the overall PR (which contained a number of renames to `working_dir`). * Fix lint error --------- Co-authored-by: Ed Morley <501702+edmorley@users.noreply.github.com> --- .github/workflows/_buildpacks-release.yml | 11 +- Cargo.lock | 261 +++++++++++++++--- Cargo.toml | 7 +- src/buildpacks.rs | 55 ++-- src/changelog.rs | 49 +--- .../generate_buildpack_matrix/command.rs | 99 ++++--- .../generate_buildpack_matrix/errors.rs | 94 ++----- src/commands/generate_changelog/command.rs | 37 +-- src/commands/generate_changelog/errors.rs | 83 +----- src/commands/mod.rs | 10 + src/commands/prepare_release/command.rs | 6 +- src/commands/prepare_release/errors.rs | 159 +++-------- src/commands/update_builder/command.rs | 56 ++-- src/commands/update_builder/errors.rs | 148 ++-------- src/github/actions.rs | 16 +- src/main.rs | 2 +- 16 files changed, 491 insertions(+), 602 deletions(-) diff --git a/.github/workflows/_buildpacks-release.yml b/.github/workflows/_buildpacks-release.yml index 75770869..7464c2d6 100644 --- a/.github/workflows/_buildpacks-release.yml +++ b/.github/workflows/_buildpacks-release.yml @@ -55,6 +55,7 @@ defaults: env: CARGO_TERM_COLOR: always + PACKAGE_DIR: ./packaged jobs: compile: @@ -101,11 +102,11 @@ jobs: - name: Package buildpacks id: libcnb-package - run: cargo libcnb package --release + run: cargo libcnb package --release --package-dir ${{ env.PACKAGE_DIR }} - name: Generate buildpack matrix id: generate-buildpack-matrix - run: actions generate-buildpack-matrix + run: actions generate-buildpack-matrix --package-dir ${{ env.PACKAGE_DIR }} - name: Generate changelog id: generate-changelog @@ -153,7 +154,7 @@ jobs: uses: actions/cache/save@v3 with: key: ${{ github.run_id }}-compiled-buildpacks - path: target/buildpack + path: ${{ env.PACKAGE_DIR }} publish-docker: name: Publish → Docker - ${{ matrix.buildpack_id }} @@ -169,7 +170,7 @@ jobs: with: fail-on-cache-miss: true key: ${{ github.run_id }}-compiled-buildpacks - path: target/buildpack + path: ${{ env.PACKAGE_DIR }} env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 @@ -207,7 +208,7 @@ jobs: with: fail-on-cache-miss: true key: ${{ github.run_id }}-compiled-buildpacks - path: target/buildpack + path: ${{ env.PACKAGE_DIR }} env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 diff --git a/Cargo.lock b/Cargo.lock index 8fc231d0..6f2f93e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,22 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bstr" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.13.0" @@ -79,9 +95,9 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.15.4" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +checksum = "fb9ac64500cc83ce4b9f8dafa78186aa008c8dea77a09b94cd307fd0cd5022a8" dependencies = [ "camino", "cargo-platform", @@ -175,6 +191,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "fancy-regex" version = "0.11.0" @@ -209,10 +246,17 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "globset" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] [[package]] name = "hashbrown" @@ -226,6 +270,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -250,13 +303,20 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "ignore" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", ] [[package]] @@ -266,7 +326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] @@ -290,8 +350,10 @@ version = "0.0.1" dependencies = [ "chrono", "clap", - "indexmap 2.0.0", + "ignore", + "indexmap", "lazy_static", + "libcnb-common", "libcnb-data", "libcnb-package", "markdown", @@ -299,8 +361,9 @@ dependencies = [ "regex", "semver", "serde_json", - "toml", - "toml_edit", + "thiserror", + "toml 0.7.6", + "toml_edit 0.19.14", "uriparse", ] @@ -312,42 +375,56 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libcnb-common" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "53c4c77089f294316c1d8a285d0ed9973e796a2653c676b22b3f7703d73aa828" +dependencies = [ + "serde", + "thiserror", + "toml 0.8.0", +] [[package]] name = "libcnb-data" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631bda3e80115baf38894609cde58b796d3b3fc0f47cca369321c230df53d563" +checksum = "7f35c4af3b47b67257263f0504897cf4db263b407b3e367971fc0b60ada69ce2" dependencies = [ "fancy-regex", "libcnb-proc-macros", "serde", "thiserror", - "toml", + "toml 0.8.0", "uriparse", ] [[package]] name = "libcnb-package" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8f85f26a1cacea4c3e3fd01484e2c86ca0d9c252a1e81adb22ab7e5ee0451" +checksum = "d5ee4c1ac95fc5f71b0f8901d62644f9d39dad0be9d1a5bf23fae173aaf2a46c" dependencies = [ "cargo_metadata", + "ignore", + "libcnb-common", "libcnb-data", "petgraph", - "toml", + "thiserror", + "uriparse", "which", ] [[package]] name = "libcnb-proc-macros" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab33c1d63ffd280516abc7ada744fc1b653a888c439f5e2962d5371d0aecaf7" +checksum = "5effc8c71a7401899ea2885681b213b779449dc0f581663ea850d9de0718434c" dependencies = [ "cargo_metadata", "fancy-regex", @@ -355,6 +432,12 @@ dependencies = [ "syn", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + [[package]] name = "log" version = "0.4.19" @@ -393,12 +476,12 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 1.9.3", + "indexmap", ] [[package]] @@ -409,18 +492,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -484,12 +567,34 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "rustix" +version = "0.38.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "semver" version = "1.0.18" @@ -501,18 +606,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.173" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.173" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -541,9 +646,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -552,24 +657,34 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.1.45" @@ -590,7 +705,19 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.19.14", +] + +[[package]] +name = "toml" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.0", ] [[package]] @@ -608,7 +735,20 @@ version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap 2.0.0", + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95" +dependencies = [ + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -637,6 +777,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -705,13 +855,14 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -730,6 +881,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -745,6 +905,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.48.1" diff --git a/Cargo.toml b/Cargo.toml index 86240852..2babcaf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,15 +23,18 @@ clap = { version = "4", default-features = false, features = [ "std", "usage", ] } +ignore = "0.4" indexmap = "2" lazy_static = "1" -libcnb-data = "=0.13.0" -libcnb-package = "=0.13.0" +libcnb-common = "=0.15.0" +libcnb-data = "=0.15.0" +libcnb-package = "=0.15.0" markdown = "1.0.0-alpha.14" rand = "0.8" regex = "1" semver = "1" serde_json = "1" +thiserror = "1" toml_edit = "0.19" uriparse = "0.6" diff --git a/src/buildpacks.rs b/src/buildpacks.rs index 58dbeebb..4fe42db5 100644 --- a/src/buildpacks.rs +++ b/src/buildpacks.rs @@ -1,11 +1,14 @@ +use libcnb_common::toml_file::{read_toml_file, TomlFileError}; use libcnb_data::buildpack::BuildpackDescriptor; -use libcnb_package::{find_buildpack_dirs, GenericMetadata}; +use libcnb_package::find_buildpack_dirs; use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus}; -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub(crate) enum CalculateDigestError { - CommandFailure(String, std::io::Error), + #[error("Failed to execute crane digest {0}\nError: {1}")] + CommandFailure(String, #[source] std::io::Error), + #[error("Command crane digest {0} exited with a non-zero status\nStatus: {1}")] ExitStatus(String, ExitStatus), } @@ -26,11 +29,11 @@ pub(crate) fn calculate_digest(digest_url: &String) -> Result, + buildpack_descriptor: &BuildpackDescriptor, ) -> Option { let metadata = match buildpack_descriptor { - BuildpackDescriptor::Single(descriptor) => &descriptor.metadata, - BuildpackDescriptor::Meta(descriptor) => &descriptor.metadata, + BuildpackDescriptor::Component(descriptor) => &descriptor.metadata, + BuildpackDescriptor::Composite(descriptor) => &descriptor.metadata, }; #[allow(clippy::redundant_closure_for_method_calls)] @@ -42,20 +45,38 @@ pub(crate) fn read_image_repository_metadata( .map(|value| value.to_string()) } -pub(crate) fn find_releasable_buildpacks(starting_dir: &Path) -> std::io::Result> { - find_buildpack_dirs(starting_dir, &[starting_dir.join("target")]).map(|results| { - results - .into_iter() - .filter(|dir| dir.join("CHANGELOG.md").exists()) - .collect() - }) +pub(crate) fn find_releasable_buildpacks( + starting_dir: &Path, +) -> Result, FindReleasableBuildpacksError> { + find_buildpack_dirs(starting_dir) + .map(|results| { + results + .into_iter() + .filter(|dir| dir.join("CHANGELOG.md").exists()) + .collect() + }) + .map_err(|e| FindReleasableBuildpacksError(starting_dir.to_path_buf(), e)) } +#[derive(Debug, thiserror::Error)] +#[error("I/O error while finding buildpacks\nPath: {}\nError: {1}", .0.display())] +pub(crate) struct FindReleasableBuildpacksError(PathBuf, ignore::Error); + +pub(crate) fn read_buildpack_descriptor( + dir: &Path, +) -> Result { + let buildpack_path = dir.join("buildpack.toml"); + read_toml_file::(&buildpack_path) + .map_err(|e| ReadBuildpackDescriptorError(buildpack_path, e)) +} + +#[derive(Debug, thiserror::Error)] +#[error("Failed to read buildpack descriptor\nPath: {}\nError: {1}", .0.display())] +pub(crate) struct ReadBuildpackDescriptorError(PathBuf, #[source] TomlFileError); #[cfg(test)] mod test { use crate::buildpacks::read_image_repository_metadata; use libcnb_data::buildpack::BuildpackDescriptor; - use libcnb_package::GenericMetadata; #[test] fn test_read_image_repository_metadata() { @@ -73,8 +94,7 @@ id = "*" repository = "repository value" "#; - let buildpack_descriptor = - toml::from_str::>(data).unwrap(); + let buildpack_descriptor = toml::from_str::(data).unwrap(); assert_eq!( read_image_repository_metadata(&buildpack_descriptor), Some("repository value".to_string()) @@ -94,8 +114,7 @@ version = "0.0.1" id = "*" "#; - let buildpack_descriptor = - toml::from_str::>(data).unwrap(); + let buildpack_descriptor = toml::from_str::(data).unwrap(); assert_eq!(read_image_repository_metadata(&buildpack_descriptor), None); } } diff --git a/src/changelog.rs b/src/changelog.rs index cbefd31a..22995d12 100644 --- a/src/changelog.rs +++ b/src/changelog.rs @@ -174,49 +174,26 @@ pub(crate) struct ReleaseEntry { pub(crate) body: String, } -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub(crate) enum ChangelogError { + #[error("No root node in changelog markdown")] NoRootNode, + #[error("Could not parse changelog - {0}")] Parse(String), - ParseVersion(semver::Error), - ParseReleaseEntryYear(ParseIntError), - ParseReleaseEntryMonth(ParseIntError), - ParseReleaseEntryDay(ParseIntError), + #[error("Invalid semver version in release entry - {0}")] + ParseVersion(#[source] semver::Error), + #[error("Invalid year in release entry - {0}")] + ParseReleaseEntryYear(#[source] ParseIntError), + #[error("Invalid month in release entry - {0}")] + ParseReleaseEntryMonth(#[source] ParseIntError), + #[error("Invalid day in release entry - {0}")] + ParseReleaseEntryDay(#[source] ParseIntError), + #[error("Invalid date in release entry - {0}")] InvalidReleaseDate(String), + #[error("Ambiguous date in release entry")] AmbiguousReleaseDate, } -impl Display for ChangelogError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ChangelogError::NoRootNode => { - write!(f, "No root node in changelog markdown") - } - ChangelogError::Parse(error) => { - write!(f, "Could not parse changelog - {error}") - } - ChangelogError::ParseVersion(error) => { - write!(f, "Invalid semver version in release entry - {error}") - } - ChangelogError::ParseReleaseEntryYear(error) => { - write!(f, "Invalid year in release entry - {error}") - } - ChangelogError::ParseReleaseEntryMonth(error) => { - write!(f, "Invalid month in release entry - {error}") - } - ChangelogError::ParseReleaseEntryDay(error) => { - write!(f, "Invalid day in release entry - {error}") - } - ChangelogError::InvalidReleaseDate(error) => { - write!(f, "Invalid date in release entry - {error}") - } - ChangelogError::AmbiguousReleaseDate => { - write!(f, "Ambiguous date in release entry") - } - } - } -} - pub(crate) fn generate_release_declarations>( changelog: &Changelog, repository: S, diff --git a/src/commands/generate_buildpack_matrix/command.rs b/src/commands/generate_buildpack_matrix/command.rs index 971d2ff9..0e79269a 100644 --- a/src/commands/generate_buildpack_matrix/command.rs +++ b/src/commands/generate_buildpack_matrix/command.rs @@ -1,34 +1,54 @@ -use crate::buildpacks::{find_releasable_buildpacks, read_image_repository_metadata}; +use crate::buildpacks::{ + find_releasable_buildpacks, read_buildpack_descriptor, read_image_repository_metadata, +}; use crate::commands::generate_buildpack_matrix::errors::Error; +use crate::commands::resolve_path; use crate::github::actions; use clap::Parser; -use libcnb_package::{ - get_buildpack_target_dir, read_buildpack_data, BuildpackData, GenericMetadata, +use libcnb_data::buildpack::{BuildpackDescriptor, BuildpackId}; +use libcnb_package::output::{ + create_packaged_buildpack_dir_resolver, default_buildpack_directory_name, }; +use libcnb_package::CargoProfile; use std::collections::{BTreeMap, HashSet}; -use std::path::Path; +use std::path::{Path, PathBuf}; type Result = std::result::Result; #[derive(Parser, Debug)] #[command(author, version, about = "Generates a JSON list of buildpack information for each buildpack detected", long_about = None)] -pub(crate) struct GenerateBuildpackMatrixArgs; +pub(crate) struct GenerateBuildpackMatrixArgs { + #[arg(long)] + pub(crate) package_dir: PathBuf, + #[arg(long, default_value = "x86_64-unknown-linux-musl")] + pub(crate) target: String, +} -pub(crate) fn execute(_: GenerateBuildpackMatrixArgs) -> Result<()> { +pub(crate) fn execute(args: &GenerateBuildpackMatrixArgs) -> Result<()> { let current_dir = std::env::current_dir().map_err(Error::GetCurrentDir)?; + let package_dir = resolve_path(&args.package_dir, ¤t_dir); + + let packaged_buildpack_dir_resolver = + create_packaged_buildpack_dir_resolver(&package_dir, CargoProfile::Release, &args.target); - let buildpack_dirs = find_releasable_buildpacks(¤t_dir) - .map_err(|e| Error::FindingBuildpacks(current_dir.clone(), e))?; + let buildpack_dirs = + find_releasable_buildpacks(¤t_dir).map_err(Error::FindReleasableBuildpacks)?; let buildpacks = buildpack_dirs .iter() - .map(|dir| read_buildpack_data(dir).map_err(Error::ReadingBuildpackData)) + .map(|dir| read_buildpack_descriptor(dir).map_err(Error::ReadBuildpackDescriptor)) .collect::>>()?; let includes = buildpack_dirs .iter() .zip(buildpacks.iter()) - .map(|(dir, data)| extract_buildpack_info(data, dir, ¤t_dir)) + .map(|(buildpack_dir, buildpack_descriptor)| { + extract_buildpack_info( + buildpack_descriptor, + buildpack_dir, + &packaged_buildpack_dir_resolver, + ) + }) .collect::>>()?; let includes_json = serde_json::to_string(&includes).map_err(Error::SerializingJson)?; @@ -37,7 +57,7 @@ pub(crate) fn execute(_: GenerateBuildpackMatrixArgs) -> Result<()> { let versions = buildpacks .iter() - .map(|data| data.buildpack_descriptor.buildpack().version.to_string()) + .map(|buildpack_descriptor| buildpack_descriptor.buildpack().version.to_string()) .collect::>(); if versions.len() != 1 { @@ -55,45 +75,38 @@ pub(crate) fn execute(_: GenerateBuildpackMatrixArgs) -> Result<()> { } pub(crate) fn extract_buildpack_info( - buildpack_data: &BuildpackData, - dir: &Path, - workspace_dir: &Path, + buildpack_descriptor: &BuildpackDescriptor, + buildpack_dir: &Path, + packaged_buildpack_dir_resolver: &impl Fn(&BuildpackId) -> PathBuf, ) -> Result> { - let buildpack_dir = dir.to_string_lossy().to_string(); - - let buildpack_path = buildpack_data.buildpack_descriptor_path.clone(); - - let buildpack_id = buildpack_data.buildpack_descriptor.buildpack().id.clone(); - - let buildpack_version = buildpack_data - .buildpack_descriptor - .buildpack() - .version - .to_string(); - - let buildpack_artifact_prefix = buildpack_id.replace('/', "_"); - - let docker_repository = read_image_repository_metadata(&buildpack_data.buildpack_descriptor) - .ok_or(Error::MissingDockerRepositoryMetadata(buildpack_path))?; - - let buildpack_output_dir = get_buildpack_target_dir( - &buildpack_id, - &workspace_dir.to_path_buf().join("target"), - true, - ); - Ok(BTreeMap::from([ - ("buildpack_id".to_string(), buildpack_id.to_string()), - ("buildpack_version".to_string(), buildpack_version), - ("buildpack_dir".to_string(), buildpack_dir), + ( + "buildpack_id".to_string(), + buildpack_descriptor.buildpack().id.to_string(), + ), + ( + "buildpack_version".to_string(), + buildpack_descriptor.buildpack().version.to_string(), + ), + ( + "buildpack_dir".to_string(), + buildpack_dir.to_string_lossy().to_string(), + ), ( "buildpack_artifact_prefix".to_string(), - buildpack_artifact_prefix, + default_buildpack_directory_name(&buildpack_descriptor.buildpack().id), ), ( "buildpack_output_dir".to_string(), - buildpack_output_dir.to_string_lossy().to_string(), + packaged_buildpack_dir_resolver(&buildpack_descriptor.buildpack().id) + .to_string_lossy() + .to_string(), + ), + ( + "docker_repository".to_string(), + read_image_repository_metadata(buildpack_descriptor).ok_or( + Error::MissingDockerRepositoryMetadata(buildpack_dir.join("buildpack.toml")), + )?, ), - ("docker_repository".to_string(), docker_repository), ])) } diff --git a/src/commands/generate_buildpack_matrix/errors.rs b/src/commands/generate_buildpack_matrix/errors.rs index f0fd653c..4901446f 100644 --- a/src/commands/generate_buildpack_matrix/errors.rs +++ b/src/commands/generate_buildpack_matrix/errors.rs @@ -1,84 +1,30 @@ -use crate::github::actions::SetOutputError; -use libcnb_package::ReadBuildpackDataError; +use crate::buildpacks::{FindReleasableBuildpacksError, ReadBuildpackDescriptorError}; +use crate::github::actions::SetActionOutputError; use std::collections::HashSet; -use std::fmt::{Display, Formatter}; use std::path::PathBuf; -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub(crate) enum Error { + #[error("Failed to get current directory\nError: {0}")] GetCurrentDir(std::io::Error), - FindingBuildpacks(PathBuf, std::io::Error), - ReadingBuildpackData(ReadBuildpackDataError), + #[error(transparent)] + FindReleasableBuildpacks(FindReleasableBuildpacksError), + #[error(transparent)] + ReadBuildpackDescriptor(ReadBuildpackDescriptorError), + #[error("The following buildpack is missing the metadata.release.docker.repository entry\nPath: {}", .0.display())] MissingDockerRepositoryMetadata(PathBuf), - SerializingJson(serde_json::Error), + #[error("Could not serialize buildpacks into json\nError: {0}")] + SerializingJson(#[source] serde_json::Error), + #[error("Expected all buildpacks to have the same version but multiple versions were found:\n{}", list_versions(.0))] FixedVersion(HashSet), - SetActionOutput(SetOutputError), + #[error(transparent)] + SetActionOutput(SetActionOutputError), } -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Error::GetCurrentDir(error) => { - write!(f, "Failed to get current directory\nError: {error}") - } - - Error::FindingBuildpacks(path, error) => { - write!( - f, - "I/O error while finding buildpacks\nPath: {}\nError: {error}", - path.display() - ) - } - - Error::SetActionOutput(set_output_error) => match set_output_error { - SetOutputError::Opening(error) | SetOutputError::Writing(error) => { - write!(f, "Could not write action output\nError: {error}") - } - }, - - Error::SerializingJson(error) => { - write!( - f, - "Could not serialize buildpacks into json\nError: {error}" - ) - } - - Error::ReadingBuildpackData(error) => match error { - ReadBuildpackDataError::ReadingBuildpack { path, source } => { - write!( - f, - "Failed to read buildpack\nPath: {}\nError: {source}", - path.display() - ) - } - ReadBuildpackDataError::ParsingBuildpack { path, source } => { - write!( - f, - "Failed to parse buildpack\nPath: {}\nError: {source}", - path.display() - ) - } - }, - - Error::MissingDockerRepositoryMetadata(path) => { - write!( - f, - "The following buildpack is missing the metadata.release.docker.repository entry\nPath: {}", - path.display() - ) - } - - Error::FixedVersion(version) => { - write!( - f, - "Expected all buildpacks to have the same version but multiple versions were found:\n{}", - version - .iter() - .map(|version| format!("• {version}")) - .collect::>() - .join("\n") - ) - } - } - } +fn list_versions(versions: &HashSet) -> String { + versions + .iter() + .map(|version| format!("• {version}")) + .collect::>() + .join("\n") } diff --git a/src/commands/generate_changelog/command.rs b/src/commands/generate_changelog/command.rs index 181d8670..f13b66d5 100644 --- a/src/commands/generate_changelog/command.rs +++ b/src/commands/generate_changelog/command.rs @@ -1,10 +1,9 @@ -use crate::buildpacks::find_releasable_buildpacks; +use crate::buildpacks::{find_releasable_buildpacks, read_buildpack_descriptor}; use crate::changelog::Changelog; use crate::commands::generate_changelog::errors::Error; use crate::github::actions; use clap::Parser; use libcnb_data::buildpack::BuildpackId; -use libcnb_package::read_buildpack_data; use std::collections::{BTreeMap, HashMap}; use std::path::PathBuf; @@ -14,11 +13,9 @@ type Result = std::result::Result; #[command(author, version, about = "Generates a changelog from one or more buildpacks in a project", long_about = None, disable_version_flag = true)] pub(crate) struct GenerateChangelogArgs { #[arg(long, group = "section")] - unreleased: bool, + pub(crate) unreleased: bool, #[arg(long, group = "section")] - version: Option, - #[arg(long)] - path: Option, + pub(crate) version: Option, } enum ChangelogEntryType { @@ -33,11 +30,9 @@ enum ChangelogEntry { } pub(crate) fn execute(args: GenerateChangelogArgs) -> Result<()> { - let working_dir = - get_working_dir_from(args.path.map(PathBuf::from)).map_err(Error::GetWorkingDir)?; - - let buildpack_dirs = find_releasable_buildpacks(&working_dir) - .map_err(|e| Error::FindingBuildpacks(working_dir.clone(), e))?; + let current_dir = std::env::current_dir().map_err(Error::GetCurrentDir)?; + let buildpack_dirs = + find_releasable_buildpacks(¤t_dir).map_err(Error::FindReleasableBuildpacks)?; let changelog_entry_type = match args.version { Some(version) => ChangelogEntryType::Version(version), @@ -47,9 +42,9 @@ pub(crate) fn execute(args: GenerateChangelogArgs) -> Result<()> { let changes_by_buildpack = buildpack_dirs .iter() .map(|dir| { - read_buildpack_data(dir) - .map_err(Error::GetBuildpackId) - .map(|data| data.buildpack_descriptor.buildpack().id.clone()) + read_buildpack_descriptor(dir) + .map_err(Error::ReadBuildpackDescriptor) + .map(|buildpack_descriptor| buildpack_descriptor.buildpack().id.clone()) .and_then(|buildpack_id| { read_changelog_entry(&dir.join("CHANGELOG.md"), &changelog_entry_type) .map(|contents| (buildpack_id, contents)) @@ -92,20 +87,6 @@ fn read_changelog_entry( }) } -fn get_working_dir_from(path: Option) -> std::io::Result { - let current_dir = std::env::current_dir()?; - Ok(match path { - Some(value) => { - if value.is_absolute() { - value - } else { - current_dir.join(value) - } - } - None => current_dir, - }) -} - fn generate_changelog(changes_by_buildpack: &HashMap) -> String { let changelog = changes_by_buildpack .iter() diff --git a/src/commands/generate_changelog/errors.rs b/src/commands/generate_changelog/errors.rs index 1f3d8e2c..877dd010 100644 --- a/src/commands/generate_changelog/errors.rs +++ b/src/commands/generate_changelog/errors.rs @@ -1,73 +1,20 @@ +use crate::buildpacks::{FindReleasableBuildpacksError, ReadBuildpackDescriptorError}; use crate::changelog::ChangelogError; -use crate::github::actions::SetOutputError; -use libcnb_package::ReadBuildpackDataError; -use std::fmt::{Display, Formatter}; +use crate::github::actions::SetActionOutputError; use std::path::PathBuf; -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub(crate) enum Error { - GetWorkingDir(std::io::Error), - FindingBuildpacks(PathBuf, std::io::Error), - GetBuildpackId(ReadBuildpackDataError), - ReadingChangelog(PathBuf, std::io::Error), - ParsingChangelog(PathBuf, ChangelogError), - SetActionOutput(SetOutputError), -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Error::GetWorkingDir(error) => { - write!(f, "Failed to get working directory\nError: {error}") - } - - Error::FindingBuildpacks(path, error) => { - write!( - f, - "I/O error while finding buildpacks\nPath: {}\nError: {error}", - path.display() - ) - } - - Error::GetBuildpackId(read_buildpack_data_error) => match read_buildpack_data_error { - ReadBuildpackDataError::ReadingBuildpack { path, source } => { - write!( - f, - "Error reading buildpack\nPath: {}\nError: {source}", - path.display() - ) - } - - ReadBuildpackDataError::ParsingBuildpack { path, source } => { - write!( - f, - "Error parsing buildpack\nPath: {}\nError: {source}", - path.display() - ) - } - }, - - Error::SetActionOutput(set_output_error) => match set_output_error { - SetOutputError::Opening(error) | SetOutputError::Writing(error) => { - write!(f, "Could not write action output\nError: {error}") - } - }, - - Error::ReadingChangelog(path, error) => { - write!( - f, - "Could not read changelog\nPath: {}\nError: {error}", - path.display() - ) - } - - Error::ParsingChangelog(path, error) => { - write!( - f, - "Could not parse changelog\nPath: {}\nError: {error}", - path.display() - ) - } - } - } + #[error("Failed to get current directory\nError: {0}")] + GetCurrentDir(std::io::Error), + #[error(transparent)] + FindReleasableBuildpacks(FindReleasableBuildpacksError), + #[error(transparent)] + ReadBuildpackDescriptor(ReadBuildpackDescriptorError), + #[error("Could not read changelog\nPath: {}\nError: {1}", .0.display())] + ReadingChangelog(PathBuf, #[source] std::io::Error), + #[error("Could not parse changelog\nPath: {}\nError: {1}", .0.display())] + ParsingChangelog(PathBuf, #[source] ChangelogError), + #[error(transparent)] + SetActionOutput(SetActionOutputError), } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 5b3ee318..79d23acf 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,14 @@ +use std::path::{Path, PathBuf}; + pub(crate) mod generate_buildpack_matrix; pub(crate) mod generate_changelog; pub(crate) mod prepare_release; pub(crate) mod update_builder; + +pub(crate) fn resolve_path(path: &Path, base: &Path) -> PathBuf { + if path.is_absolute() { + path.to_path_buf() + } else { + base.join(path) + } +} diff --git a/src/commands/prepare_release/command.rs b/src/commands/prepare_release/command.rs index d6c07c74..c9e36dd8 100644 --- a/src/commands/prepare_release/command.rs +++ b/src/commands/prepare_release/command.rs @@ -48,7 +48,7 @@ pub(crate) fn execute(args: PrepareReleaseArgs) -> Result<()> { let current_dir = std::env::current_dir().map_err(Error::GetCurrentDir)?; let repository_url = URI::try_from(args.repository_url.as_str()) - .map(uriparse::URI::into_owned) + .map(URI::into_owned) .map_err(|e| Error::InvalidRepositoryUrl(args.repository_url.clone(), e))?; let declarations_starting_version = args @@ -60,8 +60,8 @@ pub(crate) fn execute(args: PrepareReleaseArgs) -> Result<()> { }) .transpose()?; - let buildpack_dirs = find_releasable_buildpacks(¤t_dir) - .map_err(|e| Error::FindingBuildpacks(current_dir.clone(), e))?; + let buildpack_dirs = + find_releasable_buildpacks(¤t_dir).map_err(Error::FindReleasableBuildpacks)?; if buildpack_dirs.is_empty() { Err(Error::NoBuildpacksFound(current_dir))?; diff --git a/src/commands/prepare_release/errors.rs b/src/commands/prepare_release/errors.rs index e286e8da..0df3af6e 100644 --- a/src/commands/prepare_release/errors.rs +++ b/src/commands/prepare_release/errors.rs @@ -1,140 +1,53 @@ +use crate::buildpacks::FindReleasableBuildpacksError; use crate::changelog::ChangelogError; -use crate::github::actions::SetOutputError; +use crate::github::actions::SetActionOutputError; use libcnb_data::buildpack::BuildpackVersion; use std::collections::HashMap; -use std::fmt::{Display, Formatter}; use std::io; use std::path::PathBuf; -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub(crate) enum Error { + #[error("Failed to get current directory\nError: {0}")] GetCurrentDir(io::Error), - InvalidRepositoryUrl(String, uriparse::URIError), - InvalidDeclarationsStartingVersion(String, semver::Error), + #[error(transparent)] + FindReleasableBuildpacks(FindReleasableBuildpacksError), + #[error(transparent)] + SetActionOutput(SetActionOutputError), + #[error("Invalid URL `{0}` for argument --repository-url\nError: {1}")] + InvalidRepositoryUrl(String, #[source] uriparse::URIError), + #[error("Invalid Version `{0}` for argument --declarations-starting-version\nError: {1}")] + InvalidDeclarationsStartingVersion(String, #[source] semver::Error), + #[error("No buildpacks found under {}", .0.display())] NoBuildpacksFound(PathBuf), + #[error("Not all versions match:\n{}", list_versions_with_path(.0))] NotAllVersionsMatch(HashMap), + #[error("No fixed version could be determined")] NoFixedVersion, - FindingBuildpacks(PathBuf, io::Error), - ReadingChangelog(PathBuf, io::Error), - ParsingChangelog(PathBuf, ChangelogError), - ReadingBuildpack(PathBuf, io::Error), - ParsingBuildpack(PathBuf, toml_edit::TomlError), + #[error("Could not read changelog\nPath: {}\nError: {1}", .0.display())] + ReadingChangelog(PathBuf, #[source] io::Error), + #[error("Could not parse changelog\nPath: {}\nError: {1}", .0.display())] + ParsingChangelog(PathBuf, #[source] ChangelogError), + #[error("Could not write changelog\nPath: {}\nError: {1}", .0.display())] + WritingChangelog(PathBuf, #[source] io::Error), + #[error("Missing required field `{1}` in buildpack.toml\nPath: {}", .0.display())] MissingRequiredField(PathBuf, String), + #[error("Invalid buildpack id `{1}` in buildpack.toml\nPath: {}", .0.display())] InvalidBuildpackId(PathBuf, String), + #[error("Invalid buildpack version `{1}` in buildpack.toml\nPath: {}", .0.display())] InvalidBuildpackVersion(PathBuf, String), - WritingBuildpack(PathBuf, io::Error), - WritingChangelog(PathBuf, io::Error), - SetActionOutput(SetOutputError), + #[error("Could not read buildpack\nPath: {}\nError: {1}", .0.display())] + ReadingBuildpack(PathBuf, #[source] io::Error), + #[error("Could not parse buildpack\nPath: {}\nError: {1}", .0.display())] + ParsingBuildpack(PathBuf, #[source] toml_edit::TomlError), + #[error("Could not write buildpack\nPath: {}\nError: {1}", .0.display())] + WritingBuildpack(PathBuf, #[source] io::Error), } -impl Display for Error { - #[allow(clippy::too_many_lines)] - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Error::GetCurrentDir(error) => { - write!(f, "Failed to get current directory\nError: {error}") - } - Error::InvalidRepositoryUrl(value, error) => { - write!( - f, - "Invalid URL `{value}` for argument --repository-url\nError: {error}" - ) - } - Error::InvalidDeclarationsStartingVersion(value, error) => { - write!(f, "Invalid Version `{value}` for argument --declarations-starting-version\nError: {error}") - } - Error::NoBuildpacksFound(path) => { - write!(f, "No buildpacks found under {}", path.display()) - } - Error::NotAllVersionsMatch(version_map) => { - write!( - f, - "Not all versions match:\n{}", - version_map - .iter() - .map(|(path, version)| format!("• {version} ({})", path.display())) - .collect::>() - .join("\n") - ) - } - Error::NoFixedVersion => { - write!(f, "No fixed version could be determined") - } - Error::FindingBuildpacks(path, error) => { - write!( - f, - "I/O error while finding buildpacks\nPath: {}\nError: {error}", - path.display() - ) - } - Error::ReadingBuildpack(path, error) => { - write!( - f, - "Could not read buildpack\nPath: {}\nError: {error}", - path.display() - ) - } - Error::ParsingBuildpack(path, error) => { - write!( - f, - "Could not parse buildpack\nPath: {}\nError: {error}", - path.display() - ) - } - Error::WritingBuildpack(path, error) => { - write!( - f, - "Could not write buildpack\nPath: {}\nError: {error}", - path.display() - ) - } - Error::ReadingChangelog(path, error) => { - write!( - f, - "Could not read changelog\nPath: {}\nError: {error}", - path.display() - ) - } - Error::ParsingChangelog(path, error) => { - write!( - f, - "Could not parse changelog\nPath: {}\nError: {error}", - path.display() - ) - } - Error::WritingChangelog(path, error) => { - write!( - f, - "Could not write changelog\nPath: {}\nError: {error}", - path.display() - ) - } - Error::SetActionOutput(set_output_error) => match set_output_error { - SetOutputError::Opening(error) | SetOutputError::Writing(error) => { - write!(f, "Could not write action output\nError: {error}") - } - }, - Error::MissingRequiredField(path, field) => { - write!( - f, - "Missing required field `{field}` in buildpack.toml\nPath: {}", - path.display() - ) - } - Error::InvalidBuildpackId(path, id) => { - write!( - f, - "Invalid buildpack id `{id}` in buildpack.toml\nPath: {}", - path.display() - ) - } - Error::InvalidBuildpackVersion(path, version) => { - write!( - f, - "Invalid buildpack version `{version}` in buildpack.toml\nPath: {}", - path.display() - ) - } - } - } +fn list_versions_with_path(version_map: &HashMap) -> String { + version_map + .iter() + .map(|(path, version)| format!("• {version} ({})", path.display())) + .collect::>() + .join("\n") } diff --git a/src/commands/update_builder/command.rs b/src/commands/update_builder/command.rs index e6c83470..5833f0b8 100644 --- a/src/commands/update_builder/command.rs +++ b/src/commands/update_builder/command.rs @@ -1,11 +1,13 @@ use crate::buildpacks::{ - calculate_digest, find_releasable_buildpacks, read_image_repository_metadata, + calculate_digest, find_releasable_buildpacks, read_buildpack_descriptor, + read_image_repository_metadata, }; +use crate::commands::resolve_path; use crate::update_builder::errors::Error; use clap::Parser; use libcnb_data::buildpack::{BuildpackId, BuildpackVersion}; -use libcnb_package::read_buildpack_data; -use std::path::{Path, PathBuf}; +use std::collections::BTreeMap; +use std::path::PathBuf; use std::str::FromStr; use toml_edit::{value, ArrayOfTables, Document, Item}; use uriparse::URI; @@ -16,9 +18,9 @@ type Result = std::result::Result; #[command(author, version, about = "Updates all references to a buildpack in heroku/cnb-builder-images for the given list of builders", long_about = None)] pub(crate) struct UpdateBuilderArgs { #[arg(long)] - pub(crate) repository_path: String, + pub(crate) repository_path: PathBuf, #[arg(long)] - pub(crate) builder_repository_path: String, + pub(crate) builder_repository_path: PathBuf, #[arg(long, required = true, value_delimiter = ',', num_args = 1..)] pub(crate) builders: Vec, } @@ -29,16 +31,23 @@ struct BuilderFile { } pub(crate) fn execute(args: UpdateBuilderArgs) -> Result<()> { - let current_dir = std::env::current_dir().map_err(Error::GetCurrentDir)?; - let repository_path = resolve_path(PathBuf::from(args.repository_path), ¤t_dir); - let builder_repository_path = - resolve_path(PathBuf::from(args.builder_repository_path), ¤t_dir); + let repository_path = std::env::current_dir() + .map(|base| resolve_path(&args.repository_path, &base)) + .map_err(|e| Error::ResolvePath(args.repository_path, e))?; + + let builder_repository_path = std::env::current_dir() + .map(|base| resolve_path(&args.builder_repository_path, &base)) + .map_err(|e| Error::ResolvePath(args.builder_repository_path, e))?; let buildpacks = find_releasable_buildpacks(&repository_path) - .map_err(|e| Error::FindingBuildpacks(current_dir.clone(), e))? + .map_err(Error::FindReleasableBuildpacks)? .into_iter() - .map(|dir| read_buildpack_data(dir).map_err(Error::ReadingBuildpackData)) - .collect::>>()?; + .map(|dir| { + read_buildpack_descriptor(&dir) + .map_err(Error::ReadBuildpackDescriptor) + .map(|buildpack_descriptor| (dir, buildpack_descriptor)) + }) + .collect::>>()?; if buildpacks.is_empty() { Err(Error::NoBuildpacks(repository_path))?; @@ -57,17 +66,16 @@ pub(crate) fn execute(args: UpdateBuilderArgs) -> Result<()> { } for mut builder_file in builder_files { - for buildpack_data in &buildpacks { - let buildpack_path = &buildpack_data.buildpack_descriptor_path; + for (buildpack_dir, buildpack_descriptor) in &buildpacks { + let buildpack_path = buildpack_dir.join("buildpack.toml"); - let buildpack_id = &buildpack_data.buildpack_descriptor.buildpack().id; + let buildpack_id = &buildpack_descriptor.buildpack().id; - let buildpack_version = &buildpack_data.buildpack_descriptor.buildpack().version; + let buildpack_version = &buildpack_descriptor.buildpack().version; - let docker_repository = - read_image_repository_metadata(&buildpack_data.buildpack_descriptor).ok_or( - Error::MissingDockerRepositoryMetadata(buildpack_path.clone()), - )?; + let docker_repository = read_image_repository_metadata(buildpack_descriptor).ok_or( + Error::MissingDockerRepositoryMetadata(buildpack_path.clone()), + )?; let buildpack_uri = calculate_digest(&format!("{docker_repository}:{buildpack_version}")) @@ -91,14 +99,6 @@ pub(crate) fn execute(args: UpdateBuilderArgs) -> Result<()> { Ok(()) } -fn resolve_path(path: PathBuf, current_dir: &Path) -> PathBuf { - if path.is_absolute() { - path - } else { - current_dir.join(path) - } -} - fn read_builder_file(path: PathBuf) -> Result { let contents = std::fs::read_to_string(&path).map_err(|e| Error::ReadingBuilder(path.clone(), e))?; diff --git a/src/commands/update_builder/errors.rs b/src/commands/update_builder/errors.rs index 82d50336..1ad8a9df 100644 --- a/src/commands/update_builder/errors.rs +++ b/src/commands/update_builder/errors.rs @@ -1,130 +1,38 @@ -use crate::buildpacks::CalculateDigestError; -use libcnb_package::ReadBuildpackDataError; -use std::fmt::{Display, Formatter}; +use crate::buildpacks::{ + CalculateDigestError, FindReleasableBuildpacksError, ReadBuildpackDescriptorError, +}; use std::path::PathBuf; -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub(crate) enum Error { - GetCurrentDir(std::io::Error), - FindingBuildpacks(PathBuf, std::io::Error), - ReadingBuildpackData(ReadBuildpackDataError), + #[error("Failed to resolve path {}\nError: {1}", .0.display())] + ResolvePath(PathBuf, std::io::Error), + #[error(transparent)] + FindReleasableBuildpacks(FindReleasableBuildpacksError), + #[error(transparent)] + ReadBuildpackDescriptor(ReadBuildpackDescriptorError), + #[error("No buildpacks were found in the given directory\nPath: {}", .0.display())] NoBuildpacks(PathBuf), - ReadingBuilder(PathBuf, std::io::Error), - ParsingBuilder(PathBuf, toml_edit::TomlError), + #[error("Could not read builder\nPath: {}\nError: {1}", .0.display())] + ReadingBuilder(PathBuf, #[source] std::io::Error), + #[error("Could not parse builder\nPath: {}\nError: {1}", .0.display())] + ParsingBuilder(PathBuf, #[source] toml_edit::TomlError), + #[error("Error writing builder\nPath: {}\nError: {1}", .0.display())] + WritingBuilder(PathBuf, #[source] std::io::Error), + #[error("No builder.toml files found in the given builder directories\n{}", list_builders(.0))] NoBuilderFiles(Vec), + #[error("The following buildpack is missing the metadata.release.docker.repository entry\nPath: {}", .0.display())] MissingDockerRepositoryMetadata(PathBuf), - CalculatingDigest(PathBuf, CalculateDigestError), + #[error("Failed to calculate digest for buildpack\nPath: {}\nError: {1}", .0.display())] + CalculatingDigest(PathBuf, #[source] CalculateDigestError), + #[error("Missing required key `{0}` in builder")] BuilderMissingRequiredKey(String), - WritingBuilder(PathBuf, std::io::Error), } -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Error::GetCurrentDir(error) => { - write!(f, "Could not get the current directory\nError: {error}") - } - - Error::ReadingBuilder(path, error) => { - write!( - f, - "Could not read builder\nPath: {}\nError: {error}", - path.display() - ) - } - - Error::ParsingBuilder(path, error) => { - write!( - f, - "Could not parse builder\nPath: {}\nError: {error}", - path.display() - ) - } - - Error::WritingBuilder(path, error) => { - write!( - f, - "Error writing builder\nPath: {}\nError: {error}", - path.display() - ) - } - - Error::NoBuilderFiles(builders) => { - write!( - f, - "No builder.toml files found in the given builder directories\n{}", - builders - .iter() - .map(|builder| format!("• {builder}")) - .collect::>() - .join("\n") - ) - } - - Error::FindingBuildpacks(path, error) => { - write!( - f, - "I/O error while finding buildpacks\nPath: {}\nError: {error}", - path.display() - ) - } - - Error::ReadingBuildpackData(error) => match error { - ReadBuildpackDataError::ReadingBuildpack { path, source } => { - write!( - f, - "Failed to read buildpack\nPath: {}\nError: {source}", - path.display() - ) - } - - ReadBuildpackDataError::ParsingBuildpack { path, source } => { - write!( - f, - "Failed to parse buildpack\nPath: {}\nError: {source}", - path.display() - ) - } - }, - - Error::NoBuildpacks(path) => { - write!( - f, - "No buildpacks were found in the given directory\nPath: {}", - path.display() - ) - } - - Error::BuilderMissingRequiredKey(key) => { - write!(f, "Missing required key `{key}` in builder",) - } - - Error::MissingDockerRepositoryMetadata(buildpack_path) => { - write!( - f, - "The following buildpack is missing the metadata.release.docker.repository entry\nPath: {}", - buildpack_path.display() - ) - } - - Error::CalculatingDigest(buildpack_path, calculate_digest_error) => { - match calculate_digest_error { - CalculateDigestError::CommandFailure(digest_url, error) => { - write!( - f, - "Failed to execute crane digest {digest_url}\nPath: {}\nError: {error}", - buildpack_path.display() - ) - } - - CalculateDigestError::ExitStatus(digest_url, status) => { - write!( - f, - "Command crane digest {digest_url} exited with a non-zero status\nStatus: {status}", - ) - } - } - } - } - } +fn list_builders(builders: &[String]) -> String { + builders + .iter() + .map(|builder| format!("• {builder}")) + .collect::>() + .join("\n") } diff --git a/src/github/actions.rs b/src/github/actions.rs index 7cc46f57..cd531912 100644 --- a/src/github/actions.rs +++ b/src/github/actions.rs @@ -6,7 +6,7 @@ use std::io::{stdout, Write}; pub(crate) fn set_output, V: Into>( name: N, value: V, -) -> Result<(), SetOutputError> { +) -> Result<(), SetActionOutputError> { let name = name.into(); let value = value.into(); @@ -23,18 +23,20 @@ pub(crate) fn set_output, V: Into>( let append_file = OpenOptions::new() .append(true) .open(github_output) - .map_err(SetOutputError::Opening)?; + .map_err(SetActionOutputError::Opening)?; Box::new(append_file) } Err(_) => Box::new(stdout()), }; file.write_all(line.as_bytes()) - .map_err(SetOutputError::Writing) + .map_err(SetActionOutputError::Writing) } -#[derive(Debug)] -pub(crate) enum SetOutputError { - Opening(io::Error), - Writing(io::Error), +#[derive(Debug, thiserror::Error)] +pub(crate) enum SetActionOutputError { + #[error("Could not open action output\nError: {0}")] + Opening(#[source] io::Error), + #[error("Could not write action output\nError: {0}")] + Writing(#[source] io::Error), } diff --git a/src/main.rs b/src/main.rs index 5d147b40..0adb3888 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ pub(crate) enum Cli { fn main() { match Cli::parse() { Cli::GenerateBuildpackMatrix(args) => { - if let Err(error) = generate_buildpack_matrix::execute(args) { + if let Err(error) = generate_buildpack_matrix::execute(&args) { eprintln!("❌ {error}"); std::process::exit(UNSPECIFIED_ERROR); }