diff --git a/.github/configs/cr.yaml b/.github/configs/cr.yaml new file mode 100644 index 0000000..612b839 --- /dev/null +++ b/.github/configs/cr.yaml @@ -0,0 +1,2 @@ +## Reference: https://github.com/helm/chart-releaser +index-path: "./index.yaml" \ No newline at end of file diff --git a/.github/configs/ct-install.yaml b/.github/configs/ct-install.yaml new file mode 100644 index 0000000..c16cd43 --- /dev/null +++ b/.github/configs/ct-install.yaml @@ -0,0 +1,13 @@ +## Reference: https://github.com/helm/chart-testing/blob/master/doc/ct_lint-and-install.md +# Don't add the 'debug' attribute, otherwise the workflow won't work anymore +# Only Used for the CT Lint Stage +remote: origin +target-branch: master +chart-dirs: + - helm +helm-extra-args: "--timeout 600s" +validate-chart-schema: false +validate-maintainers: true +validate-yaml: true +exclude-deprecated: true +excluded-charts: [] diff --git a/.github/configs/ct-lint.yaml b/.github/configs/ct-lint.yaml new file mode 100644 index 0000000..41d52d0 --- /dev/null +++ b/.github/configs/ct-lint.yaml @@ -0,0 +1,13 @@ +## Reference: https://github.com/helm/chart-testing/blob/master/doc/ct_lint-and-install.md +# Don't add the 'debug' attribute, otherwise the workflow won't work anymore +# Only Used for the CT Lint Stage +remote: origin +target-branch: main +chart-dirs: + - helm +helm-extra-args: "--timeout 600s" +validate-chart-schema: false +validate-maintainers: true +validate-yaml: true +exclude-deprecated: true +excluded-charts: [] diff --git a/.github/configs/lintconf.yaml b/.github/configs/lintconf.yaml new file mode 100644 index 0000000..f52bab7 --- /dev/null +++ b/.github/configs/lintconf.yaml @@ -0,0 +1,42 @@ +--- +rules: + braces: + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 + brackets: + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 + colons: + max-spaces-before: 0 + max-spaces-after: 1 + commas: + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + comments: + require-starting-space: true + min-spaces-from-content: 1 + document-end: disable + document-start: disable # No --- to start a file + empty-lines: + max: 2 + max-start: 0 + max-end: 0 + hyphens: + max-spaces-after: 1 + indentation: + spaces: consistent + indent-sequences: whatever # - list indentation will handle both indentation and without + check-multi-line-strings: false + key-duplicates: enable + line-length: disable # Lines can be any length + new-line-at-end-of-file: enable + new-lines: + type: unix + trailing-spaces: enable + truthy: + level: warning \ No newline at end of file diff --git a/.github/workflows/chart-lint-and-test.yml b/.github/workflows/chart-lint-and-test.yml new file mode 100644 index 0000000..bc6c6bb --- /dev/null +++ b/.github/workflows/chart-lint-and-test.yml @@ -0,0 +1,78 @@ +name: ct-linting-and-testing +on: + pull_request: + paths: + - helm/** + - "!helm/pulumi-operator/README.md" + - "!helm/pulumi-operator/README.md.gotmpl" + +permissions: read-all + +jobs: + chart-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3.5 + with: + version: v3.6.3 + + - name: Set up python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: 3.7 + + - name: Run Trivy vulnerability scanner in IaC mode + uses: aquasecurity/trivy-action@b2933f565dbc598b29947660e66259e3c7bc8561 # 0.20.0 + with: + scan-type: 'config' + hide-progress: false + format: 'sarif' + scan-ref: 'helm' + output: 'trivy-results.sarif' + limit-severities-for-sarif: true + exit-code: '0' + ignore-unfixed: true + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@ccf74c947955fd1cf117aef6a0e4e66191ef6f61 # v3.25.4 + with: + sarif_file: 'trivy-results.sarif' + + - name: Setup Chart Linting + id: lint + uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1 + + - name: List changed charts + id: list-changed + run: | + ## If executed with debug this won't work anymore. + changed=$(ct --config ./.github/configs/ct-lint.yaml list-changed) + charts=$(echo "$changed" | tr '\n' ' ' | xargs) + if [[ -n "$changed" ]]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "changed_charts=$charts" >> "$GITHUB_OUTPUT" + fi + + - name: Run Artifact Hub lint + run: | + curl -s https://api.github.com/repos/artifacthub/hub/releases/latest | grep -E 'browser_download_url' | grep linux_amd64.tar.gz\" | grep -Eo 'https://[^\"]*' | xargs wget -O - | tar -xz + ./ah lint -p helm || exit 1 + rm -f ./ah + + - name: Run chart-testing (lint) + run: ct lint --debug --config ./.github/configs/ct-lint.yaml --lint-conf ./.github/configs/lintconf.yaml + + - name: Create kind cluster + uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0 + if: steps.list-changed.outputs.changed == 'true' + + - name: Run chart-testing (install) + run: ct install --config ./.github/configs/ct-lint.yaml + if: steps.list-changed.outputs.changed == 'true' diff --git a/.github/workflows/chart-publish.yaml b/.github/workflows/chart-publish.yaml new file mode 100644 index 0000000..b87d95d --- /dev/null +++ b/.github/workflows/chart-publish.yaml @@ -0,0 +1,78 @@ +--- +name: chart-publish +on: + push: + tags: + - v*.*.* # e.g. v2.0.0 + +env: + HELM_DOCS_VERSION: "1.11.0" + +permissions: read-all + +jobs: + publish: + permissions: + contents: write # for helm/chart-releaser-action to push chart release and create a release + packages: write # for helm/chart-releaser-action to push chart release and create a release + id-token: write # for helm/chart-releaser-action to push chart release and create a release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + with: + fetch-depth: 0 + + - name: Install Helm + uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3.5 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: install helm-docs + run: | + cd /tmp + wget https://github.com/norwoodj/helm-docs/releases/download/v${{env.HELM_DOCS_VERSION}}/helm-docs_${{env.HELM_DOCS_VERSION}}_Linux_x86_64.tar.gz + tar -xvf helm-docs_${{env.HELM_DOCS_VERSION}}_Linux_x86_64.tar.gz + sudo mv helm-docs /usr/local/sbin + + - name: run helm-docs + run: | + helm-docs -t README.md.gotmpl -o README.md -b for-the-badge + + - name: Login to GHCR + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + with: + registry: ghcr.io + username: ${ GITHUB_REPOSITORY_OWNER } + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run Artifact Hub lint + run: | + curl -s https://api.github.com/repos/artifacthub/hub/releases/latest | grep -E 'browser_download_url' | grep linux_amd64.tar.gz\" | grep -Eo 'https://[^\"]*' | xargs wget -O - | tar -xz + ./ah lint -p helm || exit 1 + rm -f ./ah + + - name: Run chart-releaser + uses: helm/chart-releaser-action@a917fd15b20e8b64b94d9158ad54cd6345335584 # v1.6.0 + with: + config: "./.github/configs/cr.yaml" + charts_dir: "helm" + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 + - name: Push chart to GHCR + env: + COSIGN_EXPERIMENTAL: 1 + run: | + shopt -s nullglob + for pkg in .cr-release-packages/*; do + if [ -z "${pkg:-}" ]; then + break + fi + helm push "${pkg}" oci://ghcr.io/pulumi/helm-charts |& tee .digest + cosign sign -y $(cat .digest | awk -F "[, ]+" '/Pushed/{print $NF}') + done diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..a3db89b --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,78 @@ +name: build-binary +on: + push: + branches: + - 'main' + tags: + - '*' + pull_request: + +permissions: read-all + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version: 1.22.x + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Tests the build + run: | + go build . + release: + permissions: + contents: write + id-token: write + packages: write + needs: build + runs-on: ubuntu-latest + if: success() && startsWith(github.ref, 'refs/tags/') + env: + DOCKER_CLI_EXPERIMENTAL: "enabled" + steps: + - name: Checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + fetch-depth: 0 + - name: Set up QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + - name: Docker Login + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Go + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version: 1.22.x + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 + - uses: anchore/sbom-action/download-syft@61119d458adab75f756bc0b9e4bde25725f86a7a # v0.17.2 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec118c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/dist +.idea +pulumi-esc-csi-provider diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..b54dfe0 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,115 @@ +version: 2 +project_name: pulumi-esc-csi-provider +snapshot: + name_template: '{{ .Tag }}-SNAPSHOT' +before: + hooks: + - go mod tidy +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + + +source: + enabled: true + +sboms: + - artifacts: archive + - id: source + artifacts: source + +checksum: + name_template: 'checksums.txt' + +signs: + - cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + certificate: '${artifact}.pem' + args: + - sign-blob + - '-y' + - '--output-certificate=${certificate}' + - '--bundle=${signature}' + - '${artifact}' + artifacts: all + output: true + +dockers: + - image_templates: [ "ghcr.io/dirien/{{ .ProjectName }}:{{ .Version }}-amd64" ] + dockerfile: Dockerfile + use: buildx + build_flag_templates: + - --platform=linux/amd64 + - --label=org.opencontainers.image.licenses=Apache-2.0 + - --label=org.opencontainers.image.description=Pulumi ESC CSI Provider + - --label=org.opencontainers.image.created={{.Date}} + - --label=org.opencontainers.image.name={{.ProjectName}} + - --label=org.opencontainers.image.revision={{.FullCommit}} + - --label=org.opencontainers.image.version={{.Version}} + - --label=org.opencontainers.image.source={{.GitURL}} + - image_templates: [ "ghcr.io/dirien/{{ .ProjectName }}:{{ .Version }}-arm64" ] + goarch: arm64 + dockerfile: Dockerfile + use: buildx + build_flag_templates: + - --platform=linux/arm64/v8 + - --label=org.opencontainers.image.licenses=Apache-2.0 + - --label=org.opencontainers.image.description=Pulumi ESC CSI Provider + - --label=org.opencontainers.image.created={{.Date}} + - --label=org.opencontainers.image.name={{.ProjectName}} + - --label=org.opencontainers.image.revision={{.FullCommit}} + - --label=org.opencontainers.image.version={{.Version}} + - --label=org.opencontainers.image.source={{.GitURL}} + +docker_manifests: + - name_template: 'ghcr.io/dirien/{{ .ProjectName }}:{{ .Version }}' + image_templates: + - 'ghcr.io/dirien/{{ .ProjectName }}:{{ .Version }}-amd64' + - 'ghcr.io/dirien/{{ .ProjectName }}:{{ .Version }}-arm64' + - name_template: 'ghcr.io/dirien/{{ .ProjectName }}:latest' + image_templates: + - 'ghcr.io/dirien/{{ .ProjectName }}:{{ .Version }}-amd64' + - 'ghcr.io/dirien/{{ .ProjectName }}:{{ .Version }}-arm64' + + +docker_signs: + - cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + artifacts: manifests + output: true + args: + - 'sign' + - '-y' + - '${artifact}' + +changelog: + sort: asc + use: github + filters: + exclude: + - '^test:' + - '^chore' + - Merge pull request + - Merge remote-tracking branch + - Merge branch + - go mod tidy + groups: + - title: 'New Features' + regexp: "^.*feat[(\\w)]*:+.*$" + order: 0 + - title: 'Bug fixes' + regexp: "^.*fix[(\\w)]*:+.*$" + order: 10 + - title: Other work + order: 999 diff --git a/Dockerfile b/Dockerfile index f2a7beb..38c5a45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,4 @@ -FROM golang:1.23-alpine AS builder -WORKDIR /usr/src/app -COPY go.mod go.sum ./ -RUN go mod download -COPY . . -RUN go build -o /usr/local/bin/app . - - -FROM alpine:3.20.3 -COPY --from=builder /usr/local/bin/app /usr/local/bin/secrets-store-csi-driver-provider-pulumi-esc - -CMD ["secrets-store-csi-driver-provider-pulumi-esc"] +# Dockerfile +FROM cgr.dev/chainguard/static +COPY pulumi-esc-csi-provider /usr/bin/pulumi-esc-csi-provider +ENTRYPOINT ["/usr/bin/pulumi-esc-csi-provider"] diff --git a/LICENSE b/LICENSE index a612ad9..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,373 +1,201 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. + 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. diff --git a/README.md b/README.md index 86e30cc..5baf8e5 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ ## Getting Started -### Deploy Dcrets Store CSI Driver using Helm +### Deploy Secret Store CSI Driver using Helm Secrets Store CSI Driver allows users to customize their installation via Helm. ```bash helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts -helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --namespace kube-system +helm repo update +helm upgrade -i csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --namespace kube-system ``` Running the above helm install command will install the Secrets Store CSI Driver on Linux nodes in the `kube-system` @@ -19,3 +20,8 @@ namespace. ```bash tilt up ``` + +### Deploy Pulumi ESC Secret Store CSI Driver - production + +See [helm/README.md](./helm/README.md) for instructions on how to deploy the Pulumi ESC Secret Store CSI Driver using Helm. + diff --git a/Tiltfile b/Tiltfile index bdb4f38..c153851 100644 --- a/Tiltfile +++ b/Tiltfile @@ -1,23 +1,23 @@ +load('ext://helm_remote', 'helm_remote') + +helm_remote('secrets-store-csi-driver', + repo_name='secrets-store-csi-driver', + repo_url='https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts') + + docker_build( - 'dirien/secrets-store-csi-driver-provider-pulumi-esc', + 'ghcr.io/dirien/secrets-store-csi-driver-provider-pulumi-esc', context='.', - dockerfile='./Dockerfile', + dockerfile='./Dockerfile.tilt', live_update=[ - sync('./pkg/', '/main.go'), + sync('./internal/', '/main.go'), ], ) -k8s_yaml( - 'deployment/pulumi-esc-csi-provider.yaml' -) +k8s_yaml(helm('./helm')) k8s_yaml( listdir('examples') ) -k8s_resource( - 'secrets-store-csi-driver-provider-pulumi-esc', - labels=['secrets-store-csi-driver-provider-pulumi-esc'] -) - tiltfile_path = config.main_path diff --git a/examples/secretproviderclass.yaml b/examples/secretproviderclass.yaml index 0f68540..a76696e 100644 --- a/examples/secretproviderclass.yaml +++ b/examples/secretproviderclass.yaml @@ -12,11 +12,8 @@ spec: environment: db authSecretName: pulumi-secret-provider-auth-credentials authSecretNamespace: default - objects: | - - objectName: postgres - #secretObjects: - #- secretName: example-provider-secret - # type: Opaque - # data: - # - objectName: postgres - # key: password + secrets: | + - secretPath: "/" + fileName: "replicas" + secretKey: postgres.replicas + diff --git a/go.mod b/go.mod index 7ec3b7e..454be8d 100644 --- a/go.mod +++ b/go.mod @@ -4,22 +4,23 @@ go 1.23.1 require ( github.com/go-playground/validator/v10 v10.22.1 - github.com/pulumi/esc-sdk/sdk v0.10.0 - google.golang.org/grpc v1.63.2 + github.com/pulumi/esc-sdk/sdk v0.10.4 + google.golang.org/grpc v1.68.1 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apimachinery v0.30.0 + k8s.io/apimachinery v0.31.3 k8s.io/client-go v0.30.0 - sigs.k8s.io/secrets-store-csi-driver v1.4.2 + sigs.k8s.io/secrets-store-csi-driver v1.4.7 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/swag v0.22.4 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -34,25 +35,24 @@ require ( 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/stretchr/testify v1.9.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ghodss/yaml.v1 v1.0.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/api v0.30.0 // indirect - k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // 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.3.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index e96e5f5..31376d6 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,23 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -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.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -22,25 +26,22 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -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/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -68,16 +69,17 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY 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 v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -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/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= -github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pulumi/esc-sdk/sdk v0.10.0 h1:tVZGVSVgSf/3UkKI3iC9E287eXw9VERvmdI4vN2BD4o= -github.com/pulumi/esc-sdk/sdk v0.10.0/go.mod h1:J6+8bCUJyLXvYOmTAc90/EhU1iUPr1Koo3NUnFzY78k= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pulumi/esc-sdk/sdk v0.10.4 h1:YOR61Kcvcml6j6gfPWNQaPxIxMb5xMwYsdGgep+6PZQ= +github.com/pulumi/esc-sdk/sdk v0.10.4/go.mod h1:J6+8bCUJyLXvYOmTAc90/EhU1iUPr1Koo3NUnFzY78k= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 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= @@ -89,74 +91,57 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.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/sync v0.0.0-20220722155255-886fb9371eb4/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -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/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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= @@ -172,21 +157,21 @@ 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.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= -k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= -k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= +k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/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/secrets-store-csi-driver v1.4.2 h1:y2ETKPoYcJAVIJAlja2RKE4/gHJ0EoCZf+CalKRs2DA= -sigs.k8s.io/secrets-store-csi-driver v1.4.2/go.mod h1:ZUdzEpDMuT6mtXzRUppfkSmyKSwVRNt0kYE92g2FGtE= +sigs.k8s.io/secrets-store-csi-driver v1.4.7 h1:AyuwmPTW2GoPD2RjyVD3OrH1J9cdPZx+0h2qJvzbGXs= +sigs.k8s.io/secrets-store-csi-driver v1.4.7/go.mod h1:0/wMVOv8qLx7YNVMGU+Sh7S4D6TH6GhyEpouo28OTUU= 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.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 0000000..fd8f3ca --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +name: pulumi-esc-csi-provider +description: A Helm chart for the Pulumi ESC CSI provider + +type: application + +version: 0.1.0 +appVersion: "0.1.0" diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 0000000..9a7ddcd --- /dev/null +++ b/helm/README.md @@ -0,0 +1,101 @@ +# Pulumi ESC Secret Store CSI Driver - Helm Chart + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=for-the-badge) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=for-the-badge) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=for-the-badge) + +## Description 📜 + +A Helm chart for the Pulumi ESC CSI provider + +## Usage (via OCI Registry) + +To install the chart using the OCI artifact, run: + +```bash +helm install pulumi-esc-csi-provider oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider --version 0.1.0 +``` + +After a few seconds, the `pulumi-esc-csi-provider` should be running. + +To install the chart in a specific namespace use following commands: + +```bash +kubectl create ns pulumi-esc-csi-provider +helm install pulumi-esc-csi-provider oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider --namespace kube-system +``` + +> **Tip**: List all releases using `helm list`, a release is a name used to track a specific deployment + +### Uninstalling the Chart 🗑️ + +To uninstall the `pulumi-esc-csi-provider` deployment: + +```bash +helm uninstall pulumi-esc-csi-provider --namespace kube-system +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| args[0] | string | `"-endpoint=/provider/pulumi.sock"` | | +| image.pullPolicy | string | `"Always"` | | +| image.repository | string | `"ghcr.io/dirien/secrets-store-csi-driver-provider-pulumi-esc"` | | +| image.tag | string | `"latest"` | | +| labels | object | `{}` | | +| livenessProbe.failureThreshold | int | `2` | | +| livenessProbe.httpGet.path | string | `"/healthz"` | | +| livenessProbe.httpGet.port | int | `8080` | | +| livenessProbe.httpGet.scheme | string | `"HTTP"` | | +| livenessProbe.initialDelaySeconds | int | `5` | | +| livenessProbe.periodSeconds | int | `5` | | +| livenessProbe.successThreshold | int | `1` | | +| livenessProbe.timeoutSeconds | int | `3` | | +| name | string | `"pulumi-esc-csi-provider"` | | +| namespace | string | `"kube-system"` | | +| nodeSelector | object | `{}` | | +| podLabels | object | `{}` | | +| providerVolume.hostPath | string | `"/etc/kubernetes/secrets-store-csi-providers"` | | +| providerVolume.mountPath | string | `"/provider"` | | +| readinessProbe.failureThreshold | int | `2` | | +| readinessProbe.httpGet.path | string | `"/readyz"` | | +| readinessProbe.httpGet.port | int | `8080` | | +| readinessProbe.httpGet.scheme | string | `"HTTP"` | | +| readinessProbe.initialDelaySeconds | int | `5` | | +| readinessProbe.periodSeconds | int | `5` | | +| readinessProbe.successThreshold | int | `1` | | +| readinessProbe.timeoutSeconds | int | `3` | | +| resources.limits.cpu | string | `"50m"` | | +| resources.limits.memory | string | `"100Mi"` | | +| resources.requests.cpu | string | `"50m"` | | +| resources.requests.memory | string | `"100Mi"` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `"pulumi-esc-csi-provider"` | | +| tolerations | list | `[]` | | + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +helm install pulumi-esc-csi-provider pulumi-esc-csi-provider/pulumi-esc-csi-provider --set image.tag=latest --namespace kube-system +``` + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +helm install pulumi-esc-csi-provider pulumi-esc-csi-provider/pulumi-esc-csi-provider -f values.yaml --namespace kube-system +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +## Contributing 🤝 + +### Contributing via GitHub + +Feel free to join. Checkout the [contributing guide](CONTRIBUTING.md) + +## License ⚖️ + +Apache License, Version 2.0 + diff --git a/helm/README.md.gotmpl b/helm/README.md.gotmpl new file mode 100644 index 0000000..4f5dc08 --- /dev/null +++ b/helm/README.md.gotmpl @@ -0,0 +1,66 @@ +# Pulumi ESC Secret Store CSI Driver - Helm Chart + +{{ template "chart.badgesSection" . }} + +## Description 📜 + +{{ template "chart.description" . }} + +## Usage (via OCI Registry) + +To install the chart using the OCI artifact, run: + +```bash +helm install pulumi-esc-csi-provider oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider --version {{ .Version }} +``` + +After a few seconds, the `pulumi-esc-csi-provider` should be running. + +To install the chart in a specific namespace use following commands: + +```bash +kubectl create ns pulumi-esc-csi-provider +helm install pulumi-esc-csi-provider oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider --namespace kube-system +``` + +> **Tip**: List all releases using `helm list`, a release is a name used to track a specific deployment + +### Uninstalling the Chart 🗑️ + +To uninstall the `pulumi-esc-csi-provider` deployment: + +```bash +helm uninstall pulumi-esc-csi-provider --namespace kube-system +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +{{ template "chart.valuesSection" . }} + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +helm install pulumi-esc-csi-provider pulumi-esc-csi-provider/pulumi-esc-csi-provider --set image.tag=latest --namespace kube-system +``` + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +helm install pulumi-esc-csi-provider pulumi-esc-csi-provider/pulumi-esc-csi-provider -f values.yaml --namespace kube-system +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +## Contributing 🤝 + +### Contributing via GitHub + +Feel free to join. Checkout the [contributing guide](CONTRIBUTING.md) + +## License ⚖️ + +Apache License, Version 2.0 + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.maintainersSection" . }} diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt new file mode 100644 index 0000000..5a3f680 --- /dev/null +++ b/helm/templates/NOTES.txt @@ -0,0 +1,44 @@ +## + +-- Pulumi ESC CSI Provider Helm Chart -- + + ███████████ ████ ███ ██████████ █████████ █████████ +░░███░░░░░███ ░░███ ░░░ ░░███░░░░░█ ███░░░░░███ ███░░░░░███ + ░███ ░███ █████ ████ ░███ █████ ████ █████████████ ████ ░███ █ ░ ░███ ░░░ ███ ░░░ + ░██████████ ░░███ ░███ ░███ ░░███ ░███ ░░███░░███░░███ ░░███ ░██████ ░░█████████ ░███ + ░███░░░░░░ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███░░█ ░░░░░░░░███░███ + ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░ █ ███ ░███░░███ ███ + █████ ░░████████ █████ ░░████████ █████░███ █████ █████ ██████████░░█████████ ░░█████████ +░░░░░ ░░░░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░ ░░░░░ ░░░░░░░░░░ ░░░░░░░░░ ░░░░░░░░░ + + +Pulumi ESC CSI Provider ({{ .Chart.Version }}) has been installed successfully! + +╭―― Thank you for installing Pulumi ESC CSI Provider! 👋 ―――――――――――――――――――――――――――――――――――――――――┤ +│ +│ Pulumi ESC CSI Provider enables Kubernetes to access secrets stored in Pulumi ESC 🔒 +│ +│ Current installation details: +│ • Namespace : {{ .Release.Namespace }} +│ • Name : {{ .Release.Name }} +│ • Image : {{ .Values.image.repository }}:{{ .Values.image.tag }} +│ +╰――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――┤ + +――― Helpful commands to get started 📝 ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――┤ + +→ Check if the CSI provider is running: +$ kubectl get pods -n {{ .Values.namespace }} -l app={{ .Values.name }} + +→ View CSI provider logs: +$ kubectl logs -n {{ .Values.namespace }} -l app={{ .Values.name }} + +→ Get your release status: +$ helm status {{ .Release.Name }} -n {{ .Release.Namespace }} + +→ Uninstall the provider: +$ helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} + +――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――┤ + +## diff --git a/helm/templates/daemonset.yaml b/helm/templates/daemonset.yaml new file mode 100644 index 0000000..4ed453f --- /dev/null +++ b/helm/templates/daemonset.yaml @@ -0,0 +1,103 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: {{ .Values.name }} + {{- with .Values.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ .Values.serviceAccount.name }} + namespace: {{ .Values.namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Values.serviceAccount.name }}-cr + namespace: {{ .Values.namespace }} + labels: + app: {{ .Values.name }} + {{- with .Values.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Values.serviceAccount.name }}-crb + namespace: {{ .Values.namespace }} + labels: + app: {{ .Values.name }} + {{- with .Values.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Values.serviceAccount.name }}-cr +subjects: +- kind: ServiceAccount + namespace: kube-system + name: {{ .Values.serviceAccount.name }} +--- +{{- end }} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app: {{ .Values.name }} + {{- with .Values.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ .Values.name }} + namespace: {{ .Values.namespace | default "kube-system" }} +spec: + selector: + matchLabels: + app: {{ .Values.name }} + template: + metadata: + labels: + app: {{ .Values.name }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + serviceAccountName: {{ .Values.serviceAccount.name }} + securityContext: {} + containers: + - name: {{ .Values.name }} + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + {{- with .Values.args }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: socket + mountPath: {{ .Values.providerVolume.mountPath }} + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + volumes: + - name: socket + hostPath: + path: {{ .Values.providerVolume.hostPath }} + type: DirectoryOrCreate + nodeSelector: + kubernetes.io/os: linux diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 0000000..72602f4 --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,57 @@ +name: pulumi-esc-csi-provider +namespace: kube-system +image: + repository: ghcr.io/dirien/secrets-store-csi-driver-provider-pulumi-esc + tag: latest + pullPolicy: Always + +args: +- "-endpoint=/provider/pulumi.sock" + +# Resource limits and requests +resources: + requests: + cpu: 50m # Minimum CPU needed + memory: 100Mi # Minimum memory needed + limits: + cpu: 50m # Maximum CPU allowed + memory: 100Mi # Maximum memory allowed + +# Volume configuration for provider socket +providerVolume: + mountPath: /provider + hostPath: /etc/kubernetes/secrets-store-csi-providers + +# Health check for container liveness +livenessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + failureThreshold: 2 + initialDelaySeconds: 5 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 3 + +# Health check for container readiness +readinessProbe: + httpGet: + path: /readyz + port: 8080 + scheme: HTTP + failureThreshold: 2 + initialDelaySeconds: 5 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 3 + +nodeSelector: {} +labels: {} +podLabels: {} +tolerations: [] + +serviceAccount: + create: true + name: pulumi-esc-csi-provider + annotations: {} diff --git a/pkg/auth/auth.go b/internal/auth/auth.go similarity index 100% rename from pkg/auth/auth.go rename to internal/auth/auth.go diff --git a/pkg/config/config.go b/internal/config/config.go similarity index 58% rename from pkg/config/config.go rename to internal/config/config.go index b061a96..6e847aa 100644 --- a/pkg/config/config.go +++ b/internal/config/config.go @@ -1,15 +1,13 @@ package config import ( - "errors" "fmt" - "io" - "log/slog" + "gopkg.in/yaml.v3" + "os" "reflect" "strings" "github.com/go-playground/validator/v10" - "gopkg.in/yaml.v3" ) type Config struct { @@ -17,7 +15,6 @@ type Config struct { Organization string `json:"organization" validate:"required"` Project string `json:"project" validate:"required"` Environment string `json:"environment" validate:"required"` - Path string `json:"secretsPath" validate:"required"` AuthSecretName string `json:"authSecretName" validate:"required"` AuthSecretNamespace string `json:"authSecretNamespace" validate:"required"` RawObjects *string `json:"objects"` @@ -27,13 +24,18 @@ type Config struct { CSIPodServiceAccountName string `json:"csi.storage.k8s.io/serviceAccount.name"` CSIEphemeral string `json:"csi.storage.k8s.io/ephemeral"` SecretProviderClass string `json:"secretProviderClass"` - parsedObjects []object + RawSecrets string `json:"secrets"` + Secrets []Secret validator validator.Validate + FilePermission os.FileMode + TargetPath string } -type object struct { - Name string `yaml:"objectName" validate:"required"` - Alias string `yaml:"objectAlias" validate:"excludes=/"` +type Secret struct { + FileName string `yaml:"fileName"` + SecretPath string `yaml:"secretPath"` + SecretKey string `yaml:"secretKey"` + Format string `yaml:"format" default:"plaintext"` } func NewValidator() *validator.Validate { @@ -59,53 +61,45 @@ func NewValidator() *validator.Validate { return validator } -func NewMountConfig(validator validator.Validate) *Config { +func NewMountConfig(validator validator.Validate, apiUrl, targetPath string) *Config { return &Config{ - Path: "/", - validator: validator, + TargetPath: targetPath, + validator: validator, + APIURL: apiUrl, } } -func (a *Config) Objects() ([]object, error) { - if a.parsedObjects != nil { - return a.parsedObjects, nil +func (a *Config) Validate() error { + if err := a.validator.Struct(a); err != nil { + return err } - if a.RawObjects == nil { - return nil, nil + secretsYaml := a.RawSecrets + if secretsYaml != "" { + err := yaml.Unmarshal([]byte(secretsYaml), &a.Secrets) + if err != nil { + return fmt.Errorf("failed to unmarshal secrets: %w", err) + } } - var objects []object - objectDecoder := yaml.NewDecoder(strings.NewReader(*a.RawObjects)) - objectDecoder.KnownFields(true) - // Decode returns io.EOF error when empty string is passed - // c.f. https://github.com/go-yaml/yaml/blob/v3.0.1/yaml.go#L123-L126 - if err := objectDecoder.Decode(&objects); err != nil && !errors.Is(err, io.EOF) { - return nil, err + if a.APIURL == "" { + return fmt.Errorf("apiUrl is required") } - a.parsedObjects = objects - return objects, nil -} - -func (a *Config) Validate() error { - if err := a.validator.Struct(a); err != nil { - return err + if a.Organization == "" { + return fmt.Errorf("organization is required") } - if a.APIURL == "" { - slog.Info("apiUrl is empty, using default value", "default", "https://api.pulumi.com/api/esc") - a.APIURL = "https://api.pulumi.com/api/esc" + if a.Project == "" { + return fmt.Errorf("project is required") } - objects, err := a.Objects() - if err != nil { - return NewConfigError("objects", err) + if a.Environment == "" { + return fmt.Errorf("environment is required") } - for i, object := range objects { - if err := a.validator.Struct(object); err != nil { - return NewConfigError("objects", fmt.Errorf("[%d]: %w", i, err)) - } + + if len(a.Secrets) == 0 { + return fmt.Errorf("secrets is required") } return nil diff --git a/pkg/provider/provider.go b/internal/provider/provider.go similarity index 86% rename from pkg/provider/provider.go rename to internal/provider/provider.go index 84d4660..9c74277 100644 --- a/pkg/provider/provider.go +++ b/internal/provider/provider.go @@ -6,8 +6,8 @@ import ( ) type PulumiClient struct { - escClient esc.EscClient - authCtx context.Context + EscClient esc.EscClient + AuthCtx context.Context project string environment string organization string @@ -24,8 +24,8 @@ func NewPulumiESCClient(accessToken, APIURL, project, environment, organization authCtx := esc.NewAuthContext(accessToken) escClient := esc.NewClient(configuration) return &PulumiClient{ - escClient: *escClient, - authCtx: authCtx, + EscClient: *escClient, + AuthCtx: authCtx, project: project, environment: environment, organization: organization, diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..0024516 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,240 @@ +package server + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/dirien/pulumi-esc-csi-provider/internal/provider" + "gopkg.in/yaml.v3" + "log/slog" + "net" + "os" + "strconv" + "strings" + "time" + + "github.com/dirien/pulumi-esc-csi-provider/internal/auth" + "github.com/dirien/pulumi-esc-csi-provider/internal/config" + "github.com/go-playground/validator/v10" + "google.golang.org/grpc" + "k8s.io/apimachinery/pkg/types" + pb "sigs.k8s.io/secrets-store-csi-driver/provider/v1alpha1" +) + +var ( + ErrorInvalidSecretProviderClass = "InvalidSecretProviderClass" + ErrorUnauthorized = "Unauthorized" + ErrorBadRequest = "BadRequest" + ErrorInterfaceType = "interface{} is not of type map[string]interface{}" + ErrSecretType = "can not handle secret value with type" + ErrUnableToGetValues = "unable to get value for key %s: %w" +) + +type PulumiESCProviderServer struct { + version string + apiUrl string + grpcServer *grpc.Server + listener net.Listener + endpoint string + auth auth.Auth + validator *validator.Validate +} + +type secretItem struct { + FileName string + Value []byte + Version string +} + +var _ pb.CSIDriverProviderServer = &PulumiESCProviderServer{} + +// NewCSIProviderServer returns a mock csi-provider grpc server +func NewCSIProviderServer(version, endpoint, apiUrl string, auth auth.Auth) *PulumiESCProviderServer { + server := grpc.NewServer(grpc.ConnectionTimeout(20 * time.Second)) + s := &PulumiESCProviderServer{ + version: version, + apiUrl: apiUrl, + grpcServer: server, + endpoint: endpoint, + auth: auth, + validator: config.NewValidator(), + } + pb.RegisterCSIDriverProviderServer(server, s) + return s +} + +func (p *PulumiESCProviderServer) Start() error { + var err error + p.listener, err = net.Listen("unix", p.endpoint) + if err != nil { + return err + } + go func() { + if err = p.grpcServer.Serve(p.listener); err != nil { + return + } + }() + return nil +} + +func (p *PulumiESCProviderServer) Stop() { + p.grpcServer.GracefulStop() +} + +// Mount implements provider csi-provider method +func (p *PulumiESCProviderServer) Mount(ctx context.Context, req *pb.MountRequest) (*pb.MountResponse, error) { + mountResponse := &pb.MountResponse{ + Error: &pb.Error{}, + } + + slog.Info("mount", "request", req) + + // parse request + mountConfig := config.NewMountConfig(*p.validator, p.apiUrl, req.TargetPath) + var filePermission os.FileMode + attributesDecoder := json.NewDecoder(strings.NewReader(req.GetAttributes())) + attributesDecoder.DisallowUnknownFields() + if err := attributesDecoder.Decode(&mountConfig); err != nil { + mountResponse.Error.Code = ErrorInvalidSecretProviderClass + return mountResponse, fmt.Errorf("failed to unmarshal parameters, error: %w", err) + } + if err := mountConfig.Validate(); err != nil { + mountResponse.Error.Code = ErrorInvalidSecretProviderClass + return mountResponse, fmt.Errorf("failed to validate parameters, error: %w", err) + } + + if err := json.Unmarshal([]byte(req.GetPermission()), &filePermission); err != nil { + return nil, fmt.Errorf("failed to unmarshal file permission, error: %w", err) + } + + // get credentials + kubeSecret := types.NamespacedName{ + Namespace: mountConfig.AuthSecretNamespace, + Name: mountConfig.AuthSecretName, + } + credentials, err := p.auth.TokenFromKubeSecret(ctx, kubeSecret) + if err != nil { + mountResponse.Error.Code = ErrorBadRequest + return mountResponse, fmt.Errorf("failed to get credentials, error: %w", err) + } + pulumiESCClint := provider.NewPulumiESCClient(credentials.Pat, mountConfig.APIURL, mountConfig.Project, mountConfig.Environment, mountConfig.Organization) + env, err := pulumiESCClint.EscClient.OpenEnvironment(pulumiESCClint.AuthCtx, mountConfig.Organization, mountConfig.Project, mountConfig.Environment) + if err != nil { + return nil, err + } + secretMap := make(map[string]*secretItem) + for _, secret := range mountConfig.Secrets { + val, _, err := pulumiESCClint.EscClient.ReadEnvironmentProperty(pulumiESCClint.AuthCtx, mountConfig.Organization, mountConfig.Project, mountConfig.Environment, env.GetId(), secret.SecretKey) + if err != nil { + return nil, err + } + + valueBytes, err := GetByteValue(val.GetValue()) + if err != nil { + return nil, fmt.Errorf(ErrUnableToGetValues, secret.SecretKey, err) + } + + if isJSON(string(valueBytes)) { + if secret.Format == "yaml" { + valueBytes, err = toYAML(string(valueBytes)) + if err != nil { + return nil, err + } + } else if secret.Format == "json" { + valueBytes, err = formatJSON(string(valueBytes)) + if err != nil { + return nil, err + } + } + } + + secretMap[env.Id] = &secretItem{ + FileName: secret.FileName, + Value: valueBytes, + Version: fmt.Sprintf("%s-%s-%s", env.Id, mountConfig.TargetPath, secret.SecretKey), + } + } + + var files []*pb.File + var ov []*pb.ObjectVersion + + for _, value := range secretMap { + files = append(files, &pb.File{Path: value.FileName, Mode: int32(mountConfig.FilePermission), Contents: value.Value}) + ov = append(ov, &pb.ObjectVersion{Id: value.FileName, Version: value.Version}) + slog.Info(fmt.Sprintf("secret added to mount response, directory: %v, file: %v", mountConfig.TargetPath, value.FileName)) + } + + return &pb.MountResponse{ + ObjectVersion: ov, + Files: files, + }, nil +} + +func GetByteValue(v any) ([]byte, error) { + switch t := v.(type) { + case string: + return []byte(t), nil + case map[string]any: + return json.Marshal(t) + case []string: + return []byte(strings.Join(t, "\n")), nil + case json.RawMessage: + return t, nil + case []byte: + return t, nil + // also covers int and float32 due to json.Marshal + case float64: + return []byte(strconv.FormatFloat(t, 'f', -1, 64)), nil + case json.Number: + return []byte(t.String()), nil + case []any: + return json.Marshal(t) + case bool: + return []byte(strconv.FormatBool(t)), nil + case nil: + return []byte(nil), nil + default: + return nil, fmt.Errorf("%w: %T", errors.New(ErrSecretType), t) + } +} + +func isJSON(str string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(str), &js) == nil +} + +// formatJSON formats a JSON string into indented JSON +func formatJSON(jsonStr string) ([]byte, error) { + var parsedJSON map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &parsedJSON); err != nil { + return nil, err + } + formattedJSON, err := json.MarshalIndent(parsedJSON, "", " ") + if err != nil { + return nil, err + } + return formattedJSON, nil +} + +// toYAML converts a JSON string to YAML +func toYAML(jsonStr string) ([]byte, error) { + var parsedJSON map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &parsedJSON); err != nil { + return nil, err + } + yamlData, err := yaml.Marshal(parsedJSON) + if err != nil { + return nil, err + } + return yamlData, nil +} + +// Version implements provider csi-provider method +func (p *PulumiESCProviderServer) Version(ctx context.Context, req *pb.VersionRequest) (*pb.VersionResponse, error) { + return &pb.VersionResponse{ + Version: "pb", + RuntimeName: "secrets-store-csi-driver-provider-pulumi-esc", + RuntimeVersion: p.version, + }, nil +} diff --git a/main.go b/main.go index f5fff6c..caf29c9 100644 --- a/main.go +++ b/main.go @@ -4,50 +4,104 @@ import ( "context" "flag" "fmt" - "github.com/dirien/pulumi-esc-csi-provider/pkg/provider" + "github.com/dirien/pulumi-esc-csi-provider/internal/auth" + "github.com/dirien/pulumi-esc-csi-provider/internal/server" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "log" + "net/http" "os" "os/signal" + "strings" "syscall" - - "github.com/dirien/pulumi-esc-csi-provider/pkg/auth" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" ) +const figletStr = ` + _____ _ _ _ _ _______ _____ _______ _______ _______ _______ _______ _____ _____ ______ _____ _ _ _____ ______ _______ ______ + |_____] | | | | | | | | | |______ |______ | | |______ | |_____] |_____/ | | \ / | | \ |______ |_____/ + | |_____| |_____ |_____| | | | __|__ |______ ______| |_____ |_____ ______| __|__ | | \_ |_____| \/ __|__ |_____/ |______ | \_` + var ( - runtimeVersion = "0.1.0" - versionFlag = flag.Bool("version", false, "print version information") + version string + commit string + date string + versionFlag = flag.Bool("version", false, "print version information") + apiUrl = flag.String("api-url", "https://api.pulumi.com/api/esc", "Pulumi ESC API URL") + endpoint = flag.String("endpoint", "/tmp/pulumi.sock", "path to the socket file") + healthPort = flag.String("health-port", "8080", "port for HTTP health check") ) func main() { flag.Parse() if *versionFlag { - fmt.Println(runtimeVersion) + fmt.Println(figletStr) + fmt.Printf("Version: %s\nCommit: %s\nDate: %s\n", version, commit, date) os.Exit(0) } + var err error + if !strings.HasPrefix(*endpoint, "@") { + err := os.Remove(*endpoint) + if err != nil && !os.IsNotExist(err) { + log.Fatalf("failed to delete the socket file: %v", err) + } + } - socketPath := "/etc/kubernetes/secrets-store-csi-providers/pulumi.sock" - _ = os.MkdirAll("/etc/kubernetes/secrets-store-csi-providers", 0755) - _ = os.Remove(socketPath) - - kubeConfig, err := rest.InClusterConfig() + config, err := rest.InClusterConfig() if err != nil { - panic(fmt.Errorf("unable to get kubeconfig: %v", err)) + log.Fatalf("unable to get kubeconfig: %v", err) } - kubeClient := kubernetes.NewForConfigOrDie(kubeConfig) - auth := auth.NewAuth(kubeClient) - provider := provider.NewCSIProviderServer(runtimeVersion, socketPath, auth) - defer provider.Stop() + kubeClient := kubernetes.NewForConfigOrDie(config) - if err := provider.Start(); err != nil { + authentication := auth.NewAuth(kubeClient) + healthCheckErrorCh := startHealthCheck() + csiProviderServer := server.NewCSIProviderServer(version, *endpoint, *apiUrl, authentication) + defer csiProviderServer.Stop() + if err := csiProviderServer.Start(); err != nil { panic(fmt.Errorf("unable to start server: %v", err)) } - log.Printf("server started at: %s\n", socketPath) + log.Printf("server started at: %s\n", *endpoint) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() + if err := <-healthCheckErrorCh; err != nil { + log.Fatalf("health check error: %v", err) + } <-ctx.Done() log.Println("shutting down server") } + +func startHealthCheck() chan error { + mux := http.NewServeMux() + ms := http.Server{ + Addr: fmt.Sprintf(":%s", *healthPort), + Handler: mux, + } + + errorCh := make(chan error) + + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + log.Printf("Initializing health check %+v", *healthPort) + + go func() { + defer func() { + err := ms.Shutdown(context.Background()) + if err != nil { + log.Printf("error shutting down health handler: %+v", err) + } + }() + + select { + case errorCh <- ms.ListenAndServe(): + default: + } + }() + + return errorCh +} diff --git a/pkg/config/error.go b/pkg/config/error.go deleted file mode 100644 index 0d9e4d5..0000000 --- a/pkg/config/error.go +++ /dev/null @@ -1,25 +0,0 @@ -package config - -type ConfigError struct { - Path string - Err error -} - -func NewConfigError(path string, err error) *ConfigError { - return &ConfigError{ - Path: path, - Err: err, - } -} - -func (e *ConfigError) Error() string { - child := e.Err - if child, ok := child.(*ConfigError); ok { - return e.Path + "." + child.Error() - } - return e.Path + ": " + child.Error() -} - -func (e *ConfigError) Unwrap() error { - return e.Err -} diff --git a/pkg/provider/server.go b/pkg/provider/server.go deleted file mode 100644 index 5f2ce59..0000000 --- a/pkg/provider/server.go +++ /dev/null @@ -1,245 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "log" - "log/slog" - "net" - "os" - "strconv" - "strings" - - "github.com/dirien/pulumi-esc-csi-provider/pkg/auth" - "github.com/dirien/pulumi-esc-csi-provider/pkg/config" - "github.com/go-playground/validator/v10" - esc "github.com/pulumi/esc-sdk/sdk/go" - "google.golang.org/grpc" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/secrets-store-csi-driver/provider/v1alpha1" -) - -var ( - ErrorInvalidSecretProviderClass = "InvalidSecretProviderClass" - ErrorUnauthorized = "Unauthorized" - ErrorBadRequest = "BadRequest" - ErrorInterfaceType = "interface{} is not of type map[string]interface{}" - ErrSecretType = "can not handle secret value with type" - ErrUnableToGetValues = "unable to get value for key %s: %w" -) - -type CSIProviderServer struct { - version string - grpcServer *grpc.Server - listener net.Listener - socketPath string - auth auth.Auth - validator *validator.Validate -} - -var _ v1alpha1.CSIDriverProviderServer = &CSIProviderServer{} - -// NewCSIProviderServer returns a mock csi-provider grpc server -func NewCSIProviderServer(version, socketPath string, auth auth.Auth) *CSIProviderServer { - server := grpc.NewServer() - s := &CSIProviderServer{ - version: version, - grpcServer: server, - socketPath: socketPath, - auth: auth, - validator: config.NewValidator(), - } - v1alpha1.RegisterCSIDriverProviderServer(server, s) - return s -} - -func (m *CSIProviderServer) Start() error { - var err error - m.listener, err = net.Listen("unix", m.socketPath) - if err != nil { - return err - } - go func() { - if err = m.grpcServer.Serve(m.listener); err != nil { - return - } - }() - return nil -} - -func (m *CSIProviderServer) Stop() { - m.grpcServer.GracefulStop() -} - -// Mount implements provider csi-provider method -func (s *CSIProviderServer) Mount(ctx context.Context, req *v1alpha1.MountRequest) (*v1alpha1.MountResponse, error) { - mountResponse := &v1alpha1.MountResponse{ - Error: &v1alpha1.Error{}, - } - - slog.Info("mount", "request", req) - - // parse request - mountConfig := config.NewMountConfig(*s.validator) - var secret map[string]string - var filePermission os.FileMode - attributesDecoder := json.NewDecoder(strings.NewReader(req.GetAttributes())) - attributesDecoder.DisallowUnknownFields() - if err := attributesDecoder.Decode(&mountConfig); err != nil { - mountResponse.Error.Code = ErrorInvalidSecretProviderClass - return mountResponse, fmt.Errorf("failed to unmarshal parameters, error: %w", err) - } - if err := mountConfig.Validate(); err != nil { - mountResponse.Error.Code = ErrorInvalidSecretProviderClass - return mountResponse, fmt.Errorf("failed to validate parameters, error: %w", err) - } - if err := json.Unmarshal([]byte(req.GetSecrets()), &secret); err != nil { - return nil, fmt.Errorf("failed to unmarshal secrets, error: %w", err) - } - if err := json.Unmarshal([]byte(req.GetPermission()), &filePermission); err != nil { - return nil, fmt.Errorf("failed to unmarshal file permission, error: %w", err) - } - objects, err := mountConfig.Objects() - if err != nil { - mountResponse.Error.Code = ErrorInvalidSecretProviderClass - return mountResponse, fmt.Errorf("failed to get objects, error: %w", err) - } - if mountConfig.RawObjects != nil && len(objects) == 0 { - mountResponse.ObjectVersion = []*v1alpha1.ObjectVersion{ - { - Id: "NO_SECRETS", - Version: "0", - }, - } - return mountResponse, nil - } - // get credentials - kubeSecret := types.NamespacedName{ - Namespace: mountConfig.AuthSecretNamespace, - Name: mountConfig.AuthSecretName, - } - credentials, err := s.auth.TokenFromKubeSecret(ctx, kubeSecret) - if err != nil { - mountResponse.Error.Code = ErrorBadRequest - return mountResponse, fmt.Errorf("failed to get credentials, error: %w", err) - } - configuration := esc.NewConfiguration() - configuration.UserAgent = "external-secrets-operator" - configuration.Servers = esc.ServerConfigurations{ - esc.ServerConfiguration{ - URL: mountConfig.APIURL, - }, - } - authCtx := esc.NewAuthContext(credentials.Pat) - escClient := esc.NewClient(configuration) - env, err := escClient.OpenEnvironment(authCtx, mountConfig.Organization, mountConfig.Project, mountConfig.Environment) - if err != nil { - return nil, err - } - - // store secrets - var objectVersions []*v1alpha1.ObjectVersion - var files []*v1alpha1.File - - fmt.Println("objects") - fmt.Println(objects) - fmt.Println("-----------------") - for _, object := range objects { - fmt.Println("inside ..-.---") - fmt.Println(object) - - _, values, err := escClient.OpenAndReadEnvironment(authCtx, mountConfig.Organization, mountConfig.Project, mountConfig.Environment) - if err != nil { - log.Fatalf("Failed to open and read environment: %v", err) - } - if err != nil { - mountResponse.Error.Code = ErrorBadRequest - return mountResponse, fmt.Errorf("failed to list secrets, error: %w", err) - } - - objectVersions = append(objectVersions, &v1alpha1.ObjectVersion{ - Id: object.Name, - Version: fmt.Sprint(env.GetId()), - }) - - jsonData, err := json.Marshal(values[object.Name]) - if err != nil { - return nil, err - } - fmt.Println(string(jsonData)) - - files = append(files, &v1alpha1.File{ - Path: func() string { - if object.Alias != "" { - return object.Alias - } else { - return object.Name - } - }(), - Mode: int32(filePermission), - Contents: jsonData, - }) - } - - mountResponse.ObjectVersion = objectVersions - mountResponse.Files = files - - return mountResponse, nil -} - -func GetMapFromInterface(i interface{}) (map[string][]byte, error) { - // Assert the interface{} to map[string]interface{} - m, ok := i.(map[string]interface{}) - if !ok { - return nil, errors.New(ErrorInterfaceType) - } - - // Create a new map to hold the result - result := make(map[string][]byte) - - // Iterate over the map and convert each value to []byte - for key, value := range m { - result[key], _ = GetByteValue(value) - } - - return result, nil -} - -func GetByteValue(v any) ([]byte, error) { - switch t := v.(type) { - case string: - return []byte(t), nil - case map[string]any: - return json.Marshal(t) - case []string: - return []byte(strings.Join(t, "\n")), nil - case json.RawMessage: - return t, nil - case []byte: - return t, nil - // also covers int and float32 due to json.Marshal - case float64: - return []byte(strconv.FormatFloat(t, 'f', -1, 64)), nil - case json.Number: - return []byte(t.String()), nil - case []any: - return json.Marshal(t) - case bool: - return []byte(strconv.FormatBool(t)), nil - case nil: - return []byte(nil), nil - default: - return nil, fmt.Errorf("%w: %T", errors.New(ErrSecretType), t) - } -} - -// Version implements provider csi-provider method -func (m *CSIProviderServer) Version(ctx context.Context, req *v1alpha1.VersionRequest) (*v1alpha1.VersionResponse, error) { - return &v1alpha1.VersionResponse{ - Version: "v1alpha1", - RuntimeName: "secrets-store-csi-driver-provider-pulumi-esc", - RuntimeVersion: m.version, - }, nil -}