diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..b84ef5d --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,71 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Rust Toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --verbose --check + + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Rust Toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - run: cargo clippy -- -Dwarnings + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Rust Toolchain + uses: dtolnay/rust-toolchain@stable + - run: cargo test --verbose + + build-container: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + # From https://podman.io/docs/installation#ubuntu + # There is a bug in earlier versions of buildah/podman where the TARGETPLATFORM arg is not set correctly + - name: Upgrade podman + run: | + sudo mkdir -p /etc/apt/keyrings && \ + curl -fsSL \ + "https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_22.04/Release.key" \ + | gpg --dearmor \ + | sudo tee /etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg > /dev/null && \ + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg] https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_22.04/ /" \ + | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list > /dev/null && \ + sudo apt update && \ + sudo apt install -y podman + + - run: podman version + + - name: Build ARM image + run: podman build --platform linux/arm64/v8 -t podlet . + + - name: Build x86 image + run: podman build --platform linux/amd64 -t podlet . + + - name: Test run image + run: podman run localhost/podlet -h diff --git a/.github/workflows/format-clippy-test.yaml b/.github/workflows/format-clippy-test.yaml deleted file mode 100644 index 3d8ba21..0000000 --- a/.github/workflows/format-clippy-test.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Rust Format, Clippy Lint, Test - -on: - push: - branches: - - main - pull_request: - -env: - CARGO_TERM_COLOR: always - -jobs: - format: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Rust Toolchain - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - run: cargo fmt --verbose --check - - clippy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Rust Toolchain - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - run: cargo clippy -- -Dwarnings - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Rust Toolchain - uses: dtolnay/rust-toolchain@stable - - run: cargo test --verbose diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc129e4..e696ba9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,137 +1,256 @@ +# Copyright 2022-2023, axodotdev +# SPDX-License-Identifier: MIT or Apache-2.0 +# # CI that: # # * checks for a Git Tag that looks like a release -# * creates a Github Release™ and fills in its text -# * builds artifacts with cargo-dist (executable-zips, installers) -# * uploads those artifacts to the Github Release™ +# * builds artifacts with cargo-dist (archives, installers, hashes) +# * uploads those artifacts to temporary workflow zip +# * on success, uploads the artifacts to a Github Release # -# Note that the Github Release™ will be created before the artifacts, -# so there will be a few minutes where the release has no artifacts -# and then they will slowly trickle in, possibly failing. To make -# this more pleasant we mark the release as a "draft" until all -# artifacts have been successfully uploaded. This allows you to -# choose what to do with partial successes and avoids spamming -# anyone with notifications before the release is actually ready. +# Note that the Github Release will be created with a generated +# title/body based on your changelogs. + name: Release permissions: contents: write # This task will run whenever you push a git tag that looks like a version -# like "v1", "v1.2.0", "v0.1.0-prerelease01", "my-app-v1.0.0", etc. -# The version will be roughly parsed as ({PACKAGE_NAME}-)?v{VERSION}, where +# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. +# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION -# must be a Cargo-style SemVer Version. +# must be a Cargo-style SemVer Version (must have at least major.minor.patch). # -# If PACKAGE_NAME is specified, then we will create a Github Release™ for that +# If PACKAGE_NAME is specified, then the announcement will be for that # package (erroring out if it doesn't have the given version or isn't cargo-dist-able). # -# If PACKAGE_NAME isn't specified, then we will create a Github Release™ for all -# (cargo-dist-able) packages in the workspace with that version (this is mode is +# If PACKAGE_NAME isn't specified, then the announcement will be for all +# (cargo-dist-able) packages in the workspace with that version (this mode is # intended for workspaces with only one dist-able package, or with all dist-able # packages versioned/released in lockstep). # # If you push multiple tags at once, separate instances of this workflow will -# spin up, creating an independent Github Release™ for each one. +# spin up, creating an independent announcement for each one. However Github +# will hard limit this to 3 tags per commit, as it will assume more tags is a +# mistake. # -# If there's a prerelease-style suffix to the version then the Github Release™ +# If there's a prerelease-style suffix to the version, then the release(s) # will be marked as a prerelease. on: push: tags: - - '*-?v[0-9]+*' + - '**[0-9]+.[0-9]+.[0-9]+*' + pull_request: jobs: - # Create the Github Release™ so the packages have something to be uploaded to - create-release: + # Run 'cargo dist plan' (or host) to determine what tasks we need to do + plan: runs-on: ubuntu-latest outputs: - has-releases: ${{ steps.create-release.outputs.has-releases }} + val: ${{ steps.plan.outputs.manifest }} + tag: ${{ !github.event.pull_request && github.ref_name || '' }} + tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} + publishing: ${{ !github.event.pull_request }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: rustup update 1.70.0 --no-self-update && rustup default 1.70.0 + - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install cargo-dist - run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.7/cargo-dist-installer.sh | sh - - id: create-release + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.5.0/cargo-dist-installer.sh | sh" + # sure would be cool if github gave us proper conditionals... + # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible + # functionality based on whether this is a pull_request, and whether it's from a fork. + # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* + # but also really annoying to build CI around when it needs secrets to work right.) + - id: plan run: | - cargo dist plan --tag=${{ github.ref_name }} --output-format=json > dist-manifest.json - echo "dist plan ran successfully" + cargo dist ${{ !github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name) || (github.event.pull_request.head.repo.fork && 'plan' || 'host --steps=check') }} --output-format=json > dist-manifest.json + echo "cargo dist ran successfully" cat dist-manifest.json + echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v3 + with: + name: artifacts + path: dist-manifest.json - # Create the Github Release™ based on what cargo-dist thinks it should be - ANNOUNCEMENT_TITLE=$(jq --raw-output ".announcement_title" dist-manifest.json) - IS_PRERELEASE=$(jq --raw-output ".announcement_is_prerelease" dist-manifest.json) - jq --raw-output ".announcement_github_body" dist-manifest.json > new_dist_announcement.md - gh release create ${{ github.ref_name }} --draft --prerelease="$IS_PRERELEASE" --title="$ANNOUNCEMENT_TITLE" --notes-file=new_dist_announcement.md - echo "created announcement!" + # Build and packages all the platform-specific things + build-local-artifacts: + name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) + # Let the initial task tell us to not run (currently very blunt) + needs: plan + if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} + strategy: + fail-fast: false + # Target platforms/runners are computed by cargo-dist in create-release. + # Each member of the matrix has the following arguments: + # + # - runner: the github runner + # - dist-args: cli flags to pass to cargo dist + # - install-dist: expression to run to install cargo-dist on the runner + # + # Typically there will be: + # - 1 "global" task that builds universal installers + # - N "local" tasks that build each platform's binaries and platform-specific installers + matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} + runs-on: ${{ matrix.runner }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: swatinem/rust-cache@v2 + - name: Install cargo-dist + run: ${{ matrix.install_dist }} + # Get the dist-manifest + - name: Fetch local artifacts + uses: actions/download-artifact@v3 + with: + name: artifacts + path: target/distrib/ + - name: Install dependencies + run: | + ${{ matrix.packages_install }} + - name: Build artifacts + run: | + # Actually do builds and make zips and whatnot + cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json + echo "cargo dist ran successfully" + - id: cargo-dist + name: Post-build + # We force bash here just because github makes it really hard to get values up + # to "real" actions without writing to env-vars, and writing to env-vars has + # inconsistent syntax between shell and powershell. + shell: bash + run: | + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" - # Upload the manifest to the Github Release™ - gh release upload ${{ github.ref_name }} dist-manifest.json - echo "uploaded manifest!" + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v3 + with: + name: artifacts + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} - # Disable all the upload-artifacts tasks if we have no actual releases - HAS_RELEASES=$(jq --raw-output ".releases != null" dist-manifest.json) - echo "has-releases=$HAS_RELEASES" >> "$GITHUB_OUTPUT" + # Build and package all the platform-agnostic(ish) things + build-global-artifacts: + needs: + - plan + - build-local-artifacts + runs-on: "ubuntu-20.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/dist-manifest.json + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install cargo-dist + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.5.0/cargo-dist-installer.sh | sh" + # Get all the local artifacts for the global tasks to use (for e.g. checksums) + - name: Fetch local artifacts + uses: actions/download-artifact@v3 + with: + name: artifacts + path: target/distrib/ + - id: cargo-dist + shell: bash + run: | + cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json + echo "cargo dist ran successfully" - # Build and packages all the things - upload-artifacts: - # Let the initial task tell us to not run (currently very blunt) - needs: create-release - if: ${{ needs.create-release.outputs.has-releases == 'true' }} - strategy: - matrix: - # For these target platforms - include: - - os: macos-11 - dist-args: --artifacts=local --target=aarch64-apple-darwin --target=x86_64-apple-darwin - install-dist: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.7/cargo-dist-installer.sh | sh - - os: ubuntu-20.04 - dist-args: --artifacts=local --target=x86_64-unknown-linux-gnu - install-dist: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.7/cargo-dist-installer.sh | sh - - os: windows-2019 - dist-args: --artifacts=local --target=x86_64-pc-windows-msvc - install-dist: irm https://github.com/axodotdev/cargo-dist/releases/download/v0.0.7/cargo-dist-installer.ps1 | iex + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" - runs-on: ${{ matrix.os }} + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v3 + with: + name: artifacts + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + # Determines if we should publish/announce + host: + needs: + - plan + - build-local-artifacts + - build-global-artifacts + # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) + if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: "ubuntu-20.04" + outputs: + val: ${{ steps.host.outputs.manifest }} steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: rustup update 1.70.0 --no-self-update && rustup default 1.70.0 + - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install cargo-dist - run: ${{ matrix.install-dist }} - - name: Run cargo-dist - # This logic is a bit janky because it's trying to be a polyglot between - # powershell and bash since this will run on windows, macos, and linux! - # The two platforms don't agree on how to talk about env vars but they - # do agree on 'cat' and '$()' so we use that to marshal values between commands. + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.5.0/cargo-dist-installer.sh | sh" + # Fetch artifacts from scratch-storage + - name: Fetch artifacts + uses: actions/download-artifact@v3 + with: + name: artifacts + path: target/distrib/ + # This is a harmless no-op for Github Releases, hosting for that happens in "announce" + - id: host + shell: bash run: | - # Actually do builds and make zips and whatnot - cargo dist build --tag=${{ github.ref_name }} --output-format=json ${{ matrix.dist-args }} > dist-manifest.json - echo "dist ran successfully" + cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json + echo "artifacts uploaded and released successfully" cat dist-manifest.json + echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v3 + with: + name: artifacts + path: dist-manifest.json - # Parse out what we just built and upload it to the Github Release™ - jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json > uploads.txt - echo "uploading..." - cat uploads.txt - gh release upload ${{ github.ref_name }} $(cat uploads.txt) - echo "uploaded!" - - # Mark the Github Release™ as a non-draft now that everything has succeeded! - publish-release: - # Only run after all the other tasks, but it's ok if upload-artifacts was skipped - needs: [create-release, upload-artifacts] - if: ${{ always() && needs.create-release.result == 'success' && (needs.upload-artifacts.result == 'skipped' || needs.upload-artifacts.result == 'success') }} - runs-on: ubuntu-latest + # Create a Github Release while uploading all files to it + announce: + needs: + - plan + - host + # use "always() && ..." to allow us to wait for all publish jobs while + # still allowing individual publish jobs to skip themselves (for prereleases). + # "host" however must run to completion, no skipping allowed! + if: ${{ always() && needs.host.result == 'success' }} + runs-on: "ubuntu-20.04" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 - - name: mark release as non-draft + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: "Download Github Artifacts" + uses: actions/download-artifact@v3 + with: + name: artifacts + path: artifacts + - name: Cleanup run: | - gh release edit ${{ github.ref_name }} --draft=false + # Remove the granular manifests + rm -f artifacts/*-dist-manifest.json + - name: Create Github Release + uses: ncipollo/release-action@v1 + with: + tag: ${{ needs.plan.outputs.tag }} + name: ${{ fromJson(needs.host.outputs.val).announcement_title }} + body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }} + prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }} + artifacts: "artifacts/*" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1c0bb..5ed4dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## [0.2.1] - 2023-11-28 + +### Features + +- Compose: Read compose file from stdin ([#18](https://github.com/k9withabone/podlet/discussions/18)) + - For `podlet compose`, if a compose file is not provided and stdin is not a terminal, or `-` is provided, podlet will attempt to read a compose file from stdin. + - For example `cat compose-example.yaml | podlet compose` or `cat compose-example.yaml | podlet compose -` + +### Bug Fixes + +- Truncate when overwriting existing files +- Compose service volumes can be mixed long and short form ([#26](https://github.com/k9withabone/podlet/issues/26)) + +### Documentation + +- Readme: Add sample podlet container usage instructions ([#17](https://github.com/k9withabone/podlet/pull/17), thanks [@Nitrousoxide](https://github.com/Nitrousoxide)!) +- Readme: Update description, add build and local ci instructions + +### Miscellaneous Tasks + +- CI: Update podman for build and publish of container +- CI: Add container builds to regular checks +- Update dependencies +- CI: Update cargo-dist to v0.5.0 + +### Refactor + +- `quadlet::writeln_escape_spaces` write to formatter +- Consistent use of `eyre::bail` and `eyre::ensure` +- Add `quadlet::Kube::new()` +- Simplify `cli::File::write()` +- Split `compose_try_into_quadlet_files()` +- Move compose functions into their own module +- Move lints to Cargo.toml, add additional lints + +### Styling + +- Fix let-else formatting + ## [0.2.0] - 2023-06-15 ### Added diff --git a/Cargo.lock b/Cargo.lock index fd38aba..bba3342 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -19,45 +19,38 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -68,24 +61,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-broadcast" @@ -93,32 +86,34 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" dependencies = [ - "event-listener", + "event-listener 2.5.3", "futures-core", ] [[package]] name = "async-channel" -version = "1.8.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 4.0.0", + "event-listener-strategy", "futures-core", + "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.5.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" dependencies = [ - "async-lock", + "async-lock 3.1.2", "async-task", "concurrent-queue", - "fastrand", - "futures-lite", + "fastrand 2.0.1", + "futures-lite 2.0.1", "slab", ] @@ -128,10 +123,10 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", - "futures-lite", + "futures-lite 1.13.0", ] [[package]] @@ -140,80 +135,127 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", - "futures-lite", + "futures-lite 1.13.0", "log", "parking", - "polling", - "rustix", + "polling 2.8.0", + "rustix 0.37.27", "slab", "socket2", "waker-fn", ] +[[package]] +name = "async-io" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff" +dependencies = [ + "async-lock 3.1.2", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.0.1", + "parking", + "polling 3.3.1", + "rustix 0.38.25", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + [[package]] name = "async-lock" -version = "2.7.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316" dependencies = [ - "event-listener", + "event-listener 4.0.0", + "event-listener-strategy", + "pin-project-lite", ] [[package]] name = "async-process" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" dependencies = [ - "async-io", - "async-lock", - "autocfg", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", "blocking", "cfg-if", - "event-listener", - "futures-lite", - "rustix", - "signal-hook", - "windows-sys", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.25", + "windows-sys 0.48.0", ] [[package]] name = "async-recursion" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.39", +] + +[[package]] +name = "async-signal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +dependencies = [ + "async-io 2.2.1", + "async-lock 2.8.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.25", + "signal-hook-registry", + "slab", + "windows-sys 0.48.0", ] [[package]] name = "async-task" -version = "4.4.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.39", ] [[package]] name = "atomic-waker" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -223,9 +265,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -238,9 +280,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" @@ -248,6 +290,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "block-buffer" version = "0.10.4" @@ -259,36 +307,40 @@ dependencies = [ [[package]] name = "blocking" -version = "1.3.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ "async-channel", - "async-lock", + "async-lock 3.1.2", "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "log", + "fastrand 2.0.1", + "futures-io", + "futures-lite 2.0.1", + "piper", + "tracing", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -298,56 +350,53 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ - "android-tzdata", "num-traits", "serde", ] [[package]] name = "clap" -version = "4.3.3" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8f255e4b8027970e78db75e78831229c9815fdbfa67eb1a1b777a62e24b4a0" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.3" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd4f3c17c83b0ba34ffbc4f8bbd74f079413f747f84a6f89292f138057e36ab" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.39", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "color-eyre" @@ -366,9 +415,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", "owo-colors", @@ -384,27 +433,27 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -508,9 +557,9 @@ dependencies = [ [[package]] name = "docker-compose-types" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "920abe4782ec03907a2f49f74a4fef2e12540a599436a9acd9303030da0a12f8" +checksum = "e06f731bc05616203869650c1a4f2c12d97edc3eef58f8c6a51fe430d31ad8ba" dependencies = [ "derive_builder", "indexmap", @@ -520,9 +569,9 @@ dependencies = [ [[package]] name = "duration-str" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f037c488d179e21c87ef5fa9c331e8e62f5dddfa84618b41bb197da03edff1" +checksum = "5e172e85f305d6a442b250bf40667ffcb91a24f52c9a1ca59e2fa991ac9b7790" dependencies = [ "nom", "rust_decimal", @@ -531,9 +580,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" dependencies = [ "enumflags2_derive", "serde", @@ -541,47 +590,74 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.39", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.1" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" dependencies = [ - "cc", - "libc", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] name = "event-listener" -version = "2.5.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.0", + "pin-project-lite", +] [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" dependencies = [ "indenter", "once_cell", @@ -596,6 +672,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "fnv" version = "1.0.7" @@ -604,24 +686,24 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-lite" @@ -629,7 +711,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ - "fastrand", + "fastrand 1.9.0", "futures-core", "futures-io", "memchr", @@ -638,23 +720,37 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-lite" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", "futures-io", @@ -678,9 +774,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -689,15 +785,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -707,9 +803,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -725,9 +821,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -741,11 +837,11 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ - "autocfg", + "equivalent", "hashbrown", "serde", ] @@ -767,38 +863,26 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" - -[[package]] -name = "is-terminal" -version = "0.4.7" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", -] +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "k8s-openapi" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd990069640f9db34b3b0f7a1afc62a05ffaa3be9b66aa3c313f58346df7f788" +checksum = "edc3606fd16aca7989db2f84bb25684d0270c6d6fa1dbcd0025af7b4130523a6" dependencies = [ "base64", "bytes", @@ -816,9 +900,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.146" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" @@ -826,17 +910,23 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + [[package]] name = "log" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -855,24 +945,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "nix" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset", - "static_assertions", ] [[package]] @@ -887,18 +976,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "object" -version = "0.30.4" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -911,9 +1000,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "ordered-float" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] @@ -936,21 +1025,21 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -958,9 +1047,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + [[package]] name = "podlet" -version = "0.2.0" +version = "0.2.1" dependencies = [ "clap", "color-eyre", @@ -984,13 +1084,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", - "bitflags", + "bitflags 1.3.2", "cfg-if", "concurrent-queue", "libc", "log", "pin-project-lite", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.25", + "tracing", + "windows-sys 0.52.0", ] [[package]] @@ -1011,18 +1125,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1059,18 +1173,30 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.8.4" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -1079,15 +1205,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rust_decimal" -version = "1.29.1" +version = "1.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" +checksum = "06676aec5ccb8fc1da723cc8c0f9a46549f21ebb8753d3915c6c41db1e7f1dc4" dependencies = [ "arrayvec", "num-traits", @@ -1101,29 +1227,42 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", - "windows-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys 0.4.11", + "windows-sys 0.48.0", ] [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -1140,20 +1279,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1162,20 +1301,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.39", ] [[package]] name = "serde_yaml" -version = "0.9.21" +version = "0.9.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" dependencies = [ "indexmap", "itoa", @@ -1186,9 +1325,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1197,28 +1336,18 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "signal-hook-registry" @@ -1231,18 +1360,18 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1273,9 +1402,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -1284,36 +1413,35 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ - "autocfg", "cfg-if", - "fastrand", + "fastrand 2.0.1", "redox_syscall", - "rustix", - "windows-sys", + "rustix 0.38.25", + "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.39", ] [[package]] @@ -1343,15 +1471,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", @@ -1360,11 +1488,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1372,20 +1499,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.39", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -1403,9 +1530,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "sharded-slab", "thread_local", @@ -1414,9 +1541,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uds_windows" @@ -1436,9 +1563,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1451,15 +1578,15 @@ dependencies = [ [[package]] name = "unsafe-libyaml" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "url" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1486,9 +1613,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "wasi" @@ -1524,71 +1651,137 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.4.1" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] @@ -1605,23 +1798,24 @@ dependencies = [ [[package]] name = "zbus" -version = "3.13.1" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c3d77c9966c28321f1907f0b6c5a5561189d1f7311eea6d94180c6be9daab29" +checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" dependencies = [ "async-broadcast", "async-executor", "async-fs", - "async-io", - "async-lock", + "async-io 1.13.0", + "async-lock 2.8.0", "async-process", "async-recursion", "async-task", "async-trait", + "blocking", "byteorder", "derivative", "enumflags2", - "event-listener", + "event-listener 2.5.3", "futures-core", "futures-sink", "futures-util", @@ -1645,24 +1839,23 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.13.1" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e341d12edaff644e539ccbbf7f161601294c9a84ed3d7e015da33155b435af" +checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "regex", "syn 1.0.109", - "winnow", "zvariant_utils", ] [[package]] name = "zbus_names" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82441e6033be0a741157a72951a3e4957d519698f3a824439cc131c5ba77ac2a" +checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" dependencies = [ "serde", "static_assertions", @@ -1671,9 +1864,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622cc473f10cef1b0d73b7b34a266be30ebdcfaea40ec297dd8cbda088f9f93c" +checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" dependencies = [ "byteorder", "enumflags2", @@ -1685,9 +1878,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d9c1b57352c25b778257c661f3c4744b7cefb7fc09dd46909a153cce7773da2" +checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index a41a69e..aa4eda8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,70 @@ [package] name = "podlet" -version = "0.2.0" +version = "0.2.1" authors = ["Paul Nettleton "] edition = "2021" -description = "Podlet generates podman quadlet (systemd-like) files from a podman command." +description = "Generate podman quadlet files from a podman command or a compose file" readme = "README.md" repository = "https://github.com/k9withabone/podlet" license = "MPL-2.0" -keywords = ["podman", "quadlet"] +keywords = ["podman", "quadlet", "containers"] categories = ["command-line-utilities"] +[lints.rust] +unused_crate_dependencies = "warn" +unused_import_braces = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" +unused_qualifications = "warn" + +[lints.clippy] +pedantic = "warn" + +cargo = { level = "warn", priority = -1 } +multiple_crate_versions = "allow" + +# restriction lint group +clone_on_ref_ptr = "warn" +dbg_macro = "warn" +empty_drop = "warn" +empty_structs_with_brackets = "warn" +exit = "warn" +format_push_string = "warn" +if_then_some_else_none = "warn" +indexing_slicing = "warn" +integer_division = "warn" +mixed_read_write_in_expression = "warn" +mod_module_files = "warn" +multiple_inherent_impl = "warn" +needless_raw_strings = "warn" +panic = "warn" +pub_without_shorthand = "warn" +rc_buffer = "warn" +rc_mutex = "warn" +redundant_type_annotations = "warn" +rest_pat_in_fully_bound_structs = "warn" +same_name_method = "warn" +semicolon_outside_block = "warn" +string_slice = "warn" +string_to_string = "warn" +suspicious_xor_used_as_pow = "warn" +tests_outside_test_module = "warn" +todo = "warn" +try_err = "warn" +unimplemented = "warn" +unnecessary_self_imports = "warn" +unreachable = "warn" +unwrap_used = "warn" +verbose_file_reads = "warn" + [dependencies] clap = { version = "4.2", features = ["derive"] } color-eyre = "0.6" -docker-compose-types = "0.5.1" -duration-str = { version = "0.5", default-features = false } -indexmap = { version = "1.9", features = ["std"] } +docker-compose-types = "0.6.1" +duration-str = { version = "0.7", default-features = false } +indexmap = "2" ipnet = "2.7" -k8s-openapi = { version = "0.18", features = ["v1_26"], default-features = false } +k8s-openapi = { version = "0.20", features = ["latest"] } serde_yaml = "0.9.21" shlex = "1.1" thiserror = "1.0.40" @@ -35,12 +82,12 @@ lto = "thin" # Config for 'cargo dist' [workspace.metadata.dist] # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) -cargo-dist-version = "0.0.7" -# The preferred Rust toolchain to use in CI (rustup toolchain syntax) -rust-toolchain-version = "1.70.0" -# CI backends to support (see 'cargo dist generate-ci') +cargo-dist-version = "0.5.0" +# CI backends to support ci = ["github"] # Target platforms to build apps for (Rust target-triple syntax) -targets = ["x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "aarch64-apple-darwin"] +targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"] # The installers to generate for each app installers = [] +# Publish jobs to run in CI +pr-run-mode = "plan" diff --git a/README.md b/README.md index f264c04..eb3a8b3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/k9withabone/podlet/format-clippy-test.yaml?event=push&label=ci&logo=github&style=flat-square) ![Crates.io License](https://img.shields.io/crates/l/podlet?style=flat-square) -Podlet generates [podman](https://podman.io/) [quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) (systemd-like) files from a podman command. +Podlet generates [podman](https://podman.io/) [quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) files from a podman command or a compose file. [![demo.gif](./demo.gif)](https://asciinema.org/a/591369) You can also view the demo on [asciinema](https://asciinema.org/a/591369). @@ -154,7 +154,35 @@ Podlet is meant to be used with podman v4.5.0 or newer. Some quadlet options are ## Contribution -This is my (@k9withabone) first real rust project and is mostly meant as a learning project for myself. That said, contributions, suggestions, and/or comments are appreciated! Feel free to create an [issue](https://github.com/k9withabone/podlet/issues), [discussion](https://github.com/k9withabone/podlet/discussions), or [pull request](https://github.com/k9withabone/podlet/pulls). +Contributions, suggestions, and/or comments are appreciated! Feel free to create an [issue](https://github.com/k9withabone/podlet/issues), [discussion](https://github.com/k9withabone/podlet/discussions), or [pull request](https://github.com/k9withabone/podlet/pulls). + +### Building + +Podlet is a normal Rust project, so once [Rust is installed](https://www.rust-lang.org/tools/install), the source code can be cloned and built with: + +```shell +git clone git@github.com:k9withabone/podlet.git +cd podlet +cargo build +``` + +Release builds are created with the `dist` profile: + +```shell +cargo build --profile dist +``` + +### Local CI + +If you are submitting code changes in a pull request and would like to run the CI jobs locally, you can run the following commands: + +- format: `cargo fmt --check` +- clippy: `cargo clippy` +- test: `cargo test` +- build-container: + - Ensure the container builds for both x86 and ARM platforms. + - `podman build --platform linux/amd64 -t podlet .` + - `podman build --platform linux/arm64/v8 -t podlet .` ## License diff --git a/src/cli.rs b/src/cli.rs index 73d1c01..8e614ef 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +mod compose; mod container; mod install; mod k8s; @@ -18,7 +19,6 @@ use std::{ fmt::{self, Display}, fs, io::{self, Write}, - iter, mem, path::{Path, PathBuf}, rc::Rc, }; @@ -28,7 +28,6 @@ use color_eyre::{ eyre::{self, Context}, Help, }; -use docker_compose_types::{Compose, MapOrEmpty}; use k8s_openapi::api::core::v1::{PersistentVolumeClaim, Pod}; use crate::quadlet; @@ -141,14 +140,6 @@ impl Cli { } for file in files { - let path: Cow = match &path { - FilePath::Full(path) => path.into(), - FilePath::Dir(path) => { - let mut path = path.join(file.name()); - path.set_extension(file.extension()); - path.into() - } - }; file.write(&path, overwrite)?; } @@ -186,9 +177,7 @@ impl Cli { } #[cfg(not(unix))] - return Err(eyre::eyre!( - "Cannot get podman unit directory on non-unix system" - )); + eyre::bail!("Cannot get podman unit directory on non-unix system"); } else if let Some(Some(path)) = &self.file { if path.is_dir() { path.clone() @@ -220,27 +209,22 @@ impl Cli { Ok(vec![file.into()]) } Commands::Compose { pod, compose_file } => { - let compose = compose_from_file(&compose_file)?; + let compose = compose::from_file_or_stdin(compose_file.as_deref())?; - if !compose.extensions.is_empty() { - eyre::bail!("extensions are not supported"); - } + eyre::ensure!( + compose.extensions.is_empty(), + "extensions are not supported" + ); if let Some(pod_name) = pod { let (pod, persistent_volume_claims) = k8s::compose_try_into_pod(compose, pod_name.clone())?; - let kube = quadlet::Kube { - config_map: Vec::new(), - log_driver: None, - network: Vec::new(), - publish_port: Vec::new(), - user_ns: None, - yaml: format!("{pod_name}-kube.yaml"), - }; + let kube_file_name = format!("{pod_name}-kube"); + let kube = quadlet::Kube::new(format!("{kube_file_name}.yaml")); let quadlet_file = quadlet::File { - name: pod_name.clone(), + name: pod_name, unit, resource: kube.into(), service: None, @@ -250,13 +234,13 @@ impl Cli { Ok(vec![ quadlet_file.into(), File::KubePod { - name: format!("{pod_name}-kube"), + name: kube_file_name, pod, persistent_volume_claims, }, ]) } else { - compose_try_into_quadlet_files(compose, &unit, &install) + compose::try_into_quadlet_files(compose, unit.as_ref(), install.as_ref()) .map(|result| result.map(Into::into)) .collect() } @@ -265,12 +249,31 @@ impl Cli { } } +/// [`PathBuf`] pointing to a file or directory #[derive(Debug)] enum FilePath { + /// [`PathBuf`] pointing to a file Full(PathBuf), + /// [`PathBuf`] pointing to a directory Dir(PathBuf), } +impl FilePath { + /// Convert to full file path + /// + /// If `self` is a directory, the [`File`] is used to set the filename. + fn to_full(&self, file: &File) -> Cow { + match self { + Self::Full(path) => path.into(), + Self::Dir(path) => { + let mut path = path.join(file.name()); + path.set_extension(file.extension()); + path.into() + } + } + } +} + #[derive(Subcommand, Debug, Clone, PartialEq)] enum Commands { /// Generate a podman quadlet file from a podman command @@ -300,7 +303,10 @@ enum Commands { /// The compose file to convert /// - /// If not provided, podlet will look for (in order) + /// If `-` or not provided and stdin is not a terminal, + /// the compose file will be read from stdin. + /// + /// If not provided, and stdin is a terminal, podlet will look for (in order) /// `compose.yaml`, `compose.yml`, `docker-compose.yaml`, and `docker-compose.yml`, /// in the current working directory. compose_file: Option, @@ -454,45 +460,40 @@ impl File { } } - fn write(&self, path: impl AsRef, overwrite: bool) -> color_eyre::Result<()> { - let path_display = path.as_ref().display().to_string(); - let mut file = fs::File::options() - .write(true) - .create_new(!overwrite) - .create(overwrite) - .open(path) - .map_err(|error| match error.kind() { + fn write(&self, path: &FilePath, overwrite: bool) -> color_eyre::Result<()> { + let path = path.to_full(self); + let mut file = open_file(&path, overwrite)?; + + let path = path.display(); + write!(file, "{self}").wrap_err_with(|| format!("Failed to write to file: {path}"))?; + println!("Wrote to file: {path}"); + + Ok(()) + } +} + +fn open_file(path: impl AsRef, overwrite: bool) -> color_eyre::Result { + fs::File::options() + .write(true) + .truncate(true) + .create_new(!overwrite) + .create(overwrite) + .open(&path) + .map_err(|error| { + let path = path.as_ref().display(); + match error.kind() { io::ErrorKind::AlreadyExists => { - eyre::eyre!("File already exists, not overwriting it: {path_display}") + eyre::eyre!("File already exists, not overwriting it: {path}") .suggestion("Use `--overwrite` if you wish overwrite existing files.") } _ => color_eyre::Report::new(error) - .wrap_err(format!("Failed to create/open file: {path_display}")) + .wrap_err(format!("Failed to create/open file: {path}")) .suggestion( "Make sure the directory exists \ and you have write permissions for the file", ), - })?; - match self { - Self::Quadlet(quadlet_file) => { - write!(file, "{quadlet_file}").map_err(color_eyre::Report::from) - } - Self::KubePod { - name: _, - pod, - persistent_volume_claims, - } => { - for volume in persistent_volume_claims { - serde_yaml::to_writer(&file, volume)?; - writeln!(file, "---")?; - } - serde_yaml::to_writer(file, pod).map_err(color_eyre::Report::from) } - } - .wrap_err_with(|| format!("Failed to write to file: {path_display}"))?; - println!("Wrote to file: {path_display}"); - Ok(()) - } + }) } #[derive(Debug)] @@ -510,133 +511,6 @@ impl ComposeService { } } -fn compose_from_file(compose_file: &Option) -> color_eyre::Result { - let (compose_file, path) = if let Some(path) = compose_file { - let compose_file = fs::File::open(path) - .wrap_err("Could not open provided compose file") - .suggestion("Make sure you have the proper permissions for the given file.")?; - (compose_file, path.display().to_string()) - } else { - let file_names = [ - "compose.yaml", - "compose.yml", - "docker-compose.yaml", - "docker-compose.yml", - ]; - let mut result = None; - for file_name in file_names { - if let Ok(compose_file) = fs::File::open(file_name) { - result = Some((compose_file, String::from(file_name))); - break; - } - } - result.ok_or_else(|| { - eyre::eyre!( - "A compose file was not provided and none of \ - `compose.yaml`, `compose.yml`, `docker-compose.yaml`, or `docker-compose.yml` \ - exist in the current directory or could not be read" - ) - })? - }; - - serde_yaml::from_reader(compose_file) - .wrap_err_with(|| format!("File `{path}` is not a valid compose file")) -} - -fn compose_try_into_quadlet_files<'a>( - mut compose: Compose, - unit: &'a Option, - install: &'a Option, -) -> impl Iterator> + 'a { - let volume_has_options = compose - .volumes - .0 - .iter() - .map(|(name, volume)| (name.clone(), matches!(volume, MapOrEmpty::Map(_)))) - .collect(); - compose_services(&mut compose) - .zip(iter::repeat(Rc::new(volume_has_options))) - .map(|(result, volume_has_options)| { - result.and_then(|(name, mut service)| { - let mut unit = unit.clone(); - if !service.depends_on.is_empty() { - unit.get_or_insert(Unit::default()) - .add_dependencies(mem::take(&mut service.depends_on)); - } - - let service = ComposeService { - service, - volume_has_options, - }; - let command: PodmanCommands = service.try_into().wrap_err_with(|| { - format!("Could not parse service `{name}` as a valid podman command") - })?; - - let service = command.service().cloned(); - - Ok(quadlet::File { - name, - unit, - resource: command.into(), - service, - install: install.clone(), - }) - }) - }) - .chain(compose.networks.0.into_iter().map(|(name, network)| { - let network = Option::::from(network) - .map(quadlet::Network::try_from) - .transpose() - .wrap_err_with(|| { - format!("Could not parse network `{name}` as a valid podman network") - })? - .unwrap_or_default(); - Ok(quadlet::File { - name, - unit: unit.clone(), - resource: network.into(), - service: None, - install: install.clone(), - }) - })) - .chain(compose.volumes.0.into_iter().filter_map(|(name, volume)| { - Option::::from(volume).map(|volume| { - let volume = quadlet::Volume::try_from(volume).wrap_err_with(|| { - format!("could not parse volume `{name}` as a valid podman volume") - })?; - Ok(quadlet::File { - name, - unit: unit.clone(), - resource: volume.into(), - service: None, - install: install.clone(), - }) - }) - })) -} - -fn compose_services( - compose: &mut Compose, -) -> impl Iterator> { - mem::take(&mut compose.services.0) - .into_iter() - .map(|(name, service)| { - let service_name = name.clone(); - service.map(|service| (name, service)).ok_or_else(|| { - eyre::eyre!( - "Service `{service_name}` does not have any corresponding options; \ - minimally, `image` is required" - ) - }) - }) - .chain( - compose - .service - .take() - .map(|service| Ok((String::from(image_to_name(service.image())), service))), - ) -} - /// Takes an image and returns an appropriate default service name fn image_to_name(image: &str) -> &str { let image = image diff --git a/src/cli/compose.rs b/src/cli/compose.rs new file mode 100644 index 0000000..74956a8 --- /dev/null +++ b/src/cli/compose.rs @@ -0,0 +1,213 @@ +use std::{ + fs::File, + io::{self, IsTerminal}, + iter, mem, + path::Path, + rc::Rc, +}; + +use color_eyre::{ + eyre::{self, WrapErr}, + Help, +}; +use docker_compose_types::{Compose, ComposeNetworks, MapOrEmpty}; + +use crate::quadlet; + +use super::{image_to_name, unit::Unit, ComposeService, PodmanCommands}; + +/// Read a [`Compose`] from a file at the given [`Path`], stdin, or a list of default files. +/// +/// If the path is '-', or stdin is not a terminal, the [`Compose`] is read from stdin. +/// If a path is not provided, the files `compose.yaml`, `compose.yml`, `docker-compose.yaml`, +/// and `docker-compose.yml` are, in order, looked for in the current directory. +pub fn from_file_or_stdin(path: Option<&Path>) -> color_eyre::Result { + let (compose_file, path) = if let Some(path) = path { + if path.as_os_str() == "-" { + return from_stdin(); + } + let compose_file = File::open(path) + .wrap_err("Could not open provided compose file") + .suggestion("Make sure you have the proper permissions for the given file.")?; + (compose_file, path) + } else { + const FILE_NAMES: [&str; 4] = [ + "compose.yaml", + "compose.yml", + "docker-compose.yaml", + "docker-compose.yml", + ]; + + if !io::stdin().is_terminal() { + return from_stdin(); + } + + let mut result = None; + for file_name in FILE_NAMES { + if let Ok(compose_file) = File::open(file_name) { + result = Some((compose_file, file_name.as_ref())); + break; + } + } + + result.ok_or_else(|| { + eyre::eyre!( + "A compose file was not provided and none of \ + `compose.yaml`, `compose.yml`, `docker-compose.yaml`, or `docker-compose.yml` \ + exist in the current directory or could not be read" + ) + })? + }; + + serde_yaml::from_reader(compose_file) + .wrap_err_with(|| format!("File `{}` is not a valid compose file", path.display())) +} + +/// Read a [`Compose`] from stdin. +fn from_stdin() -> color_eyre::Result { + let stdin = io::stdin(); + if stdin.is_terminal() { + eyre::bail!("cannot read compose from stdin, stdin is a terminal"); + } + + serde_yaml::from_reader(stdin).wrap_err("data from stdin is not a valid compose file") +} + +/// Attempt to convert a [`Compose`] into an iterator of [`quadlet::File`]. +pub fn try_into_quadlet_files<'a>( + mut compose: Compose, + unit: Option<&'a Unit>, + install: Option<&'a quadlet::Install>, +) -> impl Iterator> + 'a { + // Get a map of volumes to whether the volume has options associated with it for use in + // converting a service into a quadlet file. Extra volume options must be specified in a + // separate quadlet file which is referenced from the container quadlet file. + let volume_has_options = compose + .volumes + .0 + .iter() + .map(|(name, volume)| (name.clone(), matches!(volume, MapOrEmpty::Map(_)))) + .collect(); + + services(&mut compose) + .zip(iter::repeat(Rc::new(volume_has_options))) + .map(move |(result, volume_has_options)| { + let (name, service) = result?; + service_try_into_quadlet_file( + ComposeService { + service, + volume_has_options, + }, + name, + unit.cloned(), + install.cloned(), + ) + }) + .chain(networks_try_into_quadlet_files( + compose.networks, + unit, + install, + )) + .chain(volumes_try_into_quadlet_files( + compose.volumes, + unit, + install, + )) +} + +/// Extract an iterator of [`docker_compose_types::Service`] from a [`Compose`] +pub fn services( + compose: &mut Compose, +) -> impl Iterator> { + mem::take(&mut compose.services.0) + .into_iter() + .map(|(name, service)| { + let service_name = name.clone(); + service.map(|service| (name, service)).ok_or_else(|| { + eyre::eyre!( + "Service `{service_name}` does not have any corresponding options; \ + minimally, `image` is required" + ) + }) + }) + .chain( + compose + .service + .take() + .map(|service| Ok((String::from(image_to_name(service.image())), service))), + ) +} + +/// Attempt to convert a [`ComposeService`] into a [`quadlet::File`]. +fn service_try_into_quadlet_file( + mut service: ComposeService, + name: String, + mut unit: Option, + install: Option, +) -> color_eyre::Result { + // Add any service dependencies the [Unit] section of the quadlet file. + let depends_on = &mut service.service.depends_on; + if !depends_on.is_empty() { + unit.get_or_insert(Unit::default()) + .add_dependencies(mem::take(depends_on)); + } + + let command = PodmanCommands::try_from(service) + .wrap_err_with(|| format!("Could not parse service `{name}` as a valid podman command"))?; + + let service = command.service().cloned(); + + Ok(quadlet::File { + name, + unit, + resource: command.into(), + service, + install, + }) +} + +/// Attempt to convert [`ComposeNetworks`] into an iterator of [`quadlet::File`]. +fn networks_try_into_quadlet_files<'a>( + networks: ComposeNetworks, + unit: Option<&'a Unit>, + install: Option<&'a quadlet::Install>, +) -> impl Iterator> + 'a { + networks.0.into_iter().map(move |(name, network)| { + let network = Option::::from(network) + .map(quadlet::Network::try_from) + .transpose() + .wrap_err_with(|| { + format!("Could not parse network `{name}` as a valid podman network") + })? + .unwrap_or_default(); + Ok(quadlet::File { + name, + unit: unit.cloned(), + resource: network.into(), + service: None, + install: install.cloned(), + }) + }) +} + +/// Attempt to convert compose volumes into an iterator of [`quadlet::File`]. +fn volumes_try_into_quadlet_files<'a>( + volumes: docker_compose_types::TopLevelVolumes, + unit: Option<&'a Unit>, + install: Option<&'a quadlet::Install>, +) -> impl Iterator> + 'a { + volumes.0.into_iter().filter_map(move |(name, volume)| { + Option::::from(volume).map(|volume| { + let volume = quadlet::Volume::try_from(volume).wrap_err_with(|| { + format!("could not parse volume `{name}` as a valid podman volume") + })?; + Ok(quadlet::File { + name, + unit: unit.cloned(), + resource: volume.into(), + service: None, + install: install.cloned(), + }) + }) + }) +} diff --git a/src/cli/container.rs b/src/cli/container.rs index caf5fa1..747c58b 100644 --- a/src/cli/container.rs +++ b/src/cli/container.rs @@ -47,23 +47,22 @@ impl TryFrom for Container { fn try_from(mut value: ComposeService) -> Result { let service = &value.service; let unsupported_options = [ - ("deploy", service.deploy.is_some()), - ("build", service.build_.is_some()), - ("profiles", !service.profiles.is_empty()), - ("links", !service.links.is_empty()), - ("net", service.net.is_some()), - ("volumes_from", !service.volumes_from.is_empty()), - ("extends", !service.extends.is_empty()), - ("scale", service.scale != 0), + ("deploy", service.deploy.is_none()), + ("build", service.build_.is_none()), + ("profiles", service.profiles.is_empty()), + ("links", service.links.is_empty()), + ("net", service.net.is_none()), + ("volumes_from", service.volumes_from.is_empty()), + ("extends", service.extends.is_empty()), + ("scale", service.scale == 0), ]; - for (option, exists) in unsupported_options { - if exists { - return Err(unsupported_option(option)); - } - } - if !service.extensions.is_empty() { - return Err(eyre::eyre!("compose extensions are not supported")); + for (option, not_present) in unsupported_options { + eyre::ensure!(not_present, "`{option}` is unsupported"); } + eyre::ensure!( + service.extensions.is_empty(), + "compose extensions are not supported" + ); let security_opt = mem::take(&mut value.service.security_opt) .into_iter() @@ -99,34 +98,50 @@ impl TryFrom for Container { } } -fn unsupported_option(option: &str) -> color_eyre::Report { - eyre::eyre!("`{option}` is unsupported") -} - impl From for crate::quadlet::Container { - fn from(value: Container) -> Self { - let mut podman_args = value.podman_args.to_string(); - - let mut security_options = security_opt::QuadletOptions::default(); - for security_opt in value.security_opt { - security_options.add_security_opt(security_opt); - } - for arg in security_options.podman_args { - podman_args += &format!(" --security-opt {arg}"); + fn from( + Container { + quadlet_options, + podman_args, + security_opt, + image, + command, + }: Container, + ) -> Self { + let mut podman_args = podman_args.to_string(); + + let security_opt::QuadletOptions { + no_new_privileges, + seccomp_profile, + security_label_disable, + security_label_file_type, + security_label_level, + security_label_type, + podman_args: security_podman_args, + } = security_opt.into_iter().fold( + security_opt::QuadletOptions::default(), + |mut security_options, security_opt| { + security_options.add_security_opt(security_opt); + security_options + }, + ); + + for arg in security_podman_args { + podman_args.push_str(" --security-opt "); + podman_args.push_str(&arg); } Self { - image: value.image, - no_new_privileges: security_options.no_new_privileges, - seccomp_profile: security_options.seccomp_profile, - security_label_disable: security_options.security_label_disable, - security_label_file_type: security_options.security_label_file_type, - security_label_level: security_options.security_label_level, - security_label_type: security_options.security_label_type, + image, + no_new_privileges, + seccomp_profile, + security_label_disable, + security_label_file_type, + security_label_level, + security_label_type, podman_args: (!podman_args.is_empty()).then(|| podman_args.trim().to_string()), - exec: (!value.command.is_empty()) - .then(|| shlex::join(value.command.iter().map(String::as_str))), - ..value.quadlet_options.into() + exec: (!command.is_empty()).then(|| shlex::join(command.iter().map(String::as_str))), + ..quadlet_options.into() } } } diff --git a/src/cli/container/quadlet.rs b/src/cli/container/quadlet.rs index d1d6a32..533b4a6 100644 --- a/src/cli/container/quadlet.rs +++ b/src/cli/container/quadlet.rs @@ -6,9 +6,8 @@ use std::{ use clap::{Args, ValueEnum}; use color_eyre::eyre::{self, Context}; -use docker_compose_types::MapOrEmpty; +use docker_compose_types::{MapOrEmpty, Volumes}; -use super::unsupported_option; use crate::cli::ComposeService; #[allow(clippy::module_name_repetitions)] @@ -406,7 +405,7 @@ impl TryFrom<&mut ComposeService> for QuadletOptions { .map(|mode| match mode.as_str() { "bridge" | "host" | "none" => Ok(mode), s if s.starts_with("container") => Ok(mode), - _ => Err(unsupported_option(&format!("network_mode: {mode}"))), + _ => Err(eyre::eyre!("network_mode `{mode}` is unsupported")), }) .transpose()? .into_iter() @@ -500,6 +499,7 @@ impl From for Healthcheck { let mut command = test.and_then(|test| match test { docker_compose_types::HealthcheckTest::Single(s) => Some(s), docker_compose_types::HealthcheckTest::Multiple(test) => { + #[allow(clippy::indexing_slicing)] match test.first().map(String::as_str) { Some("NONE") => { disable = true; @@ -541,9 +541,7 @@ fn ports_try_into_publish(ports: docker_compose_types::Ports) -> color_eyre::Res mode, } = port; if let Some(mode) = mode { - if mode != "host" { - return Err(eyre::eyre!("unsupported port mode: {mode}")); - } + eyre::ensure!(mode == "host", "unsupported port mode: {mode}"); } let host_ip = host_ip.map(|host_ip| host_ip + ":").unwrap_or_default(); @@ -565,28 +563,28 @@ fn ports_try_into_publish(ports: docker_compose_types::Ports) -> color_eyre::Res } } +/// Takes the [`Volumes`] from a service and converts them to short form if possible, or adds +/// them to the `tmpfs` or `mount` options if not. fn volumes_try_into_short( service: &mut ComposeService, tmpfs: &mut Vec, mount: &mut Vec, ) -> color_eyre::Result> { - let volumes = mem::take(&mut service.service.volumes); - match volumes { - docker_compose_types::Volumes::Simple(volumes) => Ok(volumes - .into_iter() - .map(|volume| match volume.split_once(':') { + mem::take(&mut service.service.volumes) + .into_iter() + .filter_map(|volume| match volume { + Volumes::Simple(volume) => match volume.split_once(':') { Some((source, target)) - if !source.starts_with(['.', '/', '~']) // not bind mount or anonymous volume + if !source.starts_with(['.', '/', '~']) && service.volume_has_options(source) => { - format!("{source}.volume:{target}") + // not bind mount or anonymous volume which has options which require a + // serparate volume unit to define + Some(Ok(format!("{source}.volume:{target}"))) } - _ => volume, - }) - .collect()), - docker_compose_types::Volumes::Advanced(volumes) => volumes - .into_iter() - .filter_map(|volume| { + _ => Some(Ok(volume)), + }, + Volumes::Advanced(volume) => { let docker_compose_types::AdvancedVolumes { source, target, @@ -598,6 +596,7 @@ fn volumes_try_into_short( } = volume; match kind.as_str() { + // volume or bind mount without extra options "bind" | "volume" if bind.is_none() => { let Some(mut source) = source else { return Some(Err(eyre::eyre!("{kind} mount without a source"))); @@ -622,6 +621,7 @@ fn volumes_try_into_short( Some(Ok(format!("{source}{target}{options}"))) } + // bind mount with extra options "bind" => { let Some(source) = source else { return Some(Err(eyre::eyre!("bind mount without a source"))); @@ -653,9 +653,9 @@ fn volumes_try_into_short( } _ => Some(Err(eyre::eyre!("unsupported volume type: {kind}"))), } - }) - .collect(), - } + } + }) + .collect() } fn map_networks(networks: docker_compose_types::Networks) -> Vec { diff --git a/src/cli/k8s.rs b/src/cli/k8s.rs index 961ead4..8755e02 100644 --- a/src/cli/k8s.rs +++ b/src/cli/k8s.rs @@ -20,7 +20,7 @@ use k8s_openapi::{ }; use super::{ - compose_services, + compose, container::security_opt::{LabelOpt, SecurityOpt}, }; @@ -30,7 +30,7 @@ pub fn compose_try_into_pod( ) -> color_eyre::Result<(Pod, Vec)> { let mut volumes = Vec::new(); - let containers = compose_services(&mut compose) + let containers = compose::services(&mut compose) .map(|result| { result.and_then(|(name, service)| { let (container, container_volumes) = @@ -490,19 +490,18 @@ fn parse_tmpfs_volume_mount(tmpfs: &str, container_name: &str) -> (VolumeMount, } fn compose_volumes_try_into_volume_mounts( - volumes: ComposeVolumes, + volumes: Vec, container_name: &str, ) -> color_eyre::Result> { - match volumes { - ComposeVolumes::Simple(volumes) => volumes - .into_iter() - .map(|volume| parse_short_volume(volume, container_name)) - .collect(), - ComposeVolumes::Advanced(volumes) => volumes - .into_iter() - .map(|volume| advanced_volume_try_into_volume_mount(volume, container_name)) - .collect(), - } + volumes + .into_iter() + .map(|volume| match volume { + ComposeVolumes::Simple(volume) => parse_short_volume(volume, container_name), + ComposeVolumes::Advanced(volume) => { + advanced_volume_try_into_volume_mount(volume, container_name) + } + }) + .collect() } fn parse_short_volume( diff --git a/src/main.rs b/src/main.rs index b3b7a32..3b58c33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,10 +12,6 @@ //! //! Run `podlet --help` for more information. -#![warn(clippy::pedantic, clippy::cargo)] -// zbus uses some old dependencies, this is ok for now -#![allow(clippy::multiple_crate_versions)] - mod cli; mod quadlet; diff --git a/src/quadlet.rs b/src/quadlet.rs index 7ec36a5..88bac5b 100644 --- a/src/quadlet.rs +++ b/src/quadlet.rs @@ -4,10 +4,7 @@ mod kube; mod network; mod volume; -use std::{ - borrow::Cow, - fmt::{self, Display, Formatter}, -}; +use std::fmt::{self, Display, Formatter, Write}; pub use self::{ container::Container, install::Install, kube::Kube, network::Network, volume::Volume, @@ -123,16 +120,56 @@ impl Resource { } } -fn escape_spaces_join<'a>(words: impl IntoIterator) -> String { - words - .into_iter() - .map(|word| { - if word.contains(' ') { - format!("\"{word}\"").into() - } else { - word.into() +fn writeln_escape_spaces(f: &mut Formatter, key: &str, words: I) -> fmt::Result +where + I: IntoIterator, + I::Item: AsRef, +{ + write!(f, "{key}=")?; + + let mut words = words.into_iter(); + + if let Some(first) = words.next() { + escape_spaces(f, first.as_ref())?; + } + + for word in words { + f.write_char(' ')?; + escape_spaces(f, word.as_ref())?; + } + + writeln!(f) +} + +fn escape_spaces(f: &mut Formatter, word: &str) -> fmt::Result { + if word.contains(' ') { + write!(f, "\"{word}\"") + } else { + f.write_str(word) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn escape_spaces() { + struct Foo([&'static str; 3]); + + impl Display for Foo { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + writeln_escape_spaces(f, "Foo", self.0) } - }) - .collect::>>() - .join(" ") + } + + assert_eq!( + Foo(["one", "two", "three"]).to_string(), + "Foo=one two three\n" + ); + assert_eq!( + Foo(["one", "two three", "four"]).to_string(), + "Foo=one \"two three\" four\n" + ); + } } diff --git a/src/quadlet/container.rs b/src/quadlet/container.rs index 8f437a4..6f9875e 100644 --- a/src/quadlet/container.rs +++ b/src/quadlet/container.rs @@ -4,7 +4,7 @@ use std::{ path::PathBuf, }; -use super::escape_spaces_join; +use super::writeln_escape_spaces; #[derive(Debug, Default, Clone, PartialEq)] #[allow(clippy::struct_excessive_bools)] @@ -75,7 +75,7 @@ impl Display for Container { } if !self.annotation.is_empty() { - writeln!(f, "Annotation={}", escape_spaces_join(&self.annotation))?; + writeln_escape_spaces(f, "Annotation", &self.annotation)?; } if let Some(name) = &self.container_name { @@ -87,7 +87,7 @@ impl Display for Container { } if !self.environment.is_empty() { - writeln!(f, "Environment={}", escape_spaces_join(&self.environment))?; + writeln_escape_spaces(f, "Environment", &self.environment)?; } for file in &self.environment_file { @@ -159,7 +159,7 @@ impl Display for Container { } if !self.label.is_empty() { - writeln!(f, "Label={}", escape_spaces_join(&self.label))?; + writeln_escape_spaces(f, "Label", &self.label)?; } if let Some(log_driver) = &self.log_driver { diff --git a/src/quadlet/install.rs b/src/quadlet/install.rs index c56babc..6a974ad 100644 --- a/src/quadlet/install.rs +++ b/src/quadlet/install.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Display, Formatter}; -use super::escape_spaces_join; +use super::writeln_escape_spaces; #[derive(Debug, Clone, PartialEq)] pub struct Install { @@ -13,11 +13,11 @@ impl Display for Install { writeln!(f, "[Install]")?; if !self.wanted_by.is_empty() { - writeln!(f, "WantedBy={}", escape_spaces_join(&self.wanted_by))?; + writeln_escape_spaces(f, "WantedBy", &self.wanted_by)?; } if !self.required_by.is_empty() { - writeln!(f, "RequiredBy={}", escape_spaces_join(&self.required_by))?; + writeln_escape_spaces(f, "RequiredBy", &self.required_by)?; } Ok(()) diff --git a/src/quadlet/kube.rs b/src/quadlet/kube.rs index 6ce7bf9..026443f 100644 --- a/src/quadlet/kube.rs +++ b/src/quadlet/kube.rs @@ -13,6 +13,19 @@ pub struct Kube { pub yaml: String, } +impl Kube { + pub fn new(yaml: String) -> Self { + Self { + config_map: Vec::new(), + log_driver: None, + network: Vec::new(), + publish_port: Vec::new(), + user_ns: None, + yaml, + } + } +} + impl Display for Kube { fn fmt(&self, f: &mut Formatter) -> fmt::Result { writeln!(f, "[Kube]")?; diff --git a/src/quadlet/network.rs b/src/quadlet/network.rs index 78b97d8..8eb51b7 100644 --- a/src/quadlet/network.rs +++ b/src/quadlet/network.rs @@ -6,7 +6,7 @@ use std::{ use color_eyre::eyre::{self, Context}; use ipnet::IpNet; -use super::escape_spaces_join; +use super::writeln_escape_spaces; #[derive(Debug, Default, Clone, PartialEq)] pub struct Network { @@ -27,15 +27,13 @@ impl TryFrom for Network { fn try_from(value: docker_compose_types::NetworkSettings) -> Result { let unsupported_options = [ - ("attachable", value.attachable), - ("internal", value.internal), - ("external", value.external.is_some()), - ("name", value.name.is_some()), + ("attachable", !value.attachable), + ("internal", !value.internal), + ("external", value.external.is_none()), + ("name", value.name.is_none()), ]; - for (option, exists) in unsupported_options { - if exists { - return Err(eyre::eyre!("`{option}` is not supported")); - } + for (option, not_present) in unsupported_options { + eyre::ensure!(not_present, "`{option}` is not supported"); } let options: Vec = value @@ -122,7 +120,7 @@ impl Display for Network { } if !self.label.is_empty() { - writeln!(f, "Label={}", escape_spaces_join(&self.label))?; + writeln_escape_spaces(f, "Label", &self.label)?; } if let Some(options) = &self.options { diff --git a/src/quadlet/volume.rs b/src/quadlet/volume.rs index 4be9ca5..cbe7925 100644 --- a/src/quadlet/volume.rs +++ b/src/quadlet/volume.rs @@ -4,7 +4,7 @@ use color_eyre::eyre::{self, Context}; use crate::cli::volume::opt::Opt; -use super::escape_spaces_join; +use super::writeln_escape_spaces; #[derive(Debug, Default, Clone, PartialEq)] pub struct Volume { @@ -78,7 +78,7 @@ impl Display for Volume { } if !self.label.is_empty() { - writeln!(f, "Label={}", escape_spaces_join(&self.label))?; + writeln_escape_spaces(f, "Label", &self.label)?; } if let Some(options) = &self.options {