Repository: seacrew/helm-compose Branch: main Commit: 4fb6bfb902ce Files: 59 Total size: 147.8 KB Directory structure: gitextract_hj9wc7s7/ ├── .github/ │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── build.yaml │ ├── ci.yaml │ ├── dependabot-automerge.yaml │ ├── docs.yaml │ └── release.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── LICENSES/ │ ├── helm/ │ │ └── LICENSE │ ├── helm-diff/ │ │ └── LICENSE │ └── helm-template/ │ └── LICENSE ├── Makefile ├── README.md ├── cmd/ │ ├── down.go │ ├── get.go │ ├── list.go │ ├── root.go │ ├── template.go │ └── up.go ├── docs/ │ ├── commands/ │ │ ├── down.md │ │ ├── get.md │ │ ├── list.md │ │ ├── template.md │ │ └── up.md │ ├── compose-file-reference.md │ ├── index.md │ ├── key-features-and-use-cases.md │ ├── quick-start.md │ ├── storage-providers.md │ └── stylesheets/ │ └── extra.css ├── examples/ │ ├── .gitignore │ ├── k8s-storage-compose.yaml │ ├── local-storage-compose.yaml │ ├── s3-storage-compose.yaml │ ├── simple-compose.yaml │ ├── values/ │ │ └── wordpress.yaml │ └── wordpress-compose.yaml ├── go.mod ├── go.sum ├── internal/ │ ├── compose/ │ │ ├── compose.go │ │ └── helm.go │ ├── config/ │ │ ├── config.go │ │ ├── config_test.go │ │ ├── types.go │ │ └── types_test.go │ ├── provider/ │ │ ├── kubernetes.go │ │ ├── kubernetes_test.go │ │ ├── local.go │ │ ├── providers.go │ │ ├── s3.go │ │ └── s3_test.go │ └── util/ │ ├── colors.go │ ├── encoding.go │ └── util.go ├── main.go ├── mkdocs.yaml ├── plugin.yaml └── scripts/ └── install.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Description ## Motivation and Context ## How Has This Been Tested? ## Checklist: - [ ] My change requires a change to the documentation or CHANGELOG. - [ ] I have updated the CHANGELOG.md accordingly. - [ ] I have created a feature (non-master) branch for my PR. ================================================ FILE: .github/workflows/build.yaml ================================================ name: Build on: workflow_dispatch: push: branches: - main paths-ignore: - .github/** - docs/** - LICENSES/** - README.md - mkdocs.yaml workflow_run: workflows: [CI] types: [completed] branches: [main] permissions: contents: write jobs: build: runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'push' || github.event.workflow_run.conclusion == 'success' }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - uses: actions/setup-go@v5 with: go-version-file: go.mod check-latest: true cache: true - name: Create Helm Compose Distributions run: make dist - uses: actions/upload-artifact@v4 with: name: helm-compose path: release/* - name: Publish release if: "contains(github.event.head_commit.message, 'docs: release')" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | TAG=$(git describe --abbrev=0) for FILE in release/*.tgz; do gh release upload ${TAG} ${FILE} --clobber done gh release edit ${TAG} --latest --prerelease=false ================================================ FILE: .github/workflows/ci.yaml ================================================ name: CI on: workflow_dispatch: pull_request: branches: - main jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod check-latest: true cache: true - uses: azure/setup-helm@v4 with: version: v3.11.0 - name: Install Helm Compose run: make install - uses: helm/kind-action@v1.10.0 - name: Run Helm Compose run: helm compose up -f examples/simple-compose.yaml ================================================ FILE: .github/workflows/dependabot-automerge.yaml ================================================ name: Dependabot auto-approve on: pull_request permissions: pull-requests: write jobs: dependabot: runs-on: ubuntu-latest if: github.actor == 'dependabot[bot]' steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Approve PR run: gh pr review --approve "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GH_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Auto-merge PR run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} ================================================ FILE: .github/workflows/docs.yaml ================================================ name: Deploy docs on: push: branches: - main paths: - docs/** - mkdocs.yaml workflow_dispatch: permissions: contents: write jobs: deploy: runs-on: ubuntu-latest if: github.event.repository.fork == false steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - uses: actions/setup-python@v5 with: python-version: 3.x - name: Run generate cache key run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - uses: actions/cache@v4 with: key: mkdocs-cache-${{ env.cache_id }} path: .cache restore-keys: | mkdocs- - name: Run setup mkdocs dependencies run: pip install mkdocs mkdocs-material mkdocs-autolinks-plugin mkdocs-drawio-file mkdocs-git-revision-date-localized-plugin fontawesome_markdown mike pymdown-extensions - name: Run mkdocs deployment run: | git config --global user.name "github-actions[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" mike deploy -b gh-pages -p -F mkdocs.yaml -u next - name: Run mkdocs release deployment if: "contains(github.event.head_commit.message, 'docs: release')" run: | git config --global user.name "github-actions[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" LATEST_TAG=$(git describe --abbrev=0) SHORT_VERSION=${LATEST_TAG%.*} mike deploy -b gh-pages -p -F mkdocs.yaml -u ${SHORT_VERSION} latest ================================================ FILE: .github/workflows/release.yaml ================================================ name: Create Release on: workflow_dispatch: inputs: version: description: 'Version number of the new release' required: true type: string permissions: contents: write pull-requests: write jobs: release: name: release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Update and create tag run: | # Setup git git config --global user.name "github-actions[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" # Update version in documentation and helm plugin config sed -i 's\--version .*\--version ${{ inputs.version }}\g' docs/quick-start.md sed -i 's\--version .*\--version ${{ inputs.version }}\g' README.md sed -i 's\^version:.*\version: ${{ inputs.version }}\g' plugin.yaml # Update changelog sed -i 's\# Changes since .*\# ${{ inputs.version }}\g' CHANGELOG.md echo -e "# Changes since ${{ inputs.version }}\n$(cat CHANGELOG.md)" > CHANGELOG.md # Commit, tag and push git commit -am "docs: release ${{ inputs.version }}" git tag ${{ inputs.version }} -am "${{ inputs.version }}" git push --follow-tags - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ inputs.version }} run: | gh release create "${TAG}" \ --title="${TAG}" \ --generate-notes \ --prerelease ================================================ FILE: .gitignore ================================================ .vscode .hcstate vendor/ bin/ build/ release/ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ ================================================ FILE: CHANGELOG.md ================================================ # Changes since 1.3.0 # 1.3.0 - feat: add templating command - chore: upgrade to golang 1.22 # 1.2.0 - feat: add wait option - feat: apiVersion 1.1 for wait option and add better version handling - fix: stop creation of revisions if the helm compose file didn't change # 1.1.2 - chore: dependency upgrades # 1.1.1 - bugfix: when the target bucket is empty no state files are written # 1.1.0 - feat: add support for s3 as a revision storage provider / backend - chore: upgrade dependencies to the latest versions ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSES/helm/LICENSE ================================================ === https://github.com/helm/helm === Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2016 The Kubernetes Authors All Rights Reserved Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSES/helm-diff/LICENSE ================================================ === https://github.com/databus23/helm-diff === Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSES/helm-template/LICENSE ================================================ === https://github.com/technosophos/helm-template === Helm Template Plugin Copyright (C) 2016, Matt Butcher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ HELM_HOME := $(shell bash -c 'eval $$(helm env); echo $$HELM_PLUGINS') LDFLAGS := -s -w .PHONY: install install: build mkdir -p $(HELM_HOME)/helm-compose/bin cp bin/compose $(HELM_HOME)/helm-compose/bin cp plugin.yaml $(HELM_HOME)/helm-compose/ .PHONY: build build: mkdir -p bin/ go build -v -o bin/compose .PHONY: dist dist: export COPYFILE_DISABLE=1 #teach OSX tar to not put ._* files in tar archive dist: export CGO_ENABLED=0 dist: rm -rf build/compose/* release/* mkdir -p build/compose/bin release/ cp README.md LICENSE plugin.yaml build/compose GOOS=linux GOARCH=amd64 go build -o build/compose/bin/compose -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-compose-linux-amd64.tgz compose/ GOOS=linux GOARCH=arm64 go build -o build/compose/bin/compose -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-compose-linux-arm64.tgz compose/ GOOS=freebsd GOARCH=amd64 go build -o build/compose/bin/compose -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-compose-freebsd-amd64.tgz compose/ GOOS=darwin GOARCH=amd64 go build -o build/compose/bin/compose -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-compose-macos-amd64.tgz compose/ GOOS=darwin GOARCH=arm64 go build -o build/compose/bin/compose -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-compose-macos-arm64.tgz compose/ rm build/compose/bin/compose GOOS=windows GOARCH=amd64 go build -o build/compose/bin/compose.exe -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-compose-windows-amd64.tgz compose/ ================================================ FILE: README.md ================================================ # ⚠️ Project Discontinued This open-source project is **no longer actively maintained**. - No new features or bug fixes will be added. - Pull requests and issues may not receive responses. --- ![helm-compose-banner](https://user-images.githubusercontent.com/18513179/240495789-e76890d3-f0f9-48b9-9d18-89e53effe65b.png) [![Build Status](https://github.com/seacrew/helm-compose/actions/workflows/build.yaml/badge.svg)](https://github.com/seacrew/helm-compose/actions/workflows/build.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/seacrew/helm-compose)](https://goreportcard.com/report/github.com/seacrew/helm-compose) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=seacrew_helm-compose&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=seacrew_helm-compose) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=seacrew_helm-compose&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=seacrew_helm-compose) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/seacrew/helm-compose)](https://github.com/seacrew/helm-compose/releases/latest) Helm Compose is a tool for managing multiple releases of one or many different Helm charts. It is heavily inspired by Docker Compose and is an extension of the package manager idea behind Helm itself. It allows for full configuration-as-code capabilities in an single yaml file. ## Installation It is requirement to use helm v3.10.0+. Install a specific version of helm compose (recommended). Click [here](https://github.com/seacrew/helm-compose/releases/latest) for the latest version. ``` helm plugin install https://github.com/seacrew/helm-compose --version 1.3.0 ``` Install the latest version. ``` helm plugin install https://github.com/seacrew/helm-compose ``` ## Quick Start Guide Helm Compose makes it easy to define a list of Releases and all necessary Repositories for the charts you use in a single compose file. Install your releases: ```bash $ helm compose up -f helm-compose.yaml ``` Uninstall your releases ```bash $ helm compose down -f helm-compose.yaml ``` A Helm Compose file looks something like this: ```yaml apiVersion: 1.1 storage: name: mycompose type: local # default path: .hcstate # default releases: wordpress: chart: bitnami/wordpress chartVersion: 14.3.2 wordpress2: chart: bitnami/wordpress chartVersion: 15.2.22 namespace: homepage createNamespace: true postgres: chart: bitnami/postgresql chartVersion: 12.1.9 namespace: database createNamespace: true repositories: bitnami: https://charts.bitnami.com/bitnami ``` Check out the [examples](https://github.com/seacrew/helm-compose/tree/main/examples) directory. ## Documentation Checkout the complete [documentation.](https://seacrew.github.io/helm-compose/) ================================================ FILE: cmd/down.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "github.com/seacrew/helm-compose/internal/compose" "github.com/seacrew/helm-compose/internal/config" "github.com/spf13/cobra" ) // downCmd represents the down command var downCmd = &cobra.Command{ Use: "down", Short: "This command uninstalls all releases defined in your compose file.", Long: ``, RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true if err := compose.CompatibleHelmVersion(); err != nil { return err } config, err := config.ParseComposeFile(composeFile) if err != nil { return err } return compose.RunDown(config) }, } func init() { rootCmd.AddCommand(downCmd) } ================================================ FILE: cmd/get.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "fmt" "strconv" "github.com/seacrew/helm-compose/internal/compose" "github.com/seacrew/helm-compose/internal/config" "github.com/spf13/cobra" ) var getCmd = &cobra.Command{ Use: "get [revision]", Short: "This command retrieves the decoded content from the revision id you specify.", Long: ``, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true if err := compose.CompatibleHelmVersion(); err != nil { return err } config, err := config.ParseComposeFile(composeFile) if err != nil { return err } revision, err := strconv.Atoi(args[0]) if err != nil { return fmt.Errorf("REVISION must be a number") } if revision < 1 { return fmt.Errorf("REVISION must be a positiv number") } return compose.GetRevision(revision, config) }, } func init() { rootCmd.AddCommand(getCmd) } ================================================ FILE: cmd/list.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "github.com/seacrew/helm-compose/internal/compose" "github.com/seacrew/helm-compose/internal/config" "github.com/spf13/cobra" ) var listCmd = &cobra.Command{ Use: "list", Short: "This command lists the revisions ids for your compose file.", Long: ``, RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true if err := compose.CompatibleHelmVersion(); err != nil { return err } config, err := config.ParseComposeFile(composeFile) if err != nil { return err } return compose.ListRevisions(config) }, } func init() { rootCmd.AddCommand(listCmd) } ================================================ FILE: cmd/root.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "os" "github.com/spf13/cobra" ) // upCmd represents the up command var composeFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "compose", Short: "Compose is a helm plugin to define and manage multiple helm releases as a single entity.", Long: `With the Helm Compose plugin you are able to create a single compose file to manage a multitude of releases. Either for deploying one chart multiple times in the same or different namespaces or to deploy multiple charts as a single entity together. This idea is heavily inspired and influenced by the idea behind docker-compose.`, } func Execute() { err := rootCmd.Execute() if err != nil { os.Exit(1) } } func init() { rootCmd.PersistentFlags().StringVarP(&composeFile, "file", "f", "", "Compose configuration file") } ================================================ FILE: cmd/template.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "github.com/seacrew/helm-compose/internal/compose" "github.com/seacrew/helm-compose/internal/config" "github.com/spf13/cobra" ) var templateCmd = &cobra.Command{ Use: "template [RELEASE ...]", Short: "Render templates for all releases locally and display the output.", Long: ``, RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true if err := compose.CompatibleHelmVersion(); err != nil { return err } config, err := config.ParseComposeFile(composeFile) if err != nil { return err } return compose.Template(config, args) }, } func init() { rootCmd.AddCommand(templateCmd) } ================================================ FILE: cmd/up.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "github.com/seacrew/helm-compose/internal/compose" "github.com/seacrew/helm-compose/internal/config" "github.com/spf13/cobra" ) var upCmd = &cobra.Command{ Use: "up", Short: "This command installs or upgrades all the releases defined in your compose file and uninstalls releases that have been removed since the last revision.", Long: ``, RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true if err := compose.CompatibleHelmVersion(); err != nil { return err } config, err := config.ParseComposeFile(composeFile) if err != nil { return err } return compose.RunUp(config) }, } func init() { rootCmd.AddCommand(upCmd) } ================================================ FILE: docs/commands/down.md ================================================ # helm compose down Uninstall all releases defined in your `helm-compose.yaml` ## Usage The following command will uninstall all releases of the previous revision if one exists. Otherwise the releases defined in your current `helm-compose.yaml` will be uninstalled. ``` helm compose down [flags] ``` ## Options ``` Flags: -h, --help help for down Global Flags: -f, --file string Compose configuration file ``` ================================================ FILE: docs/commands/get.md ================================================ # helm compose get Get a previous revision of your `helm-compose.yaml` ## Usage The following command will return the previous `helm-compose.yaml` content on stdout. ``` helm compose get [revision] [flags] ``` ## Options ``` Flags: -h, --help help for get Global Flags: -f, --file string Compose configuration file ``` ================================================ FILE: docs/commands/list.md ================================================ # helm compose list List previous revisions of your `helm-compose.yaml` ## Usage ``` helm compose list [flags] ``` ## Options ``` Flags: -h, --help help for list Global Flags: -f, --file string Compose configuration file ``` ## Example ``` $ helm compose list | Date | Revision | | ---------------- | -------- | | 2023-05-22 10:24 | 1 | | 2023-05-22 11:19 | 2 | ``` ================================================ FILE: docs/commands/template.md ================================================ # helm compose template Template all kubernetes resources for the releases specified in your `helm-compose.yaml` and print them out to stdout. ## Usage The following command will print out all kubernetes resources that would be installed or upgraded on stdout. ``` helm compose template [releases...] [flags] ``` ## Options ``` Flags: -h, --help help for template Global Flags: -f, --file string Compose configuration file ``` ================================================ FILE: docs/commands/up.md ================================================ # helm compose up Install all releases and repositories defined in your `helm-compose.yaml` ## Usage The following command will install all releases defined in your `helm-compose.yaml` and will compare it to the latest previous revision and uninstall all releases that have been removed since then. ``` helm compose up [flags] ``` ## Options ``` Flags: -h, --help help for up Global Flags: -f, --file string Compose configuration file ``` ================================================ FILE: docs/compose-file-reference.md ================================================ # Compose File Reference ## storage ```yaml storage: name: my-compose type: local numberOfRevisions: 10 ``` | Option | Type | Description | Required | Default | ApiVersion | | ----------------- | ------ | ------------------------------------------------------------------------------------------- | -------- | ------- | ---------- | | name | string | Name to be used to store revisions with a storage provider. | true | | 1.0 | | type | string | Type / name of the storage provider you want to use. By default local files will be stored. | false | local | 1.0 | | numberOfRevisions | int | Number of revisions to be stored and to be able to rollback to. | false | 10 | 1.0 | More details regarding the available storage providers and provider specific options can be found [here.](storage-providers.md) ## releases ```yaml releases: my-website: chart: bitnami/wordpress ``` | Option | Type | Description | Required | Default | ApiVersion | | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------- | ---------- | | chart | string | Name of the chart to be used. | true | | 1.0 | | chartVersion | string | Version of the chart to be used. | false | latest | 1.0 | | forceUpdate | bool | Force resource updates through a replacement strategy | false | false | 1.0 | | historyMax | int | Limit the maximum number of revisions saved per release. | false | 10 | 1.0 | | createNamespace | bool | Create the release namespace if not present | false | false | 1.0 | | cleanUpOnFail | bool | Allow deletion of new resources created in this upgrade when upgrade fails. | false | false | 1.0 | | dependencyUpdate | bool | Update dependencies if they are missing before installing the chart | false | false | 1.0 | | skipTlsVerify | bool | Skip tls certificate checks for the chart download | false | false | 1.0 | | skipCrds | bool | If set, no CRDs will be installed. | false | false | 1.0 | | postRenderer | string | The path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path | false | | 1.0 | | postRendererArgs | array | An argument to the post-renderer (can specify multiple) (default []) | false | | 1.0 | | kubeconfig | string | Path to the kubeconfig file | false | ~/.kube/config | 1.0 | | kubecontext | string | Name of the kubeconfig context to use | false | | 1.0 | | caFile | string | Verify certificates of HTTPS-enabled servers using this CA bundle | false | | 1.0 | | certFile | string | Identify HTTPS client using this SSL certificate file | false | | 1.0 | | keyFile | string | Identify HTTPS client using this SSL key file | false | | 1.0 | | timeout | string | Time to wait for any individual Kubernetes operation (like Jobs for hooks) (default 5m0s) | false | 5m | 1.0 | | wait | bool | Waits until all Pods are in a ready state, It will wait for as long as the --timeout value | false | | 1.1 | | values | map | Map of values with highest priority to overwrite any values in the chart values or your additional values files. (Allows for usage of environment variables.) | false | | 1.0 | | valueFiles | string | List of paths to value files. | false | 5m | 1.0 | Uninstall options: | Option | Type | Description | Required | Default | ApiVersion | | ---------------- | ------ | ------------------------------------------------------------------------------------------------------------ | -------- | ---------- | ---------- | | deletionStrategy | string | Must be "background", "orphan", or "foreground". Selects the deletion cascading strategy for the dependents. | false | background | 1.0 | | deletionTimeout | string | Time to wait for any individual Kubernetes operation (like Jobs for hooks) (default 5m0s) | false | 5m | 1.0 | | deletionNoHooks | bool | Prevent hooks from running during uninstallation | false | false | 1.0 | | keepHistory | bool | Remove all associated resources and mark the release as deleted, but retain the release history | false | false | 1.0 | ## repositories A map of repository names and their respective urls. ```yaml repositories: bitnami: https://charts.bitnami.com/bitnami ``` ================================================ FILE: docs/index.md ================================================ ![helm-compose-banner](https://user-images.githubusercontent.com/18513179/212496531-1d166236-ed88-411d-8403-ad1f94d28846.png) [![Build Status](https://github.com/seacrew/helm-compose/actions/workflows/build.yaml/badge.svg)](https://github.com/seacrew/helm-compose/actions/workflows/build.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/seacrew/helm-compose)](https://goreportcard.com/report/github.com/seacrew/helm-compose) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=seacrew_helm-compose&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=seacrew_helm-compose) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=seacrew_helm-compose&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=seacrew_helm-compose) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/seacrew/helm-compose)](https://github.com/seacrew/helm-compose/releases/latest) Helm Compose is a tool for managing multiple releases for one or many different Helm charts. It is an extension of the package manager idea behind Helm and is heavily inspired by Docker Compose. ================================================ FILE: docs/key-features-and-use-cases.md ================================================ # Key Features and Use Cases The main idea behind `helm-compose` is to control / configure all helm related options as code by providing a compose file structure to configure everything you need to setup your helm based infrastructure. ## Repository handling Configuration based installation of all necessary repositories you define as a dependency in your `helm-compose.yaml` before triggering the installation of your releases. ```yaml apiVersion: 1.1 repositories: bitnami: https://charts.bitnami.com/bitnami ``` ## Multi release handling The main feature of `helm-compose` is the ability to define a multitude of releases inside a single file. `helm-compose` supports single kubernetes-cluster and multi-cluster configuration. ### Single cluster Define as many releases as you would like for one or more namespaces. ```yaml apiVersion: 1.1 releases: wordpress: chart: bitnami/wordpress chartVersion: 14.3.2 namespace: homepage wordpress2: chart: bitnami/wordpress chartVersion: 15.2.22 namespace: homepage repositories: bitnami: https://charts.bitnami.com/bitnami ``` ### Multi cluster You can either use the `kubeconfig` options to point to a different path and use the `kubecontext` to select a specific context inside your kubeconfig. ```yaml apiVersion: 1.1 releases: wordpress-dev: chart: bitnami/wordpress chartVersion: 14.3.2 namespace: homepage kubeconfig: ~/.kube/dev wordpress-int: chart: bitnami/wordpress chartVersion: 15.2.22 namespace: homepage kubeconfig: ~/.kube/int repositories: bitnami: https://charts.bitnami.com/bitnami ``` ### Environment variables `helm-compose` is able to inject environment variables inside your values block to deal with secrets that shouldn't be committed to your source control. Syntax: `${MY_ENV_VARIABLE}`. ```bash export WORDPRESS_ADMIN_PASSWORD="xxx" export MARIADB_ROOT_PASSWORD="xxx" helm compose up ``` ```yaml apiVersion: 1.1 releases: wordpress: chart: bitnami/wordpress values: wordpressPassword: ${WORDPRESS_ADMIN_PASSWORD} mariadb.auth.rootPassword: ${MARIADB_ROOT_PASSWORD} ``` ## Revision handling Revisions are essentially snapshots of your current `helm-compose.yaml`. Every time you trigger `helm compose up` a new revision will be created and stored (By default the last 10 revisions are kept). ### Configuration ```yaml apiVersion: 1.1 storage: name: wordpress type: local # default: local numberOfRevisions: 5 # default: 10 ``` ### Usage You can list your revisions and get the content of your previous revisions via the [`helm compose list`](commands/list.md) and [`helm compose get`](commands/get.md) commands. ### Rollback Just select a revision you want to go back to. You can check the content with the `helm compose get` command and parse it directly into any `helm compose` command like so: ```bash $ helm compose list | Date | Revision | | ---------------- | -------- | | 2023-05-24 23:56 | 12 | | 2023-05-24 23:56 | 13 | | 2023-05-24 23:57 | 14 | | 2023-05-24 23:57 | 15 | | 2023-05-24 23:57 | 16 | # select revision 15 and use the pipe | operator to parse the content back into compose up with -f - helm compose get 15 | helm compose up -f - ``` ================================================ FILE: docs/quick-start.md ================================================ # Quick Start ## Installation Install a specific version (recommended). Click [here](https://github.com/seacrew/helm-compose/releases/latest) for the latest version. ``` helm plugin install https://github.com/seacrew/helm-compose --version 1.3.0 ``` Install latest version. ``` helm plugin install https://github.com/seacrew/helm-compose ``` ## How to use helm compose Helm Compose makes it easy to define a Compose file containing a list of Releases and necessary Repositories for the charts you use. Install your releases: ``` helm compose up -f helm-compose.yaml ``` Uninstall your releases ``` helm compose down -f helm-compose.yaml ``` A Helm Compose file looks something like this: ```yaml apiVersion: 1.1 storage: name: mycompose type: local # default path: .hcstate # default releases: wordpress: chart: bitnami/wordpress chartVersion: 14.3.2 wordpress2: chart: bitnami/wordpress chartVersion: 15.2.22 namespace: homepage createNamespace: true postgres: chart: bitnami/postgresql chartVersion: 12.1.9 namespace: database createNamespace: true repositories: bitnami: https://charts.bitnami.com/bitnami ``` All `helm-compose` commands accept the `-f` flag to pass your compose file location. Otherwise `helm-compose` will automatically look for a list of file names inside your current directory: - helm-compose.yaml - helm-compose.yml - helmcompose.yaml - helm-compose.yml - helmcompose.yaml - helmcompose.yml - helmcompose - compose.yaml - compose.yml Check out the [helm compose examples](https://github.com/seacrew/helm-compose/tree/main/examples). ================================================ FILE: docs/storage-providers.md ================================================ # Storage Providers Following options are applicable regardless of the selected provider. | Option | Type | Description | Required | Default | ApiVersion | | ----------------- | ------ | ------------------------------------------------------------------------------------------- | -------- | ------- | ---------- | | name | string | Name to be used to store revisions with a storage provider. | true | | 1.0 | | type | string | Type / name of the storage provider you want to use. By default local files will be stored. | false | local | 1.0 | | numberOfRevisions | int | Number of revisions to be stored and to be able to rollback to. | false | 10 | 1.0 | ## Local Stores your compose revisions locally inside the `.hcstate` directory next to your `helm-compose.yaml`. | Option | Type | Description | Required | Default | ApiVersion | | ------ | ------ | ----------------------------------------------------------------------------------------------------- | -------- | -------- | ---------- | | path | string | The directory path to store your revisions (Relative to the directory you execute `helm compose` in). | false | .hcstate | 1.0 | ## Kubernetes Stores your compose revisions similar to helm releases inside secrets in a kubernetes cluster namespace. | Option | Type | Description | Required | Default | ApiVersion | | ----------- | ------ | -------------------------------------------------- | -------- | --------------- | ---------- | | namespace | string | The namespace to store your revisions in. | false | default | 1.0 | | kubeconfig | string | The path to your kubeconfig file | false | ~/.kube/config | 1.0 | | kubecontext | string | The context to use from your specified kubeconfig. | false | current-context | 1.0 | ## S3 Stores your compose revisions inside a s3 bucket. You will need to set your AWS credentials (access and secret key) via [environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) or the `~/.aws/config` file. [Official AWS documentation](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html). | Option | Type | Description | Required | Default | ApiVersion | | ---------------- | ------ | ---------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------- | ---------- | | s3bucket | string | Specify the bucket name to upload to and download from. | true | | 1.0 | | s3prefix | string | Specify the object prefix (directory) for the revisions to be uploaded to and downloaded from. | false | (root path) | 1.0 | | s3region | string | Set a custom S3 region. | false | By default the region will be read from the AWS_REGION environment variable. | 1.0 | | s3endpoint | string | Set a custom S3 endpoint / host url. | false | Default AWS S3 service endpoint. | 1.0 | | s3insecure | bool | Disable the verification of the servers certificate chain and hostname. | false | false | 1.0 | | s3disableSSL | bool | Disable the usage of SSL / https. | false | false | 1.0 | | s3forcePathStyle | bool | Enforce to use path style. Especially useful for none AWS S3 provider which often only support path style. | false | false | 1.0 | ## GCS Not yet implemented ================================================ FILE: docs/stylesheets/extra.css ================================================ :root { --md-primary-fg-color: #00acc1; } ================================================ FILE: examples/.gitignore ================================================ .hcstate .override ================================================ FILE: examples/k8s-storage-compose.yaml ================================================ apiVersion: 1.1 storage: name: k8s-test type: kubernetes numberOfRevisions: 5 releases: k8s-nginx: chart: bitnami/nginx repositories: bitnami: https://charts.bitnami.com/bitnami ================================================ FILE: examples/local-storage-compose.yaml ================================================ apiVersion: 1.1 storage: name: local type: local path: .override releases: default: chart: bitnami/nginx chartVersion: 14.2.1 wait: true repositories: bitnami: https://charts.bitnami.com/bitnami ================================================ FILE: examples/s3-storage-compose.yaml ================================================ apiVersion: 1.1 storage: name: s3-test type: s3 s3bucket: helm-compose s3region: eu-central-1 s3prefix: wordpress releases: wordpress: chart: bitnami/wordpress chartVersion: 14.3.2 namespace: wordpress createNamespace: true valueFiles: - ./values/wordpress.yaml values: wordpressBlogName: Awesome Site wordpressPassword: "${WORDPRESS_PASSWORD}}" mariadb.auth: rootPassword: "${MARIADB_ROOT_PASSWORD}}" repositories: bitnami: https://charts.bitnami.com/bitnami ================================================ FILE: examples/simple-compose.yaml ================================================ apiVersion: 1.1 storage: name: simple type: local releases: default: chart: bitnami/nginx chartVersion: 14.2.1 jekyll: chart: bitnami/nginx chartVersion: 14.2.1 values: cloneStaticSiteFromGit: enabled: true repository: https://github.com/jekyll/jekyll branch: gh-pages repositories: bitnami: https://charts.bitnami.com/bitnami ================================================ FILE: examples/values/wordpress.yaml ================================================ wordpressUsername: user wordpressEmail: user@example.com allowEmptyPassword: false mariadb: primary: persistence: enabled: false persistence: enabled: false ================================================ FILE: examples/wordpress-compose.yaml ================================================ apiVersion: 1.1 storage: name: wordpress type: local releases: site1: chart: bitnami/wordpress chartVersion: 14.3.2 namespace: site1 createNamespace: true valueFiles: - ./values/wordpress.yaml values: wordpressBlogName: Awesome Site wordpressPassword: awesome mariadb.auth: rootPassword: "awesome-password" site2: chart: bitnami/wordpress chartVersion: 14.3.2 namespace: site2 createNamespace: true valueFiles: - ./values/wordpress.yaml values: wordpressBlogName: Super Awesome Site wordpressPassword: super-awesome mariadb.auth: rootPassword: "super-awesome-password" repositories: bitnami: https://charts.bitnami.com/bitnami ================================================ FILE: go.mod ================================================ module github.com/seacrew/helm-compose go 1.22.0 toolchain go1.22.2 require ( github.com/Masterminds/semver v1.5.0 github.com/aws/aws-sdk-go v1.53.7 github.com/jwalton/go-supportscolor v1.2.0 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/apimachinery v0.30.1 k8s.io/client-go v0.30.1 ) require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/term v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.30.1 k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a // indirect k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) ================================================ FILE: go.sum ================================================ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/aws/aws-sdk-go v1.53.5 h1:1OcVWMjGlwt7EU5OWmmEEXqaYfmX581EK317QJZXItM= github.com/aws/aws-sdk-go v1.53.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go v1.53.7 h1:ZSsRYHLRxsbO2rJR2oPMz0SUkJLnBkN+1meT95B6Ixs= github.com/aws/aws-sdk-go v1.53.7/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jwalton/go-supportscolor v1.2.0 h1:g6Ha4u7Vm3LIsQ5wmeBpS4gazu0UP1DRDE8y6bre4H8= github.com/jwalton/go-supportscolor v1.2.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 h1:SbdLaI6mM6ffDSJCadEaD4IkuPzepLDGlkd2xV0t1uA= k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a h1:zD1uj3Jf+mD4zmA7W+goE5TxDkI7OGJjBNBzq5fJtLA= k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= ================================================ FILE: internal/compose/compose.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package compose import ( "fmt" "sync" cfg "github.com/seacrew/helm-compose/internal/config" prov "github.com/seacrew/helm-compose/internal/provider" "github.com/seacrew/helm-compose/internal/util" ) func RunUp(config *cfg.Config) error { for name, url := range config.Repositories { if err := addHelmRepository(name, url); err != nil { return err } } previousConfig, err := prov.Load(config) if err != nil { return err } if !config.Equal(previousConfig) { if err := prov.Store(config); err != nil { return err } } var wg sync.WaitGroup for name, release := range config.Releases { wg.Add(1) go func(name string, release cfg.Release) { installHelmRelease(name, &release) wg.Done() }(name, release) } if previousConfig == nil { wg.Wait() return nil } for name, release := range previousConfig.Releases { wg.Add(1) go func(name string, release cfg.Release) { if _, ok := config.Releases[name]; ok { wg.Done() return } uninstallHelmRelease(name, &release) wg.Done() }(name, release) } wg.Wait() return nil } func RunDown(config *cfg.Config) error { previousConfig, err := prov.Load(config) if err != nil { return err } if previousConfig != nil { config = previousConfig } var wg sync.WaitGroup for name, release := range config.Releases { wg.Add(1) go func(name string, release cfg.Release) { uninstallHelmRelease(name, &release) wg.Done() }(name, release) } wg.Wait() return nil } func ListRevisions(config *cfg.Config) error { revisions, err := prov.List(config) if err != nil { return err } fmt.Printf("| Date | Revision |\n") fmt.Printf("| ---------------- | -------- |\n") for _, rev := range revisions { fmt.Printf("| %d-%02d-%02d %02d:%02d | %8d |\n", rev.DateTime.Year(), rev.DateTime.Month(), rev.DateTime.Day(), rev.DateTime.Hour(), rev.DateTime.Minute(), rev.Revision) } return nil } func GetRevision(rev int, config *cfg.Config) error { revision, err := prov.Get(rev, config) if err != nil { return err } fmt.Printf("%s\n", *revision) return nil } func Template(config *cfg.Config, releases []string) error { util.PrintColors = false for name, url := range config.Repositories { if err := addHelmRepository(name, url); err != nil { return err } } for name, release := range config.Releases { if len(releases) == 0 { templateHelmRelease(name, &release) continue } for _, rel := range releases { if rel == name { templateHelmRelease(name, &release) } } } return nil } ================================================ FILE: internal/compose/helm.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package compose import ( "bufio" "encoding/json" "errors" "fmt" "os" "os/exec" "regexp" "strings" "github.com/Masterminds/semver" cfg "github.com/seacrew/helm-compose/internal/config" "github.com/seacrew/helm-compose/internal/util" ) var ( helm = os.Getenv("HELM_BIN") versionRE = regexp.MustCompile(`Version:\s*"([^"]+)"`) minVersion = semver.MustParse("v3.0.0") ) type HelmCommand string const ( HELM_UPGRADE HelmCommand = "upgrade" HELM_UNINSTALL HelmCommand = "uninstall" HELM_TEMPLATE HelmCommand = "template" ) func CompatibleHelmVersion() error { cmd := exec.Command(helm, "version") util.DebugPrint("Executing %s", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to run `%s version`: %v", os.Getenv("HELM_BIN"), err) } versionOutput := string(output) matches := versionRE.FindStringSubmatch(versionOutput) if matches == nil { return fmt.Errorf("failed to find version in output %#v", versionOutput) } helmVersion, err := semver.NewVersion(matches[1]) if err != nil { return fmt.Errorf("failed to parse version %#v: %v", matches[1], err) } if minVersion.GreaterThan(helmVersion) { return fmt.Errorf("helm compose requires at least helm version %s", minVersion.String()) } return nil } func addHelmRepository(name string, url string) error { output, err := util.Execute(helm, "repo", "add", "--force-update", name, url) if err != nil { return errors.New(output) } return nil } func installHelmRelease(name string, release *cfg.Release) { args, err := createHelmArguments(HELM_UPGRADE, name, release) if err != nil { cp := util.NewColorPrinter(name) cp.Printf("%s |\t\t%s", name, err) } helmExec(name, args) } func templateHelmRelease(name string, release *cfg.Release) { args, err := createHelmArguments(HELM_TEMPLATE, name, release) if err != nil { cp := util.NewColorPrinter(name) cp.Printf("# %s |\t\t%s", name, err) } helmExec("", args) } func uninstallHelmRelease(name string, release *cfg.Release) { var args []string args = append(args, "uninstall") if release.Namespace != "" { args = append(args, fmt.Sprintf("--namespace=%s", release.Namespace)) } if release.KubeConfig != "" { args = append(args, fmt.Sprintf("--kubeconfig=%s", release.KubeConfig)) } if release.KubeContext != "" { args = append(args, fmt.Sprintf("--kube-context=%s", release.KubeContext)) } if release.DeletionStrategy != "" { args = append(args, fmt.Sprintf("--cascade=%s", release.DeletionStrategy)) } if release.DeletionTimeout != "" { args = append(args, fmt.Sprintf("--timeout=%s", release.DeletionTimeout)) } if release.DeletionNoHooks { args = append(args, "--no-hooks") } if release.KeepHistory { args = append(args, "--keep-history") } args = append(args, name) helmExec(name, args) } func createHelmArguments(command HelmCommand, name string, release *cfg.Release) ([]string, error) { var args []string args = append(args, string(command)) if command == HELM_UPGRADE { args = append(args, "--install") } if release.ChartVersion != "" { args = append(args, fmt.Sprintf("--version=%s", release.ChartVersion)) } if release.Namespace != "" { args = append(args, fmt.Sprintf("--namespace=%s", release.Namespace)) } if release.ForceUpdate { args = append(args, "--force") } if release.HistoryMax < 0 { args = append(args, fmt.Sprintf("--history-max=%d", 0)) } else if release.HistoryMax > 0 { args = append(args, fmt.Sprintf("--history-max=%d", release.HistoryMax)) } if release.CreateNamespace { args = append(args, "--create-namespace") } if release.CleanUpOnFail { args = append(args, "--cleanup-on-fail") } if release.DependencyUpdate { args = append(args, "--dependency-update") } if release.SkipTLSVerify { args = append(args, "--insecure-skip-tls-verify") } if release.SkipCRDs { args = append(args, "--skip-crds") } if release.PostRenderer != "" { args = append(args, fmt.Sprintf("--post-renderer=%s", release.PostRenderer)) } if len(release.PostRendererArgs) > 0 { args = append(args, fmt.Sprintf("--post-renderer-args=[%s]", strings.Join(release.PostRendererArgs, ","))) } if release.CAFile != "" { args = append(args, fmt.Sprintf("--ca-file=%s", release.CAFile)) } if release.CertFile != "" { args = append(args, fmt.Sprintf("--cert-file=%s", release.CertFile)) } if release.KeyFile != "" { args = append(args, fmt.Sprintf("--key-file=%s", release.KeyFile)) } if release.Timeout != "" { args = append(args, fmt.Sprintf("--timeout=%s", release.Timeout)) } if release.Wait { args = append(args, "--wait") } if release.KubeConfig != "" { args = append(args, fmt.Sprintf("--kubeconfig=%s", release.KubeConfig)) } if release.KubeContext != "" { args = append(args, fmt.Sprintf("--kube-context=%s", release.KubeContext)) } for _, file := range release.ValueFiles { args = append(args, fmt.Sprintf("--values=%s", file)) } var jsonValues []string for key := range release.Values { data := util.ConvertJson(release.Values[key]) values, err := json.Marshal(data) if err != nil { return nil, err } jsonValues = append(jsonValues, fmt.Sprintf("%s=%s", key, values)) } if len(jsonValues) > 0 { args = append(args, fmt.Sprintf("--set-json=%s", strings.Join(jsonValues, ","))) } args = append(args, name) args = append(args, release.Chart) return args, nil } func helmExec(name string, args []string) { cp := util.NewColorPrinter(name) output, _ := util.Execute(helm, args...) scanner := bufio.NewScanner(strings.NewReader(output)) for scanner.Scan() { if len(name) == 0 { fmt.Printf("%s\n", scanner.Text()) } else { cp.Printf("%s |\t\t%s", name, scanner.Text()) } } err := scanner.Err() if err != nil { cp.Printf(err.Error()) } } ================================================ FILE: internal/config/config.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import ( "bufio" "fmt" "io" "io/fs" "os" "path/filepath" "github.com/Masterminds/semver" "gopkg.in/yaml.v2" ) var ( V1_0 = semver.MustParse("1.0") V1_1 = semver.MustParse("1.1") ) func findComposeConfig() []string { var files []string filenames := []string{ "helm-compose.yaml", "helm-compose.yml", "helmcompose.yaml", "helm-compose.yml", "helmcompose.yaml", "helmcompose.yml", "helmcompose", "compose.yaml", "compose.yml", } filepath.WalkDir(".", func(s string, d fs.DirEntry, e error) error { file := filepath.Base(s) for _, filename := range filenames { if file == filename { files = append(files, file) } } return nil }) return files } func ParseComposeFile(filename string) (*Config, error) { var files []string if filename == "" { files = findComposeConfig() } else if filename == "-" { reader := bufio.NewReader(os.Stdin) data := []byte{} for { b, err := reader.ReadBytes('\n') if err == io.EOF { break } else if err != nil { return nil, err } data = append(data, b...) } return parseComposeData(data) } else if _, err := os.Stat(filename); err != nil { return nil, fmt.Errorf("provided compose file not found") } else { files = []string{filename} } if len(files) == 0 { return nil, fmt.Errorf("no compose file found") } if len(files) > 1 { return nil, fmt.Errorf("expected only one compose file but found multiple: %v", files) } file, err := os.ReadFile(files[0]) if err != nil { return nil, err } return parseComposeData(file) } func parseComposeData(data []byte) (*Config, error) { config := Config{} err := yaml.Unmarshal(data, &config) if err != nil { return nil, err } if err := validateCompose(&config); err != nil { return nil, err } return &config, nil } func validateCompose(config *Config) error { if config.Version == "" { return fmt.Errorf("missing apiVersion in config") } version, err := semver.NewVersion(config.Version) if err != nil { return fmt.Errorf("failed to parse apiVersion: %s", config.Version) } if version.LessThan(V1_0) { return fmt.Errorf("helm compose requires at least apiVersion 1.0 but got %s", config.Version) } if err := validateComposeFeatures(version, config); err != nil { return err } return nil } func validateComposeFeatures(version *semver.Version, config *Config) error { if err := validateCompose1_1(version, config); err != nil { return fmt.Errorf("apiVersion 1.1+ necessary: %s", err) } return nil } func validateCompose1_1(version *semver.Version, config *Config) error { if version.GreaterThan(V1_0) { return nil } for name, release := range config.Releases { if release.Wait { return fmt.Errorf("trying to use 'wait' in release '%s'", name) } } return nil } ================================================ FILE: internal/config/config_test.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import ( "log" "testing" ) func TestParseSimpleConfig(t *testing.T) { config, err := ParseComposeFile("../../examples/simple-compose.yaml") if err != nil { log.Fatal(err) } if config.Storage.Name != "simple" { log.Fatalf("Was expecting revision name 'simple' but got '%s'", config.Storage.Name) } if config.Storage.Type != Local { log.Fatalf("Was expecting revision provider type '%s' but got '%s'", Local, config.Storage.Type) } if len(config.Releases) != 2 { log.Fatalf("Was expecting 2 release but got %d", len(config.Releases)) } } ================================================ FILE: internal/config/types.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import "reflect" type ProviderType string const ( Local ProviderType = "local" Kubernetes ProviderType = "kubernetes" S3 ProviderType = "s3" ) type Config struct { Version string `yaml:"apiVersion,omitempty"` Storage Storage `yaml:"storage,omitempty"` Releases map[string]Release `yaml:"releases,omitempty"` Repositories map[string]string `yaml:"repositories,omitempty"` } type Release struct { Name string `yaml:"name,omitempty"` Chart string `yaml:"chart,omitempty"` ChartVersion string `yaml:"chartVersion,omitempty"` Namespace string `yaml:"namespace,omitempty"` ForceUpdate bool `yaml:"forceUpdate,omitempty"` HistoryMax int `yaml:"historyMax,omitempty"` CreateNamespace bool `yaml:"createNamespace,omitempty"` CleanUpOnFail bool `yaml:"cleanupOnFail,omitempty"` DependencyUpdate bool `yaml:"dependencyUpdate,omitempty"` SkipTLSVerify bool `yaml:"skipTlsVerify,omitempty"` SkipCRDs bool `yaml:"skipCrds,omitempty"` PostRenderer string `yaml:"postRenderer,omitempty"` PostRendererArgs []string `yaml:"postRendererArgs,omitempty"` KubeConfig string `yaml:"kubeconfig,omitempty"` KubeContext string `yaml:"kubecontext,omitempty"` CAFile string `yaml:"caFile,omitempty"` CertFile string `yaml:"certFile,omitempty"` KeyFile string `yaml:"keyFile,omitempty"` Timeout string `yaml:"timeout,omitempty"` Wait bool `yaml:"wait,omitempty"` Values map[string]interface{} `yaml:"values,omitempty"` ValueFiles []string `yaml:"valueFiles,omitempty"` // Uninstall flags DeletionStrategy string `yaml:"deletionStrategy,omitempty"` DeletionTimeout string `yaml:"deletionTimeout,omitempty"` DeletionNoHooks bool `yaml:"deletionNoHooks,omitempty"` KeepHistory bool `yaml:"keepHistory,omitempty"` } type Storage struct { Type ProviderType `yaml:"type,omitempty"` Name string `yaml:"name,omitempty"` NumberOfRevisions int `yaml:"numberOfRevisions,omitempty"` // Local storage fields Path string `yaml:"path,omitempty"` // K8s storage fields Namespace string `yaml:"namespace,omitempty"` KubeConfig string `yaml:"kubeconfig,omitempty"` KubeContext string `yaml:"kubecontext,omitempty"` // S3 storage fields S3Bucket string `yaml:"s3bucket,omitempty"` S3Prefix string `yaml:"s3prefix,omitempty"` S3Region string `yaml:"s3region,omitempty"` S3Endpoint string `yaml:"s3endpoint,omitempty"` S3Insecure bool `yaml:"s3insecure,omitempty"` S3DisableSSL bool `yaml:"s3disableSSL,omitempty"` S3ForcePathStyle bool `yaml:"s3forcePathStyle,omitempty"` } func (c *Config) Equal(o *Config) bool { if o == nil { return false } if c.Version != o.Version { return false } if c.Storage != o.Storage { return false } if len(c.Releases) != len(o.Releases) { return false } if !reflect.DeepEqual(c.Releases, o.Releases) { return false } if len(c.Repositories) != len(o.Repositories) { return false } for key, value := range c.Repositories { if val, ok := o.Repositories[key]; !ok || val != value { return false } } return true } ================================================ FILE: internal/config/types_test.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import ( "testing" "github.com/stretchr/testify/assert" ) func TestConfigEqualVersion(t *testing.T) { cfg := Config{Version: "v1.0"} otherCfg := Config{Version: "v1.0"} assert.True(t, cfg.Equal(&otherCfg)) cfg = Config{Version: "v1.0"} otherCfg = Config{Version: "v1.1"} assert.False(t, cfg.Equal(&otherCfg)) } func TestConfigEqualStorage(t *testing.T) { cfg := Config{ Storage: Storage{ Type: Local, Name: "mycompose", Path: ".state", }, } assert.True(t, cfg.Equal(&cfg)) otherCfg := Config{ Storage: Storage{ Type: Local, Name: "mycompose", }, } assert.False(t, cfg.Equal(&otherCfg)) } func TestConfigEqualRepositories(t *testing.T) { cfg := Config{ Repositories: map[string]string{}, } otherCfg := Config{ Repositories: map[string]string{ "bitnami": "https://charts.bitnami.com/bitnami", }, } assert.False(t, cfg.Equal(&otherCfg)) cfg = Config{ Repositories: map[string]string{ "bitnami": "https://charts.bitnami.com/bitnami", }, } otherCfg = Config{ Repositories: map[string]string{ "bitnami": "https://charts.bitnami.com/bitnami", }, } assert.True(t, cfg.Equal(&otherCfg)) cfg = Config{ Repositories: map[string]string{ "bitnami": "https://charts.bitnami.com/bitnami", }, } otherCfg = Config{ Repositories: map[string]string{ "bitnami": "https://charts.bitnami.net/bitnami", }, } assert.False(t, cfg.Equal(&otherCfg)) } func TestConfigEqualReleases(t *testing.T) { cfg := Config{ Releases: map[string]Release{}, } otherCfg := Config{ Releases: map[string]Release{ "wordpress": { Chart: "bitnami/wordpress", ChartVersion: "14.2.1", }, }, } assert.False(t, cfg.Equal(&otherCfg)) cfg = Config{ Releases: map[string]Release{ "wordpress": { Chart: "bitnami/wordpress", ChartVersion: "14.2.1", Values: map[string]interface{}{ "wordpressBlogName": "my-site", }, }, }, } otherCfg = Config{ Releases: map[string]Release{ "wordpress": { Chart: "bitnami/wordpress", ChartVersion: "14.2.1", Values: map[string]interface{}{ "wordpressBlogName": "my-site", }, }, }, } assert.True(t, cfg.Equal(&otherCfg)) cfg = Config{ Releases: map[string]Release{ "wordpress": { Chart: "bitnami/wordpress", ChartVersion: "14.2.1", Values: map[string]interface{}{ "wordpressBlogName": "my-site", }, }, }, } otherCfg = Config{ Releases: map[string]Release{ "wordpress": { Chart: "bitnami/wordpress", ChartVersion: "14.2.1", Values: map[string]interface{}{ "wordpressBlogName": "my-new-site", }, }, }, } assert.False(t, cfg.Equal(&otherCfg)) } ================================================ FILE: internal/provider/kubernetes.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package provider import ( "context" "fmt" "math" "os" "path/filepath" "regexp" "strconv" b64 "encoding/base64" cfg "github.com/seacrew/helm-compose/internal/config" "github.com/seacrew/helm-compose/internal/util" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" ) const ( k8sSecretNameFormat = "helm.compose.%s.v%d" k8sSecretNamePattern = "^helm.compose.%s.v(\\d+)$" ) type KubernetesProvider struct { name string numberOfRevisions int namespace string client *kubernetes.Clientset listOptions *metav1.ListOptions } func newKubernetesProvider(providerConfig *cfg.Storage) (*KubernetesProvider, error) { namespace := providerConfig.Namespace if len(namespace) == 0 { namespace = "default" } kubeconfig := providerConfig.KubeConfig if len(kubeconfig) == 0 { homedir, err := os.UserHomeDir() if err != nil { return nil, err } kubeconfig = filepath.Join( homedir, ".kube", "config", ) } var err error var config *rest.Config if len(providerConfig.KubeContext) == 0 { config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, &clientcmd.ConfigOverrides{CurrentContext: providerConfig.KubeContext}).ClientConfig() if err != nil { return nil, err } } else { config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return nil, err } } clientset, err := kubernetes.NewForConfig(config) if err != nil { return nil, err } labelSelector := metav1.LabelSelector{MatchLabels: map[string]string{"app.kubernetes.io/managed-by": "Helm-Compose", "helm-compose/name": providerConfig.Name}} listOptions := metav1.ListOptions{ LabelSelector: labels.Set(labelSelector.MatchLabels).String(), } provider := &KubernetesProvider{ name: providerConfig.Name, numberOfRevisions: providerConfig.NumberOfRevisions, namespace: namespace, client: clientset, listOptions: &listOptions, } return provider, nil } func (p KubernetesProvider) load() (*[]byte, error) { secrets, err := p.client.CoreV1().Secrets(p.namespace).List(context.Background(), *p.listOptions) if err != nil { return nil, err } if len(secrets.Items) == 0 { return nil, nil } _, _, latest, err := p.minMax(secrets.Items) if err != nil { return nil, err } if latest == nil { return nil, nil } data, err := b64.StdEncoding.DecodeString(string(latest.Data["compose"])) if err != nil { return nil, err } return &data, nil } func (p KubernetesProvider) store(encodedConfig *string) error { secrets, err := p.client.CoreV1().Secrets(p.namespace).List(context.Background(), *p.listOptions) if err != nil { return err } minimum, maximum, _, err := p.minMax(secrets.Items) if err != nil { return err } revision := maximum + 1 data := b64.StdEncoding.EncodeToString([]byte(*encodedConfig)) secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf(k8sSecretNameFormat, p.name, revision), Namespace: p.namespace, Labels: map[string]string{ "app.kubernetes.io/managed-by": "Helm-Compose", "helm-compose/name": p.name, }, }, Immutable: util.NewBool(true), Data: map[string][]byte{ "compose": []byte(data), }, Type: "helm-compose/revision.v1", } _, err = p.client.CoreV1().Secrets(p.namespace).Create(context.Background(), secret, metav1.CreateOptions{}) if err != nil { return err } if minimum > revision-p.numberOfRevisions { return nil } for i := minimum; i <= revision-p.numberOfRevisions; i++ { if err = p.client.CoreV1().Secrets(p.namespace).Delete(context.Background(), fmt.Sprintf(k8sSecretNameFormat, p.name, i), metav1.DeleteOptions{}); err != nil { fmt.Println(err) } } return nil } func (p KubernetesProvider) list() ([]ComposeRevision, error) { secrets, err := p.client.CoreV1().Secrets(p.namespace).List(context.Background(), metav1.ListOptions{}) if err != nil { return nil, err } if len(secrets.Items) == 0 { return nil, nil } revisions := []ComposeRevision{} r, err := regexp.Compile(fmt.Sprintf(k8sSecretNamePattern, p.name)) if err != nil { return nil, err } for _, item := range secrets.Items { matches := r.FindStringSubmatch(item.Name) if len(matches) == 0 { continue } revision, err := strconv.Atoi(matches[1]) if err != nil { return nil, err } revisions = append(revisions, ComposeRevision{revision, item.CreationTimestamp.Time}) } return revisions, nil } func (p KubernetesProvider) get(revision int) (*[]byte, error) { secret, err := p.client.CoreV1().Secrets(p.namespace).Get(context.Background(), fmt.Sprintf(k8sSecretNameFormat, p.name, revision), metav1.GetOptions{}) if err != nil { return nil, err } data, err := b64.StdEncoding.DecodeString(string(secret.Data["compose"])) if err != nil { return nil, err } return &data, nil } func (p KubernetesProvider) minMax(secrets []corev1.Secret) (int, int, *corev1.Secret, error) { if len(secrets) == 0 { return 0, 0, nil, nil } minimum, maximum := math.MaxInt, 0 r, err := regexp.Compile(fmt.Sprintf(k8sSecretNamePattern, p.name)) if err != nil { return -1, -1, nil, err } var latest corev1.Secret for _, secret := range secrets { matches := r.FindStringSubmatch(secret.Name) if len(matches) == 0 { continue } revision, err := strconv.Atoi(matches[1]) if err != nil { return -1, -1, nil, err } if revision > maximum { maximum = revision latest = secret } if revision < minimum { minimum = revision } } return minimum, maximum, &latest, nil } ================================================ FILE: internal/provider/kubernetes_test.go ================================================ package provider import ( "testing" ) func TestStoreAndLoad(t *testing.T) { //k8s, err := newKubernetes(&config.Storage{ // Name: "hello-world", //}) //if err != nil { // log.Fatal(err) //} //_, err = k8s.load() //if err != nil { // log.Fatal(err) //} } ================================================ FILE: internal/provider/local.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package provider import ( "fmt" "os" "regexp" "strconv" cfg "github.com/seacrew/helm-compose/internal/config" "github.com/seacrew/helm-compose/internal/util" ) const ( pathFormat = "%s/%s-%d" ) type LocalProvider struct { name string path string numberOfRevisions int } func newLocalProvider(providerConfig *cfg.Storage) *LocalProvider { provider := LocalProvider{ name: providerConfig.Name, path: providerConfig.Path, numberOfRevisions: providerConfig.NumberOfRevisions, } if len(provider.path) == 0 { provider.path = ".hcstate" } return &provider } func (p LocalProvider) load() (*[]byte, error) { if _, err := os.Stat(p.path); os.IsNotExist(err) { if err := os.Mkdir(p.path, os.ModePerm); err != nil { return nil, err } } _, maximum, err := p.minMax(p.name, p.path) if err != nil { return nil, err } if maximum == 0 { return nil, nil } file, err := os.ReadFile(fmt.Sprintf(pathFormat, p.path, p.name, maximum)) if err != nil { return nil, err } return &file, nil } func (p LocalProvider) store(encodedConfig *string) error { minimum, maximum, err := p.minMax(p.name, p.path) if err != nil { return err } maximum = maximum + 1 if err := os.WriteFile(fmt.Sprintf(pathFormat, p.path, p.name, maximum), []byte(*encodedConfig), 0644); err != nil { return err } if minimum > maximum-p.numberOfRevisions { return nil } for i := minimum; i <= maximum-p.numberOfRevisions; i++ { if err := os.Remove(fmt.Sprintf(pathFormat, p.path, p.name, i)); err != nil { fmt.Println(err) } } return nil } func (p LocalProvider) list() ([]ComposeRevision, error) { files, err := os.ReadDir(p.path) if err != nil { return nil, err } r, _ := regexp.Compile(fmt.Sprintf("^%s-(\\d+)$", p.name)) revisions := []ComposeRevision{} for _, file := range files { if file.IsDir() { continue } matches := r.FindStringSubmatch(file.Name()) if len(matches) == 0 { continue } revision, err := strconv.Atoi(matches[1]) if err != nil { return nil, err } info, err := file.Info() if err != nil { return nil, err } revisions = append(revisions, ComposeRevision{revision, info.ModTime()}) } return revisions, nil } func (p LocalProvider) get(revision int) (*[]byte, error) { file, err := os.ReadFile(fmt.Sprintf(pathFormat, p.path, p.name, revision)) if err != nil { return nil, err } return &file, nil } func (p LocalProvider) minMax(name string, path string) (int, int, error) { files, err := os.ReadDir(path) if err != nil { return -1, -1, err } r, _ := regexp.Compile(fmt.Sprintf("^%s-(\\d+)$", name)) revisions := []int{} for _, file := range files { if file.IsDir() { continue } matches := r.FindStringSubmatch(file.Name()) if len(matches) == 0 { continue } revision, err := strconv.Atoi(matches[1]) if err != nil { return -1, -1, nil } revisions = append(revisions, revision) } minimum, maximum := util.MinMax(revisions) return minimum, maximum, nil } ================================================ FILE: internal/provider/providers.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package provider import ( "fmt" "sort" "time" cfg "github.com/seacrew/helm-compose/internal/config" "github.com/seacrew/helm-compose/internal/util" "gopkg.in/yaml.v2" ) type ComposeRevision struct { Revision int DateTime time.Time } type Provider interface { load() (*[]byte, error) store(encodedConfig *string) error list() ([]ComposeRevision, error) get(revision int) (*[]byte, error) } var provider Provider func getProvider(providerConfig *cfg.Storage) (Provider, error) { if provider != nil { return provider, nil } if providerConfig.NumberOfRevisions <= 0 { providerConfig.NumberOfRevisions = 10 } var err error switch providerConfig.Type { case cfg.Local: provider = newLocalProvider(providerConfig) return provider, nil case cfg.Kubernetes: provider, err = newKubernetesProvider(providerConfig) return provider, err case cfg.S3: provider, err = newS3Provider(providerConfig) return provider, err default: return nil, fmt.Errorf("unknown provider type %q", providerConfig.Type) } } func Load(config *cfg.Config) (*cfg.Config, error) { provider, err := getProvider(&config.Storage) if err != nil { return nil, err } data, err := provider.load() if err != nil { return nil, err } if data == nil { return nil, nil } prevConfig, err := util.DecodeComposeConfig(string(*data)) if err != nil { return nil, err } return prevConfig, nil } func Store(config *cfg.Config) error { provider, err := getProvider(&config.Storage) if err != nil { return err } encodedConfig, err := util.EncodeComposeConfig(config) if err != nil { return err } if err := provider.store(&encodedConfig); err != nil { return err } return nil } func List(config *cfg.Config) ([]ComposeRevision, error) { provider, err := getProvider(&config.Storage) if err != nil { return nil, err } revisions, err := provider.list() if err != nil { return nil, err } sort.Slice(revisions, func(i, j int) bool { return revisions[i].Revision < revisions[j].Revision }) return revisions, nil } func Get(revision int, config *cfg.Config) (*string, error) { provider, err := getProvider(&config.Storage) if err != nil { return nil, err } data, err := provider.get(revision) if err != nil { return nil, err } revConfig, err := util.DecodeComposeConfig(string(*data)) if err != nil { return nil, err } b, err := yaml.Marshal(revConfig) if err != nil { return nil, err } revYaml := string(b) return &revYaml, nil } ================================================ FILE: internal/provider/s3.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package provider import ( "bytes" "crypto/tls" "fmt" "math" "net/http" "os" "regexp" "strconv" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" cfg "github.com/seacrew/helm-compose/internal/config" "github.com/seacrew/helm-compose/internal/util" ) const ( s3ObjectNameFormat = "%s.v%d.hcstate" s3ObjectNamePattern = "%s.v(\\d+).hcstate$" ) type S3Provider struct { name string numberOfRevisions int bucket *string prefix *string lister *s3.S3 uploader *s3manager.Uploader downloader *s3manager.Downloader } func newS3Provider(providerConfig *cfg.Storage) (*S3Provider, error) { config := &aws.Config{} if len(providerConfig.S3Region) > 0 { config.Region = &providerConfig.S3Region } else if os.Getenv("AWS_REGION") != "" { config.Region = aws.String(os.Getenv("AWS_REGION")) } else { return nil, fmt.Errorf("AWS region not specified") } if len(providerConfig.S3Endpoint) > 0 { config.Endpoint = &providerConfig.S3Endpoint } if providerConfig.S3DisableSSL { config.DisableSSL = util.NewBool(true) } if providerConfig.S3Insecure { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } config.HTTPClient = &http.Client{Transport: tr} } if providerConfig.S3ForcePathStyle { config.S3ForcePathStyle = util.NewBool(true) } sess, err := session.NewSession(config) if err != nil { return nil, err } provider := &S3Provider{ name: providerConfig.Name, numberOfRevisions: providerConfig.NumberOfRevisions, bucket: &providerConfig.S3Bucket, prefix: &providerConfig.S3Prefix, lister: s3.New(sess), uploader: s3manager.NewUploader(sess), downloader: s3manager.NewDownloader(sess), } return provider, nil } func (p S3Provider) load() (*[]byte, error) { resp, err := p.lister.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: p.bucket, Prefix: p.prefix}) if err != nil { return nil, err } if len(resp.Contents) == 0 { return nil, nil } _, _, latest, err := p.minMax(resp.Contents) if err != nil { return nil, err } if latest == nil { return nil, nil } buff := &aws.WriteAtBuffer{} if _, err := p.downloader.Download(buff, &s3.GetObjectInput{Bucket: p.bucket, Key: latest.Key}); err != nil { return nil, err } content := buff.Bytes() return &content, nil } func (p S3Provider) store(encodedConfig *string) error { resp, err := p.lister.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: p.bucket, Prefix: p.prefix}) if err != nil { return err } minimum, maximum, _, err := p.minMax(resp.Contents) if err != nil { return err } revision := maximum + 1 key := fmt.Sprintf(s3ObjectNameFormat, p.name, revision) if len(*p.prefix) > 0 { key = fmt.Sprintf("%s/%s", *p.prefix, key) } reader := bytes.NewReader([]byte(*encodedConfig)) _, err = p.uploader.Upload( &s3manager.UploadInput{ Bucket: p.bucket, Key: &key, Body: reader, }) if err != nil { return err } if minimum > revision-p.numberOfRevisions { return nil } for i := minimum; i < revision-p.numberOfRevisions; i++ { key := fmt.Sprintf(s3ObjectNameFormat, p.name, i) if len(*p.prefix) > 0 { key = fmt.Sprintf("%s/%s", *p.prefix, key) } _, err := p.lister.DeleteObject(&s3.DeleteObjectInput{Bucket: p.bucket, Key: &key}) if err != nil { fmt.Println(err) } } return nil } func (p S3Provider) list() ([]ComposeRevision, error) { resp, err := p.lister.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: p.bucket, Prefix: p.prefix}) if err != nil { return nil, err } var revisions []ComposeRevision r, err := regexp.Compile(fmt.Sprintf(s3ObjectNamePattern, p.name)) if err != nil { return nil, err } for _, item := range resp.Contents { matches := r.FindStringSubmatch(*item.Key) if len(matches) == 0 { continue } revision, err := strconv.Atoi(matches[1]) if err != nil { return nil, err } revisions = append(revisions, ComposeRevision{revision, *item.LastModified}) } return revisions, nil } func (p S3Provider) get(revision int) (*[]byte, error) { key := fmt.Sprintf(s3ObjectNameFormat, p.name, revision) if len(*p.prefix) > 0 { key = fmt.Sprintf("%s/%s", *p.prefix, key) } buff := &aws.WriteAtBuffer{} _, err := p.downloader.Download(buff, &s3.GetObjectInput{ Bucket: p.bucket, Key: &key, }) if err != nil { return nil, err } content := buff.Bytes() return &content, nil } func (p S3Provider) minMax(objects []*s3.Object) (int, int, *s3.Object, error) { if len(objects) == 0 { return 0, 0, nil, nil } minimum, maximum := math.MaxInt, 0 var latest *s3.Object r, err := regexp.Compile(fmt.Sprintf(s3ObjectNamePattern, p.name)) if err != nil { return -1, -1, nil, err } for _, item := range objects { matches := r.FindStringSubmatch(*item.Key) if len(matches) == 0 { continue } revision, err := strconv.Atoi(matches[1]) if err != nil { return -1, -1, nil, err } if revision < minimum { minimum = revision } if revision > maximum { maximum = revision latest = item } } return minimum, maximum, latest, nil } ================================================ FILE: internal/provider/s3_test.go ================================================ package provider import ( "log" "testing" cfg "github.com/seacrew/helm-compose/internal/config" ) func TestS3List(t *testing.T) { config := &cfg.Storage{ Name: "test", NumberOfRevisions: 5, S3Bucket: "helm-compose", S3Prefix: "test", S3Region: "eu-central-1", } provider, err := newS3Provider(config) if err != nil { log.Fatal(err) } revisions, err := provider.list() if err != nil { log.Fatal(err) } log.Fatal(revisions) } ================================================ FILE: internal/util/colors.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package util import ( "fmt" "hash/fnv" "strconv" "github.com/jwalton/go-supportscolor" ) type ColorPrinter struct { colorFunc func(...interface{}) string } var PrintColors = true func NewColorPrinter(s string) *ColorPrinter { c := hashColor(s) return &ColorPrinter{ colorFunc: c, } } func (c ColorPrinter) Printf(format string, a ...any) { if PrintColors { fmt.Printf(c.colorFunc(format)+"\n", a...) return } fmt.Printf(format+"\n", a...) } var colorFuncs [](func(...interface{}) string) = [](func(...interface{}) string){ color("%s"), // fallback color("\033[1;32m%s\033[0m"), color("\033[1;33m%s\033[0m"), color("\033[1;34m%s\033[0m"), color("\033[1;35m%s\033[0m"), color("\033[1;36m%s\033[0m"), color("\033[1;90m%s\033[0m"), color("\033[1;92m%s\033[0m"), color("\033[1;93m%s\033[0m"), color("\033[1;94m%s\033[0m"), color("\033[1;95m%s\033[0m"), color("\033[1;96m%s\033[0m"), } func color(colorString string) func(...interface{}) string { sprint := func(args ...interface{}) string { return fmt.Sprintf(colorString, fmt.Sprint(args...)) } return sprint } func hashColor(s string) func(...interface{}) string { if !supportscolor.Stdout().SupportsColor { return colorFuncs[0] } h := fnv.New32a() h.Write([]byte(s)) hash := fmt.Sprint(h.Sum32()) for { subtotal := 0 for _, r := range hash { value, _ := strconv.Atoi(string(r)) subtotal += value } if subtotal < len(colorFuncs) { return colorFuncs[subtotal] } hash = fmt.Sprint(subtotal) } } ================================================ FILE: internal/util/encoding.go ================================================ /* Copyright 2016 The Kubernetes Authors All Rights Reserved Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package util import ( "bytes" "compress/gzip" "encoding/base64" "io/ioutil" c "github.com/seacrew/helm-compose/internal/config" "gopkg.in/yaml.v2" ) var b64 = base64.StdEncoding var magicGzip = []byte{0x1f, 0x8b, 0x08} // encodeComposeConfig encodes the config file returning a base64 encoded // gzipped string representation, or error. func EncodeComposeConfig(config *c.Config) (string, error) { b, err := yaml.Marshal(config) if err != nil { return "", err } var buf bytes.Buffer w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) if err != nil { return "", err } if _, err = w.Write(b); err != nil { return "", err } w.Close() return b64.EncodeToString(buf.Bytes()), nil } // decodeComposeConfig decodes the bytes of data into a compose // config. Data must contain a base64 encoded gzipped string of a // valid release, otherwise an error is returned. func DecodeComposeConfig(data string) (*c.Config, error) { // base64 decode string b, err := b64.DecodeString(data) if err != nil { return nil, err } r, err := gzip.NewReader(bytes.NewReader(b)) if err != nil { return nil, err } defer r.Close() b2, err := ioutil.ReadAll(r) if err != nil { return nil, err } b = b2 var config c.Config // unmarshal release object bytes if err := yaml.Unmarshal(b, &config); err != nil { return nil, err } return &config, nil } ================================================ FILE: internal/util/util.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package util import ( "fmt" "math" "os" "os/exec" "regexp" "strings" ) var ( re = regexp.MustCompile(`\$\{(.*?)\}`) ) func IsDebug() bool { return os.Getenv("HELM_DEBUG") == "true" } func DebugPrint(format string, a ...interface{}) { if IsDebug() { fmt.Printf(format+"\n", a...) } } func Execute(command string, args ...string) (string, error) { cmd := exec.Command(command, args...) output, err := cmd.CombinedOutput() if output == nil { return "", err } text := string(output) if len(text) > 5 && text[:6] == "Error:" { text = strings.TrimSpace(text[6:]) } return text, err } func ConvertJson(obj interface{}) interface{} { switch c := obj.(type) { case map[interface{}]interface{}: m := map[string]interface{}{} for k, v := range c { m[k.(string)] = ConvertJson(v) } return m case []interface{}: for k, v := range c { c[k] = ConvertJson(v) } case string: str := obj.(string) matches := re.FindStringSubmatch(str) for _, match := range matches { str = strings.Replace(str, "${"+match+"}", os.Getenv(match), 1) } return str } return obj } func MinMax(ints []int) (int, int) { if len(ints) == 0 { return 0, 0 } minimum, maximum := math.MaxInt, 0 for _, i := range ints { if i > maximum { maximum = i } if i < minimum { minimum = i } } return minimum, maximum } func NewBool(b bool) *bool { return &b } ================================================ FILE: main.go ================================================ /* Copyright © 2023 The Helm Compose Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import "github.com/seacrew/helm-compose/cmd" func main() { cmd.Execute() } ================================================ FILE: mkdocs.yaml ================================================ site_name: Helm Compose Documentation docs_dir: docs repo_url: https://github.com/seacrew/helm-compose nav: - Home: "index.md" - "Quick Start": "quick-start.md" - "Key Features and Use Cases": "key-features-and-use-cases.md" - "Compose File Reference": "compose-file-reference.md" - "Storage Providers": "storage-providers.md" - Commands: - "helm compose up": "commands/up.md" - "helm compose down": "commands/down.md" - "helm compose list": "commands/list.md" - "helm compose get": "commands/get.md" - "helm compose template": "commands/template.md" theme: name: material logo: https://user-images.githubusercontent.com/18513179/239762487-ec236277-f782-4da3-8a23-692ad017bb92.svg favicon: https://user-images.githubusercontent.com/18513179/212496536-76db8b48-fe7c-42a3-b851-da1f281e1ad6.svg palette: - media: "(prefers-color-scheme: light)" scheme: default primary: custom accent: custom toggle: icon: material/brightness-7 name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: custom accent: custom toggle: icon: material/brightness-4 name: Switch to light mode features: - navigation.sections - navigation.expand - navigation.path - navigation.indexes - navigation.footer - content.code.copy extra_css: - stylesheets/extra.css plugins: - search - autolinks - drawio_file - git-revision-date-localized markdown_extensions: - abbr - tables - pymdownx.highlight: anchor_linenums: true line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences extra: copyright: Copyright © 2023 Seacrew Authors social: - icon: fontawesome/brands/github link: https://github.com/seacrew version: provider: mike consent: title: Cookie consent description: >- We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better. ================================================ FILE: plugin.yaml ================================================ name: compose version: 1.3.0 usage: Compose is a helm plugin to define and manage multiple helm releases as single entity. command: $HELM_PLUGIN_DIR/bin/compose hooks: install: $HELM_PLUGIN_DIR/scripts/install.sh update: $HELM_PLUGIN_DIR/scripts/install.sh ================================================ FILE: scripts/install.sh ================================================ #!/usr/bin/env bash if [ "${HELM_DEBUG:-}" = "1" ] || [ "${HELM_DEBUG:-}" = "true" ] || [ -n "${HELM_SECRETS_DEBUG+x}" ]; then set -x fi PLUGIN_NAME="helm-compose" GITHUB_REPO="seacrew/helm-compose" [ -z "$HELM_BIN" ] && HELM_BIN=$(command -v helm) [ -z "$HELM_HOME" ] && HELM_HOME=$(helm env | grep 'HELM_DATA_HOME' | cut -d '=' -f2 | tr -d '"') # Convert HELM_BIN and HELM_PLUGIN_DIR to unix if cygpath is # available. This is the case when using MSYS2 or Cygwin # on Windows where helm returns a Windows path but we # need a Unix path if command -v cygpath >/dev/null 2>&1; then HELM_BIN="$(cygpath -u "${HELM_BIN}")" HELM_PLUGIN_DIR="$(cygpath -u "${HELM_PLUGIN_DIR}")" fi mkdir -p "$HELM_HOME" : "${HELM_PLUGIN_DIR:="$HELM_HOME/plugins/helm-compose"}" if [[ $SKIP_BIN_INSTALL == "1" ]]; then echo "Skipping binary install" exit fi # which mode is the common installer script running in SCRIPT_MODE="install" if [ "$1" = "-u" ]; then SCRIPT_MODE="update" fi # initArch discovers the architecture for this system. initArch() { ARCH=$(uname -m) case $ARCH in armv5*) ARCH="armv5";; armv6*) ARCH="armv6";; armv7*) ARCH="armv7";; aarch64) ARCH="arm64";; x86) ARCH="386";; x86_64) ARCH="amd64";; i686) ARCH="386";; i386) ARCH="386";; esac } # initOS discovers the operating system for this system. initOS() { OS=$(echo `uname`|tr '[:upper:]' '[:lower:]') case "$OS" in # Msys support msys*) OS='windows';; # Minimalist GNU for Windows mingw*) OS='windows';; darwin) OS='macos';; esac } # verifySupported checks that the os/arch combination is supported for # binary builds. verifySupported() { local supported="linux-amd64\nlinux-arm64\nfreebsd-amd64\nmacos-amd64\nmacos-arm64\nwindows-amd64" if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then echo "No prebuild binary for ${OS}-${ARCH}." exit 1 fi if ! type "curl" > /dev/null && ! type "wget" > /dev/null; then echo "Either curl or wget is required" exit 1 fi } # getDownloadURL checks the latest available version. getDownloadURL() { version=$(git -C "$HELM_PLUGIN_DIR" describe --tags --exact-match 2>/dev/null || :) if [ "$SCRIPT_MODE" = "install" ] && [ -n "$version" ]; then DOWNLOAD_URL="https://github.com/$GITHUB_REPO/releases/download/$version/helm-compose-$OS-$ARCH.tgz" else DOWNLOAD_URL="https://github.com/$GITHUB_REPO/releases/latest/download/helm-compose-$OS-$ARCH.tgz" fi } # downloadFile downloads the latest binary package and also the checksum # for that binary. downloadFile() { HELM_TMP="$(mktemp -d -t ${PLUGIN_NAME}-XXXXXX)" PLUGIN_TMP_FILE="${HELM_TMP}/${PLUGIN_NAME}.tgz" echo "Downloading $DOWNLOAD_URL" if command -v curl >/dev/null 2>&1; then curl -sSf -L "$DOWNLOAD_URL" >"$PLUGIN_TMP_FILE" elif command -v wget >/dev/null 2>&1; then wget -q -O - "$DOWNLOAD_URL" >"$PLUGIN_TMP_FILE" fi } # installFile verifies the SHA256 for the file, then unpacks and # installs it. installFile() { tar xzf "$PLUGIN_TMP_FILE" -C "$HELM_TMP" HELM_TMP_BIN="$HELM_TMP/compose/bin/compose" if [ "${OS}" = "windows" ]; then HELM_TMP_BIN="$HELM_TMP_BIN.exe" fi echo "Preparing to install into ${HELM_PLUGIN_DIR}" mkdir -p "$HELM_PLUGIN_DIR/bin" cp "$HELM_TMP_BIN" "$HELM_PLUGIN_DIR/bin" } # fail_trap is executed if an error occurs. fail_trap() { result=$? if [ "$result" != "0" ]; then echo "Failed to install $PLUGIN_NAME" echo "\tFor support, go to https://github.com/$GITHUB_REPO/helm-compose." fi exit $result } # testVersion tests the installed client to make sure it is working. testVersion() { echo "$PLUGIN_NAME installed into $HELM_PLUGIN_DIR" #"${HELM_PLUGIN_DIR}/bin/compose" -h } # Execution #Stop execution on any error trap "fail_trap" EXIT initArch initOS verifySupported getDownloadURL downloadFile installFile testVersion