├── .devcontainer ├── devcontainer.json └── provision.sh ├── .dockerignore ├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTION.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── UPGRADE.md ├── docs ├── Build.md └── v1.md ├── src ├── config.rs ├── main.rs ├── render_cmd.rs └── validate_cmd.rs └── tests ├── data ├── config_chart_does_not_exist.yaml ├── config_example.yaml ├── config_invalid.yaml ├── config_invalid_helm_option.yaml ├── nginx-chart │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ ├── values.yaml │ └── values │ │ ├── default.yaml │ │ ├── edge.yaml │ │ ├── next-edge.yaml │ │ ├── prod-eu-w4.yaml │ │ ├── prod.yaml │ │ └── stage.yaml └── rendered_manifests │ └── edge-eu-w4 │ └── my-app │ └── manifest.yaml └── integration ├── main.rs ├── render.rs └── validate.rs /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/rust 3 | { 4 | "name": "Rust", 5 | "image": "mcr.microsoft.com/devcontainers/rust:1-bullseye", 6 | // Features to add to the dev container. More info: https://containers.dev/features. 7 | // "features": {}, 8 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 9 | // "forwardPorts": [], 10 | // Use 'postCreateCommand' to run commands after the container is created. 11 | "postCreateCommand": "sh .devcontainer/provision.sh", 12 | // Configure tool-specific properties. 13 | "customizations": { 14 | "vscode": { 15 | "extensions": [ 16 | "redhat.vscode-yaml", 17 | "bungcip.better-toml", 18 | "ms-azuretools.vscode-docker", 19 | "rust-lang.rust-analyzer" 20 | ] 21 | } 22 | } 23 | // "hostRequirements": { 24 | // "cpus": 4 25 | // } 26 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 27 | // "remoteUser": "root" 28 | } 29 | -------------------------------------------------------------------------------- /.devcontainer/provision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # rust toolchain and optional additional tools 4 | rustc --version 5 | rustup component add clippy rustfmt 6 | 7 | # if you want to use some additional tolling 8 | # you might want to grant the container some more cpu cycles though 9 | # see hostRequirements.cpus in the devcontainer definition file 10 | #cargo install cargo-watch cargo-nextest 11 | 12 | # install latest available version of helm 13 | curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null 14 | sudo apt-get install apt-transport-https --yes 15 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list 16 | sudo apt-get update 17 | sudo apt-get install helm 18 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !target/x86_64-unknown-linux-musl/release/helm-templexer 3 | .git/ 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # based on: https://github.com/actions-rs/meta/blob/d7602e71e8b4e6054edbeda7732ed0da8fbb8989/recipes/quickstart.md 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | name: Continuous integration 13 | 14 | jobs: 15 | check: 16 | name: Check 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | include: 21 | - target: x86_64-unknown-linux-gnu 22 | os: ubuntu-22.04 23 | - target: aarch64-unknown-linux-gnu 24 | os: ubuntu-22.04 25 | - target: x86_64-apple-darwin 26 | os: macos-11 27 | - target: aarch64-apple-darwin 28 | os: macos-11 29 | - target: x86_64-pc-windows-msvc 30 | os: windows-2022 31 | steps: 32 | - uses: actions/checkout@v3.1.0 33 | - uses: actions-rs/toolchain@v1.0.6 34 | with: 35 | profile: minimal 36 | target: ${{ matrix.target }} 37 | toolchain: stable 38 | override: true 39 | - uses: Swatinem/rust-cache@v2.2.0 40 | - uses: actions-rs/cargo@v1.0.1 41 | with: 42 | command: check 43 | 44 | test: 45 | name: Test Suite 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v3.1.0 49 | - run: | 50 | curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 51 | chmod 700 get_helm.sh 52 | ./get_helm.sh 53 | helm version --client 54 | - uses: actions-rs/toolchain@v1.0.6 55 | with: 56 | profile: minimal 57 | toolchain: stable 58 | override: true 59 | - uses: Swatinem/rust-cache@v2.2.0 60 | - uses: actions-rs/cargo@v1.0.1 61 | with: 62 | command: test 63 | 64 | fmt: 65 | name: Rustfmt 66 | runs-on: ubuntu-latest 67 | steps: 68 | - uses: actions/checkout@v3.1.0 69 | - uses: actions-rs/toolchain@v1.0.6 70 | with: 71 | profile: minimal 72 | toolchain: stable 73 | override: true 74 | - uses: Swatinem/rust-cache@v2.2.0 75 | - run: rustup component add rustfmt 76 | - uses: actions-rs/cargo@v1.0.1 77 | with: 78 | command: fmt 79 | args: --all -- --check 80 | 81 | clippy: 82 | name: Clippy 83 | runs-on: ubuntu-latest 84 | steps: 85 | - uses: actions/checkout@v3.1.0 86 | - uses: actions-rs/toolchain@v1.0.6 87 | with: 88 | profile: minimal 89 | toolchain: stable 90 | override: true 91 | - uses: Swatinem/rust-cache@v2.2.0 92 | - run: rustup component add clippy 93 | - uses: actions-rs/cargo@v1.0.1 94 | with: 95 | command: clippy 96 | args: -- -D warnings 97 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | 12 | jobs: 13 | release-please: 14 | name: Release Please # https://github.com/googleapis/release-please 15 | runs-on: ubuntu-22.04 16 | outputs: 17 | release_created: ${{ steps.release.outputs.release_created }} 18 | upload_url: ${{ steps.release.outputs.upload_url }} 19 | tag_name: ${{ steps.release.outputs.tag_name }} 20 | major: ${{ steps.release.outputs.major }} 21 | minor: ${{ steps.release.outputs.minor }} 22 | patch: ${{ steps.release.outputs.patch }} 23 | steps: 24 | - name: Release Please 25 | uses: google-github-actions/release-please-action@v3.6.1 26 | id: release 27 | with: 28 | release-type: rust 29 | package-name: release-please-action 30 | extra-files: | 31 | README.md 32 | 33 | release-binaries: 34 | name: Release ${{ matrix.target }} 35 | needs: 36 | - release-please 37 | if: ${{ needs.release-please.outputs.release_created }} 38 | runs-on: ${{ matrix.os }} 39 | strategy: 40 | fail-fast: false 41 | matrix: 42 | include: 43 | - target: x86_64-unknown-linux-gnu 44 | os: ubuntu-22.04 45 | - target: aarch64-unknown-linux-gnu 46 | os: ubuntu-22.04 47 | - target: x86_64-apple-darwin 48 | os: macos-11 49 | - target: aarch64-apple-darwin 50 | os: macos-11 51 | - target: x86_64-pc-windows-msvc 52 | os: windows-2022 53 | steps: 54 | - uses: actions/checkout@v3.1.0 55 | - uses: actions-rs/toolchain@v1.0.6 56 | with: 57 | toolchain: stable 58 | profile: minimal 59 | override: true 60 | target: ${{ matrix.target }} 61 | - uses: Swatinem/rust-cache@v2.2.0 62 | with: 63 | sharedKey: shared-cache 64 | - uses: actions-rs/cargo@v1.0.1 65 | with: 66 | use-cross: true 67 | command: build 68 | args: --release --target ${{ matrix.target }} 69 | - shell: bash 70 | env: 71 | TAG_NAME: "${{ needs.release-please.outputs.tag_name }}" 72 | run: | 73 | archive_name="helm-templexer-${TAG_NAME#v}-${{ matrix.target }}.tar.gz" 74 | 75 | cd target/${{ matrix.target }}/release 76 | tar czvf ../../../"${archive_name}" helm-templexer* 77 | cd - 78 | 79 | openssl dgst -sha256 -r "${archive_name}" \ 80 | | awk '{print $1}' > "${archive_name}.sha256" 81 | - uses: actions/upload-release-asset@v1.0.2 82 | env: 83 | GITHUB_TOKEN: ${{ github.token }} 84 | with: 85 | upload_url: ${{ needs.release-please.outputs.upload_url }} 86 | asset_path: ./helm-templexer-${{ needs.release-please.outputs.major }}.${{ needs.release-please.outputs.minor }}.${{ needs.release-please.outputs.patch }}-${{ matrix.target }}.tar.gz 87 | asset_name: helm-templexer-${{ needs.release-please.outputs.major }}.${{ needs.release-please.outputs.minor }}.${{ needs.release-please.outputs.patch }}-${{ matrix.target }}.tar.gz 88 | asset_content_type: application/gzip 89 | - uses: actions/upload-release-asset@v1.0.2 90 | env: 91 | GITHUB_TOKEN: ${{ github.token }} 92 | with: 93 | upload_url: ${{ needs.release-please.outputs.upload_url }} 94 | asset_path: ./helm-templexer-${{ needs.release-please.outputs.major }}.${{ needs.release-please.outputs.minor }}.${{ needs.release-please.outputs.patch }}-${{ matrix.target }}.tar.gz.sha256 95 | asset_name: helm-templexer-${{ needs.release-please.outputs.major }}.${{ needs.release-please.outputs.minor }}.${{ needs.release-please.outputs.patch }}-${{ matrix.target }}.tar.gz.sha256 96 | asset_content_type: application/gzip 97 | 98 | publish-cratesio: 99 | name: Publish to crates.io 100 | needs: 101 | - release-please 102 | if: ${{ needs.release-please.outputs.release_created }} 103 | runs-on: ubuntu-22.04 104 | steps: 105 | - uses: actions/checkout@v3.1.0 106 | - uses: actions-rs/toolchain@v1.0.6 107 | with: 108 | profile: minimal 109 | toolchain: stable 110 | override: true 111 | - uses: Swatinem/rust-cache@v2.2.0 112 | with: 113 | sharedKey: shared-cache 114 | - uses: katyo/publish-crates@v1 115 | with: 116 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 117 | 118 | containerize: 119 | name: Build & push container image 120 | needs: 121 | - release-please 122 | if: ${{ needs.release-please.outputs.release_created }} 123 | runs-on: ubuntu-22.04 124 | permissions: 125 | contents: read 126 | packages: write 127 | steps: 128 | - uses: actions/checkout@v3.1.0 129 | - uses: actions-rs/toolchain@v1.0.6 130 | with: 131 | profile: minimal 132 | toolchain: stable 133 | override: true 134 | - uses: Swatinem/rust-cache@v2.2.0 135 | with: 136 | sharedKey: shared-cache 137 | - name: Build binary 138 | uses: actions-rs/cargo@v1.0.1 139 | with: 140 | command: build 141 | args: --release --target x86_64-unknown-linux-musl 142 | use-cross: true 143 | - name: Log in to Docker Hub 144 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 145 | with: 146 | username: ${{ secrets.DOCKER_USERNAME }} 147 | password: ${{ secrets.DOCKER_PASSWORD }} 148 | - name: Log in to the Container registry 149 | uses: docker/login-action@v2.1.0 150 | with: 151 | registry: ${{ env.REGISTRY }} 152 | username: ${{ github.actor }} 153 | password: ${{ secrets.GITHUB_TOKEN }} 154 | - name: Extract metadata (tags, labels) for Docker 155 | id: meta 156 | uses: docker/metadata-action@v4.1.1 157 | with: 158 | images: | 159 | ${{ env.IMAGE_NAME }} 160 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 161 | - name: Build and push Docker image 162 | uses: docker/build-push-action@v3.2.0 163 | with: 164 | context: . 165 | push: true 166 | tags: ${{ steps.meta.outputs.tags }} 167 | labels: ${{ steps.meta.outputs.labels }} 168 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | tests/data/manifests 4 | tests/data/test_config_* 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.7](https://github.com/hendrikmaus/helm-templexer/compare/v2.0.6...v2.0.7) (2023-05-09) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * resolve remove_dir_all vuln ([#116](https://github.com/hendrikmaus/helm-templexer/issues/116)) ([760ccf1](https://github.com/hendrikmaus/helm-templexer/commit/760ccf1451703009b0d716195572aaab2f028299)) 9 | 10 | ## [2.0.6](https://github.com/hendrikmaus/helm-templexer/compare/v2.0.5...v2.0.6) (2022-12-19) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **cd:** generate and upload checksum files for release artifacts ([#113](https://github.com/hendrikmaus/helm-templexer/issues/113)) ([a92f9bc](https://github.com/hendrikmaus/helm-templexer/commit/a92f9bcfa29e450013d0097b944d9be96b5757c6)) 16 | 17 | ## [2.0.5](https://github.com/hendrikmaus/helm-templexer/compare/v2.0.4...v2.0.5) (2022-12-19) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * **cd:** use gha expressions instead of env var ([#111](https://github.com/hendrikmaus/helm-templexer/issues/111)) ([f1e3435](https://github.com/hendrikmaus/helm-templexer/commit/f1e34356a8cea00e5da3cbb4f76cd33185b63e05)) 23 | 24 | ## [2.0.4](https://github.com/hendrikmaus/helm-templexer/compare/v2.0.3...v2.0.4) (2022-12-19) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * **cd:** include tag name in the release artifacts ([#109](https://github.com/hendrikmaus/helm-templexer/issues/109)) ([fcfe5db](https://github.com/hendrikmaus/helm-templexer/commit/fcfe5db3403dd32ff13a425819ebbf059b28276e)) 30 | 31 | ## [2.0.3](https://github.com/hendrikmaus/helm-templexer/compare/v2.0.2...v2.0.3) (2022-12-19) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * **cd:** pass release upload url to binary build job ([#108](https://github.com/hendrikmaus/helm-templexer/issues/108)) ([9eb97cc](https://github.com/hendrikmaus/helm-templexer/commit/9eb97ccfc3691482c6af46b595b4fa004fdc6e06)) 37 | * **cd:** re-enable `cross` in containerise job ([#106](https://github.com/hendrikmaus/helm-templexer/issues/106)) ([5c18ae0](https://github.com/hendrikmaus/helm-templexer/commit/5c18ae07d692405ef9912943d9fdd7b0b0dd668d)) 38 | 39 | ## [2.0.2](https://github.com/hendrikmaus/helm-templexer/compare/2.0.1...v2.0.2) (2022-12-19) 40 | 41 | 42 | ### Miscellaneous Chores 43 | 44 | * print stdout and stderr on failure if not empty ([#103](https://github.com/hendrikmaus/helm-templexer/issues/103)) ([8a02905](https://github.com/hendrikmaus/helm-templexer/commit/8a02905dd0bbc51399b57e35ff60d4c054eb2f91)) 45 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @AlexanderThaller @hendrikmaus @Kenec 2 | -------------------------------------------------------------------------------- /CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | # Contribution 2 | 3 | Please use pull-requests and write tests for your changeset. 4 | 5 | ## Release 6 | 7 | This process is not automated at the moment: 8 | 9 | - bump the version in `Cargo.toml` 10 | - create a GitHub release from the existing draft 11 | - update version in [hendrikmaus/homebrew-tap](https://github.com/hendrikmaus/homebrew-tap) 12 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "anyhow" 25 | version = "1.0.71" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" 28 | 29 | [[package]] 30 | name = "assert_cmd" 31 | version = "1.0.8" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe" 34 | dependencies = [ 35 | "bstr", 36 | "doc-comment", 37 | "predicates 2.1.5", 38 | "predicates-core", 39 | "predicates-tree", 40 | "wait-timeout", 41 | ] 42 | 43 | [[package]] 44 | name = "atty" 45 | version = "0.2.14" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 48 | dependencies = [ 49 | "hermit-abi 0.1.19", 50 | "libc", 51 | "winapi", 52 | ] 53 | 54 | [[package]] 55 | name = "autocfg" 56 | version = "1.1.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 59 | 60 | [[package]] 61 | name = "bitflags" 62 | version = "1.3.2" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 65 | 66 | [[package]] 67 | name = "bstr" 68 | version = "0.2.17" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 71 | dependencies = [ 72 | "lazy_static", 73 | "memchr", 74 | "regex-automata", 75 | ] 76 | 77 | [[package]] 78 | name = "bytecount" 79 | version = "0.6.3" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" 82 | 83 | [[package]] 84 | name = "camino" 85 | version = "1.1.4" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" 88 | dependencies = [ 89 | "serde", 90 | ] 91 | 92 | [[package]] 93 | name = "cargo-platform" 94 | version = "0.1.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" 97 | dependencies = [ 98 | "serde", 99 | ] 100 | 101 | [[package]] 102 | name = "cargo_metadata" 103 | version = "0.14.2" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" 106 | dependencies = [ 107 | "camino", 108 | "cargo-platform", 109 | "semver", 110 | "serde", 111 | "serde_json", 112 | ] 113 | 114 | [[package]] 115 | name = "cc" 116 | version = "1.0.79" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 119 | 120 | [[package]] 121 | name = "cfg-if" 122 | version = "1.0.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 125 | 126 | [[package]] 127 | name = "clap" 128 | version = "2.34.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 131 | dependencies = [ 132 | "ansi_term", 133 | "atty", 134 | "bitflags", 135 | "strsim", 136 | "textwrap", 137 | "unicode-width", 138 | "vec_map", 139 | ] 140 | 141 | [[package]] 142 | name = "cmd_lib" 143 | version = "1.3.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "0ba0f413777386d37f85afa5242f277a7b461905254c1af3c339d4af06800f62" 146 | dependencies = [ 147 | "cmd_lib_macros", 148 | "faccess", 149 | "lazy_static", 150 | "log", 151 | "os_pipe", 152 | ] 153 | 154 | [[package]] 155 | name = "cmd_lib_macros" 156 | version = "1.3.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "9e66605092ff6c6e37e0246601ae6c3f62dc1880e0599359b5f303497c112dc0" 159 | dependencies = [ 160 | "proc-macro-error", 161 | "proc-macro2", 162 | "quote", 163 | "syn 1.0.109", 164 | ] 165 | 166 | [[package]] 167 | name = "colored" 168 | version = "2.0.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 171 | dependencies = [ 172 | "atty", 173 | "lazy_static", 174 | "winapi", 175 | ] 176 | 177 | [[package]] 178 | name = "ctor" 179 | version = "0.1.26" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" 182 | dependencies = [ 183 | "quote", 184 | "syn 1.0.109", 185 | ] 186 | 187 | [[package]] 188 | name = "diff" 189 | version = "0.1.13" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 192 | 193 | [[package]] 194 | name = "difference" 195 | version = "2.0.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 198 | 199 | [[package]] 200 | name = "difflib" 201 | version = "0.4.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 204 | 205 | [[package]] 206 | name = "doc-comment" 207 | version = "0.3.3" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 210 | 211 | [[package]] 212 | name = "either" 213 | version = "1.8.1" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 216 | 217 | [[package]] 218 | name = "env_logger" 219 | version = "0.8.4" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 222 | dependencies = [ 223 | "atty", 224 | "humantime", 225 | "log", 226 | "regex", 227 | "termcolor", 228 | ] 229 | 230 | [[package]] 231 | name = "errno" 232 | version = "0.3.1" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 235 | dependencies = [ 236 | "errno-dragonfly", 237 | "libc", 238 | "windows-sys 0.48.0", 239 | ] 240 | 241 | [[package]] 242 | name = "errno-dragonfly" 243 | version = "0.1.2" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 246 | dependencies = [ 247 | "cc", 248 | "libc", 249 | ] 250 | 251 | [[package]] 252 | name = "error-chain" 253 | version = "0.12.4" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 256 | dependencies = [ 257 | "version_check", 258 | ] 259 | 260 | [[package]] 261 | name = "faccess" 262 | version = "0.2.4" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" 265 | dependencies = [ 266 | "bitflags", 267 | "libc", 268 | "winapi", 269 | ] 270 | 271 | [[package]] 272 | name = "fastrand" 273 | version = "1.9.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 276 | dependencies = [ 277 | "instant", 278 | ] 279 | 280 | [[package]] 281 | name = "float-cmp" 282 | version = "0.8.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" 285 | dependencies = [ 286 | "num-traits", 287 | ] 288 | 289 | [[package]] 290 | name = "format_serde_error" 291 | version = "0.3.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "e5837b8e6a4001f99fe4746767fb7379e8510c508a843caa136cc12ed9c0bad0" 294 | dependencies = [ 295 | "colored", 296 | "serde", 297 | "serde_json", 298 | "serde_yaml", 299 | "unicode-segmentation", 300 | ] 301 | 302 | [[package]] 303 | name = "glob" 304 | version = "0.3.1" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 307 | 308 | [[package]] 309 | name = "hashbrown" 310 | version = "0.12.3" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 313 | 314 | [[package]] 315 | name = "heck" 316 | version = "0.3.3" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 319 | dependencies = [ 320 | "unicode-segmentation", 321 | ] 322 | 323 | [[package]] 324 | name = "helm-templexer" 325 | version = "2.0.7" 326 | dependencies = [ 327 | "anyhow", 328 | "assert_cmd", 329 | "cmd_lib", 330 | "env_logger", 331 | "format_serde_error", 332 | "indexmap", 333 | "log", 334 | "predicates 1.0.8", 335 | "pretty_assertions", 336 | "regex", 337 | "serde", 338 | "serde_yaml", 339 | "structopt", 340 | "structopt-flags", 341 | "subprocess", 342 | ] 343 | 344 | [[package]] 345 | name = "hermit-abi" 346 | version = "0.1.19" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 349 | dependencies = [ 350 | "libc", 351 | ] 352 | 353 | [[package]] 354 | name = "hermit-abi" 355 | version = "0.3.1" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 358 | 359 | [[package]] 360 | name = "humantime" 361 | version = "2.1.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 364 | 365 | [[package]] 366 | name = "indexmap" 367 | version = "1.9.3" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 370 | dependencies = [ 371 | "autocfg", 372 | "hashbrown", 373 | ] 374 | 375 | [[package]] 376 | name = "instant" 377 | version = "0.1.12" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 380 | dependencies = [ 381 | "cfg-if", 382 | ] 383 | 384 | [[package]] 385 | name = "io-lifetimes" 386 | version = "1.0.10" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" 389 | dependencies = [ 390 | "hermit-abi 0.3.1", 391 | "libc", 392 | "windows-sys 0.48.0", 393 | ] 394 | 395 | [[package]] 396 | name = "itertools" 397 | version = "0.10.5" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 400 | dependencies = [ 401 | "either", 402 | ] 403 | 404 | [[package]] 405 | name = "itoa" 406 | version = "1.0.6" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 409 | 410 | [[package]] 411 | name = "lazy_static" 412 | version = "1.4.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 415 | 416 | [[package]] 417 | name = "libc" 418 | version = "0.2.144" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" 421 | 422 | [[package]] 423 | name = "linked-hash-map" 424 | version = "0.5.6" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 427 | 428 | [[package]] 429 | name = "linux-raw-sys" 430 | version = "0.3.7" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" 433 | 434 | [[package]] 435 | name = "log" 436 | version = "0.4.17" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 439 | dependencies = [ 440 | "cfg-if", 441 | ] 442 | 443 | [[package]] 444 | name = "memchr" 445 | version = "2.5.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 448 | 449 | [[package]] 450 | name = "normalize-line-endings" 451 | version = "0.3.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 454 | 455 | [[package]] 456 | name = "num-traits" 457 | version = "0.2.15" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 460 | dependencies = [ 461 | "autocfg", 462 | ] 463 | 464 | [[package]] 465 | name = "os_pipe" 466 | version = "0.9.2" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" 469 | dependencies = [ 470 | "libc", 471 | "winapi", 472 | ] 473 | 474 | [[package]] 475 | name = "output_vt100" 476 | version = "0.1.3" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" 479 | dependencies = [ 480 | "winapi", 481 | ] 482 | 483 | [[package]] 484 | name = "predicates" 485 | version = "1.0.8" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" 488 | dependencies = [ 489 | "difference", 490 | "float-cmp", 491 | "normalize-line-endings", 492 | "predicates-core", 493 | "regex", 494 | ] 495 | 496 | [[package]] 497 | name = "predicates" 498 | version = "2.1.5" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" 501 | dependencies = [ 502 | "difflib", 503 | "itertools", 504 | "predicates-core", 505 | ] 506 | 507 | [[package]] 508 | name = "predicates-core" 509 | version = "1.0.6" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" 512 | 513 | [[package]] 514 | name = "predicates-tree" 515 | version = "1.0.9" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" 518 | dependencies = [ 519 | "predicates-core", 520 | "termtree", 521 | ] 522 | 523 | [[package]] 524 | name = "pretty_assertions" 525 | version = "0.7.2" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" 528 | dependencies = [ 529 | "ansi_term", 530 | "ctor", 531 | "diff", 532 | "output_vt100", 533 | ] 534 | 535 | [[package]] 536 | name = "proc-macro-error" 537 | version = "1.0.4" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 540 | dependencies = [ 541 | "proc-macro-error-attr", 542 | "proc-macro2", 543 | "quote", 544 | "syn 1.0.109", 545 | "version_check", 546 | ] 547 | 548 | [[package]] 549 | name = "proc-macro-error-attr" 550 | version = "1.0.4" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 553 | dependencies = [ 554 | "proc-macro2", 555 | "quote", 556 | "version_check", 557 | ] 558 | 559 | [[package]] 560 | name = "proc-macro2" 561 | version = "1.0.56" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 564 | dependencies = [ 565 | "unicode-ident", 566 | ] 567 | 568 | [[package]] 569 | name = "pulldown-cmark" 570 | version = "0.9.2" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" 573 | dependencies = [ 574 | "bitflags", 575 | "memchr", 576 | "unicase", 577 | ] 578 | 579 | [[package]] 580 | name = "quote" 581 | version = "1.0.27" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 584 | dependencies = [ 585 | "proc-macro2", 586 | ] 587 | 588 | [[package]] 589 | name = "redox_syscall" 590 | version = "0.3.5" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 593 | dependencies = [ 594 | "bitflags", 595 | ] 596 | 597 | [[package]] 598 | name = "regex" 599 | version = "1.8.1" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" 602 | dependencies = [ 603 | "aho-corasick", 604 | "memchr", 605 | "regex-syntax", 606 | ] 607 | 608 | [[package]] 609 | name = "regex-automata" 610 | version = "0.1.10" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 613 | 614 | [[package]] 615 | name = "regex-syntax" 616 | version = "0.7.1" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" 619 | 620 | [[package]] 621 | name = "rustix" 622 | version = "0.37.19" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" 625 | dependencies = [ 626 | "bitflags", 627 | "errno", 628 | "io-lifetimes", 629 | "libc", 630 | "linux-raw-sys", 631 | "windows-sys 0.48.0", 632 | ] 633 | 634 | [[package]] 635 | name = "ryu" 636 | version = "1.0.13" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 639 | 640 | [[package]] 641 | name = "same-file" 642 | version = "1.0.6" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 645 | dependencies = [ 646 | "winapi-util", 647 | ] 648 | 649 | [[package]] 650 | name = "semver" 651 | version = "1.0.17" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" 654 | dependencies = [ 655 | "serde", 656 | ] 657 | 658 | [[package]] 659 | name = "serde" 660 | version = "1.0.162" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" 663 | dependencies = [ 664 | "serde_derive", 665 | ] 666 | 667 | [[package]] 668 | name = "serde_derive" 669 | version = "1.0.162" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" 672 | dependencies = [ 673 | "proc-macro2", 674 | "quote", 675 | "syn 2.0.15", 676 | ] 677 | 678 | [[package]] 679 | name = "serde_json" 680 | version = "1.0.96" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 683 | dependencies = [ 684 | "itoa", 685 | "ryu", 686 | "serde", 687 | ] 688 | 689 | [[package]] 690 | name = "serde_yaml" 691 | version = "0.8.26" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" 694 | dependencies = [ 695 | "indexmap", 696 | "ryu", 697 | "serde", 698 | "yaml-rust", 699 | ] 700 | 701 | [[package]] 702 | name = "skeptic" 703 | version = "0.13.7" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" 706 | dependencies = [ 707 | "bytecount", 708 | "cargo_metadata", 709 | "error-chain", 710 | "glob", 711 | "pulldown-cmark", 712 | "tempfile", 713 | "walkdir", 714 | ] 715 | 716 | [[package]] 717 | name = "strsim" 718 | version = "0.8.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 721 | 722 | [[package]] 723 | name = "structopt" 724 | version = "0.3.26" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 727 | dependencies = [ 728 | "clap", 729 | "lazy_static", 730 | "structopt-derive", 731 | ] 732 | 733 | [[package]] 734 | name = "structopt-derive" 735 | version = "0.4.18" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 738 | dependencies = [ 739 | "heck", 740 | "proc-macro-error", 741 | "proc-macro2", 742 | "quote", 743 | "syn 1.0.109", 744 | ] 745 | 746 | [[package]] 747 | name = "structopt-flags" 748 | version = "0.3.6" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "4654ef901a3897697bc76c48c1d0e73f925e5d801959db6d870d39a87beeae85" 751 | dependencies = [ 752 | "log", 753 | "skeptic", 754 | "structopt", 755 | ] 756 | 757 | [[package]] 758 | name = "subprocess" 759 | version = "0.2.9" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" 762 | dependencies = [ 763 | "libc", 764 | "winapi", 765 | ] 766 | 767 | [[package]] 768 | name = "syn" 769 | version = "1.0.109" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 772 | dependencies = [ 773 | "proc-macro2", 774 | "quote", 775 | "unicode-ident", 776 | ] 777 | 778 | [[package]] 779 | name = "syn" 780 | version = "2.0.15" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" 783 | dependencies = [ 784 | "proc-macro2", 785 | "quote", 786 | "unicode-ident", 787 | ] 788 | 789 | [[package]] 790 | name = "tempfile" 791 | version = "3.5.0" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" 794 | dependencies = [ 795 | "cfg-if", 796 | "fastrand", 797 | "redox_syscall", 798 | "rustix", 799 | "windows-sys 0.45.0", 800 | ] 801 | 802 | [[package]] 803 | name = "termcolor" 804 | version = "1.2.0" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 807 | dependencies = [ 808 | "winapi-util", 809 | ] 810 | 811 | [[package]] 812 | name = "termtree" 813 | version = "0.4.1" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 816 | 817 | [[package]] 818 | name = "textwrap" 819 | version = "0.11.0" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 822 | dependencies = [ 823 | "unicode-width", 824 | ] 825 | 826 | [[package]] 827 | name = "unicase" 828 | version = "2.6.0" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 831 | dependencies = [ 832 | "version_check", 833 | ] 834 | 835 | [[package]] 836 | name = "unicode-ident" 837 | version = "1.0.8" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 840 | 841 | [[package]] 842 | name = "unicode-segmentation" 843 | version = "1.10.1" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 846 | 847 | [[package]] 848 | name = "unicode-width" 849 | version = "0.1.10" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 852 | 853 | [[package]] 854 | name = "vec_map" 855 | version = "0.8.2" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 858 | 859 | [[package]] 860 | name = "version_check" 861 | version = "0.9.4" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 864 | 865 | [[package]] 866 | name = "wait-timeout" 867 | version = "0.2.0" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 870 | dependencies = [ 871 | "libc", 872 | ] 873 | 874 | [[package]] 875 | name = "walkdir" 876 | version = "2.3.3" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 879 | dependencies = [ 880 | "same-file", 881 | "winapi-util", 882 | ] 883 | 884 | [[package]] 885 | name = "winapi" 886 | version = "0.3.9" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 889 | dependencies = [ 890 | "winapi-i686-pc-windows-gnu", 891 | "winapi-x86_64-pc-windows-gnu", 892 | ] 893 | 894 | [[package]] 895 | name = "winapi-i686-pc-windows-gnu" 896 | version = "0.4.0" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 899 | 900 | [[package]] 901 | name = "winapi-util" 902 | version = "0.1.5" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 905 | dependencies = [ 906 | "winapi", 907 | ] 908 | 909 | [[package]] 910 | name = "winapi-x86_64-pc-windows-gnu" 911 | version = "0.4.0" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 914 | 915 | [[package]] 916 | name = "windows-sys" 917 | version = "0.45.0" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 920 | dependencies = [ 921 | "windows-targets 0.42.2", 922 | ] 923 | 924 | [[package]] 925 | name = "windows-sys" 926 | version = "0.48.0" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 929 | dependencies = [ 930 | "windows-targets 0.48.0", 931 | ] 932 | 933 | [[package]] 934 | name = "windows-targets" 935 | version = "0.42.2" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 938 | dependencies = [ 939 | "windows_aarch64_gnullvm 0.42.2", 940 | "windows_aarch64_msvc 0.42.2", 941 | "windows_i686_gnu 0.42.2", 942 | "windows_i686_msvc 0.42.2", 943 | "windows_x86_64_gnu 0.42.2", 944 | "windows_x86_64_gnullvm 0.42.2", 945 | "windows_x86_64_msvc 0.42.2", 946 | ] 947 | 948 | [[package]] 949 | name = "windows-targets" 950 | version = "0.48.0" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 953 | dependencies = [ 954 | "windows_aarch64_gnullvm 0.48.0", 955 | "windows_aarch64_msvc 0.48.0", 956 | "windows_i686_gnu 0.48.0", 957 | "windows_i686_msvc 0.48.0", 958 | "windows_x86_64_gnu 0.48.0", 959 | "windows_x86_64_gnullvm 0.48.0", 960 | "windows_x86_64_msvc 0.48.0", 961 | ] 962 | 963 | [[package]] 964 | name = "windows_aarch64_gnullvm" 965 | version = "0.42.2" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 968 | 969 | [[package]] 970 | name = "windows_aarch64_gnullvm" 971 | version = "0.48.0" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 974 | 975 | [[package]] 976 | name = "windows_aarch64_msvc" 977 | version = "0.42.2" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 980 | 981 | [[package]] 982 | name = "windows_aarch64_msvc" 983 | version = "0.48.0" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 986 | 987 | [[package]] 988 | name = "windows_i686_gnu" 989 | version = "0.42.2" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 992 | 993 | [[package]] 994 | name = "windows_i686_gnu" 995 | version = "0.48.0" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 998 | 999 | [[package]] 1000 | name = "windows_i686_msvc" 1001 | version = "0.42.2" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1004 | 1005 | [[package]] 1006 | name = "windows_i686_msvc" 1007 | version = "0.48.0" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1010 | 1011 | [[package]] 1012 | name = "windows_x86_64_gnu" 1013 | version = "0.42.2" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1016 | 1017 | [[package]] 1018 | name = "windows_x86_64_gnu" 1019 | version = "0.48.0" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1022 | 1023 | [[package]] 1024 | name = "windows_x86_64_gnullvm" 1025 | version = "0.42.2" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1028 | 1029 | [[package]] 1030 | name = "windows_x86_64_gnullvm" 1031 | version = "0.48.0" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1034 | 1035 | [[package]] 1036 | name = "windows_x86_64_msvc" 1037 | version = "0.42.2" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1040 | 1041 | [[package]] 1042 | name = "windows_x86_64_msvc" 1043 | version = "0.48.0" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1046 | 1047 | [[package]] 1048 | name = "yaml-rust" 1049 | version = "0.4.5" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 1052 | dependencies = [ 1053 | "linked-hash-map", 1054 | ] 1055 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "helm-templexer" 3 | version = "2.0.7" 4 | authors = ["Hendrik Maus "] 5 | edition = "2018" 6 | 7 | description = "Render Kubernetes Helm charts for multiple environments with _explicit config_ while keeping the overhead at ease" 8 | license = "MIT" 9 | keywords = ["kubernetes", "helm"] 10 | readme = "README.md" 11 | categories = ["command-line-utilities"] 12 | 13 | [[bin]] 14 | name = "helm-templexer" 15 | path = "src/main.rs" 16 | doc = false 17 | 18 | [dependencies] 19 | structopt = "0.3" 20 | structopt-flags = "0.3" 21 | log = "0.4" 22 | env_logger = "0.8" 23 | anyhow = "1" 24 | serde = "1" 25 | serde_yaml = "0.8" 26 | subprocess = "0.2" 27 | indexmap = "1" 28 | format_serde_error = "0.3" 29 | regex = "1" 30 | cmd_lib = "1" 31 | 32 | [dev-dependencies] 33 | assert_cmd = "1" 34 | predicates = "1" 35 | pretty_assertions = "0.7" 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim as builder 2 | SHELL ["/bin/bash", "-ceuxo", "pipefail"] 3 | RUN apt-get update && apt-get install -y curl 4 | 5 | ARG HELM_VERSION=3.5.4 6 | ARG HELM_SHASUM=a8ddb4e30435b5fd45308ecce5eaad676d64a5de9c89660b56face3fe990b318 7 | RUN curl --location --retry 3 --show-error --silent -O "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" \ 8 | && echo "${HELM_SHASUM} helm-v${HELM_VERSION}-linux-amd64.tar.gz" | sha256sum --check --strict --status \ 9 | && tar -xzf helm-v${HELM_VERSION}-linux-amd64.tar.gz \ 10 | && rm helm-v${HELM_VERSION}-linux-amd64.tar.gz \ 11 | && mv "linux-amd64/helm" /usr/bin/ \ 12 | && chmod +x /usr/bin/helm 13 | 14 | FROM debian:buster-slim as runtime 15 | # labels according to opencontainers https://github.com/opencontainers/image-spec/blob/main/annotations.md 16 | LABEL org.opencontainers.image.authors="Hendrik Maus " 17 | LABEL org.opencontainers.image.url="https://github.com/hendrikmaus/helm-templexer" 18 | LABEL org.opencontainers.image.documentation="https://github.com/hendrikmaus/helm-templexer" 19 | LABEL org.opencontainers.image.source="https://github.com/hendrikmaus/helm-templexer/blob/master/Dockerfile" 20 | LABEL org.opencontainers.image.description="Render Helm charts for multiple environments using explicit configuration." 21 | COPY --from=builder /usr/bin/helm /usr/bin/ 22 | COPY target/x86_64-unknown-linux-musl/release/helm-templexer /usr/bin/ 23 | USER 1001 24 | CMD ["--help"] 25 | ENTRYPOINT ["/usr/bin/helm-templexer"] 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 [Hendrik Maus ] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⎈ Helm Templexer 2 | 3 | [![crates.io](https://img.shields.io/crates/v/helm-templexer.svg)](https://crates.io/crates/helm-templexer) 4 | 5 | Render Helm charts for multiple environments with _explicit config_ while keeping the overhead at ease. 6 | 7 | > The `helm-templexer` wraps **Helm v3+**, please ensure that it is installed and in the `PATH`. 8 | 9 | ```shell 10 | cat > my-app.yaml < Looking for schema `v1`? Please see [helm-templexer 1.x](https://github.com/hendrikmaus/helm-templexer/tree/v1). 47 | 48 | | **Parameter** | **Description** | **Condition** | **Default** | **Example** | 49 | |----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------:|-------------|--------------------------------------| 50 | | `version` | Schema version to use | **required** | | `"v2"` | 51 | | `enabled` | Whether to render deployments or not | optional | `true` | | 52 | | `chart` | Path to the chart to render | **required** | | `"path/to/some-chart"` | 53 | | `namespace` | Namespace to pass on to `helm`; when omitted, no namespace is passed | optional | `""` | | 54 | | `release_name` | Release name to pass to `helm` | **required** | | `"some-release"` | 55 | | `output_path` | Base path to use for writing the manifests to disk.

The fully-qualified output path is built as follows (`config` refers to the top-level):
`config.output_path/deployment.name/<[config/deployment].release_name>/manifest.yaml` | **required** | | | 56 | | `additional_options` | Pass additional options to `helm template`; you can use all supported options of the tool.

Common use case: use `--set-string` to provide a container tag to use.
This can be achieved by modifying the configuration file in your build pipeline using mikefarah/yq | optional | `[]` | `["--set-string image.tag=42"]` | 57 | | `values` | A list of base value files which are passed to each `helm template` call.
This is commonly used to provide a sane base config. | optional | `[]` | | 58 | | `deployments` | The list of deployments to render. | **required** | | `[[deployments]]`
`name = "edge"` | 59 | 60 | Deployments can override several top-level fields: 61 | 62 | | **Parameter** | **Description** | **Condition** | **Default** | **Example** | 63 | |----------------------|--------------------------------------------------------------------|---------------|-------------|----------------| 64 | | `name` | Name of the deployment; only used in the output path | **required** | | `"edge-eu-w4"` | 65 | | `enabled` | Allows for disabling individual deployments | optional | `true` | | 66 | | `release_name` | Override the release name | optional | `""` | | 67 | | `additional_options` | Additional options, as seen above, but specific to this deployment | optional | `[]` | | 68 | | `values` | Value files to use for this deployment | optional | `[]` | | 69 | 70 | ## Additional Options to The Render Command 71 | 72 | ### Extending The `helm template` Call 73 | 74 | Use `--additional-options` to pass data to the underlying `helm template` call. Beware that these additional options get added to *every* call, i.e. to each deployment. 75 | 76 | A common use case we found was to provide the container tag: 77 | 78 | ```shell 79 | helm-templexer render --additional-options="--set-string image.tag=${revision}" my-app.yaml 80 | ``` 81 | 82 | ### Render a Subset of Deployments 83 | 84 | Use `--filter` to render a specific deployment. Example: To render only the `prod`, pass the regex to the filter option. 85 | 86 | ```shell 87 | helm-templexer render --filter="prod" my-app.yaml 88 | ``` 89 | 90 | ### Update Helm Dependencies Before Rendering 91 | 92 | Use `--update-dependencies` to run `helm dependencies update` *once* before starting to render the deployments. 93 | 94 | ```shell 95 | helm-templexer render --update-dependencies my-app.yaml 96 | ``` 97 | 98 | ### Pipe Manifest Output Through Tool(s) Before Writing to Disk 99 | 100 | Use `--pipe` to pass the manifest output through a tool or set of tools before writing to a file. 101 | 102 | Please mind that this option **requires** an equal sign (`=`), i.e. `--pipe="". 103 | 104 | ```shell 105 | helm-templexer render --pipe="kbld -f -" my-app.yaml 106 | ``` 107 | 108 | You can define the argument multiple times; the commands will be added in order of appearance to the final command. 109 | 110 | ```shell 111 | helm-templexer render --pipe="kbld -f -" --pipe="tee /dev/stdout" my-app.yaml 112 | ``` 113 | 114 | If anything unexpected happens, you can use `-v`, `-vv` and `-vvv` to increase the log level and see the underlying command. 115 | 116 | ## Installation 117 | 118 | ### Docker 119 | 120 | ```shell 121 | # create the directory where helm-templexer will render to 122 | mkdir -p tests/data/manifests 123 | 124 | # let helm-templexer's user id (1001) own the directory 125 | sudo chown -R 1001 tests/data/manifests 126 | 127 | # pull and run the image 128 | docker pull ghcr.io/hendrikmaus/helm-templexer 129 | docker run --rm --volume $(pwd):/srv --workdir /srv/tests/data ghcr.io/hendrikmaus/helm-templexer render config_example.yaml 130 | ``` 131 | 132 | Include `helm-templexer` in your `Dockerfile`: 133 | 134 | ```Dockerfile 135 | FROM ghcr.io/hendrikmaus/helm-templexer AS helm-templexer-provider 136 | COPY --from=helm-templexer-provider /usr/bin/helm-templexer /usr/bin 137 | COPY --from=helm-templexer-provider /usr/bin/helm /usr/bin 138 | ``` 139 | 140 | ### Pre-compiled Binary 141 | 142 | Please set/replace `$TARGET` and `$VERSION` accordingly. 143 | 144 | ```shell 145 | wget https://github.com/hendrikmaus/helm-templexer/releases/download/v$VERSION/helm-templexer-$VERSION-$TARGET.tar.gz -O - | tar xz && mv helm-templexer /usr/bin/helm-templexer 146 | ``` 147 | 148 | For example `VERSION=2.0.0` and `TARGET=aarch64-apple-darwin` 149 | 150 | The `mv` to `/usr/bin` might require `sudo`. 151 | 152 | #### Validate Against Checksum 153 | 154 | To validate the downloaded **archive** against the checksum: 155 | 156 | ```shell 157 | wget https://github.com/hendrikmaus/helm-templexer/releases/download/v$VERSION/helm-templexer-$VERSION-$TARGET.tar.gz 158 | echo "$(wget https://github.com/hendrikmaus/helm-templexer/releases/download/v$VERSION/helm-templexer-$VERSION-$TARGET.tar.gz.sha256 -O -) helm-templexer-$VERSION-$TARGET.tar.gz" | sha256sum --check --strict --status 159 | ``` 160 | 161 | ### Homebrew 162 | 163 | ```shell 164 | brew tap hendrikmaus/tap 165 | brew install helm-templexer 166 | ``` 167 | 168 | ### Cargo Install 169 | 170 | Helm Templexer is written in [Rust](http://www.rust-lang.org/). You will need `rustc` version 1.35.0 or higher. The recommended way to install Rust is from the official download page. Once you have it set up, a simple `make install` will compile `helm-templexer` and install it into `$HOME/.cargo/bin`. 171 | 172 | If you’re using a recent version of Cargo (0.5.0 or higher), you can use the `cargo install` command: 173 | 174 | ```shell 175 | cargo install helm-templexer 176 | ``` 177 | 178 | Cargo will build the binary and place it in `$HOME/.cargo/bin` (this location can be overridden by setting the --root option). 179 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guides 2 | 3 | ## 1.x -> 2.x 4 | 5 | - Change `version: v1` to `version: v2` in your workload files 6 | - Manifests are now written to a single file called `manifest.yaml` for each deployment. 7 | 8 | The directory structure of the output changed from: 9 | 10 | ```shell 11 | manifests 12 | └── edge-eu-w4 13 | └── my-app 14 | └── nginx-chart 15 | └── templates 16 | ``` 17 | 18 | To: 19 | 20 | ```shell 21 | manifests 22 | └── edge 23 | └── my-app 24 | └── manifest.yaml 25 | ``` 26 | 27 | The former behavior has been completely removed. 28 | 29 | - All paths in the workloads files are now evaluated **relative to the file itself**, rather than relative to the location from where `heml-templexer` is called. 30 | -------------------------------------------------------------------------------- /docs/Build.md: -------------------------------------------------------------------------------- 1 | # Building From Source 2 | 3 | Install the linux musl target: 4 | 5 | ```shell 6 | rustup target add x86_64-unknown-linux-musl 7 | ``` 8 | 9 | Build the release binary: 10 | 11 | ```shell 12 | cargo build --release --target x86_64-unknown-linux-musl 13 | ``` 14 | 15 | Build the linux container: 16 | 17 | ```shell 18 | docker build -t . 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/v1.md: -------------------------------------------------------------------------------- 1 | # Version 1.x 2 | 3 | - Version `1.x` of the `helm-templexer` program is maintained within the branch [`v1`](https://github.com/hendrikmaus/helm-templexer/tree/v1). 4 | - As of 2021-08-24, `1.x` will only receive the minimum of changes, if any. 5 | - The configuration file `version: v1` can henceforth only be used with `helm-templexer 1.x` 6 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, bail}; 2 | use log::info; 3 | use serde::Deserialize; 4 | use std::path::Path; 5 | use std::path::PathBuf; 6 | 7 | #[derive(Deserialize, Debug)] 8 | pub struct Config { 9 | /// Schema version to use 10 | pub version: String, 11 | 12 | /// Activate/deactivate rendering of contained deployments 13 | pub enabled: Option, 14 | 15 | /// Chart to use 16 | pub chart: PathBuf, 17 | 18 | /// Namespace to pass via `--namespace` 19 | pub namespace: Option, 20 | 21 | /// Release name passed to `helm template` call 22 | pub release_name: String, 23 | 24 | /// Output path to write manifests to 25 | pub output_path: PathBuf, 26 | 27 | /// Use any other option that `helm template` supports 28 | /// Contents are not validated against actual `helm` options 29 | pub additional_options: Option>, 30 | 31 | /// Value files to pass via `--values` 32 | pub values: Option>, 33 | 34 | /// List of deployments to render given Chart 35 | pub deployments: Vec, 36 | 37 | /// Utility field to store the working directory the templxeer started in 38 | /// Used to return to the origin after processing each configuration file, as we switch 39 | /// working directory every time 40 | #[serde(skip)] 41 | pub original_working_directory: PathBuf, 42 | } 43 | 44 | #[derive(Deserialize, Debug)] 45 | pub struct Deployment { 46 | /// Name of the deployment, used to create the output path 47 | pub name: String, 48 | 49 | /// Activate/deactivate rendering of this specific deployment 50 | pub enabled: Option, 51 | 52 | /// Override the release name passed to `helm template` 53 | pub release_name: Option, 54 | 55 | /// Append any additional options to the top level options 56 | pub additional_options: Option>, 57 | 58 | /// Append value files to the top level value files 59 | pub values: Option>, 60 | } 61 | 62 | #[derive(Default)] 63 | pub struct ValidationOpts { 64 | pub skip_disabled: bool, 65 | pub config_file: Option, 66 | } 67 | 68 | impl Config { 69 | /// Load given configuration file and deserialize it. 70 | /// Does not call Config::validate - only checks the path and runs Serde 71 | pub fn load>(file: S) -> anyhow::Result { 72 | Self::check_file_exists_and_readable(file.as_ref())?; 73 | 74 | let cfg = std::fs::read_to_string(&file)?; 75 | let mut cfg = serde_yaml::from_str::(&cfg) 76 | .map_err(|err| format_serde_error::SerdeError::new(cfg.clone(), err))?; 77 | cfg.original_working_directory = std::env::current_dir()?; 78 | 79 | Ok(cfg) 80 | } 81 | 82 | /// Change the working directory to the place where the config file is, so that all 83 | /// paths are relative to the config file instead of the location where the templexer is called from 84 | pub fn switch_working_directory(&self, config_file: &Path) -> anyhow::Result<&Self> { 85 | let base_path = config_file.canonicalize()?; 86 | log::trace!( 87 | "absolute path of {:?} discovered as {:?}", 88 | config_file, 89 | base_path 90 | ); 91 | 92 | let base_path = base_path.parent().ok_or_else(|| { 93 | anyhow!( 94 | "could not determine base path of given configuration file {:?}", 95 | config_file 96 | ) 97 | })?; 98 | 99 | log::trace!( 100 | "base path of {:?} discovered as {:?}", 101 | config_file, 102 | base_path 103 | ); 104 | 105 | // if we're already next to the config file, the base path will be empty 106 | if base_path.components().next().is_some() { 107 | log::trace!("changing base path for execution to {:?}", base_path); 108 | std::env::set_current_dir(base_path)?; 109 | } 110 | 111 | Ok(self) 112 | } 113 | 114 | /// Reset the working directory to where the templexer started from 115 | /// This needs to be called after each input file 116 | pub fn reset_working_directory(&self) -> std::io::Result<&Self> { 117 | std::env::set_current_dir(&self.original_working_directory)?; 118 | Ok(self) 119 | } 120 | 121 | /// Validate the loaded configuration file 122 | /// Make sure to switch the working directory and reset it afterwards using `switch_working_directory` and `reset_working_directory`. 123 | pub fn validate(&self, opts: &ValidationOpts) -> anyhow::Result<&Self> { 124 | if let Some(enabled) = self.enabled { 125 | if !enabled && opts.skip_disabled { 126 | info!("Skipped validation of disabled file"); 127 | return Ok(self); 128 | } 129 | } 130 | 131 | self.check_chart_exists_and_readable()?; 132 | self.check_value_files_exist_and_readable()?; 133 | self.check_schema_version()?; 134 | self.check_if_at_least_one_deployment_is_enabled()?; 135 | 136 | Ok(self) 137 | } 138 | 139 | /// Check whether the given input file exists and is readable 140 | fn check_file_exists_and_readable(input_file: &Path) -> anyhow::Result<()> { 141 | if !input_file.exists() { 142 | bail!("File {:?} does not exist or is not readable", input_file); 143 | } 144 | 145 | Ok(()) 146 | } 147 | 148 | /// Assert that the designated Helm chart can be found on disk 149 | fn check_chart_exists_and_readable(&self) -> anyhow::Result<()> { 150 | if !self.chart.exists() { 151 | bail!("Chart {:?} does not exist or is not readable", self.chart); 152 | } 153 | 154 | Ok(()) 155 | } 156 | 157 | /// Find all referenced value files in the given config and check if they exist 158 | fn check_value_files_exist_and_readable(&self) -> anyhow::Result<()> { 159 | match &self.values { 160 | Some(values) => Self::check_pathbuf_vec(values)?, 161 | None => (), 162 | } 163 | 164 | for deployment in &self.deployments { 165 | if matches!(deployment.enabled, Some(enabled) if !enabled) { 166 | continue; 167 | } 168 | 169 | match &deployment.values { 170 | Some(values) => Self::check_pathbuf_vec(values)?, 171 | None => (), 172 | } 173 | } 174 | 175 | Ok(()) 176 | } 177 | 178 | /// Helper to iterate a vector of paths and check their existence 179 | fn check_pathbuf_vec(files: &[PathBuf]) -> anyhow::Result<()> { 180 | for f in files { 181 | if !f.exists() { 182 | bail!("values file {:?} does not exist or is not readable", f) 183 | } 184 | } 185 | 186 | Ok(()) 187 | } 188 | 189 | /// Check the given schema version; should be extended once multiple are available 190 | fn check_schema_version(&self) -> anyhow::Result<()> { 191 | if self.version != "v2" { 192 | bail!("invalid schema version used; only 'v2' is supported") 193 | } 194 | 195 | Ok(()) 196 | } 197 | 198 | /// Go through all deployments and check if at least one of them is enabled 199 | fn check_if_at_least_one_deployment_is_enabled(&self) -> anyhow::Result<()> { 200 | let mut all_disabled = true; 201 | 202 | for d in &self.deployments { 203 | match d.enabled { 204 | Some(e) => { 205 | if e { 206 | all_disabled = false; 207 | break; 208 | } 209 | } 210 | None => { 211 | all_disabled = false; 212 | break; 213 | } 214 | } 215 | } 216 | 217 | if all_disabled { 218 | bail!("All deployments are disabled") 219 | } 220 | 221 | Ok(()) 222 | } 223 | } 224 | 225 | #[cfg(test)] 226 | mod tests { 227 | use super::*; 228 | 229 | // TODO these are duplicated in render_cmd as well 230 | // time to create a unit test module? 231 | fn get_config() -> Config { 232 | Config { 233 | version: "v2".to_string(), 234 | enabled: Some(true), 235 | chart: Default::default(), 236 | namespace: None, 237 | release_name: "".to_string(), 238 | output_path: Default::default(), 239 | additional_options: None, 240 | values: None, 241 | deployments: vec![], 242 | original_working_directory: Default::default(), 243 | } 244 | } 245 | 246 | fn get_deployment() -> Deployment { 247 | Deployment { 248 | name: "".to_string(), 249 | enabled: Some(true), 250 | release_name: None, 251 | additional_options: None, 252 | values: None, 253 | } 254 | } 255 | 256 | #[test] 257 | #[should_panic] 258 | fn input_file_does_not_exist() { 259 | // TODO feels more like an integration test, rather than a unit test 260 | Config::load("does-not-exist").unwrap(); 261 | } 262 | 263 | #[test] 264 | fn input_file_exists() { 265 | // TODO feels more like an integration test, rather than a unit test 266 | Config::load("tests/data/config_example.yaml").unwrap(); 267 | } 268 | 269 | #[test] 270 | #[should_panic] 271 | fn schema_version_must_be_v1() { 272 | let mut cfg = get_config(); 273 | cfg.version = "invalid".to_string(); 274 | cfg.enabled = Some(false); 275 | 276 | let mut deployment = get_deployment(); 277 | deployment.name = "edge".to_string(); 278 | cfg.deployments = vec![deployment]; 279 | 280 | cfg.check_schema_version().unwrap(); 281 | } 282 | 283 | #[test] 284 | fn disabled_files_can_be_skipped_during_validation() { 285 | let mut cfg = get_config(); 286 | cfg.version = "invalid".to_string(); 287 | cfg.enabled = Some(false); 288 | 289 | let mut deployment = get_deployment(); 290 | deployment.name = "edge".to_string(); 291 | cfg.deployments = vec![deployment]; 292 | 293 | cfg.validate(&ValidationOpts { 294 | skip_disabled: true, 295 | config_file: Default::default(), 296 | }) 297 | .unwrap(); 298 | } 299 | 300 | #[test] 301 | fn disabled_deployments_can_be_skipped_during_validation() { 302 | let mut cfg = get_config(); 303 | cfg.chart = PathBuf::from("tests/data/nginx-chart"); 304 | 305 | let mut edge_deployment = get_deployment(); 306 | edge_deployment.name = "edge".to_string(); 307 | 308 | let mut stage_deployment = get_deployment(); 309 | stage_deployment.name = " stage".to_string(); 310 | stage_deployment.enabled = Some(false); 311 | stage_deployment.values = Some(vec![PathBuf::from("does-not-exist")]); 312 | 313 | cfg.deployments = vec![edge_deployment, stage_deployment]; 314 | 315 | cfg.validate(&ValidationOpts { 316 | skip_disabled: true, 317 | config_file: Default::default(), 318 | }) 319 | .unwrap(); 320 | } 321 | 322 | #[test] 323 | #[should_panic] 324 | fn fail_if_all_deployments_are_disabled() { 325 | let mut cfg = get_config(); 326 | cfg.chart = PathBuf::from("tests/data/nginx-chart"); 327 | 328 | let mut edge_deployment = get_deployment(); 329 | edge_deployment.name = "edge".to_string(); 330 | edge_deployment.enabled = Some(false); 331 | 332 | let mut stage_deployment = get_deployment(); 333 | stage_deployment.name = " stage".to_string(); 334 | stage_deployment.enabled = Some(false); 335 | 336 | cfg.deployments = vec![edge_deployment, stage_deployment]; 337 | 338 | cfg.validate(&ValidationOpts::default()).unwrap(); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use std::path::PathBuf; 3 | use structopt::{ 4 | clap::AppSettings::{ColoredHelp, GlobalVersion, VersionlessSubcommands}, 5 | StructOpt, 6 | }; 7 | use structopt_flags::GetWithDefault; 8 | 9 | use validate_cmd::ValidateCmd; 10 | 11 | use crate::render_cmd::RenderCmd; 12 | 13 | mod config; 14 | mod render_cmd; 15 | mod validate_cmd; 16 | 17 | #[derive(StructOpt, Debug)] 18 | #[structopt( 19 | name = "helm-templexer", 20 | about = "Render Helm charts for multiple environments using explicit config", 21 | global_settings = &[ColoredHelp, VersionlessSubcommands, GlobalVersion] 22 | )] 23 | struct Args { 24 | #[structopt(flatten)] 25 | verbose: structopt_flags::VerboseNoDef, 26 | 27 | #[structopt(subcommand)] 28 | cmd: SubCmd, 29 | } 30 | 31 | #[derive(StructOpt, Debug)] 32 | enum SubCmd { 33 | #[structopt(name = "validate", about = "Validate given configuration file(s)")] 34 | Validate(ValidateCmdOpts), 35 | 36 | #[structopt( 37 | name = "render", 38 | about = "Render deployments for given configuration file(s)" 39 | )] 40 | Render(RenderCmdOpts), 41 | } 42 | 43 | #[derive(StructOpt, Debug)] 44 | pub struct ValidateCmdOpts { 45 | /// Configuration file(s) to validate (supported format: yaml) 46 | input_files: Vec, 47 | 48 | // Future use 49 | #[allow(dead_code)] 50 | #[structopt(short, long, about = "Skip validation if `enabled` is set to false")] 51 | skip_disabled: bool, 52 | } 53 | 54 | #[derive(StructOpt, Debug)] 55 | pub struct RenderCmdOpts { 56 | /// Configuration file(s) to render deployments for (supported format: yaml) 57 | input_files: Vec, 58 | 59 | /// Pass additional options to the underlying 'helm template' call, e.g. '--set-string image.tag=${revision}' 60 | #[structopt(short, long, multiple = true)] 61 | additional_options: Option>, 62 | 63 | /// Run `helm dependencies update` before rendering deployments 64 | #[structopt(short, long)] 65 | update_dependencies: bool, 66 | 67 | /// Pass a regular expression to this flag to render only selected deployment(s). eg: to render only edge 'helm-templexer render --filter edge my-app.yaml' 68 | #[structopt(short, long)] 69 | filter: Option, 70 | 71 | /// Pass one or multiple command(s) to pipe the manifest for each deployment through before writing to disk, eg: 'helm-templexer render --pipe="kbld -f -" my-app.yaml' 72 | #[structopt(short, long, multiple = true)] 73 | pipe: Option>, 74 | } 75 | 76 | fn main() -> anyhow::Result<()> { 77 | let args = Args::from_args(); 78 | 79 | let log_level = args.verbose.get_with_default(log::LevelFilter::Info); 80 | env_logger::Builder::from_default_env() 81 | .filter_level(log_level) 82 | .init(); 83 | 84 | match args.cmd { 85 | SubCmd::Validate(opts) => ValidateCmd::new(opts) 86 | .run() 87 | .context("Configuration failed validation")?, 88 | SubCmd::Render(opts) => RenderCmd::new(opts).run().context("Rendering failed")?, 89 | }; 90 | 91 | Ok(()) 92 | } 93 | -------------------------------------------------------------------------------- /src/render_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{Config, ValidationOpts}; 2 | use crate::RenderCmdOpts; 3 | use anyhow::{bail, Context}; 4 | use indexmap::map::IndexMap; 5 | use log::{debug, info}; 6 | use regex::Regex; 7 | use std::collections::HashMap; 8 | use std::path::PathBuf; 9 | use subprocess::{Exec, Redirection}; 10 | 11 | /// Special name used in the commands map of a plan when a helm dependency update is requested 12 | const PRE_CMD_DEPENDENCY_UPDATE: &str = "helm-dependency-update"; 13 | 14 | pub struct RenderCmd { 15 | opts: RenderCmdOpts, 16 | } 17 | 18 | /// Plan which contains all commands to be executed 19 | /// Can be skipped if the config is disabled at the top level 20 | /// Disabled deployments are not added to the plan 21 | struct Plan { 22 | /// Skip this plan; set to true if the config is disabled on the top level 23 | skip: bool, 24 | 25 | /// Commands to be executed in order of appearance before running `self.commands`. 26 | /// This field uses a IndexMap to guarantee order of iteration. 27 | pre_commands: IndexMap>, 28 | 29 | /// Commands to be executed on the host system 30 | /// key: deployment.name 31 | /// value: vector of strings containing the complete command, e.g. vec!["helm", "template", ...] 32 | commands: HashMap)>, 33 | } 34 | 35 | impl RenderCmd { 36 | /// Create sub command struct to render deployments of the given input file(s) 37 | pub fn new(opts: RenderCmdOpts) -> Self { 38 | Self { opts } 39 | } 40 | 41 | /// Main entry point to run the rendering process 42 | /// will return nothing on the happy path and descriptive errors on failure 43 | pub fn run(&self) -> anyhow::Result<()> { 44 | debug!("render options: {:?}", self.opts); 45 | 46 | for file in &self.opts.input_files { 47 | info!("processing {:?}", file); 48 | 49 | let opts = ValidationOpts { 50 | config_file: Some(file.clone()), 51 | ..Default::default() 52 | }; 53 | let cfg = Config::load(file)?; 54 | cfg.switch_working_directory(file)?.validate(&opts)?; 55 | 56 | let plan = self.plan(&cfg)?; 57 | 58 | if plan.skip { 59 | info!("config is disabled (skipped)"); 60 | continue; 61 | } 62 | 63 | self.exec_plan(&plan)?; 64 | 65 | cfg.reset_working_directory()?; 66 | } 67 | 68 | Ok(()) 69 | } 70 | 71 | /// Create a plan of commands to execute 72 | fn plan(&self, cfg: &Config) -> anyhow::Result { 73 | let mut plan = Plan { 74 | skip: false, 75 | pre_commands: Default::default(), 76 | commands: Default::default(), 77 | }; 78 | 79 | if let Some(enabled) = cfg.enabled { 80 | if !enabled { 81 | plan.skip = true; 82 | return Ok(plan); 83 | } 84 | } 85 | 86 | let chart = match cfg.chart.to_str() { 87 | Some(s) => s, 88 | None => bail!( 89 | "failed to convert given chart path {:?} to string", 90 | cfg.chart 91 | ), 92 | }; 93 | 94 | if self.opts.update_dependencies { 95 | let cmd = vec![ 96 | "helm".to_string(), 97 | "dependencies".to_string(), 98 | "update".to_string(), 99 | chart.to_string(), 100 | ]; 101 | plan.pre_commands 102 | .insert(PRE_CMD_DEPENDENCY_UPDATE.to_string(), cmd); 103 | } 104 | 105 | let values: Vec = self 106 | .get_values_as_strings(&cfg.values)? 107 | .iter() 108 | .map(|f| format!("--values={}", f)) 109 | .collect(); 110 | 111 | let mut base_cmd = vec![ 112 | "helm".to_string(), 113 | "template".to_string(), 114 | cfg.release_name.clone(), 115 | chart.to_string(), 116 | ]; 117 | 118 | if let Some(namespace) = &cfg.namespace { 119 | base_cmd.push(format!("--namespace={}", namespace)) 120 | } 121 | 122 | base_cmd.extend(values); 123 | 124 | if let Some(opts) = &cfg.additional_options { 125 | base_cmd.extend(opts.to_owned()); 126 | } 127 | 128 | match &self.opts.additional_options { 129 | Some(opts) => base_cmd.extend(opts.clone()), 130 | None => (), 131 | } 132 | 133 | for d in &cfg.deployments { 134 | if self.opts.filter.is_some() 135 | && !self.is_name_filtered( 136 | self.opts 137 | .filter 138 | .as_ref() 139 | .ok_or_else(|| anyhow::anyhow!("failed to extract filter"))?, 140 | &d.name, 141 | )? 142 | { 143 | info!(" - (skip) {}", d.name); 144 | continue; 145 | } 146 | if let Some(enabled) = d.enabled { 147 | if !enabled { 148 | info!(" - (skip) {}", d.name); 149 | continue; 150 | } 151 | } 152 | 153 | let mut cmd = base_cmd.clone(); 154 | 155 | let values: Vec = self 156 | .get_values_as_strings(&d.values)? 157 | .iter() 158 | .map(|f| format!("--values={}", f)) 159 | .collect(); 160 | 161 | cmd.extend(values); 162 | 163 | match &d.additional_options { 164 | Some(opts) => cmd.extend(opts.clone()), 165 | None => (), 166 | } 167 | 168 | let mut release_name = cfg.release_name.clone(); 169 | match &d.release_name { 170 | Some(n) => release_name = n.to_owned(), 171 | None => (), 172 | } 173 | cmd[2] = release_name.to_owned(); 174 | 175 | let mut fully_qualified_output = cfg 176 | .output_path 177 | .join(&d.name) 178 | .join(release_name) 179 | .join("manifest"); 180 | fully_qualified_output.set_extension("yaml"); 181 | 182 | if let Some(opts) = &self.opts.pipe { 183 | let pipe_command: Vec = opts.iter().map(|p| format!("| {}", p)).collect(); 184 | cmd.extend(pipe_command) 185 | } 186 | 187 | plan.commands 188 | .insert(d.name.to_owned(), (fully_qualified_output, cmd)); 189 | } 190 | 191 | Ok(plan) 192 | } 193 | 194 | /// Execute the commands in the given plan 195 | fn exec_plan(&self, plan: &Plan) -> anyhow::Result<()> { 196 | if !&plan.pre_commands.is_empty() { 197 | info!("pre-commands:"); 198 | 199 | for (command_id, cmd) in &plan.pre_commands { 200 | info!(" - {}", command_id); 201 | 202 | debug!( 203 | "executing pre-command {}:\n \t {:#?}", 204 | command_id, 205 | cmd.join(" ") 206 | ); 207 | 208 | self.run_helm(&cmd.join(" "), std::io::sink())?; 209 | } 210 | } 211 | 212 | if !&plan.commands.is_empty() { 213 | info!("deployments:"); 214 | 215 | for (deployment, cmd) in &plan.commands { 216 | info!(" - {}", deployment); 217 | 218 | debug!( 219 | "executing planned command for deployment {}:\n \t {:#?}", 220 | deployment, 221 | cmd.1.join(" ") 222 | ); 223 | 224 | let output_parent = cmd 225 | .0 226 | .parent() 227 | .ok_or_else(|| anyhow::anyhow!("missing parent. this should never happen"))?; 228 | 229 | if output_parent.exists() { 230 | debug!("cleaning up output path: {:?}", output_parent); 231 | std::fs::remove_dir_all(output_parent)?; 232 | } 233 | std::fs::create_dir_all(output_parent)?; 234 | 235 | let output_file = 236 | std::fs::File::create(&cmd.0).context("can not create output file")?; 237 | let output_writer = std::io::BufWriter::new(output_file); 238 | 239 | self.run_helm(&cmd.1.join(" "), output_writer)?; 240 | } 241 | } 242 | 243 | Ok(()) 244 | } 245 | 246 | /// Run `helm` commands 247 | /// 248 | /// With special result handling as `helm` could exit 0 while logging `exit status 1`. 249 | /// It is unclear if the issue is actually resolved, see 250 | /// https://github.com/helm/helm/issues/8268 251 | fn run_helm(&self, cmd: &str, mut output: impl std::io::Write) -> anyhow::Result<()> { 252 | // `helm` logs that it wanted to exit 1 but actually exits 0: 253 | // 254 | // ❯ helm version --client 255 | // version.BuildInfo{Version:"v3.2.4", GitCommit:"0ad800ef43d3b826f31a5ad8dfbb4fe05d143688", GitTreeState:"clean", GoVersion:"go1.13.12"} 256 | // 257 | // ❯ helm faulty-command 258 | // Error: unknown command "faulty-command" for "helm" 259 | // Run 'helm --help' for usage. 260 | // exit status 1 261 | // 262 | // ❯ echo $? 263 | // 0 264 | // 265 | // So we'll examine stdout/stderr to detect if helm failed but did not exit 266 | // with a code other than 0. 267 | // 268 | // The issue is reported and open https://github.com/helm/helm/issues/8268 269 | // as of 2020-07-26 270 | let result = Exec::shell(cmd) 271 | .stdout(Redirection::Pipe) 272 | .stderr(Redirection::Pipe) 273 | .capture()?; 274 | 275 | debug!("stdout:\n{}", result.stdout_str()); 276 | debug!("stderr:\n{}", result.stderr_str()); 277 | 278 | if !result.exit_status.success() || result.stdout_str().contains("exit status 1") { 279 | let mut error_msg = format!("failed while running:\n {cmd}"); 280 | 281 | let stderr = result.stderr_str(); 282 | if !stderr.is_empty() { 283 | error_msg.push_str(&format!("\n\nstderr:\n {stderr}")); 284 | } 285 | 286 | let stdout = result.stdout_str(); 287 | if !stdout.is_empty() { 288 | error_msg.push_str(&format!("\n\nstdout:\n {stdout}")); 289 | } 290 | 291 | bail!(error_msg); 292 | } 293 | 294 | output 295 | .write_all(result.stdout_str().as_bytes()) 296 | .context("can not write to output")?; 297 | 298 | Ok(()) 299 | } 300 | 301 | /// Utility to turn an option for a vector of pathbufs into a vector of strings 302 | fn get_values_as_strings(&self, input: &Option>) -> anyhow::Result> { 303 | let mut buffer: Vec = vec![]; 304 | 305 | if let Some(items) = input { 306 | for i in items { 307 | match i.to_str() { 308 | Some(s) => buffer.push(s.to_string()), 309 | None => bail!("failed to convert {:?} to string", i), 310 | } 311 | } 312 | } 313 | Ok(buffer) 314 | } 315 | 316 | /// Check if deployment name is filtered 317 | fn is_name_filtered(&self, regex: &str, name: &str) -> anyhow::Result { 318 | Ok(Regex::new(regex)?.is_match(name)) 319 | } 320 | } 321 | 322 | #[cfg(test)] 323 | mod tests { 324 | use super::*; 325 | use crate::config::Deployment; 326 | use pretty_assertions::assert_eq; 327 | 328 | /// Help function to abstract the construction of `Config` for test cases 329 | /// This is useful once `Config` changes as only this function needs to be changed, not every test case 330 | fn get_config() -> Config { 331 | Config { 332 | version: "v2".to_string(), 333 | enabled: Option::from(true), 334 | chart: Default::default(), 335 | namespace: None, 336 | release_name: "".to_string(), 337 | output_path: Default::default(), 338 | additional_options: None, 339 | values: None, 340 | deployments: vec![], 341 | original_working_directory: Default::default(), 342 | } 343 | } 344 | 345 | /// Help function to abstract the construction of `RenderCmd` for test cases 346 | /// This is useful once `RenderCmd` changes as only this function needs to be changed, not every test case 347 | fn get_cmd() -> RenderCmd { 348 | RenderCmd { 349 | opts: RenderCmdOpts { 350 | input_files: vec![], 351 | additional_options: None, 352 | update_dependencies: false, 353 | filter: None, 354 | pipe: None, 355 | }, 356 | } 357 | } 358 | 359 | /// Help function to abstract the construction of `Deployment` for test cases 360 | /// This is useful once `Deployment` changes as only this function needs to be changed, not every test case 361 | fn get_deployment() -> Deployment { 362 | Deployment { 363 | name: "".to_string(), 364 | enabled: Option::from(true), 365 | release_name: None, 366 | additional_options: None, 367 | values: None, 368 | } 369 | } 370 | 371 | #[test] 372 | fn disabled_files_are_skipped() { 373 | let mut cfg = get_config(); 374 | cfg.enabled = Option::from(false); 375 | let cmd = get_cmd(); 376 | 377 | let res = cmd.plan(&cfg).unwrap(); 378 | assert_eq!(true, res.skip); 379 | } 380 | 381 | #[test] 382 | fn simple_deployment_command() { 383 | let mut cfg = get_config(); 384 | cfg.chart = PathBuf::from("charts/some-chart"); 385 | cfg.namespace = Option::from("default".to_string()); 386 | cfg.release_name = "some-release".to_string(); 387 | cfg.output_path = PathBuf::from("manifests"); 388 | cfg.additional_options = 389 | Option::from(vec!["--no-hooks".to_string(), "--debug".to_string()]); 390 | cfg.values = Option::from(vec![PathBuf::from("some-base.yaml")]); 391 | 392 | let mut deployment = get_deployment(); 393 | deployment.name = "edge".to_string(); 394 | deployment.additional_options = Option::from(vec!["--set=env=edge".to_string()]); 395 | deployment.values = Option::from(vec![PathBuf::from("edge.yaml")]); 396 | cfg.deployments = vec![deployment]; 397 | 398 | let cmd = get_cmd(); 399 | let res = cmd.plan(&cfg).unwrap(); 400 | let expected_helm_cmd = "helm template some-release charts/some-chart --namespace=default \ 401 | --values=some-base.yaml --no-hooks --debug --values=edge.yaml --set=env=edge"; 402 | let expected_helm_cmd: Vec = expected_helm_cmd 403 | .split_whitespace() 404 | .map(String::from) 405 | .collect(); 406 | 407 | let got = res.commands.get("edge").unwrap(); 408 | 409 | assert_eq!(expected_helm_cmd, got.1) 410 | } 411 | 412 | #[test] 413 | fn disabled_deployments_are_not_planned() { 414 | let mut cfg = get_config(); 415 | cfg.chart = PathBuf::from("charts/some-chart"); 416 | cfg.release_name = "some-release".to_string(); 417 | cfg.output_path = PathBuf::from("manifests"); 418 | 419 | let mut deployment = get_deployment(); 420 | deployment.name = "edge".to_string(); 421 | deployment.enabled = Option::from(false); 422 | cfg.deployments = vec![deployment]; 423 | 424 | let cmd = get_cmd(); 425 | let res = cmd.plan(&cfg).unwrap(); 426 | assert_eq!(None, res.commands.get("edge")); 427 | } 428 | 429 | #[test] 430 | fn deployment_can_override_release_name() { 431 | let mut cfg = get_config(); 432 | cfg.chart = PathBuf::from("charts/some-chart"); 433 | cfg.release_name = "some-release".to_string(); 434 | cfg.output_path = PathBuf::from("manifests"); 435 | 436 | let mut deployment = get_deployment(); 437 | deployment.name = "edge".to_string(); 438 | deployment.release_name = Option::from("edge-release".to_string()); 439 | cfg.deployments = vec![deployment]; 440 | 441 | let cmd = get_cmd(); 442 | let res = cmd.plan(&cfg).unwrap(); 443 | let expected_helm_cmd = "helm template edge-release charts/some-chart"; 444 | let expected_helm_cmd: Vec = expected_helm_cmd 445 | .split_whitespace() 446 | .map(String::from) 447 | .collect(); 448 | 449 | let got = res.commands.get("edge").unwrap(); 450 | 451 | assert_eq!(expected_helm_cmd, got.1); 452 | } 453 | 454 | #[test] 455 | fn render_can_accept_additional_options_via_cli_option() { 456 | let mut cfg = get_config(); 457 | cfg.chart = PathBuf::from("charts/some-chart"); 458 | cfg.namespace = Option::from("default".to_string()); 459 | cfg.release_name = "some-release".to_string(); 460 | cfg.output_path = PathBuf::from("manifests"); 461 | cfg.additional_options = 462 | Option::from(vec!["--no-hooks".to_string(), "--debug".to_string()]); 463 | cfg.values = Option::from(vec![PathBuf::from("some-base.yaml")]); 464 | 465 | let mut deployment = get_deployment(); 466 | deployment.name = "edge".to_string(); 467 | cfg.deployments = vec![deployment]; 468 | 469 | let mut cmd = get_cmd(); 470 | cmd.opts.additional_options = 471 | Option::from(vec!["--set-string=image.tag=424242a".to_string()]); 472 | 473 | let res = cmd.plan(&cfg).unwrap(); 474 | let expected_helm_cmd = "helm template some-release charts/some-chart --namespace=default \ 475 | --values=some-base.yaml --no-hooks --debug --set-string=image.tag=424242a"; 476 | let expected_helm_cmd: Vec = expected_helm_cmd 477 | .split_whitespace() 478 | .map(String::from) 479 | .collect(); 480 | 481 | let got = res.commands.get("edge").unwrap(); 482 | 483 | assert_eq!(expected_helm_cmd, got.1); 484 | } 485 | 486 | #[test] 487 | fn render_can_update_dependencies() { 488 | let mut cfg = get_config(); 489 | cfg.chart = PathBuf::from("charts/some-chart"); 490 | cfg.output_path = PathBuf::from("manifests"); 491 | 492 | let mut deployment = get_deployment(); 493 | deployment.name = "edge".to_string(); 494 | cfg.deployments = vec![deployment]; 495 | 496 | let mut cmd = get_cmd(); 497 | cmd.opts.update_dependencies = true; 498 | 499 | let res = cmd.plan(&cfg).unwrap(); 500 | let expected_helm_cmd = "helm dependencies update charts/some-chart"; 501 | let expected_helm_cmd: Vec = expected_helm_cmd 502 | .split_whitespace() 503 | .map(String::from) 504 | .collect(); 505 | 506 | assert_eq!( 507 | &expected_helm_cmd, 508 | res.pre_commands.get(PRE_CMD_DEPENDENCY_UPDATE).unwrap() 509 | ); 510 | } 511 | 512 | #[test] 513 | fn filter_only_edge_deployment() { 514 | let mut cfg = get_config(); 515 | cfg.chart = PathBuf::from("charts/some-chart"); 516 | cfg.namespace = Option::from("default".to_string()); 517 | cfg.release_name = "some-release".to_string(); 518 | cfg.output_path = PathBuf::from("manifests"); 519 | 520 | let mut edge_eu_w4_deployment = get_deployment(); 521 | let mut stage_eu_w4_deployment = get_deployment(); 522 | let mut prod_as_e1_deployment = get_deployment(); 523 | let mut prod_eu_w4_deployment = get_deployment(); 524 | let mut prod_us_c1_deployment = get_deployment(); 525 | 526 | edge_eu_w4_deployment.name = "edge_eu_w4_deployment".to_string(); 527 | stage_eu_w4_deployment.name = "stage_eu_w4_deployment".to_string(); 528 | prod_as_e1_deployment.name = "prod_as_e1_deployment".to_string(); 529 | prod_eu_w4_deployment.name = "prod_eu_w4_deployment".to_string(); 530 | prod_us_c1_deployment.name = "prod_us_c1_deployment".to_string(); 531 | 532 | cfg.deployments = vec![ 533 | edge_eu_w4_deployment, 534 | stage_eu_w4_deployment, 535 | prod_as_e1_deployment, 536 | prod_eu_w4_deployment, 537 | prod_us_c1_deployment, 538 | ]; 539 | 540 | let mut cmd = get_cmd(); 541 | cmd.opts.filter = Option::from("edge".to_string()); 542 | 543 | let res = cmd.plan(&cfg).unwrap(); 544 | let expected_helm_cmd = "helm template some-release charts/some-chart --namespace=default"; 545 | let expected_helm_cmd: Vec = expected_helm_cmd 546 | .split_whitespace() 547 | .map(String::from) 548 | .collect(); 549 | let got = res.commands.get("edge_eu_w4_deployment").unwrap(); 550 | 551 | assert_eq!(expected_helm_cmd, got.1); 552 | assert_eq!(res.commands.len(), 1); 553 | } 554 | 555 | #[test] 556 | fn filter_only_prod_deployment() { 557 | let mut cfg = get_config(); 558 | cfg.chart = PathBuf::from("charts/some-chart"); 559 | cfg.namespace = Option::from("default".to_string()); 560 | cfg.release_name = "some-release".to_string(); 561 | cfg.output_path = PathBuf::from("manifests"); 562 | 563 | let mut edge_eu_w4_deployment = get_deployment(); 564 | let mut stage_eu_w4_deployment = get_deployment(); 565 | let mut prod_as_e1_deployment = get_deployment(); 566 | let mut prod_eu_w4_deployment = get_deployment(); 567 | let mut prod_us_c1_deployment = get_deployment(); 568 | 569 | edge_eu_w4_deployment.name = "edge_eu_w4_deployment".to_string(); 570 | stage_eu_w4_deployment.name = "stage_eu_w4_deployment".to_string(); 571 | prod_as_e1_deployment.name = "prod_as_e1_deployment".to_string(); 572 | prod_eu_w4_deployment.name = "prod_eu_w4_deployment".to_string(); 573 | prod_us_c1_deployment.name = "prod_us_c1_deployment".to_string(); 574 | 575 | cfg.deployments = vec![ 576 | edge_eu_w4_deployment, 577 | stage_eu_w4_deployment, 578 | prod_as_e1_deployment, 579 | prod_eu_w4_deployment, 580 | prod_us_c1_deployment, 581 | ]; 582 | 583 | let mut cmd = get_cmd(); 584 | cmd.opts.filter = Option::from("^prod".to_string()); 585 | 586 | let res = cmd.plan(&cfg).unwrap(); 587 | let prod_as_e1_deployment_expected_helm_cmd = 588 | "helm template some-release charts/some-chart --namespace=default"; 589 | let prod_eu_w4_deployment_expected_helm_cmd = 590 | "helm template some-release charts/some-chart --namespace=default"; 591 | let prod_us_c1_deployment_expected_helm_cmd = 592 | "helm template some-release charts/some-chart --namespace=default"; 593 | 594 | let prod_as_e1_deployment_expected_helm_cmd: Vec = 595 | prod_as_e1_deployment_expected_helm_cmd 596 | .split_whitespace() 597 | .map(String::from) 598 | .collect(); 599 | 600 | let prod_eu_w4_deployment_expected_helm_cmd: Vec = 601 | prod_eu_w4_deployment_expected_helm_cmd 602 | .split_whitespace() 603 | .map(String::from) 604 | .collect(); 605 | 606 | let prod_us_c1_deployment_expected_helm_cmd: Vec = 607 | prod_us_c1_deployment_expected_helm_cmd 608 | .split_whitespace() 609 | .map(String::from) 610 | .collect(); 611 | 612 | let got_as_e1 = res.commands.get("prod_as_e1_deployment").unwrap(); 613 | let got_eu_w4 = res.commands.get("prod_eu_w4_deployment").unwrap(); 614 | let got_us_c1 = res.commands.get("prod_us_c1_deployment").unwrap(); 615 | 616 | assert_eq!(prod_as_e1_deployment_expected_helm_cmd, got_as_e1.1); 617 | assert_eq!(prod_eu_w4_deployment_expected_helm_cmd, got_eu_w4.1); 618 | assert_eq!(prod_us_c1_deployment_expected_helm_cmd, got_us_c1.1); 619 | assert_eq!(res.commands.len(), 3); 620 | } 621 | 622 | #[test] 623 | fn filter_all_eu_w4_deployment() { 624 | let mut cfg = get_config(); 625 | cfg.chart = PathBuf::from("charts/some-chart"); 626 | cfg.namespace = Option::from("default".to_string()); 627 | cfg.release_name = "some-release".to_string(); 628 | cfg.output_path = PathBuf::from("manifests"); 629 | 630 | let mut edge_eu_w4_deployment = get_deployment(); 631 | let mut stage_eu_w4_deployment = get_deployment(); 632 | let mut prod_as_e1_deployment = get_deployment(); 633 | let mut prod_eu_w4_deployment = get_deployment(); 634 | let mut prod_us_c1_deployment = get_deployment(); 635 | 636 | edge_eu_w4_deployment.name = "edge_eu_w4_deployment".to_string(); 637 | stage_eu_w4_deployment.name = "stage_eu_w4_deployment".to_string(); 638 | prod_as_e1_deployment.name = "prod_as_e1_deployment".to_string(); 639 | prod_eu_w4_deployment.name = "prod_eu_w4_deployment".to_string(); 640 | prod_us_c1_deployment.name = "prod_us_c1_deployment".to_string(); 641 | 642 | cfg.deployments = vec![ 643 | edge_eu_w4_deployment, 644 | stage_eu_w4_deployment, 645 | prod_as_e1_deployment, 646 | prod_eu_w4_deployment, 647 | prod_us_c1_deployment, 648 | ]; 649 | 650 | let mut cmd = get_cmd(); 651 | cmd.opts.filter = Option::from("eu_w4".to_string()); 652 | 653 | let res = cmd.plan(&cfg).unwrap(); 654 | 655 | let edge_eu_w4_deployment_expected_helm_cmd = 656 | "helm template some-release charts/some-chart --namespace=default"; 657 | let prod_eu_w4_deployment_expected_helm_cmd = 658 | "helm template some-release charts/some-chart --namespace=default"; 659 | let stage_eu_w4_deployment_expected_helm_cmd = 660 | "helm template some-release charts/some-chart --namespace=default"; 661 | 662 | let edge_eu_w4_deployment_expected_helm_cmd: Vec = 663 | edge_eu_w4_deployment_expected_helm_cmd 664 | .split_whitespace() 665 | .map(String::from) 666 | .collect(); 667 | 668 | let prod_eu_w4_deployment_expected_helm_cmd: Vec = 669 | prod_eu_w4_deployment_expected_helm_cmd 670 | .split_whitespace() 671 | .map(String::from) 672 | .collect(); 673 | 674 | let stage_eu_w4_deployment_expected_helm_cmd: Vec = 675 | stage_eu_w4_deployment_expected_helm_cmd 676 | .split_whitespace() 677 | .map(String::from) 678 | .collect(); 679 | 680 | let got_edge = res.commands.get("edge_eu_w4_deployment").unwrap(); 681 | let got_stage = res.commands.get("stage_eu_w4_deployment").unwrap(); 682 | let got_prod = res.commands.get("prod_eu_w4_deployment").unwrap(); 683 | 684 | assert_eq!(edge_eu_w4_deployment_expected_helm_cmd, got_edge.1); 685 | assert_eq!(prod_eu_w4_deployment_expected_helm_cmd, got_prod.1); 686 | assert_eq!(stage_eu_w4_deployment_expected_helm_cmd, got_stage.1); 687 | assert_eq!(res.commands.len(), 3); 688 | } 689 | 690 | #[test] 691 | fn pipe_output_through_tool() { 692 | let mut cfg = get_config(); 693 | cfg.chart = PathBuf::from("charts/some-chart"); 694 | cfg.namespace = Option::from("default".to_string()); 695 | cfg.release_name = "some-release".to_string(); 696 | cfg.output_path = PathBuf::from("manifests"); 697 | 698 | let mut edge = get_deployment(); 699 | edge.name = "edge".to_string(); 700 | 701 | cfg.deployments = vec![edge]; 702 | 703 | let mut cmd = get_cmd(); 704 | let pipe_command = vec!["grep images".to_string()]; 705 | 706 | cmd.opts.pipe = Option::from(pipe_command); 707 | 708 | let res = cmd.plan(&cfg).unwrap(); 709 | 710 | let base_helm_cmd = "helm template some-release charts/some-chart --namespace=default"; 711 | 712 | let mut edge_expected_helm_cmd: Vec = 713 | base_helm_cmd.split_whitespace().map(String::from).collect(); 714 | 715 | edge_expected_helm_cmd.push("| grep images".to_owned()); 716 | 717 | let got_edge = res.commands.get("edge").unwrap(); 718 | 719 | assert_eq!(edge_expected_helm_cmd, got_edge.1); 720 | assert_eq!(res.commands.len(), 1); 721 | } 722 | 723 | #[test] 724 | fn pipe_multiple_output_through_tool() { 725 | let mut cfg = get_config(); 726 | cfg.chart = PathBuf::from("charts/some-chart"); 727 | cfg.namespace = Option::from("default".to_string()); 728 | cfg.release_name = "some-release".to_string(); 729 | cfg.output_path = PathBuf::from("manifests"); 730 | 731 | let mut edge = get_deployment(); 732 | edge.name = "edge".to_string(); 733 | 734 | cfg.deployments = vec![edge]; 735 | 736 | let mut cmd = get_cmd(); 737 | let pipe_command = vec!["grep images".to_string(), "kbld -f manifest".to_string()]; 738 | 739 | cmd.opts.pipe = Option::from(pipe_command); 740 | 741 | let res = cmd.plan(&cfg).unwrap(); 742 | 743 | let base_helm_cmd = "helm template some-release charts/some-chart --namespace=default"; 744 | 745 | let mut edge_expected_helm_cmd: Vec = 746 | base_helm_cmd.split_whitespace().map(String::from).collect(); 747 | 748 | edge_expected_helm_cmd.push("| grep images".to_owned()); 749 | edge_expected_helm_cmd.push("| kbld -f manifest".to_owned()); 750 | 751 | let got_edge = res.commands.get("edge").unwrap(); 752 | 753 | assert_eq!(edge_expected_helm_cmd, got_edge.1); 754 | assert_eq!(res.commands.len(), 1); 755 | } 756 | } 757 | -------------------------------------------------------------------------------- /src/validate_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{Config, ValidationOpts}; 2 | use crate::ValidateCmdOpts; 3 | 4 | /// The validate sub command allows for checking any given configuration file without 5 | /// rendering to disk. 6 | pub struct ValidateCmd { 7 | opts: ValidateCmdOpts, 8 | } 9 | 10 | impl ValidateCmd { 11 | /// Create sub command struct to run validation of the given input file 12 | pub fn new(opts: ValidateCmdOpts) -> Self { 13 | Self { opts } 14 | } 15 | 16 | /// Main entry point to run the validator 17 | /// will return nothing on the happy path and descriptive errors on failure 18 | pub fn run(&self) -> anyhow::Result<()> { 19 | log::debug!("validation options: {:?}", self.opts); 20 | 21 | for file in &self.opts.input_files { 22 | let opts = ValidationOpts { 23 | config_file: Some(file.clone()), 24 | ..Default::default() 25 | }; 26 | 27 | Config::load(file)? 28 | .switch_working_directory(file)? 29 | .validate(&opts)? 30 | .reset_working_directory()?; 31 | } 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/data/config_chart_does_not_exist.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: v1 3 | enabled: true 4 | chart: this-does-not-exist 5 | namespace: my-namespace 6 | release_name: my-app 7 | output_path: manifests 8 | additional_options: 9 | - "--skip-crds" 10 | - "--no-hooks" 11 | values: 12 | - nginx-chart/values/default.yaml 13 | deployments: 14 | - name: edge-eu-w4 15 | values: 16 | - nginx-chart/values/edge.yaml 17 | additional_options: 18 | - "--set image.tag=latest" 19 | - name: next-edge-eu-w4 20 | enabled: false 21 | values: 22 | - nginx-chart/values/edge.yaml 23 | - nginx-chart/values/next-edge.yaml 24 | - name: stage-eu-w4 25 | values: 26 | - nginx-chart/values/stage.yaml 27 | - name: prod-eu-w4 28 | release_name: my-app-prod-eu-w4 29 | values: 30 | - nginx-chart/values/prod.yaml 31 | - nginx-chart/values/prod-eu-w4.yaml 32 | -------------------------------------------------------------------------------- /tests/data/config_example.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: v2 3 | enabled: true 4 | chart: nginx-chart 5 | namespace: my-namespace 6 | release_name: my-app 7 | output_path: manifests 8 | additional_options: 9 | - "--skip-crds" 10 | - "--no-hooks" 11 | values: 12 | - nginx-chart/values/default.yaml 13 | deployments: 14 | - name: edge-eu-w4 15 | values: 16 | - nginx-chart/values/edge.yaml 17 | additional_options: 18 | - "--set image.tag=latest" 19 | - name: next-edge-eu-w4 20 | enabled: false 21 | values: 22 | - nginx-chart/values/edge.yaml 23 | - nginx-chart/values/next-edge.yaml 24 | - name: stage-eu-w4 25 | values: 26 | - nginx-chart/values/stage.yaml 27 | - name: prod-eu-w4 28 | release_name: my-app-prod-eu-w4 29 | values: 30 | - nginx-chart/values/prod.yaml 31 | - nginx-chart/values/prod-eu-w4.yaml 32 | -------------------------------------------------------------------------------- /tests/data/config_invalid.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | #version: v1 3 | enabled: true 4 | chart: nginx-chart 5 | namespace: my-namespace 6 | release_name: my-app 7 | output_path: manifests 8 | additional_options: 9 | - "--skip-crds" 10 | - "--no-hooks" 11 | values: 12 | - nginx-chart/values/default.yaml 13 | deployments: 14 | - name: edge-eu-w4 15 | values: 16 | - nginx-chart/values/edge.yaml 17 | additional_options: 18 | - "--set image.tag=latest" 19 | - name: next-edge-eu-w4 20 | enabled: false 21 | values: 22 | - nginx-chart/values/edge.yaml 23 | - nginx-chart/values/next-edge.yaml 24 | - name: stage-eu-w4 25 | values: 26 | - nginx-chart/values/stage.yaml 27 | - name: prod-eu-w4 28 | release_name: my-app-prod-eu-w4 29 | values: 30 | - nginx-chart/values/prod.yaml 31 | - nginx-chart/values/prod-eu-w4.yaml 32 | -------------------------------------------------------------------------------- /tests/data/config_invalid_helm_option.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: v2 3 | enabled: true 4 | chart: nginx-chart 5 | namespace: my-namespace 6 | release_name: my-app 7 | output_path: manifests 8 | additional_options: 9 | - '--skip-crds' 10 | - '--no-hooks' 11 | - 'INVALID-OPTION' 12 | values: 13 | - nginx-chart/values/default.yaml 14 | deployments: 15 | - name: edge-eu-w4 16 | values: 17 | - nginx-chart/values/edge.yaml 18 | additional_options: 19 | - '--set image.tag=latest' 20 | - name: next-edge-eu-w4 21 | enabled: false 22 | values: 23 | - nginx-chart/values/edge.yaml 24 | - nginx-chart/values/next-edge.yaml 25 | - name: stage-eu-w4 26 | values: 27 | - nginx-chart/values/stage.yaml 28 | - name: prod-eu-w4 29 | release_name: my-app-prod-eu-w4 30 | values: 31 | - nginx-chart/values/prod.yaml 32 | - nginx-chart/values/prod-eu-w4.yaml 33 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: nginx-chart 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | appVersion: 1.16.0 24 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nginx-chart.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nginx-chart.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nginx-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nginx-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:8080 to use your application" 20 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "nginx-chart.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 7 | {{- end }} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "nginx-chart.fullname" -}} 15 | {{- if .Values.fullnameOverride }} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 17 | {{- else }} 18 | {{- $name := default .Chart.Name .Values.nameOverride }} 19 | {{- if contains $name .Release.Name }} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 21 | {{- else }} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 23 | {{- end }} 24 | {{- end }} 25 | {{- end }} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "nginx-chart.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 32 | {{- end }} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "nginx-chart.labels" -}} 38 | helm.sh/chart: {{ include "nginx-chart.chart" . }} 39 | {{ include "nginx-chart.selectorLabels" . }} 40 | {{- if .Chart.AppVersion }} 41 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 42 | {{- end }} 43 | app.kubernetes.io/managed-by: {{ .Release.Service }} 44 | {{- end }} 45 | 46 | {{/* 47 | Selector labels 48 | */}} 49 | {{- define "nginx-chart.selectorLabels" -}} 50 | app.kubernetes.io/name: {{ include "nginx-chart.name" . }} 51 | app.kubernetes.io/instance: {{ .Release.Name }} 52 | {{- end }} 53 | 54 | {{/* 55 | Create the name of the service account to use 56 | */}} 57 | {{- define "nginx-chart.serviceAccountName" -}} 58 | {{- if .Values.serviceAccount.create }} 59 | {{- default (include "nginx-chart.fullname" .) .Values.serviceAccount.name }} 60 | {{- else }} 61 | {{- default "default" .Values.serviceAccount.name }} 62 | {{- end }} 63 | {{- end }} 64 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "nginx-chart.fullname" . }} 5 | labels: 6 | {{- include "nginx-chart.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "nginx-chart.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "nginx-chart.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "nginx-chart.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | ports: 37 | - name: http 38 | containerPort: 80 39 | protocol: TCP 40 | livenessProbe: 41 | httpGet: 42 | path: / 43 | port: http 44 | readinessProbe: 45 | httpGet: 46 | path: / 47 | port: http 48 | resources: 49 | {{- toYaml .Values.resources | nindent 12 }} 50 | {{- with .Values.nodeSelector }} 51 | nodeSelector: 52 | {{- toYaml . | nindent 8 }} 53 | {{- end }} 54 | {{- with .Values.affinity }} 55 | affinity: 56 | {{- toYaml . | nindent 8 }} 57 | {{- end }} 58 | {{- with .Values.tolerations }} 59 | tolerations: 60 | {{- toYaml . | nindent 8 }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "nginx-chart.fullname" . }} 6 | labels: 7 | {{- include "nginx-chart.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "nginx-chart.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "nginx-chart.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 5 | apiVersion: networking.k8s.io/v1beta1 6 | {{- else -}} 7 | apiVersion: extensions/v1beta1 8 | {{- end }} 9 | kind: Ingress 10 | metadata: 11 | name: {{ $fullName }} 12 | labels: 13 | {{- include "nginx-chart.labels" . | nindent 4 }} 14 | {{- with .Values.ingress.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.ingress.tls }} 20 | tls: 21 | {{- range .Values.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.ingress.hosts }} 31 | - host: {{ .host | quote }} 32 | http: 33 | paths: 34 | {{- range .paths }} 35 | - path: {{ . }} 36 | backend: 37 | serviceName: {{ $fullName }} 38 | servicePort: {{ $svcPort }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "nginx-chart.fullname" . }} 5 | labels: 6 | {{- include "nginx-chart.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "nginx-chart.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "nginx-chart.serviceAccountName" . }} 6 | labels: 7 | {{- include "nginx-chart.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "nginx-chart.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "nginx-chart.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test-success 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "nginx-chart.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for nginx-chart. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: nginx 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: "" 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: "" 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: {} 29 | # fsGroup: 2000 30 | 31 | securityContext: {} 32 | # capabilities: 33 | # drop: 34 | # - ALL 35 | # readOnlyRootFilesystem: true 36 | # runAsNonRoot: true 37 | # runAsUser: 1000 38 | 39 | service: 40 | type: ClusterIP 41 | port: 80 42 | 43 | ingress: 44 | enabled: false 45 | annotations: {} 46 | # kubernetes.io/ingress.class: nginx 47 | # kubernetes.io/tls-acme: "true" 48 | hosts: 49 | - host: chart-example.local 50 | paths: [] 51 | tls: [] 52 | # - secretName: chart-example-tls 53 | # hosts: 54 | # - chart-example.local 55 | 56 | resources: {} 57 | # We usually recommend not to specify default resources and to leave this as a conscious 58 | # choice for the user. This also increases chances charts run on environments with little 59 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 60 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 61 | # limits: 62 | # cpu: 100m 63 | # memory: 128Mi 64 | # requests: 65 | # cpu: 100m 66 | # memory: 128Mi 67 | 68 | autoscaling: 69 | enabled: false 70 | minReplicas: 1 71 | maxReplicas: 100 72 | targetCPUUtilizationPercentage: 80 73 | # targetMemoryUtilizationPercentage: 80 74 | 75 | nodeSelector: {} 76 | 77 | tolerations: [] 78 | 79 | affinity: {} 80 | -------------------------------------------------------------------------------- /tests/data/nginx-chart/values/default.yaml: -------------------------------------------------------------------------------- 1 | --- -------------------------------------------------------------------------------- /tests/data/nginx-chart/values/edge.yaml: -------------------------------------------------------------------------------- 1 | --- -------------------------------------------------------------------------------- /tests/data/nginx-chart/values/next-edge.yaml: -------------------------------------------------------------------------------- 1 | --- -------------------------------------------------------------------------------- /tests/data/nginx-chart/values/prod-eu-w4.yaml: -------------------------------------------------------------------------------- 1 | --- -------------------------------------------------------------------------------- /tests/data/nginx-chart/values/prod.yaml: -------------------------------------------------------------------------------- 1 | --- -------------------------------------------------------------------------------- /tests/data/nginx-chart/values/stage.yaml: -------------------------------------------------------------------------------- 1 | --- -------------------------------------------------------------------------------- /tests/data/rendered_manifests/edge-eu-w4/my-app/manifest.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: nginx-chart/templates/serviceaccount.yaml 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: my-app-nginx-chart 7 | labels: 8 | helm.sh/chart: nginx-chart-0.1.0 9 | app.kubernetes.io/name: nginx-chart 10 | app.kubernetes.io/instance: my-app 11 | app.kubernetes.io/version: "1.16.0" 12 | app.kubernetes.io/managed-by: Helm 13 | --- 14 | # Source: nginx-chart/templates/service.yaml 15 | apiVersion: v1 16 | kind: Service 17 | metadata: 18 | name: my-app-nginx-chart 19 | labels: 20 | helm.sh/chart: nginx-chart-0.1.0 21 | app.kubernetes.io/name: nginx-chart 22 | app.kubernetes.io/instance: my-app 23 | app.kubernetes.io/version: "1.16.0" 24 | app.kubernetes.io/managed-by: Helm 25 | spec: 26 | type: ClusterIP 27 | ports: 28 | - port: 80 29 | targetPort: http 30 | protocol: TCP 31 | name: http 32 | selector: 33 | app.kubernetes.io/name: nginx-chart 34 | app.kubernetes.io/instance: my-app 35 | --- 36 | # Source: nginx-chart/templates/deployment.yaml 37 | apiVersion: apps/v1 38 | kind: Deployment 39 | metadata: 40 | name: my-app-nginx-chart 41 | labels: 42 | helm.sh/chart: nginx-chart-0.1.0 43 | app.kubernetes.io/name: nginx-chart 44 | app.kubernetes.io/instance: my-app 45 | app.kubernetes.io/version: "1.16.0" 46 | app.kubernetes.io/managed-by: Helm 47 | spec: 48 | replicas: 1 49 | selector: 50 | matchLabels: 51 | app.kubernetes.io/name: nginx-chart 52 | app.kubernetes.io/instance: my-app 53 | template: 54 | metadata: 55 | labels: 56 | app.kubernetes.io/name: nginx-chart 57 | app.kubernetes.io/instance: my-app 58 | spec: 59 | serviceAccountName: my-app-nginx-chart 60 | securityContext: 61 | {} 62 | containers: 63 | - name: nginx-chart 64 | securityContext: 65 | {} 66 | image: "nginx:latest" 67 | imagePullPolicy: IfNotPresent 68 | ports: 69 | - name: http 70 | containerPort: 80 71 | protocol: TCP 72 | livenessProbe: 73 | httpGet: 74 | path: / 75 | port: http 76 | readinessProbe: 77 | httpGet: 78 | path: / 79 | port: http 80 | resources: 81 | {} 82 | -------------------------------------------------------------------------------- /tests/integration/main.rs: -------------------------------------------------------------------------------- 1 | mod render; 2 | mod validate; 3 | -------------------------------------------------------------------------------- /tests/integration/render.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::prelude::*; 2 | use cmd_lib::run_fun; 3 | use predicates::prelude::predicate; 4 | use std::fs::OpenOptions; 5 | use std::io::Read; 6 | use std::io::Write; 7 | use std::path::PathBuf; 8 | use std::process::Command; 9 | 10 | const BIN_NAME: &'static str = env!("CARGO_PKG_NAME"); 11 | 12 | struct Config { 13 | temp_dir: PathBuf, 14 | path: PathBuf, 15 | } 16 | 17 | impl Config { 18 | /// Create a new config in a unique location; can be `drop`'ed after usage 19 | fn new() -> anyhow::Result { 20 | let config = r#"--- 21 | version: v2 22 | enabled: true 23 | chart: ../nginx-chart 24 | namespace: my-namespace 25 | release_name: my-app 26 | output_path: manifests 27 | additional_options: 28 | - "--skip-crds" 29 | - "--no-hooks" 30 | values: 31 | - ../nginx-chart/values/default.yaml 32 | deployments: 33 | - name: edge-eu-w4 34 | values: 35 | - ../nginx-chart/values/edge.yaml 36 | additional_options: 37 | - "--set image.tag=latest" 38 | - name: next-edge-eu-w4 39 | enabled: false 40 | values: 41 | - ../nginx-chart/values/edge.yaml 42 | - ../nginx-chart/values/next-edge.yaml 43 | - name: stage-eu-w4 44 | values: 45 | - ../nginx-chart/values/stage.yaml 46 | - name: prod-eu-w4 47 | release_name: my-app-prod-eu-w4 48 | values: 49 | - ../nginx-chart/values/prod.yaml 50 | - ../nginx-chart/values/prod-eu-w4.yaml 51 | "#; 52 | 53 | let config_folder = run_fun!(mktemp -d "tests/data"/test_config_XXXX)?; 54 | let path = format!("{}/config.yaml", &config_folder); 55 | let mut tmp_file = OpenOptions::new() 56 | .write(true) 57 | .read(true) 58 | .create(true) 59 | .open(&path) 60 | .unwrap(); 61 | 62 | writeln!(tmp_file, "{}", config)?; 63 | 64 | Ok(Config { 65 | temp_dir: PathBuf::from(config_folder.to_owned()), 66 | path: PathBuf::from(path.to_owned()), 67 | }) 68 | } 69 | } 70 | 71 | impl Drop for Config { 72 | fn drop(&mut self) { 73 | std::fs::remove_dir_all(&self.temp_dir) 74 | .expect("Failed to drop temporary directory of config") 75 | } 76 | } 77 | 78 | #[test] 79 | fn render_config_example() -> anyhow::Result<()> { 80 | let mut cmd = Command::cargo_bin(BIN_NAME)?; 81 | 82 | let config = Config::new()?; 83 | 84 | cmd.arg("render").arg(&config.path); 85 | 86 | cmd.assert().success(); 87 | 88 | // manifests parent folder 89 | let manifests_folder = format!("{}/manifests", config.temp_dir.to_string_lossy()); 90 | 91 | // edge manifests folder 92 | let edge_manifests_folder = 93 | format!("{}/manifests/edge-eu-w4", config.temp_dir.to_string_lossy()); 94 | 95 | let stage_manifest_folder = format!( 96 | "{}/manifests/stage-eu-w4", 97 | config.temp_dir.to_string_lossy() 98 | ); 99 | 100 | let prod_manifest_folder = 101 | format!("{}/manifests/prod-eu-w4", config.temp_dir.to_string_lossy()); 102 | 103 | let next_edge_manifest_folder = format!( 104 | "{}/manifests/next-edge-eu-w4", 105 | config.temp_dir.to_string_lossy() 106 | ); 107 | 108 | // assert that all the deployment directories exist 109 | assert_eq!(PathBuf::from(&manifests_folder).exists(), true); 110 | assert_eq!(PathBuf::from(&edge_manifests_folder).exists(), true); 111 | assert_eq!(PathBuf::from(stage_manifest_folder).exists(), true); 112 | assert_eq!(PathBuf::from(prod_manifest_folder).exists(), true); 113 | assert_eq!(PathBuf::from(next_edge_manifest_folder).exists(), false); 114 | 115 | // asert that the release name override for prod-eu-e4 worked 116 | assert_eq!( 117 | PathBuf::from(format!( 118 | "{}/prod-eu-w4/my-app-prod-eu-w4/manifest.yaml", 119 | manifests_folder 120 | )) 121 | .exists(), 122 | true 123 | ); 124 | 125 | assert_eq!( 126 | PathBuf::from(format!( 127 | "{}/edge-eu-w4/my-app/manifest.yaml", 128 | manifests_folder 129 | )) 130 | .exists(), 131 | true 132 | ); 133 | 134 | let edge_rendered_output = format!( 135 | "{}/manifests/edge-eu-w4/my-app/manifest.yaml", 136 | config.temp_dir.to_string_lossy() 137 | ); 138 | 139 | let mut edge_deployment_yaml = std::fs::File::open(edge_rendered_output)?; 140 | let mut contents = "".to_string(); 141 | edge_deployment_yaml.read_to_string(&mut contents)?; 142 | assert_eq!(contents.contains("image: \"nginx:latest\""), true); 143 | 144 | assert_eq!( 145 | contents, 146 | include_str!("../../tests/data/rendered_manifests/edge-eu-w4/my-app/manifest.yaml") 147 | ); 148 | 149 | Ok(()) 150 | } 151 | 152 | #[test] 153 | fn pipe_output_to_a_tool_that_exists() -> anyhow::Result<()> { 154 | let mut cmd = Command::cargo_bin(BIN_NAME)?; 155 | 156 | let config = Config::new()?; 157 | 158 | cmd.arg("render") 159 | .arg("--pipe=grep 'image'") 160 | .arg(&config.path); 161 | 162 | cmd.assert().success(); 163 | 164 | let edge_rendered_output = format!( 165 | "{}/manifests/edge-eu-w4/my-app/manifest.yaml", 166 | config.temp_dir.to_string_lossy() 167 | ); 168 | 169 | let mut yaml = std::fs::File::open(edge_rendered_output)?; 170 | 171 | let mut contents = "".to_string(); 172 | yaml.read_to_string(&mut contents)?; 173 | 174 | // assert that the output contains only images related content 175 | assert_eq!( 176 | contents.trim_start(), 177 | "image: \"nginx:latest\"\n imagePullPolicy: IfNotPresent\n" 178 | ); 179 | 180 | Ok(()) 181 | } 182 | 183 | #[test] 184 | fn pipe_output_to_multiple_tools() -> anyhow::Result<()> { 185 | let mut cmd = Command::cargo_bin(BIN_NAME)?; 186 | 187 | let config = Config::new()?; 188 | 189 | cmd.arg("render") 190 | .arg("--pipe=grep 'image'") 191 | .arg("--pipe=grep 'imagePullPolicy'") 192 | .arg(&config.path); 193 | 194 | cmd.assert().success(); 195 | 196 | let edge_rendered_output = format!( 197 | "{}/manifests/edge-eu-w4/my-app/manifest.yaml", 198 | config.temp_dir.to_string_lossy() 199 | ); 200 | 201 | let mut yaml = std::fs::File::open(edge_rendered_output)?; 202 | 203 | let mut contents = "".to_string(); 204 | yaml.read_to_string(&mut contents)?; 205 | 206 | // assert that the output contains only imagePullPolicy related content 207 | assert_eq!(contents.trim_start(), "imagePullPolicy: IfNotPresent\n"); 208 | 209 | Ok(()) 210 | } 211 | 212 | #[test] 213 | fn pipe_output_to_a_tool_that_doesnt_exist() -> anyhow::Result<()> { 214 | let mut cmd = Command::cargo_bin(BIN_NAME)?; 215 | let config = Config::new()?; 216 | 217 | cmd.arg("render") 218 | .arg("--pipe=xyz 'image'") 219 | .arg(&config.path); 220 | 221 | // the binary execution should fail because xyz tool doesn't exist. 222 | cmd.assert().failure(); 223 | 224 | Ok(()) 225 | } 226 | 227 | #[test] 228 | fn render_multiple_files() -> anyhow::Result<()> { 229 | let mut cmd = Command::cargo_bin(BIN_NAME)?; 230 | let config0 = Config::new()?; 231 | let config1 = Config::new()?; 232 | cmd.arg("render").arg(&config0.path).arg(&config1.path); 233 | cmd.assert().success(); 234 | 235 | Ok(()) 236 | } 237 | 238 | #[test] 239 | fn errors_are_logged() -> anyhow::Result<()> { 240 | let mut cmd = Command::cargo_bin(BIN_NAME)?; 241 | cmd.arg("render") 242 | .arg("./tests/data/config_invalid_helm_option.yaml"); 243 | let output = cmd.assert().failure(); 244 | output.stderr(predicate::str::contains( 245 | "expected at most two arguments, unexpected arguments: INVALID-OPTION", 246 | )); 247 | Ok(()) 248 | } 249 | -------------------------------------------------------------------------------- /tests/integration/validate.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::prelude::*; 2 | use predicates::prelude::*; 3 | use std::process::Command; 4 | 5 | #[test] 6 | fn file_is_valid() -> anyhow::Result<()> { 7 | let mut cmd = Command::cargo_bin("helm-templexer")?; 8 | 9 | cmd.current_dir("tests/data") 10 | .arg("validate") 11 | .arg("config_example.yaml"); 12 | 13 | cmd.assert().success(); 14 | 15 | Ok(()) 16 | } 17 | 18 | #[test] 19 | fn file_does_not_exist() -> anyhow::Result<()> { 20 | let mut cmd = Command::cargo_bin("helm-templexer")?; 21 | 22 | cmd.arg("validate").arg("this-file-does-not-exist"); 23 | cmd.assert().failure().stderr(predicate::str::contains( 24 | r#"File "this-file-does-not-exist" does not exist or is not readable"#, 25 | )); 26 | 27 | Ok(()) 28 | } 29 | 30 | #[test] 31 | fn chart_does_not_exist() -> anyhow::Result<()> { 32 | let mut cmd = Command::cargo_bin("helm-templexer")?; 33 | 34 | cmd.current_dir("tests/data") 35 | .arg("validate") 36 | .arg("config_chart_does_not_exist.yaml"); 37 | cmd.assert().failure().stderr(predicate::str::contains( 38 | r#"does not exist or is not readable"#, 39 | )); 40 | 41 | Ok(()) 42 | } 43 | 44 | #[test] 45 | fn validate_accepts_multiple_files() -> anyhow::Result<()> { 46 | let mut cmd = Command::cargo_bin("helm-templexer")?; 47 | 48 | cmd.current_dir("tests/data") 49 | .arg("validate") 50 | .arg("config_example.yaml") 51 | .arg("config_example.yaml") 52 | .arg("config_example.yaml"); 53 | cmd.assert().success(); 54 | 55 | Ok(()) 56 | } 57 | --------------------------------------------------------------------------------