├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ ├── pr.yml │ ├── release.yml │ └── updatecli.yml ├── .gitignore ├── .golangci.json ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── generate.go ├── go.mod ├── go.sum ├── hack └── crdgen.go ├── kustomization.yaml ├── main.go ├── manifests ├── deployment.yaml ├── example-helmchart.yaml ├── ns.yaml └── rbac.yaml ├── pkg ├── apis │ └── helm.cattle.io │ │ ├── v1 │ │ ├── doc.go │ │ ├── types.go │ │ ├── zz_generated_deepcopy.go │ │ ├── zz_generated_list_types.go │ │ └── zz_generated_register.go │ │ └── zz_generated_register.go ├── cmd │ └── cmd.go ├── codegen │ ├── cleanup │ │ └── main.go │ └── main.go ├── controllers │ ├── chart │ │ ├── chart.go │ │ └── chart_test.go │ ├── common │ │ ├── constants.go │ │ └── options.go │ └── controllers.go ├── crd │ └── crds.go ├── generated │ ├── clientset │ │ └── versioned │ │ │ ├── clientset.go │ │ │ ├── fake │ │ │ ├── clientset_generated.go │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ ├── scheme │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ └── typed │ │ │ └── helm.cattle.io │ │ │ └── v1 │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_helm.cattle.io_client.go │ │ │ ├── fake_helmchart.go │ │ │ └── fake_helmchartconfig.go │ │ │ ├── generated_expansion.go │ │ │ ├── helm.cattle.io_client.go │ │ │ ├── helmchart.go │ │ │ └── helmchartconfig.go │ └── controllers │ │ └── helm.cattle.io │ │ ├── factory.go │ │ ├── interface.go │ │ └── v1 │ │ ├── helmchart.go │ │ ├── helmchartconfig.go │ │ └── interface.go ├── remove │ └── handler.go └── version │ └── version.go ├── scripts ├── boilerplate.go.txt ├── build ├── e2e ├── package ├── test ├── validate └── version ├── test ├── framework │ ├── controller.go │ └── framework.go └── suite │ ├── helm_test.go │ └── zz_suite_test.go └── updatecli ├── updatecli.d └── updatecli.yml ├── validate.yml └── values.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | ./.dapper 2 | ./.cache 3 | ./.trash-cache 4 | ./dist 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for Docker Images 5 | - package-ecosystem: "docker" 6 | directory: "/" 7 | labels: 8 | - "kind/dependabot" 9 | reviewers: 10 | - "k3s-io/k3s-dev" 11 | schedule: 12 | interval: "weekly" 13 | 14 | - package-ecosystem: "docker" 15 | directory: "/package" 16 | labels: 17 | - "kind/dependabot" 18 | reviewers: 19 | - "k3s-io/k3s-dev" 20 | schedule: 21 | interval: "weekly" 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: {} 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | unit: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version-file: 'go.mod' 22 | 23 | - name: Unit Test 24 | run: go test ./pkg/... -cover -tags=test 25 | 26 | ci: 27 | runs-on: ubuntu-latest 28 | env: 29 | ARCH: amd64 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Build binary 35 | run: make build 36 | 37 | - name: Validate 38 | run: make validate 39 | 40 | - name: Package 41 | run: make package 42 | 43 | - name: E2E Test 44 | run: ./scripts/e2e -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | env: 9 | GHCR_REPO: ghcr.io/${{ github.repository_owner }}/helm-controller 10 | 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | env: 16 | ARCH: amd64 17 | permissions: 18 | contents: read 19 | packages: write 20 | id-token: write # needed for the Vault authentication 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up QEMU 26 | uses: docker/setup-qemu-action@v3 27 | 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v3 30 | 31 | - name: Build and package 32 | run: make 33 | 34 | - name: Run E2E Tests 35 | run: ./scripts/e2e 36 | 37 | - name: Upload binary 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: helm-controller-amd64 41 | path: ./dist/artifacts/* 42 | 43 | - name: Set DOCKERHUB_REPO 44 | run: | 45 | if [ "${{ github.repository_owner }}" == "k3s-io" ]; then 46 | echo "DOCKERHUB_REPO=rancher/helm-controller" >> $GITHUB_ENV 47 | else 48 | echo "DOCKERHUB_REPO=${{ secrets.DOCKER_USERNAME }}/helm-controller" >> $GITHUB_ENV 49 | fi 50 | 51 | - name: Docker source meta 52 | id: meta 53 | uses: docker/metadata-action@v5 54 | with: 55 | images: | 56 | ${{ env.DOCKERHUB_REPO }} 57 | ${{ env.GHCR_REPO }} 58 | 59 | - name: "Read Vault secrets" 60 | if: github.repository_owner == 'k3s-io' 61 | uses: rancher-eio/read-vault-secrets@main 62 | with: 63 | secrets: | 64 | secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ; 65 | secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_TOKEN ; 66 | 67 | - name: Login to DockerHub with Rancher Secrets 68 | if: github.repository_owner == 'k3s-io' 69 | uses: docker/login-action@v3 70 | with: 71 | username: ${{ env.DOCKER_USERNAME }} 72 | password: ${{ env.DOCKER_TOKEN }} 73 | 74 | # For forks, setup DockerHub login with GHA secrets 75 | - name: Login to DockerHub with GHA Secrets 76 | if: github.repository_owner != 'k3s-io' 77 | uses: docker/login-action@v3 78 | with: 79 | username: ${{ secrets.DOCKER_USERNAME }} 80 | password: ${{ secrets.DOCKER_TOKEN }} 81 | 82 | - name: Login to GitHub Container Registry 83 | uses: docker/login-action@v3 84 | with: 85 | registry: ghcr.io 86 | username: ${{ github.repository_owner }} 87 | password: ${{ secrets.GITHUB_TOKEN }} 88 | 89 | - name: Build and push by digest 90 | id: build 91 | uses: docker/build-push-action@v6 92 | with: 93 | platforms: linux/amd64 94 | context: . # Required to see the new binary file we just built 95 | outputs: type=image,"name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true 96 | target: production 97 | 98 | - name: Export digest 99 | run: | 100 | mkdir -p ${{ runner.temp }}/digests 101 | digest="${{ steps.build.outputs.digest }}" 102 | touch "${{ runner.temp }}/digests/${digest#sha256:}" 103 | 104 | - name: Upload digest 105 | uses: actions/upload-artifact@v4 106 | with: 107 | name: digests-amd64 108 | path: ${{ runner.temp }}/digests/* 109 | if-no-files-found: error 110 | retention-days: 1 111 | 112 | build-arm: 113 | runs-on: ubuntu-24.04-arm 114 | strategy: 115 | fail-fast: false 116 | matrix: 117 | platform: [arm64, arm/v7] 118 | permissions: 119 | contents: read 120 | packages: write 121 | id-token: write # needed for the Vault authentication 122 | steps: 123 | - name: Checkout 124 | uses: actions/checkout@v4 125 | 126 | - name: Set ARCH 127 | run: | 128 | if [ ${{ matrix.platform }} = 'arm/v7' ]; then 129 | echo "ARCH=arm" >> $GITHUB_ENV 130 | else 131 | echo "ARCH=${{ matrix.platform }}" >> $GITHUB_ENV 132 | fi 133 | 134 | - name: Set up QEMU 135 | uses: docker/setup-qemu-action@v3 136 | 137 | - name: Set up Docker Buildx 138 | uses: docker/setup-buildx-action@v3 139 | 140 | - name: Build binary 141 | run: | 142 | docker buildx build --platform linux/${{ matrix.platform }} --target binary --output type=local,dest=. . 143 | cp ./bin/helm-controller ./bin/helm-controller-${{ env.ARCH }} 144 | 145 | - name: Upload binary 146 | uses: actions/upload-artifact@v4 147 | with: 148 | name: helm-controller-${{ env.ARCH }} 149 | path: ./bin/helm-controller-${{ env.ARCH }} 150 | 151 | - name: Set DOCKERHUB_REPO 152 | run: | 153 | if [ "${{ github.repository_owner }}" == "k3s-io" ]; then 154 | echo "DOCKERHUB_REPO=rancher/helm-controller" >> $GITHUB_ENV 155 | else 156 | echo "DOCKERHUB_REPO=${{ secrets.DOCKER_USERNAME }}/helm-controller" >> $GITHUB_ENV 157 | fi 158 | 159 | - name: Docker source meta 160 | id: meta 161 | uses: docker/metadata-action@v5 162 | with: 163 | images: | 164 | ${{ env.DOCKERHUB_REPO }} 165 | ${{ env.GHCR_REPO }} 166 | 167 | - name: "Read Vault secrets" 168 | if: github.repository_owner == 'k3s-io' 169 | uses: rancher-eio/read-vault-secrets@main 170 | with: 171 | secrets: | 172 | secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ; 173 | secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_TOKEN ; 174 | 175 | - name: Login to DockerHub with Rancher Secrets 176 | if: github.repository_owner == 'k3s-io' 177 | uses: docker/login-action@v3 178 | with: 179 | username: ${{ env.DOCKER_USERNAME }} 180 | password: ${{ env.DOCKER_TOKEN }} 181 | 182 | # For forks, setup DockerHub login with GHA secrets 183 | - name: Login to DockerHub with GHA Secrets 184 | if: github.repository_owner != 'k3s-io' 185 | uses: docker/login-action@v3 186 | with: 187 | username: ${{ secrets.DOCKER_USERNAME }} 188 | password: ${{ secrets.DOCKER_TOKEN }} 189 | 190 | - name: Login to GitHub Container Registry 191 | uses: docker/login-action@v3 192 | with: 193 | registry: ghcr.io 194 | username: ${{ github.repository_owner }} 195 | password: ${{ secrets.GITHUB_TOKEN }} 196 | 197 | - name: Build and push by digest 198 | id: build 199 | uses: docker/build-push-action@v6 200 | with: 201 | platforms: linux/${{ matrix.platform }} 202 | context: . # Required to see the new binary file we just built 203 | outputs: type=image,"name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true 204 | target: production 205 | 206 | - name: Export digest 207 | run: | 208 | mkdir -p ${{ runner.temp }}/digests 209 | digest="${{ steps.build.outputs.digest }}" 210 | touch "${{ runner.temp }}/digests/${digest#sha256:}" 211 | 212 | - name: Upload digest 213 | uses: actions/upload-artifact@v4 214 | with: 215 | name: digests-${{ env.ARCH }} 216 | path: ${{ runner.temp }}/digests/* 217 | if-no-files-found: error 218 | retention-days: 1 219 | 220 | test: 221 | runs-on: ubuntu-latest 222 | steps: 223 | - name: Checkout 224 | uses: actions/checkout@v4 225 | 226 | - name: Setup Go 227 | uses: actions/setup-go@v5 228 | with: 229 | go-version-file: 'go.mod' 230 | 231 | - name: Test 232 | run: go test ./pkg/... -cover -tags=test 233 | 234 | binary-release: 235 | needs: [build, build-arm, test] 236 | runs-on: ubuntu-latest 237 | permissions: 238 | contents: write # Needed to update release with binary assets 239 | steps: 240 | - name: Checkout 241 | uses: actions/checkout@v4 242 | 243 | - name: Download binaries 244 | uses: actions/download-artifact@v4 245 | with: 246 | pattern: helm-controller-* 247 | path: ./dist/artifacts 248 | merge-multiple: true 249 | 250 | - name: Compute checksum for each binary 251 | run: | 252 | arch=("amd64" "arm64" "arm") 253 | cd dist/artifacts 254 | ls 255 | for a in "${arch[@]}"; do 256 | sha256sum helm-controller-"${a}" > sha256sum-"${a}".txt 257 | done 258 | 259 | - name: Upload binaries to release 260 | uses: softprops/action-gh-release@v2 261 | with: 262 | files: | 263 | dist/artifacts/helm-controller-* 264 | dist/artifacts/*.txt 265 | dist/artifacts/deploy* 266 | token: ${{ secrets.GITHUB_TOKEN }} 267 | 268 | merge-manifests: 269 | runs-on: ubuntu-latest 270 | needs: 271 | - build 272 | - build-arm 273 | permissions: 274 | contents: read 275 | packages: write 276 | id-token: write # needed for the Vault authentication 277 | steps: 278 | - name: Download digests 279 | uses: actions/download-artifact@v4 280 | with: 281 | path: ${{ runner.temp }}/digests 282 | pattern: digests-* 283 | merge-multiple: true 284 | 285 | - name: Set DOCKERHUB_REPO 286 | run: | 287 | if [ "${{ github.repository_owner }}" == "k3s-io" ]; then 288 | echo "DOCKERHUB_REPO=rancher/helm-controller" >> $GITHUB_ENV 289 | else 290 | echo "DOCKERHUB_REPO=${{ secrets.DOCKER_USERNAME }}/helm-controller" >> $GITHUB_ENV 291 | fi 292 | 293 | - name: "Read Vault secrets" 294 | if: github.repository_owner == 'k3s-io' 295 | uses: rancher-eio/read-vault-secrets@main 296 | with: 297 | secrets: | 298 | secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ; 299 | secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_TOKEN ; 300 | 301 | - name: Login to DockerHub with Rancher Secrets 302 | if: github.repository_owner == 'k3s-io' 303 | uses: docker/login-action@v3 304 | with: 305 | username: ${{ env.DOCKER_USERNAME }} 306 | password: ${{ env.DOCKER_TOKEN }} 307 | 308 | # For forks, setup DockerHub login with GHA secrets 309 | - name: Login to DockerHub with GHA Secrets 310 | if: github.repository_owner != 'k3s-io' 311 | uses: docker/login-action@v3 312 | with: 313 | username: ${{ secrets.DOCKER_USERNAME }} 314 | password: ${{ secrets.DOCKER_TOKEN }} 315 | 316 | - name: Login to GitHub Container Registry 317 | uses: docker/login-action@v3 318 | with: 319 | registry: ghcr.io 320 | username: ${{ github.repository_owner }} 321 | password: ${{ secrets.GITHUB_TOKEN }} 322 | 323 | - name: Set up Docker Buildx 324 | uses: docker/setup-buildx-action@v3 325 | 326 | - name: Docker meta 327 | id: meta 328 | uses: docker/metadata-action@v5 329 | with: 330 | images: | 331 | ${{ env.DOCKERHUB_REPO }} 332 | ${{ env.GHCR_REPO }} 333 | 334 | - name: Create manifest list and push 335 | working-directory: ${{ runner.temp }}/digests 336 | run: | 337 | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ 338 | $(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *) 339 | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ 340 | $(printf '${{ env.GHCR_REPO }}@sha256:%s ' *) 341 | 342 | - name: Inspect image 343 | run: | 344 | docker buildx imagetools inspect ${{ env.DOCKERHUB_REPO }}:${{ steps.meta.outputs.version }} 345 | docker buildx imagetools inspect ${{ env.GHCR_REPO }}:${{ steps.meta.outputs.version }} -------------------------------------------------------------------------------- /.github/workflows/updatecli.yml: -------------------------------------------------------------------------------- 1 | name: "Updatecli: Dependency Management" 2 | 3 | on: 4 | schedule: 5 | # Runs at 06 PM UTC 6 | - cron: '0 18 * * *' 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | issues: write 13 | pull-requests: write 14 | 15 | jobs: 16 | updatecli: 17 | runs-on: ubuntu-latest 18 | if: github.ref == 'refs/heads/master' 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Install Go 24 | uses: actions/setup-go@v4 25 | with: 26 | go-version: 'stable' 27 | 28 | - name: Install Updatecli 29 | uses: updatecli/updatecli-action@v2 30 | 31 | - name: Delete leftover UpdateCLI branches 32 | run: | 33 | gh pr list \ 34 | --search "is:closed is:pr head:updatecli_" \ 35 | --json headRefName \ 36 | --jq ".[].headRefName" | sort -u > closed_prs_branches.txt 37 | gh pr list \ 38 | --search "is:open is:pr head:updatecli_" \ 39 | --json headRefName \ 40 | --jq ".[].headRefName" | sort -u > open_prs_branches.txt 41 | for branch in $(comm -23 closed_prs_branches.txt open_prs_branches.txt); do 42 | if (git ls-remote --exit-code --heads origin "$branch"); then 43 | echo "Deleting leftover UpdateCLI branch - $branch"; 44 | git push origin --delete "$branch"; 45 | fi 46 | done 47 | env: 48 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | 50 | - name: Apply Updatecli 51 | # Never use '--debug' option, because it might leak the access tokens. 52 | run: "updatecli apply --clean --config ./updatecli/updatecli.d/ --values ./updatecli/values.yaml" 53 | env: 54 | UPDATECLI_GITHUB_ACTOR: ${{ github.actor }} 55 | UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.dapper 3 | /.idea 4 | /Dockerfile.dapper* 5 | !/Dockerfile.dapper 6 | /bin 7 | /dist 8 | helm-controller 9 | /.vscode -------------------------------------------------------------------------------- /.golangci.json: -------------------------------------------------------------------------------- 1 | { 2 | "linters": { 3 | "disable-all": true, 4 | "enable": [ 5 | "govet", 6 | "revive", 7 | "goimports", 8 | "misspell", 9 | "ineffassign", 10 | "gofmt" 11 | ] 12 | }, 13 | "run": { 14 | "exclude-dirs": [ 15 | "build", 16 | "hack", 17 | "manifests", 18 | "package", 19 | "scripts" 20 | ], 21 | "exclude-files": [ 22 | "/zz_generated_" 23 | ], 24 | "deadline": "5m" 25 | }, 26 | "issues": { 27 | "exclude-rules": [ 28 | { 29 | "linters": "typecheck", 30 | "text": "imported but not used" 31 | }, 32 | { 33 | "linters": "revive", 34 | "text": "should have comment" 35 | }, 36 | { 37 | "linters": "revive", 38 | "text": "unused-parameter" 39 | }, 40 | { 41 | "path": "_test\\.go$", 42 | "text": "dot-imports: should not use dot imports" 43 | } 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine3.21 AS builder 2 | 3 | RUN apk add --no-cache bash git gcc musl-dev 4 | 5 | WORKDIR /src 6 | COPY . . 7 | 8 | RUN --mount=type=cache,id=gomod,target=/go/pkg/mod \ 9 | --mount=type=cache,id=gobuild,target=/root/.cache/go-build \ 10 | ./scripts/build 11 | 12 | FROM scratch AS binary 13 | COPY --from=builder /src/bin/helm-controller /bin/ 14 | 15 | # Dev stage for package, testing, and validation 16 | FROM golang:1.23-alpine3.21 AS dev 17 | ARG ARCH 18 | ENV ARCH=$ARCH 19 | RUN apk add --no-cache bash git gcc musl-dev curl 20 | RUN GOPROXY=direct go install golang.org/x/tools/cmd/goimports@gopls/v0.18.1 21 | RUN if [ "${ARCH}" != "arm" ]; then \ 22 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.64.7; \ 23 | fi 24 | RUN if [ "${ARCH}" = "amd64" ]; then \ 25 | go install sigs.k8s.io/kustomize/kustomize/v5@v5.6.0; \ 26 | fi 27 | 28 | WORKDIR /src 29 | COPY go.mod go.sum pkg/ main.go ./ 30 | RUN go mod download 31 | COPY . . 32 | 33 | FROM dev AS package 34 | RUN ./scripts/package 35 | 36 | FROM scratch AS artifacts 37 | COPY --from=package /src/dist/artifacts /dist/artifacts 38 | 39 | FROM alpine:3.21 AS production 40 | COPY bin/helm-controller /usr/bin/ 41 | CMD ["helm-controller"] 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMAGE_NAME ?= helm-controller 2 | ARCH ?= amd64 3 | 4 | .DEFAULT_GOAL := ci 5 | .PHONY: build test validate package clean 6 | 7 | build: 8 | DOCKER_BUILDKIT=1 docker build \ 9 | --target binary \ 10 | --output type=local,dest=. . 11 | 12 | validate: 13 | docker build --target dev --build-arg ARCH=$(ARCH) -t $(IMAGE_NAME)-dev . 14 | docker run --rm $(IMAGE_NAME)-dev ./scripts/validate 15 | 16 | test: 17 | docker build --target dev --build-arg ARCH=$(ARCH) -t $(IMAGE_NAME)-dev . 18 | docker run --rm $(IMAGE_NAME)-dev ./scripts/test 19 | 20 | package: SHELL:=/bin/bash 21 | package: 22 | docker build --target artifacts --build-arg ARCH=$(ARCH) --output type=local,dest=. . 23 | source ./scripts/version && IMAGE=$${REPO}/helm-controller:$${TAG}; \ 24 | docker build -t $${IMAGE} --target production .; \ 25 | echo $${IMAGE} > bin/helm-controller-image.txt; \ 26 | echo Built $${IMAGE} 27 | 28 | clean: 29 | rm -rf bin dist 30 | 31 | ci: build validate test package 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | helm-controller 2 | ======== 3 | 4 | _NOTE: this repository has been recently (2020-10-06) moved out of the github.com/rancher org to github.com/k3s-io 5 | supporting the [acceptance of K3s as a CNCF sandbox project](https://github.com/cncf/toc/pull/447)_. 6 | 7 | --- 8 | 9 | A simple way to manage helm charts (v2 and v3) with Custom Resource Definitions in k8s. 10 | 11 | ## Manifests and Deploying 12 | The `./manifests` folder contains useful YAML manifests to use for deploying and developing the Helm Controller. This simple YAML deployment creates a HelmChart CRD + a Deployment using the `rancher/helm-controller` container. The YAML might need some modifications for your environment so read below for Namespaced vs Cluster deployments and how to use them properly. 13 | 14 | #### Namespaced Deploys 15 | Use the `deploy-namespaced.yaml` to create a namespace and add the Helm Controller and CRD to that namespace locking down the Helm Controller to only see changes to CRDs within that namespace. This is defaulted to `helm-controller` so update the YAML to your needs before running `kubectl create` 16 | 17 | #### Cluster Scoped Deploys 18 | If you'd like your helm controller to watch the entire cluster for HelmChart CRD changes use the `deploy-cluster-scoped.yaml` deploy manifest. By default it will add the helm-controller to the `kube-system` so update `metadata.namespace` for your needs. 19 | 20 | ## Uninstalling 21 | To remove the Helm Controller run `kubectl delete` and pass the deployment YAML used using to create the Deployment `-f` parameter. 22 | 23 | ## Developing and Building 24 | The Helm Controller is easy to get running locally, follow the instructions for your needs and requires a running k8s server + CRDs etc. When you have a working k8s cluster, you can use `./manifests/crd.yaml` to create the CRD and `./manifests/example-helmchart.yaml` which runs the `stable/traefik` helm chart. 25 | 26 | #### Locally 27 | Building and running natively will start a daemon which will watch a local k8s API. See Manifests section above about how to create the CRD and Objects using the provided manifests. 28 | 29 | ``` 30 | go build -o ./bin/helm-controller 31 | ./bin/helm-controller --kubeconfig $HOME/.kube/config 32 | ``` 33 | 34 | #### docker/k8s 35 | An easy way to get started with docker/k8s is to install docker for windows/mac and use the included k8s cluster. Once functioning you can easily build locally and get a docker container to pull the Helm Controller container and run it in k8s. Use `make` to launch a Linux container and build to create a container. Use the `./manifests/deploy-*.yaml` definitions to get it into your cluster and update `containers.image` to point to your locally image e.g. `image: rancher/helm-controller:dev` 36 | 37 | #### Options and Usage 38 | Use `./bin/helm-controller help` to get full usage details. The outside of a k8s Pod the most important options are `--kubeconfig` or `--masterurl` or it will not run. All options have corresponding ENV variables you could use. 39 | 40 | ## Testing 41 | `go test ./...` 42 | 43 | ## License 44 | Copyright (c) 2019 [Rancher Labs, Inc.](http://rancher.com) 45 | 46 | Licensed under the Apache License, Version 2.0 (the "License"); 47 | you may not use this file except in compliance with the License. 48 | You may obtain a copy of the License at 49 | 50 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 51 | 52 | Unless required by applicable law or agreed to in writing, software 53 | distributed under the License is distributed on an "AS IS" BASIS, 54 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 55 | See the License for the specific language governing permissions and 56 | limitations under the License. 57 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | //go:generate go run pkg/codegen/cleanup/main.go 2 | //go:generate go run pkg/codegen/main.go 3 | 4 | package main 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/k3s-io/helm-controller 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.22.2 7 | github.com/onsi/gomega v1.36.2 8 | github.com/rancher/lasso v0.0.0-20250109193533-00757eec2dbd 9 | github.com/rancher/wrangler/v3 v3.1.0 10 | github.com/sirupsen/logrus v1.9.3 11 | github.com/stretchr/testify v1.10.0 12 | github.com/urfave/cli/v2 v2.27.5 13 | k8s.io/api v0.31.1 14 | k8s.io/apimachinery v0.31.1 15 | k8s.io/client-go v0.31.1 16 | k8s.io/klog/v2 v2.130.1 17 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 18 | ) 19 | 20 | require ( 21 | github.com/beorn7/perks v1.0.1 // indirect 22 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 23 | github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect 24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 25 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 26 | github.com/evanphx/json-patch v5.9.0+incompatible // indirect 27 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 28 | github.com/ghodss/yaml v1.0.0 // indirect 29 | github.com/go-logr/logr v1.4.2 // indirect 30 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 31 | github.com/go-openapi/jsonreference v0.20.2 // indirect 32 | github.com/go-openapi/swag v0.23.0 // indirect 33 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 34 | github.com/gogo/protobuf v1.3.2 // indirect 35 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 36 | github.com/golang/protobuf v1.5.4 // indirect 37 | github.com/google/gnostic-models v0.6.8 // indirect 38 | github.com/google/go-cmp v0.6.0 // indirect 39 | github.com/google/gofuzz v1.2.0 // indirect 40 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect 41 | github.com/google/uuid v1.6.0 // indirect 42 | github.com/imdario/mergo v0.3.13 // indirect 43 | github.com/josharian/intern v1.0.0 // indirect 44 | github.com/json-iterator/go v1.1.12 // indirect 45 | github.com/mailru/easyjson v0.7.7 // indirect 46 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 47 | github.com/modern-go/reflect2 v1.0.2 // indirect 48 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 49 | github.com/pkg/errors v0.9.1 // indirect 50 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 51 | github.com/prometheus/client_golang v1.19.1 // indirect 52 | github.com/prometheus/client_model v0.6.1 // indirect 53 | github.com/prometheus/common v0.55.0 // indirect 54 | github.com/prometheus/procfs v0.15.1 // indirect 55 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 56 | github.com/spf13/pflag v1.0.5 // indirect 57 | github.com/x448/float16 v0.8.4 // indirect 58 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 59 | golang.org/x/mod v0.22.0 // indirect 60 | golang.org/x/net v0.33.0 // indirect 61 | golang.org/x/oauth2 v0.21.0 // indirect 62 | golang.org/x/sync v0.10.0 // indirect 63 | golang.org/x/sys v0.28.0 // indirect 64 | golang.org/x/term v0.27.0 // indirect 65 | golang.org/x/text v0.21.0 // indirect 66 | golang.org/x/time v0.5.0 // indirect 67 | golang.org/x/tools v0.28.0 // indirect 68 | google.golang.org/protobuf v1.36.1 // indirect 69 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 70 | gopkg.in/inf.v0 v0.9.1 // indirect 71 | gopkg.in/yaml.v2 v2.4.0 // indirect 72 | gopkg.in/yaml.v3 v3.0.1 // indirect 73 | k8s.io/apiextensions-apiserver v0.31.1 // indirect 74 | k8s.io/code-generator v0.31.1 // indirect 75 | k8s.io/gengo v0.0.0-20240826214909-a7b603a56eb7 // indirect 76 | k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect 77 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 78 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 79 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 80 | sigs.k8s.io/yaml v1.4.0 // indirect 81 | ) 82 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= 2 | github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 3 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= 4 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 5 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 8 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 9 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 10 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 11 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 12 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 13 | github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= 14 | github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 15 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 16 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 19 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 21 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 22 | github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= 23 | github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 24 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 25 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 26 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 27 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 28 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 29 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 30 | github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= 31 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 32 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 33 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 34 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 35 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 36 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 37 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 38 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 39 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 40 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 41 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 42 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 43 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 44 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 45 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 46 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 47 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 48 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 49 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 50 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 51 | github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= 52 | github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= 53 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 54 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 55 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 57 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 58 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 59 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 60 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 61 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 62 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 63 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= 64 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 65 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 66 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 67 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= 68 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= 69 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= 70 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 71 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 72 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 73 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 74 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 75 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 76 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 77 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 78 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 79 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 80 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 81 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 82 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 83 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 84 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 85 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 86 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 87 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 88 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 89 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 90 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 91 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 92 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 93 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 94 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 95 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 96 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 97 | github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= 98 | github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= 99 | github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= 100 | github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= 101 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 102 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 103 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 104 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 105 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 106 | github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 107 | github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 108 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 109 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 110 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 111 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 112 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 113 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 114 | github.com/rancher/lasso v0.0.0-20250109193533-00757eec2dbd h1:W4hWtKB8A2SSJlxkphvzwA+RkiOrgHg9Gytc78S0pvk= 115 | github.com/rancher/lasso v0.0.0-20250109193533-00757eec2dbd/go.mod h1:IxgTBO55lziYhTEETyVKiT8/B5Rg92qYiRmcIIYoPgI= 116 | github.com/rancher/wrangler/v3 v3.1.0 h1:8ETBnQOEcZaR6WBmUSysWW7WnERBOiNTMJr4Dj3UG/s= 117 | github.com/rancher/wrangler/v3 v3.1.0/go.mod h1:gUPHS1ANs2NyByfeERHwkGiQ1rlIa8BpTJZtNSgMlZw= 118 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 119 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 120 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 121 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 122 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 123 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 124 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 125 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 126 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 127 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 128 | github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= 129 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 130 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 131 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 132 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 133 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 134 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 135 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 136 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 137 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 138 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 139 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 140 | github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= 141 | github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= 142 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 143 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 144 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 145 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 146 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 147 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 148 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= 149 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= 150 | go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= 151 | go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= 152 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= 153 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= 154 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= 155 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= 156 | go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= 157 | go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= 158 | go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= 159 | go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= 160 | go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= 161 | go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= 162 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= 163 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 164 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 165 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 166 | go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= 167 | go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 168 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 169 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 170 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 171 | golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= 172 | golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 173 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 174 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 175 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 176 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 177 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 178 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 179 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 180 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 181 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 182 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 183 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 184 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 185 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 186 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 187 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 188 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 189 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 190 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 191 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 192 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 193 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 194 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 195 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 196 | golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= 197 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 198 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 199 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 200 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 201 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 202 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 203 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 204 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 205 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 206 | golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 207 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 208 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 209 | golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= 210 | golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= 211 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 212 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 213 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 214 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 215 | google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= 216 | google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= 217 | google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= 218 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= 219 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 220 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 221 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 222 | google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= 223 | google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 224 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 225 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 226 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 227 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 228 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 229 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 230 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 231 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 232 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 233 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 234 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 235 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 236 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 237 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 238 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 239 | k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= 240 | k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= 241 | k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= 242 | k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= 243 | k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= 244 | k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= 245 | k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= 246 | k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= 247 | k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= 248 | k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= 249 | k8s.io/code-generator v0.31.1 h1:GvkRZEP2g2UnB2QKT2Dgc/kYxIkDxCHENv2Q1itioVs= 250 | k8s.io/code-generator v0.31.1/go.mod h1:oL2ky46L48osNqqZAeOcWWy0S5BXj50vVdwOtTefqIs= 251 | k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= 252 | k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= 253 | k8s.io/gengo v0.0.0-20240826214909-a7b603a56eb7 h1:HCbtr1pVu/ElMcTTs18KdMtH5y6f7PQvrjh1QZj3qCI= 254 | k8s.io/gengo v0.0.0-20240826214909-a7b603a56eb7/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 255 | k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= 256 | k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= 257 | k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= 258 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 259 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 260 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= 261 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 262 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= 263 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 264 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= 265 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 266 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 267 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 268 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 269 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 270 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 271 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 272 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 273 | -------------------------------------------------------------------------------- /hack/crdgen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/k3s-io/helm-controller/pkg/crd" 7 | _ "github.com/k3s-io/helm-controller/pkg/generated/controllers/helm.cattle.io/v1" 8 | wcrd "github.com/rancher/wrangler/v3/pkg/crd" 9 | ) 10 | 11 | func main() { 12 | wcrd.Print(os.Stdout, crd.List()) 13 | } 14 | -------------------------------------------------------------------------------- /kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | sortOptions: 4 | order: fifo 5 | resources: 6 | - manifests/crd.yaml 7 | - manifests/ns.yaml 8 | - manifests/rbac.yaml 9 | - manifests/deployment.yaml 10 | images: 11 | - name: rancher/helm-controller 12 | newTag: v0.12.1 13 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "net/http/pprof" 5 | "os" 6 | 7 | "github.com/k3s-io/helm-controller/pkg/cmd" 8 | "github.com/k3s-io/helm-controller/pkg/version" 9 | _ "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiextensions.k8s.io" 10 | _ "github.com/rancher/wrangler/v3/pkg/generated/controllers/networking.k8s.io" 11 | "github.com/rancher/wrangler/v3/pkg/signals" 12 | "github.com/sirupsen/logrus" 13 | "github.com/urfave/cli/v2" 14 | ) 15 | 16 | var config = cmd.HelmController{} 17 | 18 | func main() { 19 | app := &cli.App{ 20 | Name: "helm-controller", 21 | Description: "A simple way to manage helm charts with CRDs in K8s.", 22 | Version: version.FriendlyVersion(), 23 | Action: func(app *cli.Context) error { 24 | return config.Run(app) 25 | }, 26 | Flags: []cli.Flag{ 27 | &cli.StringFlag{ 28 | Name: "controller-name", 29 | Value: "helm-controller", 30 | Usage: "Unique name to identify this controller that is added to all HelmCharts tracked by this controller", 31 | EnvVars: []string{"CONTROLLER_NAME"}, 32 | Destination: &config.ControllerName, 33 | }, 34 | &cli.BoolFlag{ 35 | Name: "debug", 36 | Usage: "Turn on debug logging", 37 | Destination: &config.Debug, 38 | }, 39 | &cli.IntFlag{ 40 | Name: "debug-level", 41 | Usage: "If debugging is enabled, set klog -v=X", 42 | Destination: &config.DebugLevel, 43 | }, 44 | &cli.StringFlag{ 45 | Name: "default-job-image", 46 | Usage: "Default image to use by jobs managing helm charts", 47 | EnvVars: []string{"DEFAULT_JOB_IMAGE"}, 48 | Destination: &config.DefaultJobImage, 49 | }, 50 | &cli.StringFlag{ 51 | Name: "job-cluster-role", 52 | Value: "cluster-admin", 53 | Usage: "Name of the cluster role to use for jobs created to manage helm charts", 54 | EnvVars: []string{"JOB_CLUSTER_ROLE"}, 55 | Destination: &config.JobClusterRole, 56 | }, 57 | &cli.StringFlag{ 58 | Name: "kubeconfig", 59 | Usage: "Kubernetes config files, e.g. $HOME/.kube/config", 60 | EnvVars: []string{"KUBECONFIG"}, 61 | Destination: &config.Kubeconfig, 62 | }, 63 | &cli.StringFlag{ 64 | Name: "master-url", 65 | Usage: "Kubernetes cluster master URL", 66 | EnvVars: []string{"MASTERURL"}, 67 | Destination: &config.MasterURL, 68 | }, 69 | &cli.StringFlag{ 70 | Name: "namespace", 71 | Usage: "Namespace to watch, empty means it will watch CRDs in all namespaces", 72 | EnvVars: []string{"NAMESPACE"}, 73 | Destination: &config.Namespace, 74 | }, 75 | &cli.StringFlag{ 76 | Name: "node-name", 77 | Usage: "Name of the node this controller is running on", 78 | EnvVars: []string{"NODE_NAME"}, 79 | Destination: &config.NodeName, 80 | }, 81 | &cli.IntFlag{ 82 | Name: "pprof-port", 83 | Value: 6060, 84 | Usage: "Port to publish HTTP server runtime profiling data in the format expected by the pprof visualization tool. Only enabled if in debug mode", 85 | Destination: &config.PprofPort, 86 | }, 87 | &cli.IntFlag{ 88 | Name: "threads", 89 | Value: 2, 90 | Usage: "Threadiness level to set", 91 | EnvVars: []string{"THREADS"}, 92 | Destination: &config.Threads, 93 | }, 94 | }, 95 | } 96 | 97 | ctx := signals.SetupSignalContext() 98 | if err := app.RunContext(ctx, os.Args); err != nil { 99 | logrus.Fatal(err) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: helm-controller 5 | namespace: helm-controller 6 | labels: 7 | app: helm-controller 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: helm-controller 13 | template: 14 | metadata: 15 | labels: 16 | app: helm-controller 17 | spec: 18 | serviceAccountName: helm-controller 19 | containers: 20 | - name: helm-controller 21 | image: rancher/helm-controller:v0.12.1 22 | command: ["helm-controller"] 23 | env: 24 | - name: NODE_NAME 25 | valueFrom: 26 | fieldRef: 27 | fieldPath: spec.nodeName 28 | - name: JOB_CLUSTER_ROLE 29 | valueFrom: 30 | fieldRef: 31 | fieldPath: spec.serviceAccountName 32 | -------------------------------------------------------------------------------- /manifests/example-helmchart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.cattle.io/v1 2 | kind: HelmChart 3 | metadata: 4 | name: traefik 5 | namespace: kube-system 6 | spec: 7 | chart: stable/traefik 8 | set: 9 | rbac.enabled: "true" 10 | ssl.enabled: "true" 11 | 12 | -------------------------------------------------------------------------------- /manifests/ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: helm-controller 5 | labels: 6 | name: helm-controller 7 | -------------------------------------------------------------------------------- /manifests/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: helm-controller 5 | rules: 6 | - apiGroups: 7 | - "*" 8 | resources: 9 | - "*" 10 | verbs: 11 | - "*" 12 | --- 13 | apiVersion: rbac.authorization.k8s.io/v1 14 | kind: ClusterRoleBinding 15 | metadata: 16 | name: helm-controller 17 | roleRef: 18 | apiGroup: rbac.authorization.k8s.io 19 | kind: ClusterRole 20 | name: helm-controller 21 | subjects: 22 | - kind: ServiceAccount 23 | name: helm-controller 24 | namespace: helm-controller 25 | --- 26 | apiVersion: v1 27 | kind: ServiceAccount 28 | metadata: 29 | name: helm-controller 30 | namespace: helm-controller 31 | -------------------------------------------------------------------------------- /pkg/apis/helm.cattle.io/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | // +k8s:deepcopy-gen=package 20 | // +groupName=helm.cattle.io 21 | package v1 22 | -------------------------------------------------------------------------------- /pkg/apis/helm.cattle.io/v1/types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | 7 | "k8s.io/apimachinery/pkg/util/intstr" 8 | ) 9 | 10 | // +genclient 11 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 12 | 13 | type HelmChart struct { 14 | metav1.TypeMeta `json:",inline"` 15 | metav1.ObjectMeta `json:"metadata,omitempty"` 16 | 17 | Spec HelmChartSpec `json:"spec,omitempty"` 18 | Status HelmChartStatus `json:"status,omitempty"` 19 | } 20 | 21 | type HelmChartSpec struct { 22 | TargetNamespace string `json:"targetNamespace,omitempty"` 23 | CreateNamespace bool `json:"createNamespace,omitempty"` 24 | Chart string `json:"chart,omitempty"` 25 | Version string `json:"version,omitempty"` 26 | Repo string `json:"repo,omitempty"` 27 | RepoCA string `json:"repoCA,omitempty"` 28 | RepoCAConfigMap *corev1.LocalObjectReference `json:"repoCAConfigMap,omitempty"` 29 | Set map[string]intstr.IntOrString `json:"set,omitempty"` 30 | ValuesContent string `json:"valuesContent,omitempty"` 31 | ValuesSecrets []SecretSpec `json:"valuesSecrets,omitempty"` 32 | HelmVersion string `json:"helmVersion,omitempty"` 33 | Bootstrap bool `json:"bootstrap,omitempty"` 34 | ChartContent string `json:"chartContent,omitempty"` 35 | JobImage string `json:"jobImage,omitempty"` 36 | BackOffLimit *int32 `json:"backOffLimit,omitempty"` 37 | Timeout *metav1.Duration `json:"timeout,omitempty"` 38 | FailurePolicy string `json:"failurePolicy,omitempty"` 39 | AuthSecret *corev1.LocalObjectReference `json:"authSecret,omitempty"` 40 | 41 | AuthPassCredentials bool `json:"authPassCredentials,omitempty"` 42 | InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"` 43 | PlainHTTP bool `json:"plainHTTP,omitempty"` 44 | 45 | DockerRegistrySecret *corev1.LocalObjectReference `json:"dockerRegistrySecret,omitempty"` 46 | PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` 47 | SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` 48 | } 49 | 50 | type HelmChartStatus struct { 51 | JobName string `json:"jobName,omitempty"` 52 | Conditions []HelmChartCondition `json:"conditions,omitempty"` 53 | } 54 | 55 | // +genclient 56 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 57 | 58 | type HelmChartConfig struct { 59 | metav1.TypeMeta `json:",inline"` 60 | metav1.ObjectMeta `json:"metadata,omitempty"` 61 | 62 | Spec HelmChartConfigSpec `json:"spec,omitempty"` 63 | } 64 | 65 | type HelmChartConfigSpec struct { 66 | ValuesContent string `json:"valuesContent,omitempty"` 67 | ValuesSecrets []SecretSpec `json:"valuesSecrets,omitempty"` 68 | FailurePolicy string `json:"failurePolicy,omitempty"` 69 | } 70 | 71 | type HelmChartConditionType string 72 | 73 | const ( 74 | HelmChartJobCreated HelmChartConditionType = "JobCreated" 75 | HelmChartFailed HelmChartConditionType = "Failed" 76 | ) 77 | 78 | type HelmChartCondition struct { 79 | // Type of job condition. 80 | Type HelmChartConditionType `json:"type"` 81 | // Status of the condition, one of True, False, Unknown. 82 | Status corev1.ConditionStatus `json:"status"` 83 | // (brief) reason for the condition's last transition. 84 | // +optional 85 | Reason string `json:"reason,omitempty"` 86 | // Human readable message indicating details about last transition. 87 | // +optional 88 | Message string `json:"message,omitempty"` 89 | } 90 | 91 | // SecretSpec describes a key in a secret to load chart values from. 92 | type SecretSpec struct { 93 | Name string `json:"name,omitempty"` 94 | Keys []string `json:"keys,omitempty"` 95 | IgnoreUpdates bool `json:"ignoreUpdates,omitempty"` 96 | } 97 | -------------------------------------------------------------------------------- /pkg/apis/helm.cattle.io/v1/zz_generated_deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by main. DO NOT EDIT. 21 | 22 | package v1 23 | 24 | import ( 25 | corev1 "k8s.io/api/core/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | runtime "k8s.io/apimachinery/pkg/runtime" 28 | intstr "k8s.io/apimachinery/pkg/util/intstr" 29 | ) 30 | 31 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 32 | func (in *HelmChart) DeepCopyInto(out *HelmChart) { 33 | *out = *in 34 | out.TypeMeta = in.TypeMeta 35 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 36 | in.Spec.DeepCopyInto(&out.Spec) 37 | in.Status.DeepCopyInto(&out.Status) 38 | return 39 | } 40 | 41 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChart. 42 | func (in *HelmChart) DeepCopy() *HelmChart { 43 | if in == nil { 44 | return nil 45 | } 46 | out := new(HelmChart) 47 | in.DeepCopyInto(out) 48 | return out 49 | } 50 | 51 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 52 | func (in *HelmChart) DeepCopyObject() runtime.Object { 53 | if c := in.DeepCopy(); c != nil { 54 | return c 55 | } 56 | return nil 57 | } 58 | 59 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 60 | func (in *HelmChartCondition) DeepCopyInto(out *HelmChartCondition) { 61 | *out = *in 62 | return 63 | } 64 | 65 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartCondition. 66 | func (in *HelmChartCondition) DeepCopy() *HelmChartCondition { 67 | if in == nil { 68 | return nil 69 | } 70 | out := new(HelmChartCondition) 71 | in.DeepCopyInto(out) 72 | return out 73 | } 74 | 75 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 76 | func (in *HelmChartConfig) DeepCopyInto(out *HelmChartConfig) { 77 | *out = *in 78 | out.TypeMeta = in.TypeMeta 79 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 80 | in.Spec.DeepCopyInto(&out.Spec) 81 | return 82 | } 83 | 84 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartConfig. 85 | func (in *HelmChartConfig) DeepCopy() *HelmChartConfig { 86 | if in == nil { 87 | return nil 88 | } 89 | out := new(HelmChartConfig) 90 | in.DeepCopyInto(out) 91 | return out 92 | } 93 | 94 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 95 | func (in *HelmChartConfig) DeepCopyObject() runtime.Object { 96 | if c := in.DeepCopy(); c != nil { 97 | return c 98 | } 99 | return nil 100 | } 101 | 102 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 103 | func (in *HelmChartConfigList) DeepCopyInto(out *HelmChartConfigList) { 104 | *out = *in 105 | out.TypeMeta = in.TypeMeta 106 | in.ListMeta.DeepCopyInto(&out.ListMeta) 107 | if in.Items != nil { 108 | in, out := &in.Items, &out.Items 109 | *out = make([]HelmChartConfig, len(*in)) 110 | for i := range *in { 111 | (*in)[i].DeepCopyInto(&(*out)[i]) 112 | } 113 | } 114 | return 115 | } 116 | 117 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartConfigList. 118 | func (in *HelmChartConfigList) DeepCopy() *HelmChartConfigList { 119 | if in == nil { 120 | return nil 121 | } 122 | out := new(HelmChartConfigList) 123 | in.DeepCopyInto(out) 124 | return out 125 | } 126 | 127 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 128 | func (in *HelmChartConfigList) DeepCopyObject() runtime.Object { 129 | if c := in.DeepCopy(); c != nil { 130 | return c 131 | } 132 | return nil 133 | } 134 | 135 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 136 | func (in *HelmChartConfigSpec) DeepCopyInto(out *HelmChartConfigSpec) { 137 | *out = *in 138 | if in.ValuesSecrets != nil { 139 | in, out := &in.ValuesSecrets, &out.ValuesSecrets 140 | *out = make([]SecretSpec, len(*in)) 141 | for i := range *in { 142 | (*in)[i].DeepCopyInto(&(*out)[i]) 143 | } 144 | } 145 | return 146 | } 147 | 148 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartConfigSpec. 149 | func (in *HelmChartConfigSpec) DeepCopy() *HelmChartConfigSpec { 150 | if in == nil { 151 | return nil 152 | } 153 | out := new(HelmChartConfigSpec) 154 | in.DeepCopyInto(out) 155 | return out 156 | } 157 | 158 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 159 | func (in *HelmChartList) DeepCopyInto(out *HelmChartList) { 160 | *out = *in 161 | out.TypeMeta = in.TypeMeta 162 | in.ListMeta.DeepCopyInto(&out.ListMeta) 163 | if in.Items != nil { 164 | in, out := &in.Items, &out.Items 165 | *out = make([]HelmChart, len(*in)) 166 | for i := range *in { 167 | (*in)[i].DeepCopyInto(&(*out)[i]) 168 | } 169 | } 170 | return 171 | } 172 | 173 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartList. 174 | func (in *HelmChartList) DeepCopy() *HelmChartList { 175 | if in == nil { 176 | return nil 177 | } 178 | out := new(HelmChartList) 179 | in.DeepCopyInto(out) 180 | return out 181 | } 182 | 183 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 184 | func (in *HelmChartList) DeepCopyObject() runtime.Object { 185 | if c := in.DeepCopy(); c != nil { 186 | return c 187 | } 188 | return nil 189 | } 190 | 191 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 192 | func (in *HelmChartSpec) DeepCopyInto(out *HelmChartSpec) { 193 | *out = *in 194 | if in.RepoCAConfigMap != nil { 195 | in, out := &in.RepoCAConfigMap, &out.RepoCAConfigMap 196 | *out = new(corev1.LocalObjectReference) 197 | **out = **in 198 | } 199 | if in.Set != nil { 200 | in, out := &in.Set, &out.Set 201 | *out = make(map[string]intstr.IntOrString, len(*in)) 202 | for key, val := range *in { 203 | (*out)[key] = val 204 | } 205 | } 206 | if in.ValuesSecrets != nil { 207 | in, out := &in.ValuesSecrets, &out.ValuesSecrets 208 | *out = make([]SecretSpec, len(*in)) 209 | for i := range *in { 210 | (*in)[i].DeepCopyInto(&(*out)[i]) 211 | } 212 | } 213 | if in.BackOffLimit != nil { 214 | in, out := &in.BackOffLimit, &out.BackOffLimit 215 | *out = new(int32) 216 | **out = **in 217 | } 218 | if in.Timeout != nil { 219 | in, out := &in.Timeout, &out.Timeout 220 | *out = new(metav1.Duration) 221 | **out = **in 222 | } 223 | if in.AuthSecret != nil { 224 | in, out := &in.AuthSecret, &out.AuthSecret 225 | *out = new(corev1.LocalObjectReference) 226 | **out = **in 227 | } 228 | if in.DockerRegistrySecret != nil { 229 | in, out := &in.DockerRegistrySecret, &out.DockerRegistrySecret 230 | *out = new(corev1.LocalObjectReference) 231 | **out = **in 232 | } 233 | if in.PodSecurityContext != nil { 234 | in, out := &in.PodSecurityContext, &out.PodSecurityContext 235 | *out = new(corev1.PodSecurityContext) 236 | (*in).DeepCopyInto(*out) 237 | } 238 | if in.SecurityContext != nil { 239 | in, out := &in.SecurityContext, &out.SecurityContext 240 | *out = new(corev1.SecurityContext) 241 | (*in).DeepCopyInto(*out) 242 | } 243 | return 244 | } 245 | 246 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartSpec. 247 | func (in *HelmChartSpec) DeepCopy() *HelmChartSpec { 248 | if in == nil { 249 | return nil 250 | } 251 | out := new(HelmChartSpec) 252 | in.DeepCopyInto(out) 253 | return out 254 | } 255 | 256 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 257 | func (in *HelmChartStatus) DeepCopyInto(out *HelmChartStatus) { 258 | *out = *in 259 | if in.Conditions != nil { 260 | in, out := &in.Conditions, &out.Conditions 261 | *out = make([]HelmChartCondition, len(*in)) 262 | copy(*out, *in) 263 | } 264 | return 265 | } 266 | 267 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartStatus. 268 | func (in *HelmChartStatus) DeepCopy() *HelmChartStatus { 269 | if in == nil { 270 | return nil 271 | } 272 | out := new(HelmChartStatus) 273 | in.DeepCopyInto(out) 274 | return out 275 | } 276 | 277 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 278 | func (in *SecretSpec) DeepCopyInto(out *SecretSpec) { 279 | *out = *in 280 | if in.Keys != nil { 281 | in, out := &in.Keys, &out.Keys 282 | *out = make([]string, len(*in)) 283 | copy(*out, *in) 284 | } 285 | return 286 | } 287 | 288 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretSpec. 289 | func (in *SecretSpec) DeepCopy() *SecretSpec { 290 | if in == nil { 291 | return nil 292 | } 293 | out := new(SecretSpec) 294 | in.DeepCopyInto(out) 295 | return out 296 | } 297 | -------------------------------------------------------------------------------- /pkg/apis/helm.cattle.io/v1/zz_generated_list_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | // +k8s:deepcopy-gen=package 20 | // +groupName=helm.cattle.io 21 | package v1 22 | 23 | import ( 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | ) 26 | 27 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 28 | 29 | // HelmChartList is a list of HelmChart resources 30 | type HelmChartList struct { 31 | metav1.TypeMeta `json:",inline"` 32 | metav1.ListMeta `json:"metadata"` 33 | 34 | Items []HelmChart `json:"items"` 35 | } 36 | 37 | func NewHelmChart(namespace, name string, obj HelmChart) *HelmChart { 38 | obj.APIVersion, obj.Kind = SchemeGroupVersion.WithKind("HelmChart").ToAPIVersionAndKind() 39 | obj.Name = name 40 | obj.Namespace = namespace 41 | return &obj 42 | } 43 | 44 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 45 | 46 | // HelmChartConfigList is a list of HelmChartConfig resources 47 | type HelmChartConfigList struct { 48 | metav1.TypeMeta `json:",inline"` 49 | metav1.ListMeta `json:"metadata"` 50 | 51 | Items []HelmChartConfig `json:"items"` 52 | } 53 | 54 | func NewHelmChartConfig(namespace, name string, obj HelmChartConfig) *HelmChartConfig { 55 | obj.APIVersion, obj.Kind = SchemeGroupVersion.WithKind("HelmChartConfig").ToAPIVersionAndKind() 56 | obj.Name = name 57 | obj.Namespace = namespace 58 | return &obj 59 | } 60 | -------------------------------------------------------------------------------- /pkg/apis/helm.cattle.io/v1/zz_generated_register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | // +k8s:deepcopy-gen=package 20 | // +groupName=helm.cattle.io 21 | package v1 22 | 23 | import ( 24 | helm "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "k8s.io/apimachinery/pkg/runtime/schema" 28 | ) 29 | 30 | var ( 31 | HelmChartResourceName = "helmcharts" 32 | HelmChartConfigResourceName = "helmchartconfigs" 33 | ) 34 | 35 | // SchemeGroupVersion is group version used to register these objects 36 | var SchemeGroupVersion = schema.GroupVersion{Group: helm.GroupName, Version: "v1"} 37 | 38 | // Kind takes an unqualified kind and returns back a Group qualified GroupKind 39 | func Kind(kind string) schema.GroupKind { 40 | return SchemeGroupVersion.WithKind(kind).GroupKind() 41 | } 42 | 43 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 44 | func Resource(resource string) schema.GroupResource { 45 | return SchemeGroupVersion.WithResource(resource).GroupResource() 46 | } 47 | 48 | var ( 49 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 50 | AddToScheme = SchemeBuilder.AddToScheme 51 | ) 52 | 53 | // Adds the list of known types to Scheme. 54 | func addKnownTypes(scheme *runtime.Scheme) error { 55 | scheme.AddKnownTypes(SchemeGroupVersion, 56 | &HelmChart{}, 57 | &HelmChartList{}, 58 | &HelmChartConfig{}, 59 | &HelmChartConfigList{}, 60 | ) 61 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/apis/helm.cattle.io/zz_generated_register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package helm 20 | 21 | const ( 22 | // Package-wide consts from generator "zz_generated_register". 23 | GroupName = "helm.cattle.io" 24 | ) 25 | -------------------------------------------------------------------------------- /pkg/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/sirupsen/logrus" 8 | "k8s.io/klog/v2" 9 | 10 | "log" 11 | "net/http" 12 | 13 | "github.com/k3s-io/helm-controller/pkg/controllers" 14 | "github.com/k3s-io/helm-controller/pkg/controllers/common" 15 | "github.com/k3s-io/helm-controller/pkg/crd" 16 | wcrd "github.com/rancher/wrangler/v3/pkg/crd" 17 | "github.com/rancher/wrangler/v3/pkg/kubeconfig" 18 | "github.com/rancher/wrangler/v3/pkg/ratelimit" 19 | "github.com/urfave/cli/v2" 20 | "k8s.io/client-go/tools/clientcmd" 21 | clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 22 | ) 23 | 24 | type HelmController struct { 25 | Debug bool 26 | DebugLevel int 27 | Kubeconfig string 28 | MasterURL string 29 | Namespace string 30 | Threads int 31 | ControllerName string 32 | NodeName string 33 | JobClusterRole string 34 | DefaultJobImage string 35 | PprofPort int 36 | } 37 | 38 | func (hc *HelmController) SetupDebug() error { 39 | logging := flag.NewFlagSet("", flag.PanicOnError) 40 | klog.InitFlags(logging) 41 | if hc.Debug { 42 | logrus.SetLevel(logrus.DebugLevel) 43 | if err := logging.Parse([]string{ 44 | fmt.Sprintf("-v=%d", hc.DebugLevel), 45 | }); err != nil { 46 | return err 47 | } 48 | } else { 49 | if err := logging.Parse([]string{ 50 | "-v=0", 51 | }); err != nil { 52 | return err 53 | } 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (hc *HelmController) Run(app *cli.Context) error { 60 | if hc.Debug && hc.PprofPort > 0 { 61 | go func() { 62 | // Serves HTTP server runtime profiling data in the format expected by the 63 | // pprof visualization tool at the provided endpoint on the local network 64 | // See https://pkg.go.dev/net/http/pprof?utm_source=gopls for more information 65 | log.Println(http.ListenAndServe(fmt.Sprintf("localhost:%d", hc.PprofPort), nil)) 66 | }() 67 | } 68 | err := hc.SetupDebug() 69 | if err != nil { 70 | panic("failed to setup debug logging: " + err.Error()) 71 | } 72 | 73 | cfg := hc.GetNonInteractiveClientConfig() 74 | 75 | clientConfig, err := cfg.ClientConfig() 76 | if err != nil { 77 | return err 78 | } 79 | clientConfig.RateLimiter = ratelimit.None 80 | ctx := app.Context 81 | if err := wcrd.Create(ctx, clientConfig, crd.List()); err != nil { 82 | return err 83 | } 84 | 85 | opts := common.Options{ 86 | Threadiness: hc.Threads, 87 | NodeName: hc.NodeName, 88 | JobClusterRole: hc.JobClusterRole, 89 | DefaultJobImage: hc.DefaultJobImage, 90 | } 91 | 92 | if err := opts.Validate(); err != nil { 93 | return err 94 | } 95 | 96 | if err := controllers.Register(ctx, hc.Namespace, hc.ControllerName, cfg, opts); err != nil { 97 | return err 98 | } 99 | 100 | <-ctx.Done() 101 | return nil 102 | } 103 | 104 | func (hc *HelmController) GetNonInteractiveClientConfig() clientcmd.ClientConfig { 105 | // Modified https://github.com/rancher/wrangler/blob/3ecd23dfea3bb4c76cbe8e06fb158eed6ae3dd31/pkg/kubeconfig/loader.go#L12-L32 106 | return clientcmd.NewInteractiveDeferredLoadingClientConfig( 107 | kubeconfig.GetLoadingRules(hc.Kubeconfig), 108 | &clientcmd.ConfigOverrides{ 109 | ClusterDefaults: clientcmd.ClusterDefaults, 110 | ClusterInfo: clientcmdapi.Cluster{Server: hc.MasterURL}, 111 | }, nil) 112 | } 113 | -------------------------------------------------------------------------------- /pkg/codegen/cleanup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/rancher/wrangler/v3/pkg/cleanup" 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | func main() { 11 | if err := cleanup.Cleanup("./pkg/apis"); err != nil { 12 | logrus.Fatal(err) 13 | } 14 | if err := os.RemoveAll("./pkg/generated"); err != nil { 15 | logrus.Fatal(err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/codegen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 7 | 8 | controllergen "github.com/rancher/wrangler/v3/pkg/controller-gen" 9 | "github.com/rancher/wrangler/v3/pkg/controller-gen/args" 10 | ) 11 | 12 | func main() { 13 | os.Unsetenv("GOPATH") 14 | controllergen.Run(args.Options{ 15 | OutputPackage: "github.com/k3s-io/helm-controller/pkg/generated", 16 | Boilerplate: "scripts/boilerplate.go.txt", 17 | Groups: map[string]args.Group{ 18 | "helm.cattle.io": { 19 | Types: []interface{}{ 20 | v1.HelmChart{}, 21 | v1.HelmChartConfig{}, 22 | }, 23 | GenerateTypes: true, 24 | GenerateClients: true, 25 | }, 26 | }, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/controllers/chart/chart_test.go: -------------------------------------------------------------------------------- 1 | package chart 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | 8 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 9 | "github.com/rancher/wrangler/v3/pkg/yaml" 10 | "github.com/sirupsen/logrus" 11 | "github.com/stretchr/testify/assert" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | "k8s.io/apimachinery/pkg/util/intstr" 15 | ) 16 | 17 | func init() { 18 | logrus.SetLevel(logrus.DebugLevel) 19 | } 20 | 21 | func TestHashObjects(t *testing.T) { 22 | type args struct { 23 | chartValuesContent string 24 | configValuesContent string 25 | hash string 26 | } 27 | 28 | tests := map[string]args{ 29 | "No Values": { 30 | hash: "SHA256=E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", 31 | }, 32 | "Chart Only 1": { 33 | hash: "SHA256=B7D684A932E5B3AC74E009951700E032CE9936BF6BE82CD2DED22B5EA647EE5D", 34 | chartValuesContent: "foo: bar\n", 35 | }, 36 | "Chart Only 2": { 37 | hash: "SHA256=F3756AFACE793965D81AE9E9BD85A51369E60C18FE024E4D950BF56054258070", 38 | chartValuesContent: "foo:\n a: true\n b: 1\n c: 'true'\n", 39 | }, 40 | "Chart Only 3": { 41 | hash: "SHA256=FFE4DB5EFB61ACC03F197C464414B5BB65885E8F03AE11B9EBB657D5DD3CCC55", 42 | chartValuesContent: "{}", 43 | }, 44 | "Config Only 1": { 45 | hash: "SHA256=E00641CFFEB2D8EA3403D56DD456DAAF9578B4871F2FDB41B0F1AA33C25B69AF", 46 | configValuesContent: "foo: baz\n", 47 | }, 48 | "Config Only 2": { 49 | hash: "SHA256=309A32E491B3F0F43432948D90B4E766A278D0A3B3220E691EE35BC6429ECB52", 50 | configValuesContent: "foo:\n a: false\n b: 0\n c: 'false'\n", 51 | }, 52 | "Config Only 3": { 53 | hash: "SHA256=E1D81D53C173950A8F35BB397759CF49B3F43C0C797AD4F7C7AD6A3A47180E03", 54 | configValuesContent: "{}", 55 | }, 56 | "Chart and Config 1": { 57 | hash: "SHA256=F81EFF0BAF43F57D87FB53BCFAB06271091B411C4A582FCC130C33951CB7C81D", 58 | chartValuesContent: "foo: bar\n", 59 | configValuesContent: "foo: baz\n", 60 | }, 61 | "Chart and Config 2": { 62 | hash: "SHA256=E41407A16AAC1DBD0B6D00A1818B0A73B0EB9A506131F3CAFD102ED751A8AA3D", 63 | chartValuesContent: "foo:\n a: true\n b: 1\n c: 'true'\n", 64 | configValuesContent: "bar:\n a: false\n b: 0\n c: 'false'\n", 65 | }, 66 | } 67 | 68 | for name, test := range tests { 69 | t.Run(name, func(t *testing.T) { 70 | assert := assert.New(t) 71 | chart := NewChart() 72 | config := &v1.HelmChartConfig{} 73 | chart.Spec.ValuesContent = test.chartValuesContent 74 | config.Spec.ValuesContent = test.configValuesContent 75 | 76 | job, secret, configMap := job(chart, "6443") 77 | objects := []metav1.Object{configMap, secret} 78 | 79 | valuesSecretAddConfig(job, secret, config) 80 | 81 | assert.Nil(secret.StringData, "Secret StringData should be nil") 82 | assert.Nil(configMap.BinaryData, "ConfigMap BinaryData should be nil") 83 | 84 | if test.chartValuesContent == "" && test.configValuesContent == "" { 85 | assert.Empty(secret.Data, "Secret Data should be empty if HelmChart and HelmChartConfig ValuesContent are empty") 86 | } else { 87 | assert.NotEmpty(secret.Data, "Secret Data should not be empty if HelmChart and/or HelmChartConfig ValuesContent are not empty") 88 | } 89 | 90 | hashObjects(job, objects...) 91 | 92 | b, _ := yaml.ToBytes([]runtime.Object{job}) 93 | t.Logf("Generated Job:\n%s", b) 94 | 95 | assert.Equalf(test.hash, job.Spec.Template.ObjectMeta.Annotations[Annotation], "%s annotation value does not match", Annotation) 96 | }) 97 | } 98 | } 99 | 100 | func TestSetVals(t *testing.T) { 101 | assert := assert.New(t) 102 | tests := map[string]bool{ 103 | "": false, 104 | " ": false, 105 | "foo": false, 106 | "1.0": false, 107 | "0.1": false, 108 | "0": true, 109 | "1": true, 110 | "-1": true, 111 | "true": true, 112 | "TrUe": true, 113 | "false": true, 114 | "FaLsE": true, 115 | "null": true, 116 | "NuLl": true, 117 | } 118 | for testString, isTyped := range tests { 119 | ret := typedVal(intstr.Parse(testString)) 120 | assert.Equal(isTyped, ret, "expected typedVal(%s) = %t", testString, isTyped) 121 | } 122 | } 123 | 124 | func TestInstallJob(t *testing.T) { 125 | assert := assert.New(t) 126 | chart := NewChart() 127 | job, _, _ := job(chart, "6443") 128 | assert.Equal("helm-install-traefik", job.Name) 129 | assert.Equal(DefaultJobImage, job.Spec.Template.Spec.Containers[0].Image) 130 | assert.Equal("helm-traefik", job.Spec.Template.Spec.ServiceAccountName) 131 | } 132 | 133 | func TestDeleteJob(t *testing.T) { 134 | assert := assert.New(t) 135 | chart := NewChart() 136 | deleteTime := metav1.NewTime(time.Time{}) 137 | chart.DeletionTimestamp = &deleteTime 138 | job, _, _ := job(chart, "6443") 139 | assert.Equal("helm-delete-traefik", job.Name) 140 | } 141 | 142 | func TestInstallJobImage(t *testing.T) { 143 | assert := assert.New(t) 144 | chart := NewChart() 145 | chart.Spec.JobImage = "custom-job-image" 146 | job, _, _ := job(chart, "6443") 147 | assert.Equal("custom-job-image", job.Spec.Template.Spec.Containers[0].Image) 148 | } 149 | 150 | func TestInstallArgs(t *testing.T) { 151 | assert := assert.New(t) 152 | stringArgs := strings.Join(args(NewChart()), " ") 153 | assert.Equal("install "+ 154 | "--set-string acme.dnsProvider.name=cloudflare "+ 155 | "--set-string global.clusterCIDR=10.42.0.0/16\\,fd42::/48 "+ 156 | "--set-string global.systemDefaultRegistry= "+ 157 | "--set rbac.enabled=true "+ 158 | "--set ssl.enabled=false", 159 | stringArgs) 160 | } 161 | 162 | func TestDeleteArgs(t *testing.T) { 163 | assert := assert.New(t) 164 | chart := NewChart() 165 | deleteTime := metav1.NewTime(time.Time{}) 166 | chart.DeletionTimestamp = &deleteTime 167 | stringArgs := strings.Join(args(chart), " ") 168 | assert.Equal("delete", stringArgs) 169 | } 170 | 171 | func NewChart() *v1.HelmChart { 172 | return v1.NewHelmChart("kube-system", "traefik", v1.HelmChart{ 173 | Spec: v1.HelmChartSpec{ 174 | Chart: "stable/traefik", 175 | Set: map[string]intstr.IntOrString{ 176 | "rbac.enabled": intstr.Parse("true"), 177 | "ssl.enabled": intstr.Parse("false"), 178 | "acme.dnsProvider.name": intstr.Parse("cloudflare"), 179 | "global.clusterCIDR": intstr.Parse("10.42.0.0/16,fd42::/48"), 180 | "global.systemDefaultRegistry": intstr.Parse(""), 181 | }, 182 | }, 183 | }) 184 | } 185 | -------------------------------------------------------------------------------- /pkg/controllers/common/constants.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | Name = "helm-controller" 5 | ) 6 | -------------------------------------------------------------------------------- /pkg/controllers/common/options.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "fmt" 4 | 5 | // Options defines options that can be set on initializing the Helm Controller 6 | type Options struct { 7 | Threadiness int 8 | NodeName string 9 | JobClusterRole string 10 | DefaultJobImage string 11 | } 12 | 13 | func (opts Options) Validate() error { 14 | if opts.Threadiness <= 0 { 15 | return fmt.Errorf("cannot start with thread count of %d, please pass a proper thread count", opts.Threadiness) 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/controllers/controllers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/k3s-io/helm-controller/pkg/controllers/chart" 8 | "github.com/k3s-io/helm-controller/pkg/controllers/common" 9 | "github.com/k3s-io/helm-controller/pkg/generated/controllers/helm.cattle.io" 10 | helmcontroller "github.com/k3s-io/helm-controller/pkg/generated/controllers/helm.cattle.io/v1" 11 | "github.com/rancher/lasso/pkg/cache" 12 | "github.com/rancher/lasso/pkg/client" 13 | "github.com/rancher/lasso/pkg/controller" 14 | "github.com/rancher/wrangler/v3/pkg/apply" 15 | "github.com/rancher/wrangler/v3/pkg/generated/controllers/batch" 16 | batchcontroller "github.com/rancher/wrangler/v3/pkg/generated/controllers/batch/v1" 17 | "github.com/rancher/wrangler/v3/pkg/generated/controllers/core" 18 | corecontroller "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" 19 | "github.com/rancher/wrangler/v3/pkg/generated/controllers/rbac" 20 | rbaccontroller "github.com/rancher/wrangler/v3/pkg/generated/controllers/rbac/v1" 21 | "github.com/rancher/wrangler/v3/pkg/generic" 22 | "github.com/rancher/wrangler/v3/pkg/leader" 23 | "github.com/rancher/wrangler/v3/pkg/ratelimit" 24 | "github.com/rancher/wrangler/v3/pkg/schemes" 25 | "github.com/rancher/wrangler/v3/pkg/start" 26 | "github.com/sirupsen/logrus" 27 | corev1 "k8s.io/api/core/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | "k8s.io/client-go/kubernetes" 30 | typedv1 "k8s.io/client-go/kubernetes/typed/core/v1" 31 | "k8s.io/client-go/rest" 32 | "k8s.io/client-go/tools/clientcmd" 33 | "k8s.io/client-go/tools/record" 34 | "k8s.io/client-go/util/workqueue" 35 | "k8s.io/klog/v2" 36 | ) 37 | 38 | type appContext struct { 39 | helmcontroller.Interface 40 | 41 | K8s kubernetes.Interface 42 | Core corecontroller.Interface 43 | RBAC rbaccontroller.Interface 44 | Batch batchcontroller.Interface 45 | 46 | Apply apply.Apply 47 | EventBroadcaster record.EventBroadcaster 48 | 49 | ClientConfig clientcmd.ClientConfig 50 | starters []start.Starter 51 | } 52 | 53 | func (a *appContext) start(ctx context.Context) error { 54 | return start.All(ctx, 50, a.starters...) 55 | } 56 | 57 | func Register(ctx context.Context, systemNamespace, controllerName string, cfg clientcmd.ClientConfig, opts common.Options) error { 58 | appCtx, err := newContext(cfg, systemNamespace, opts) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | if len(controllerName) == 0 { 64 | controllerName = "helm-controller" 65 | } 66 | 67 | appCtx.EventBroadcaster.StartLogging(logrus.Infof) 68 | appCtx.EventBroadcaster.StartRecordingToSink(&typedv1.EventSinkImpl{ 69 | Interface: appCtx.K8s.CoreV1().Events(systemNamespace), 70 | }) 71 | recorder := appCtx.EventBroadcaster.NewRecorder(schemes.All, corev1.EventSource{ 72 | Component: controllerName, 73 | Host: opts.NodeName, 74 | }) 75 | 76 | // apply custom DefaultJobImage option to Helm before starting charts controller 77 | if opts.DefaultJobImage != "" { 78 | chart.DefaultJobImage = opts.DefaultJobImage 79 | } 80 | 81 | chart.Register(ctx, 82 | systemNamespace, 83 | controllerName, 84 | opts.JobClusterRole, 85 | "6443", 86 | appCtx.K8s, 87 | appCtx.Apply, 88 | recorder, 89 | appCtx.HelmChart(), 90 | appCtx.HelmChart().Cache(), 91 | appCtx.HelmChartConfig(), 92 | appCtx.HelmChartConfig().Cache(), 93 | appCtx.Batch.Job(), 94 | appCtx.Batch.Job().Cache(), 95 | appCtx.RBAC.ClusterRoleBinding(), 96 | appCtx.Core.ServiceAccount(), 97 | appCtx.Core.ConfigMap(), 98 | appCtx.Core.Secret(), 99 | appCtx.Core.Secret().Cache(), 100 | ) 101 | 102 | klog.Infof("Starting helm controller with %d threads", opts.Threadiness) 103 | klog.Infof("Using cluster role '%s' for jobs managing helm charts", opts.JobClusterRole) 104 | klog.Infof("Using default image '%s' for jobs managing helm charts", chart.DefaultJobImage) 105 | 106 | if len(systemNamespace) == 0 { 107 | systemNamespace = metav1.NamespaceSystem 108 | klog.Infof("Starting %s for all namespaces with lock in %s", controllerName, systemNamespace) 109 | } else { 110 | klog.Infof("Starting %s for namespace %s", controllerName, systemNamespace) 111 | } 112 | 113 | controllerLockName := controllerName + "-lock" 114 | leader.RunOrDie(ctx, systemNamespace, controllerLockName, appCtx.K8s, func(ctx context.Context) { 115 | if err := appCtx.start(ctx); err != nil { 116 | klog.Fatal(err) 117 | } 118 | klog.Info("All controllers have been started") 119 | }) 120 | 121 | return nil 122 | } 123 | 124 | func controllerFactory(rest *rest.Config) (controller.SharedControllerFactory, error) { 125 | rateLimit := workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 60*time.Second) 126 | clientFactory, err := client.NewSharedClientFactory(rest, nil) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | cacheFactory := cache.NewSharedCachedFactory(clientFactory, nil) 132 | return controller.NewSharedControllerFactory(cacheFactory, &controller.SharedControllerFactoryOptions{ 133 | DefaultRateLimiter: rateLimit, 134 | DefaultWorkers: 50, 135 | }), nil 136 | } 137 | 138 | func newContext(cfg clientcmd.ClientConfig, systemNamespace string, opts common.Options) (*appContext, error) { 139 | client, err := cfg.ClientConfig() 140 | if err != nil { 141 | return nil, err 142 | } 143 | client.RateLimiter = ratelimit.None 144 | 145 | apply, err := apply.NewForConfig(client) 146 | if err != nil { 147 | return nil, err 148 | } 149 | apply = apply.WithSetOwnerReference(false, false) 150 | 151 | k8s, err := kubernetes.NewForConfig(client) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | scf, err := controllerFactory(client) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | core, err := core.NewFactoryFromConfigWithOptions(client, &generic.FactoryOptions{ 162 | SharedControllerFactory: scf, 163 | Namespace: systemNamespace, 164 | }) 165 | if err != nil { 166 | return nil, err 167 | } 168 | corev := core.Core().V1() 169 | 170 | batch, err := batch.NewFactoryFromConfigWithOptions(client, &generic.FactoryOptions{ 171 | SharedControllerFactory: scf, 172 | Namespace: systemNamespace, 173 | }) 174 | if err != nil { 175 | return nil, err 176 | } 177 | batchv := batch.Batch().V1() 178 | 179 | rbac, err := rbac.NewFactoryFromConfigWithOptions(client, &generic.FactoryOptions{ 180 | SharedControllerFactory: scf, 181 | Namespace: systemNamespace, 182 | }) 183 | if err != nil { 184 | return nil, err 185 | } 186 | rbacv := rbac.Rbac().V1() 187 | 188 | helm, err := helm.NewFactoryFromConfigWithOptions(client, &generic.FactoryOptions{ 189 | SharedControllerFactory: scf, 190 | Namespace: systemNamespace, 191 | }) 192 | if err != nil { 193 | return nil, err 194 | } 195 | helmv := helm.Helm().V1() 196 | 197 | return &appContext{ 198 | Interface: helmv, 199 | 200 | K8s: k8s, 201 | Core: corev, 202 | Batch: batchv, 203 | RBAC: rbacv, 204 | 205 | Apply: apply, 206 | EventBroadcaster: record.NewBroadcaster(), 207 | 208 | ClientConfig: cfg, 209 | starters: []start.Starter{ 210 | core, 211 | batch, 212 | rbac, 213 | helm, 214 | }, 215 | }, nil 216 | } 217 | -------------------------------------------------------------------------------- /pkg/crd/crds.go: -------------------------------------------------------------------------------- 1 | package crd 2 | 3 | import ( 4 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 5 | "github.com/rancher/wrangler/v3/pkg/crd" 6 | ) 7 | 8 | func List() []crd.CRD { 9 | chart := crd.NamespacedType("HelmChart.helm.cattle.io/v1"). 10 | WithSchemaFromStruct(v1.HelmChart{}). 11 | WithColumn("Job", ".status.jobName"). 12 | WithColumn("Chart", ".spec.chart"). 13 | WithColumn("TargetNamespace", ".spec.targetNamespace"). 14 | WithColumn("Version", ".spec.version"). 15 | WithColumn("Repo", ".spec.repo"). 16 | WithColumn("HelmVersion", ".spec.helmVersion"). 17 | WithColumn("Bootstrap", ".spec.bootstrap"). 18 | WithStatus() 19 | 20 | config := crd.NamespacedType("HelmChartConfig.helm.cattle.io/v1"). 21 | WithSchemaFromStruct(v1.HelmChartConfig{}) 22 | 23 | return []crd.CRD{chart, config} 24 | } 25 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package versioned 20 | 21 | import ( 22 | "fmt" 23 | "net/http" 24 | 25 | helmv1 "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1" 26 | discovery "k8s.io/client-go/discovery" 27 | rest "k8s.io/client-go/rest" 28 | flowcontrol "k8s.io/client-go/util/flowcontrol" 29 | ) 30 | 31 | type Interface interface { 32 | Discovery() discovery.DiscoveryInterface 33 | HelmV1() helmv1.HelmV1Interface 34 | } 35 | 36 | // Clientset contains the clients for groups. 37 | type Clientset struct { 38 | *discovery.DiscoveryClient 39 | helmV1 *helmv1.HelmV1Client 40 | } 41 | 42 | // HelmV1 retrieves the HelmV1Client 43 | func (c *Clientset) HelmV1() helmv1.HelmV1Interface { 44 | return c.helmV1 45 | } 46 | 47 | // Discovery retrieves the DiscoveryClient 48 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 49 | if c == nil { 50 | return nil 51 | } 52 | return c.DiscoveryClient 53 | } 54 | 55 | // NewForConfig creates a new Clientset for the given config. 56 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 57 | // NewForConfig will generate a rate-limiter in configShallowCopy. 58 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 59 | // where httpClient was generated with rest.HTTPClientFor(c). 60 | func NewForConfig(c *rest.Config) (*Clientset, error) { 61 | configShallowCopy := *c 62 | 63 | if configShallowCopy.UserAgent == "" { 64 | configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() 65 | } 66 | 67 | // share the transport between all clients 68 | httpClient, err := rest.HTTPClientFor(&configShallowCopy) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | return NewForConfigAndClient(&configShallowCopy, httpClient) 74 | } 75 | 76 | // NewForConfigAndClient creates a new Clientset for the given config and http client. 77 | // Note the http client provided takes precedence over the configured transport values. 78 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 79 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy. 80 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { 81 | configShallowCopy := *c 82 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 83 | if configShallowCopy.Burst <= 0 { 84 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 85 | } 86 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 87 | } 88 | 89 | var cs Clientset 90 | var err error 91 | cs.helmV1, err = helmv1.NewForConfigAndClient(&configShallowCopy, httpClient) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) 97 | if err != nil { 98 | return nil, err 99 | } 100 | return &cs, nil 101 | } 102 | 103 | // NewForConfigOrDie creates a new Clientset for the given config and 104 | // panics if there is an error in the config. 105 | func NewForConfigOrDie(c *rest.Config) *Clientset { 106 | cs, err := NewForConfig(c) 107 | if err != nil { 108 | panic(err) 109 | } 110 | return cs 111 | } 112 | 113 | // New creates a new Clientset for the given RESTClient. 114 | func New(c rest.Interface) *Clientset { 115 | var cs Clientset 116 | cs.helmV1 = helmv1.New(c) 117 | 118 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 119 | return &cs 120 | } 121 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | clientset "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned" 23 | helmv1 "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1" 24 | fakehelmv1 "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/fake" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/watch" 27 | "k8s.io/client-go/discovery" 28 | fakediscovery "k8s.io/client-go/discovery/fake" 29 | "k8s.io/client-go/testing" 30 | ) 31 | 32 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 33 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 34 | // without applying any field management, validations and/or defaults. It shouldn't be considered a replacement 35 | // for a real clientset and is mostly useful in simple unit tests. 36 | // 37 | // DEPRECATED: NewClientset replaces this with support for field management, which significantly improves 38 | // server side apply testing. NewClientset is only available when apply configurations are generated (e.g. 39 | // via --with-applyconfig). 40 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 41 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 42 | for _, obj := range objects { 43 | if err := o.Add(obj); err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | cs := &Clientset{tracker: o} 49 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 50 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 51 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 52 | gvr := action.GetResource() 53 | ns := action.GetNamespace() 54 | watch, err := o.Watch(gvr, ns) 55 | if err != nil { 56 | return false, nil, err 57 | } 58 | return true, watch, nil 59 | }) 60 | 61 | return cs 62 | } 63 | 64 | // Clientset implements clientset.Interface. Meant to be embedded into a 65 | // struct to get a default implementation. This makes faking out just the method 66 | // you want to test easier. 67 | type Clientset struct { 68 | testing.Fake 69 | discovery *fakediscovery.FakeDiscovery 70 | tracker testing.ObjectTracker 71 | } 72 | 73 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 74 | return c.discovery 75 | } 76 | 77 | func (c *Clientset) Tracker() testing.ObjectTracker { 78 | return c.tracker 79 | } 80 | 81 | var ( 82 | _ clientset.Interface = &Clientset{} 83 | _ testing.FakeClient = &Clientset{} 84 | ) 85 | 86 | // HelmV1 retrieves the HelmV1Client 87 | func (c *Clientset) HelmV1() helmv1.HelmV1Interface { 88 | return &fakehelmv1.FakeHelmV1{Fake: &c.Fake} 89 | } 90 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | // This package has the automatically generated fake clientset. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | helmv1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 27 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 | ) 29 | 30 | var scheme = runtime.NewScheme() 31 | var codecs = serializer.NewCodecFactory(scheme) 32 | 33 | var localSchemeBuilder = runtime.SchemeBuilder{ 34 | helmv1.AddToScheme, 35 | } 36 | 37 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 38 | // of clientsets, like in: 39 | // 40 | // import ( 41 | // "k8s.io/client-go/kubernetes" 42 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 43 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 44 | // ) 45 | // 46 | // kclientset, _ := kubernetes.NewForConfig(c) 47 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 48 | // 49 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 50 | // correctly. 51 | var AddToScheme = localSchemeBuilder.AddToScheme 52 | 53 | func init() { 54 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 55 | utilruntime.Must(AddToScheme(scheme)) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | // This package contains the scheme of the automatically generated clientset. 20 | package scheme 21 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package scheme 20 | 21 | import ( 22 | helmv1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 27 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 | ) 29 | 30 | var Scheme = runtime.NewScheme() 31 | var Codecs = serializer.NewCodecFactory(Scheme) 32 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 33 | var localSchemeBuilder = runtime.SchemeBuilder{ 34 | helmv1.AddToScheme, 35 | } 36 | 37 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 38 | // of clientsets, like in: 39 | // 40 | // import ( 41 | // "k8s.io/client-go/kubernetes" 42 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 43 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 44 | // ) 45 | // 46 | // kclientset, _ := kubernetes.NewForConfig(c) 47 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 48 | // 49 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 50 | // correctly. 51 | var AddToScheme = localSchemeBuilder.AddToScheme 52 | 53 | func init() { 54 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 55 | utilruntime.Must(AddToScheme(Scheme)) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1 21 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/fake/fake_helm.cattle.io_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | v1 "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1" 23 | rest "k8s.io/client-go/rest" 24 | testing "k8s.io/client-go/testing" 25 | ) 26 | 27 | type FakeHelmV1 struct { 28 | *testing.Fake 29 | } 30 | 31 | func (c *FakeHelmV1) HelmCharts(namespace string) v1.HelmChartInterface { 32 | return &FakeHelmCharts{c, namespace} 33 | } 34 | 35 | func (c *FakeHelmV1) HelmChartConfigs(namespace string) v1.HelmChartConfigInterface { 36 | return &FakeHelmChartConfigs{c, namespace} 37 | } 38 | 39 | // RESTClient returns a RESTClient that is used to communicate 40 | // with API server by this client implementation. 41 | func (c *FakeHelmV1) RESTClient() rest.Interface { 42 | var ret *rest.RESTClient 43 | return ret 44 | } 45 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/fake/fake_helmchart.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | "context" 23 | 24 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | labels "k8s.io/apimachinery/pkg/labels" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | testing "k8s.io/client-go/testing" 30 | ) 31 | 32 | // FakeHelmCharts implements HelmChartInterface 33 | type FakeHelmCharts struct { 34 | Fake *FakeHelmV1 35 | ns string 36 | } 37 | 38 | var helmchartsResource = v1.SchemeGroupVersion.WithResource("helmcharts") 39 | 40 | var helmchartsKind = v1.SchemeGroupVersion.WithKind("HelmChart") 41 | 42 | // Get takes name of the helmChart, and returns the corresponding helmChart object, and an error if there is any. 43 | func (c *FakeHelmCharts) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.HelmChart, err error) { 44 | emptyResult := &v1.HelmChart{} 45 | obj, err := c.Fake. 46 | Invokes(testing.NewGetActionWithOptions(helmchartsResource, c.ns, name, options), emptyResult) 47 | 48 | if obj == nil { 49 | return emptyResult, err 50 | } 51 | return obj.(*v1.HelmChart), err 52 | } 53 | 54 | // List takes label and field selectors, and returns the list of HelmCharts that match those selectors. 55 | func (c *FakeHelmCharts) List(ctx context.Context, opts metav1.ListOptions) (result *v1.HelmChartList, err error) { 56 | emptyResult := &v1.HelmChartList{} 57 | obj, err := c.Fake. 58 | Invokes(testing.NewListActionWithOptions(helmchartsResource, helmchartsKind, c.ns, opts), emptyResult) 59 | 60 | if obj == nil { 61 | return emptyResult, err 62 | } 63 | 64 | label, _, _ := testing.ExtractFromListOptions(opts) 65 | if label == nil { 66 | label = labels.Everything() 67 | } 68 | list := &v1.HelmChartList{ListMeta: obj.(*v1.HelmChartList).ListMeta} 69 | for _, item := range obj.(*v1.HelmChartList).Items { 70 | if label.Matches(labels.Set(item.Labels)) { 71 | list.Items = append(list.Items, item) 72 | } 73 | } 74 | return list, err 75 | } 76 | 77 | // Watch returns a watch.Interface that watches the requested helmCharts. 78 | func (c *FakeHelmCharts) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 79 | return c.Fake. 80 | InvokesWatch(testing.NewWatchActionWithOptions(helmchartsResource, c.ns, opts)) 81 | 82 | } 83 | 84 | // Create takes the representation of a helmChart and creates it. Returns the server's representation of the helmChart, and an error, if there is any. 85 | func (c *FakeHelmCharts) Create(ctx context.Context, helmChart *v1.HelmChart, opts metav1.CreateOptions) (result *v1.HelmChart, err error) { 86 | emptyResult := &v1.HelmChart{} 87 | obj, err := c.Fake. 88 | Invokes(testing.NewCreateActionWithOptions(helmchartsResource, c.ns, helmChart, opts), emptyResult) 89 | 90 | if obj == nil { 91 | return emptyResult, err 92 | } 93 | return obj.(*v1.HelmChart), err 94 | } 95 | 96 | // Update takes the representation of a helmChart and updates it. Returns the server's representation of the helmChart, and an error, if there is any. 97 | func (c *FakeHelmCharts) Update(ctx context.Context, helmChart *v1.HelmChart, opts metav1.UpdateOptions) (result *v1.HelmChart, err error) { 98 | emptyResult := &v1.HelmChart{} 99 | obj, err := c.Fake. 100 | Invokes(testing.NewUpdateActionWithOptions(helmchartsResource, c.ns, helmChart, opts), emptyResult) 101 | 102 | if obj == nil { 103 | return emptyResult, err 104 | } 105 | return obj.(*v1.HelmChart), err 106 | } 107 | 108 | // UpdateStatus was generated because the type contains a Status member. 109 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 110 | func (c *FakeHelmCharts) UpdateStatus(ctx context.Context, helmChart *v1.HelmChart, opts metav1.UpdateOptions) (result *v1.HelmChart, err error) { 111 | emptyResult := &v1.HelmChart{} 112 | obj, err := c.Fake. 113 | Invokes(testing.NewUpdateSubresourceActionWithOptions(helmchartsResource, "status", c.ns, helmChart, opts), emptyResult) 114 | 115 | if obj == nil { 116 | return emptyResult, err 117 | } 118 | return obj.(*v1.HelmChart), err 119 | } 120 | 121 | // Delete takes name of the helmChart and deletes it. Returns an error if one occurs. 122 | func (c *FakeHelmCharts) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 123 | _, err := c.Fake. 124 | Invokes(testing.NewDeleteActionWithOptions(helmchartsResource, c.ns, name, opts), &v1.HelmChart{}) 125 | 126 | return err 127 | } 128 | 129 | // DeleteCollection deletes a collection of objects. 130 | func (c *FakeHelmCharts) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { 131 | action := testing.NewDeleteCollectionActionWithOptions(helmchartsResource, c.ns, opts, listOpts) 132 | 133 | _, err := c.Fake.Invokes(action, &v1.HelmChartList{}) 134 | return err 135 | } 136 | 137 | // Patch applies the patch and returns the patched helmChart. 138 | func (c *FakeHelmCharts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.HelmChart, err error) { 139 | emptyResult := &v1.HelmChart{} 140 | obj, err := c.Fake. 141 | Invokes(testing.NewPatchSubresourceActionWithOptions(helmchartsResource, c.ns, name, pt, data, opts, subresources...), emptyResult) 142 | 143 | if obj == nil { 144 | return emptyResult, err 145 | } 146 | return obj.(*v1.HelmChart), err 147 | } 148 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/fake/fake_helmchartconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | "context" 23 | 24 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | labels "k8s.io/apimachinery/pkg/labels" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | testing "k8s.io/client-go/testing" 30 | ) 31 | 32 | // FakeHelmChartConfigs implements HelmChartConfigInterface 33 | type FakeHelmChartConfigs struct { 34 | Fake *FakeHelmV1 35 | ns string 36 | } 37 | 38 | var helmchartconfigsResource = v1.SchemeGroupVersion.WithResource("helmchartconfigs") 39 | 40 | var helmchartconfigsKind = v1.SchemeGroupVersion.WithKind("HelmChartConfig") 41 | 42 | // Get takes name of the helmChartConfig, and returns the corresponding helmChartConfig object, and an error if there is any. 43 | func (c *FakeHelmChartConfigs) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.HelmChartConfig, err error) { 44 | emptyResult := &v1.HelmChartConfig{} 45 | obj, err := c.Fake. 46 | Invokes(testing.NewGetActionWithOptions(helmchartconfigsResource, c.ns, name, options), emptyResult) 47 | 48 | if obj == nil { 49 | return emptyResult, err 50 | } 51 | return obj.(*v1.HelmChartConfig), err 52 | } 53 | 54 | // List takes label and field selectors, and returns the list of HelmChartConfigs that match those selectors. 55 | func (c *FakeHelmChartConfigs) List(ctx context.Context, opts metav1.ListOptions) (result *v1.HelmChartConfigList, err error) { 56 | emptyResult := &v1.HelmChartConfigList{} 57 | obj, err := c.Fake. 58 | Invokes(testing.NewListActionWithOptions(helmchartconfigsResource, helmchartconfigsKind, c.ns, opts), emptyResult) 59 | 60 | if obj == nil { 61 | return emptyResult, err 62 | } 63 | 64 | label, _, _ := testing.ExtractFromListOptions(opts) 65 | if label == nil { 66 | label = labels.Everything() 67 | } 68 | list := &v1.HelmChartConfigList{ListMeta: obj.(*v1.HelmChartConfigList).ListMeta} 69 | for _, item := range obj.(*v1.HelmChartConfigList).Items { 70 | if label.Matches(labels.Set(item.Labels)) { 71 | list.Items = append(list.Items, item) 72 | } 73 | } 74 | return list, err 75 | } 76 | 77 | // Watch returns a watch.Interface that watches the requested helmChartConfigs. 78 | func (c *FakeHelmChartConfigs) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 79 | return c.Fake. 80 | InvokesWatch(testing.NewWatchActionWithOptions(helmchartconfigsResource, c.ns, opts)) 81 | 82 | } 83 | 84 | // Create takes the representation of a helmChartConfig and creates it. Returns the server's representation of the helmChartConfig, and an error, if there is any. 85 | func (c *FakeHelmChartConfigs) Create(ctx context.Context, helmChartConfig *v1.HelmChartConfig, opts metav1.CreateOptions) (result *v1.HelmChartConfig, err error) { 86 | emptyResult := &v1.HelmChartConfig{} 87 | obj, err := c.Fake. 88 | Invokes(testing.NewCreateActionWithOptions(helmchartconfigsResource, c.ns, helmChartConfig, opts), emptyResult) 89 | 90 | if obj == nil { 91 | return emptyResult, err 92 | } 93 | return obj.(*v1.HelmChartConfig), err 94 | } 95 | 96 | // Update takes the representation of a helmChartConfig and updates it. Returns the server's representation of the helmChartConfig, and an error, if there is any. 97 | func (c *FakeHelmChartConfigs) Update(ctx context.Context, helmChartConfig *v1.HelmChartConfig, opts metav1.UpdateOptions) (result *v1.HelmChartConfig, err error) { 98 | emptyResult := &v1.HelmChartConfig{} 99 | obj, err := c.Fake. 100 | Invokes(testing.NewUpdateActionWithOptions(helmchartconfigsResource, c.ns, helmChartConfig, opts), emptyResult) 101 | 102 | if obj == nil { 103 | return emptyResult, err 104 | } 105 | return obj.(*v1.HelmChartConfig), err 106 | } 107 | 108 | // Delete takes name of the helmChartConfig and deletes it. Returns an error if one occurs. 109 | func (c *FakeHelmChartConfigs) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 110 | _, err := c.Fake. 111 | Invokes(testing.NewDeleteActionWithOptions(helmchartconfigsResource, c.ns, name, opts), &v1.HelmChartConfig{}) 112 | 113 | return err 114 | } 115 | 116 | // DeleteCollection deletes a collection of objects. 117 | func (c *FakeHelmChartConfigs) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { 118 | action := testing.NewDeleteCollectionActionWithOptions(helmchartconfigsResource, c.ns, opts, listOpts) 119 | 120 | _, err := c.Fake.Invokes(action, &v1.HelmChartConfigList{}) 121 | return err 122 | } 123 | 124 | // Patch applies the patch and returns the patched helmChartConfig. 125 | func (c *FakeHelmChartConfigs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.HelmChartConfig, err error) { 126 | emptyResult := &v1.HelmChartConfig{} 127 | obj, err := c.Fake. 128 | Invokes(testing.NewPatchSubresourceActionWithOptions(helmchartconfigsResource, c.ns, name, pt, data, opts, subresources...), emptyResult) 129 | 130 | if obj == nil { 131 | return emptyResult, err 132 | } 133 | return obj.(*v1.HelmChartConfig), err 134 | } 135 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | type HelmChartExpansion interface{} 22 | 23 | type HelmChartConfigExpansion interface{} 24 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/helm.cattle.io_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "net/http" 23 | 24 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 25 | "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/scheme" 26 | rest "k8s.io/client-go/rest" 27 | ) 28 | 29 | type HelmV1Interface interface { 30 | RESTClient() rest.Interface 31 | HelmChartsGetter 32 | HelmChartConfigsGetter 33 | } 34 | 35 | // HelmV1Client is used to interact with features provided by the helm.cattle.io group. 36 | type HelmV1Client struct { 37 | restClient rest.Interface 38 | } 39 | 40 | func (c *HelmV1Client) HelmCharts(namespace string) HelmChartInterface { 41 | return newHelmCharts(c, namespace) 42 | } 43 | 44 | func (c *HelmV1Client) HelmChartConfigs(namespace string) HelmChartConfigInterface { 45 | return newHelmChartConfigs(c, namespace) 46 | } 47 | 48 | // NewForConfig creates a new HelmV1Client for the given config. 49 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 50 | // where httpClient was generated with rest.HTTPClientFor(c). 51 | func NewForConfig(c *rest.Config) (*HelmV1Client, error) { 52 | config := *c 53 | if err := setConfigDefaults(&config); err != nil { 54 | return nil, err 55 | } 56 | httpClient, err := rest.HTTPClientFor(&config) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return NewForConfigAndClient(&config, httpClient) 61 | } 62 | 63 | // NewForConfigAndClient creates a new HelmV1Client for the given config and http client. 64 | // Note the http client provided takes precedence over the configured transport values. 65 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*HelmV1Client, error) { 66 | config := *c 67 | if err := setConfigDefaults(&config); err != nil { 68 | return nil, err 69 | } 70 | client, err := rest.RESTClientForConfigAndClient(&config, h) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return &HelmV1Client{client}, nil 75 | } 76 | 77 | // NewForConfigOrDie creates a new HelmV1Client for the given config and 78 | // panics if there is an error in the config. 79 | func NewForConfigOrDie(c *rest.Config) *HelmV1Client { 80 | client, err := NewForConfig(c) 81 | if err != nil { 82 | panic(err) 83 | } 84 | return client 85 | } 86 | 87 | // New creates a new HelmV1Client for the given RESTClient. 88 | func New(c rest.Interface) *HelmV1Client { 89 | return &HelmV1Client{c} 90 | } 91 | 92 | func setConfigDefaults(config *rest.Config) error { 93 | gv := v1.SchemeGroupVersion 94 | config.GroupVersion = &gv 95 | config.APIPath = "/apis" 96 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 97 | 98 | if config.UserAgent == "" { 99 | config.UserAgent = rest.DefaultKubernetesUserAgent() 100 | } 101 | 102 | return nil 103 | } 104 | 105 | // RESTClient returns a RESTClient that is used to communicate 106 | // with API server by this client implementation. 107 | func (c *HelmV1Client) RESTClient() rest.Interface { 108 | if c == nil { 109 | return nil 110 | } 111 | return c.restClient 112 | } 113 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/helmchart.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "context" 23 | 24 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 25 | scheme "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/scheme" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | gentype "k8s.io/client-go/gentype" 30 | ) 31 | 32 | // HelmChartsGetter has a method to return a HelmChartInterface. 33 | // A group's client should implement this interface. 34 | type HelmChartsGetter interface { 35 | HelmCharts(namespace string) HelmChartInterface 36 | } 37 | 38 | // HelmChartInterface has methods to work with HelmChart resources. 39 | type HelmChartInterface interface { 40 | Create(ctx context.Context, helmChart *v1.HelmChart, opts metav1.CreateOptions) (*v1.HelmChart, error) 41 | Update(ctx context.Context, helmChart *v1.HelmChart, opts metav1.UpdateOptions) (*v1.HelmChart, error) 42 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 43 | UpdateStatus(ctx context.Context, helmChart *v1.HelmChart, opts metav1.UpdateOptions) (*v1.HelmChart, error) 44 | Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error 45 | DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error 46 | Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.HelmChart, error) 47 | List(ctx context.Context, opts metav1.ListOptions) (*v1.HelmChartList, error) 48 | Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) 49 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.HelmChart, err error) 50 | HelmChartExpansion 51 | } 52 | 53 | // helmCharts implements HelmChartInterface 54 | type helmCharts struct { 55 | *gentype.ClientWithList[*v1.HelmChart, *v1.HelmChartList] 56 | } 57 | 58 | // newHelmCharts returns a HelmCharts 59 | func newHelmCharts(c *HelmV1Client, namespace string) *helmCharts { 60 | return &helmCharts{ 61 | gentype.NewClientWithList[*v1.HelmChart, *v1.HelmChartList]( 62 | "helmcharts", 63 | c.RESTClient(), 64 | scheme.ParameterCodec, 65 | namespace, 66 | func() *v1.HelmChart { return &v1.HelmChart{} }, 67 | func() *v1.HelmChartList { return &v1.HelmChartList{} }), 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/helmchartconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "context" 23 | 24 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 25 | scheme "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/scheme" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | gentype "k8s.io/client-go/gentype" 30 | ) 31 | 32 | // HelmChartConfigsGetter has a method to return a HelmChartConfigInterface. 33 | // A group's client should implement this interface. 34 | type HelmChartConfigsGetter interface { 35 | HelmChartConfigs(namespace string) HelmChartConfigInterface 36 | } 37 | 38 | // HelmChartConfigInterface has methods to work with HelmChartConfig resources. 39 | type HelmChartConfigInterface interface { 40 | Create(ctx context.Context, helmChartConfig *v1.HelmChartConfig, opts metav1.CreateOptions) (*v1.HelmChartConfig, error) 41 | Update(ctx context.Context, helmChartConfig *v1.HelmChartConfig, opts metav1.UpdateOptions) (*v1.HelmChartConfig, error) 42 | Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error 43 | DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error 44 | Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.HelmChartConfig, error) 45 | List(ctx context.Context, opts metav1.ListOptions) (*v1.HelmChartConfigList, error) 46 | Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) 47 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.HelmChartConfig, err error) 48 | HelmChartConfigExpansion 49 | } 50 | 51 | // helmChartConfigs implements HelmChartConfigInterface 52 | type helmChartConfigs struct { 53 | *gentype.ClientWithList[*v1.HelmChartConfig, *v1.HelmChartConfigList] 54 | } 55 | 56 | // newHelmChartConfigs returns a HelmChartConfigs 57 | func newHelmChartConfigs(c *HelmV1Client, namespace string) *helmChartConfigs { 58 | return &helmChartConfigs{ 59 | gentype.NewClientWithList[*v1.HelmChartConfig, *v1.HelmChartConfigList]( 60 | "helmchartconfigs", 61 | c.RESTClient(), 62 | scheme.ParameterCodec, 63 | namespace, 64 | func() *v1.HelmChartConfig { return &v1.HelmChartConfig{} }, 65 | func() *v1.HelmChartConfigList { return &v1.HelmChartConfigList{} }), 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/generated/controllers/helm.cattle.io/factory.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package helm 20 | 21 | import ( 22 | "github.com/rancher/lasso/pkg/controller" 23 | "github.com/rancher/wrangler/v3/pkg/generic" 24 | "k8s.io/client-go/rest" 25 | ) 26 | 27 | type Factory struct { 28 | *generic.Factory 29 | } 30 | 31 | func NewFactoryFromConfigOrDie(config *rest.Config) *Factory { 32 | f, err := NewFactoryFromConfig(config) 33 | if err != nil { 34 | panic(err) 35 | } 36 | return f 37 | } 38 | 39 | func NewFactoryFromConfig(config *rest.Config) (*Factory, error) { 40 | return NewFactoryFromConfigWithOptions(config, nil) 41 | } 42 | 43 | func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) { 44 | return NewFactoryFromConfigWithOptions(config, &FactoryOptions{ 45 | Namespace: namespace, 46 | }) 47 | } 48 | 49 | type FactoryOptions = generic.FactoryOptions 50 | 51 | func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) { 52 | f, err := generic.NewFactoryFromConfigWithOptions(config, opts) 53 | return &Factory{ 54 | Factory: f, 55 | }, err 56 | } 57 | 58 | func NewFactoryFromConfigWithOptionsOrDie(config *rest.Config, opts *FactoryOptions) *Factory { 59 | f, err := NewFactoryFromConfigWithOptions(config, opts) 60 | if err != nil { 61 | panic(err) 62 | } 63 | return f 64 | } 65 | 66 | func (c *Factory) Helm() Interface { 67 | return New(c.ControllerFactory()) 68 | } 69 | 70 | func (c *Factory) WithAgent(userAgent string) Interface { 71 | return New(controller.NewSharedControllerFactoryWithAgent(userAgent, c.ControllerFactory())) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/generated/controllers/helm.cattle.io/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package helm 20 | 21 | import ( 22 | v1 "github.com/k3s-io/helm-controller/pkg/generated/controllers/helm.cattle.io/v1" 23 | "github.com/rancher/lasso/pkg/controller" 24 | ) 25 | 26 | type Interface interface { 27 | V1() v1.Interface 28 | } 29 | 30 | type group struct { 31 | controllerFactory controller.SharedControllerFactory 32 | } 33 | 34 | // New returns a new Interface. 35 | func New(controllerFactory controller.SharedControllerFactory) Interface { 36 | return &group{ 37 | controllerFactory: controllerFactory, 38 | } 39 | } 40 | 41 | func (g *group) V1() v1.Interface { 42 | return v1.New(g.controllerFactory) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/generated/controllers/helm.cattle.io/v1/helmchart.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "context" 23 | "sync" 24 | "time" 25 | 26 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 27 | "github.com/rancher/wrangler/v3/pkg/apply" 28 | "github.com/rancher/wrangler/v3/pkg/condition" 29 | "github.com/rancher/wrangler/v3/pkg/generic" 30 | "github.com/rancher/wrangler/v3/pkg/kv" 31 | "k8s.io/apimachinery/pkg/api/equality" 32 | "k8s.io/apimachinery/pkg/api/errors" 33 | "k8s.io/apimachinery/pkg/runtime" 34 | "k8s.io/apimachinery/pkg/runtime/schema" 35 | ) 36 | 37 | // HelmChartController interface for managing HelmChart resources. 38 | type HelmChartController interface { 39 | generic.ControllerInterface[*v1.HelmChart, *v1.HelmChartList] 40 | } 41 | 42 | // HelmChartClient interface for managing HelmChart resources in Kubernetes. 43 | type HelmChartClient interface { 44 | generic.ClientInterface[*v1.HelmChart, *v1.HelmChartList] 45 | } 46 | 47 | // HelmChartCache interface for retrieving HelmChart resources in memory. 48 | type HelmChartCache interface { 49 | generic.CacheInterface[*v1.HelmChart] 50 | } 51 | 52 | // HelmChartStatusHandler is executed for every added or modified HelmChart. Should return the new status to be updated 53 | type HelmChartStatusHandler func(obj *v1.HelmChart, status v1.HelmChartStatus) (v1.HelmChartStatus, error) 54 | 55 | // HelmChartGeneratingHandler is the top-level handler that is executed for every HelmChart event. It extends HelmChartStatusHandler by a returning a slice of child objects to be passed to apply.Apply 56 | type HelmChartGeneratingHandler func(obj *v1.HelmChart, status v1.HelmChartStatus) ([]runtime.Object, v1.HelmChartStatus, error) 57 | 58 | // RegisterHelmChartStatusHandler configures a HelmChartController to execute a HelmChartStatusHandler for every events observed. 59 | // If a non-empty condition is provided, it will be updated in the status conditions for every handler execution 60 | func RegisterHelmChartStatusHandler(ctx context.Context, controller HelmChartController, condition condition.Cond, name string, handler HelmChartStatusHandler) { 61 | statusHandler := &helmChartStatusHandler{ 62 | client: controller, 63 | condition: condition, 64 | handler: handler, 65 | } 66 | controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) 67 | } 68 | 69 | // RegisterHelmChartGeneratingHandler configures a HelmChartController to execute a HelmChartGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. 70 | // If a non-empty condition is provided, it will be updated in the status conditions for every handler execution 71 | func RegisterHelmChartGeneratingHandler(ctx context.Context, controller HelmChartController, apply apply.Apply, 72 | condition condition.Cond, name string, handler HelmChartGeneratingHandler, opts *generic.GeneratingHandlerOptions) { 73 | statusHandler := &helmChartGeneratingHandler{ 74 | HelmChartGeneratingHandler: handler, 75 | apply: apply, 76 | name: name, 77 | gvk: controller.GroupVersionKind(), 78 | } 79 | if opts != nil { 80 | statusHandler.opts = *opts 81 | } 82 | controller.OnChange(ctx, name, statusHandler.Remove) 83 | RegisterHelmChartStatusHandler(ctx, controller, condition, name, statusHandler.Handle) 84 | } 85 | 86 | type helmChartStatusHandler struct { 87 | client HelmChartClient 88 | condition condition.Cond 89 | handler HelmChartStatusHandler 90 | } 91 | 92 | // sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API 93 | func (a *helmChartStatusHandler) sync(key string, obj *v1.HelmChart) (*v1.HelmChart, error) { 94 | if obj == nil { 95 | return obj, nil 96 | } 97 | 98 | origStatus := obj.Status.DeepCopy() 99 | obj = obj.DeepCopy() 100 | newStatus, err := a.handler(obj, obj.Status) 101 | if err != nil { 102 | // Revert to old status on error 103 | newStatus = *origStatus.DeepCopy() 104 | } 105 | 106 | if a.condition != "" { 107 | if errors.IsConflict(err) { 108 | a.condition.SetError(&newStatus, "", nil) 109 | } else { 110 | a.condition.SetError(&newStatus, "", err) 111 | } 112 | } 113 | if !equality.Semantic.DeepEqual(origStatus, &newStatus) { 114 | if a.condition != "" { 115 | // Since status has changed, update the lastUpdatedTime 116 | a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) 117 | } 118 | 119 | var newErr error 120 | obj.Status = newStatus 121 | newObj, newErr := a.client.UpdateStatus(obj) 122 | if err == nil { 123 | err = newErr 124 | } 125 | if newErr == nil { 126 | obj = newObj 127 | } 128 | } 129 | return obj, err 130 | } 131 | 132 | type helmChartGeneratingHandler struct { 133 | HelmChartGeneratingHandler 134 | apply apply.Apply 135 | opts generic.GeneratingHandlerOptions 136 | gvk schema.GroupVersionKind 137 | name string 138 | seen sync.Map 139 | } 140 | 141 | // Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied 142 | func (a *helmChartGeneratingHandler) Remove(key string, obj *v1.HelmChart) (*v1.HelmChart, error) { 143 | if obj != nil { 144 | return obj, nil 145 | } 146 | 147 | obj = &v1.HelmChart{} 148 | obj.Namespace, obj.Name = kv.RSplit(key, "/") 149 | obj.SetGroupVersionKind(a.gvk) 150 | 151 | if a.opts.UniqueApplyForResourceVersion { 152 | a.seen.Delete(key) 153 | } 154 | 155 | return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). 156 | WithOwner(obj). 157 | WithSetID(a.name). 158 | ApplyObjects() 159 | } 160 | 161 | // Handle executes the configured HelmChartGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource 162 | func (a *helmChartGeneratingHandler) Handle(obj *v1.HelmChart, status v1.HelmChartStatus) (v1.HelmChartStatus, error) { 163 | if !obj.DeletionTimestamp.IsZero() { 164 | return status, nil 165 | } 166 | 167 | objs, newStatus, err := a.HelmChartGeneratingHandler(obj, status) 168 | if err != nil { 169 | return newStatus, err 170 | } 171 | if !a.isNewResourceVersion(obj) { 172 | return newStatus, nil 173 | } 174 | 175 | err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). 176 | WithOwner(obj). 177 | WithSetID(a.name). 178 | ApplyObjects(objs...) 179 | if err != nil { 180 | return newStatus, err 181 | } 182 | a.storeResourceVersion(obj) 183 | return newStatus, nil 184 | } 185 | 186 | // isNewResourceVersion detects if a specific resource version was already successfully processed. 187 | // Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions 188 | func (a *helmChartGeneratingHandler) isNewResourceVersion(obj *v1.HelmChart) bool { 189 | if !a.opts.UniqueApplyForResourceVersion { 190 | return true 191 | } 192 | 193 | // Apply once per resource version 194 | key := obj.Namespace + "/" + obj.Name 195 | previous, ok := a.seen.Load(key) 196 | return !ok || previous != obj.ResourceVersion 197 | } 198 | 199 | // storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed 200 | // Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions 201 | func (a *helmChartGeneratingHandler) storeResourceVersion(obj *v1.HelmChart) { 202 | if !a.opts.UniqueApplyForResourceVersion { 203 | return 204 | } 205 | 206 | key := obj.Namespace + "/" + obj.Name 207 | a.seen.Store(key, obj.ResourceVersion) 208 | } 209 | -------------------------------------------------------------------------------- /pkg/generated/controllers/helm.cattle.io/v1/helmchartconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 23 | "github.com/rancher/wrangler/v3/pkg/generic" 24 | ) 25 | 26 | // HelmChartConfigController interface for managing HelmChartConfig resources. 27 | type HelmChartConfigController interface { 28 | generic.ControllerInterface[*v1.HelmChartConfig, *v1.HelmChartConfigList] 29 | } 30 | 31 | // HelmChartConfigClient interface for managing HelmChartConfig resources in Kubernetes. 32 | type HelmChartConfigClient interface { 33 | generic.ClientInterface[*v1.HelmChartConfig, *v1.HelmChartConfigList] 34 | } 35 | 36 | // HelmChartConfigCache interface for retrieving HelmChartConfig resources in memory. 37 | type HelmChartConfigCache interface { 38 | generic.CacheInterface[*v1.HelmChartConfig] 39 | } 40 | -------------------------------------------------------------------------------- /pkg/generated/controllers/helm.cattle.io/v1/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by main. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 23 | "github.com/rancher/lasso/pkg/controller" 24 | "github.com/rancher/wrangler/v3/pkg/generic" 25 | "github.com/rancher/wrangler/v3/pkg/schemes" 26 | "k8s.io/apimachinery/pkg/runtime/schema" 27 | ) 28 | 29 | func init() { 30 | schemes.Register(v1.AddToScheme) 31 | } 32 | 33 | type Interface interface { 34 | HelmChart() HelmChartController 35 | HelmChartConfig() HelmChartConfigController 36 | } 37 | 38 | func New(controllerFactory controller.SharedControllerFactory) Interface { 39 | return &version{ 40 | controllerFactory: controllerFactory, 41 | } 42 | } 43 | 44 | type version struct { 45 | controllerFactory controller.SharedControllerFactory 46 | } 47 | 48 | func (v *version) HelmChart() HelmChartController { 49 | return generic.NewController[*v1.HelmChart, *v1.HelmChartList](schema.GroupVersionKind{Group: "helm.cattle.io", Version: "v1", Kind: "HelmChart"}, "helmcharts", true, v.controllerFactory) 50 | } 51 | 52 | func (v *version) HelmChartConfig() HelmChartConfigController { 53 | return generic.NewController[*v1.HelmChartConfig, *v1.HelmChartConfigList](schema.GroupVersionKind{Group: "helm.cattle.io", Version: "v1", Kind: "HelmChartConfig"}, "helmchartconfigs", true, v.controllerFactory) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/remove/handler.go: -------------------------------------------------------------------------------- 1 | package remove 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/rancher/wrangler/v3/pkg/generic" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | ) 9 | 10 | // Controller is an interface that allows the ScopedOnRemoveHandler to register a generic RemoveHandler 11 | type Controller interface { 12 | AddGenericHandler(ctx context.Context, name string, handler generic.Handler) 13 | Updater() generic.Updater 14 | } 15 | 16 | // ScopeFunc is a function that determines whether the ScopedOnRemoveHandler should manage the lifecycle of the given object 17 | type ScopeFunc func(key string, obj runtime.Object) (bool, error) 18 | 19 | // RegisterScopedOnRemoveHandler registers a handler that does the same thing as an OnRemove handler but only applies finalizers or sync logic 20 | // to objects that pass the provided scopeFunc; this ensures that finalizers are not added to all resources across an entire cluster but are 21 | // instead only scoped to resources that this controller is meant to watch. 22 | // 23 | // TODO: move this to rancher/wrangler as a generic construct to be used across multiple controllers as part of the auto-generated code 24 | func RegisterScopedOnRemoveHandler(ctx context.Context, controller Controller, name string, scopeFunc ScopeFunc, handler generic.Handler) { 25 | onRemove := generic.NewRemoveHandler(name, controller.Updater(), handler) 26 | controller.AddGenericHandler(ctx, name, func(key string, obj runtime.Object) (runtime.Object, error) { 27 | if obj == nil { 28 | return nil, nil 29 | } 30 | isScoped, err := scopeFunc(key, obj) 31 | if err != nil { 32 | return obj, err 33 | } 34 | if !isScoped { 35 | return obj, nil 36 | } 37 | return onRemove(key, obj) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import "fmt" 4 | 5 | var ( 6 | Version = "v0.0.0-dev" 7 | GitCommit = "HEAD" 8 | ) 9 | 10 | func FriendlyVersion() string { 11 | return fmt.Sprintf("%s (%s)", Version, GitCommit) 12 | } 13 | -------------------------------------------------------------------------------- /scripts/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source $(dirname $0)/version 5 | 6 | cd $(dirname $0)/.. 7 | 8 | mkdir -p bin 9 | [ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s" 10 | CGO_ENABLED=0 go build -ldflags "-X github.com/k3s-io/helm-controller/pkg/version.Version=$VERSION $LINKFLAGS" -o bin/helm-controller 11 | -------------------------------------------------------------------------------- /scripts/e2e: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | K3S_WAIT_TIME=15 5 | K3S_NODE_NAME=helmtest-node1 6 | K3S_VERSION=v1.30.2-k3s2 7 | 8 | cd $(dirname $0)/.. 9 | 10 | if [[ ! -f 'bin/helm-controller-image.txt' ]]; then 11 | echo "Run 'make package' first." 12 | exit 1 13 | fi 14 | 15 | setup_k8s(){ 16 | # Drone launches each step in an isolated network. Check and see if there is another bridge network 17 | # that is not the default bridge network. If so, use that network for the k3s node. 18 | BRIDGE_NETWORKS=$(docker network ls --filter driver=bridge --format '{{.Name}}' | grep -v bridge || true) 19 | if [ -n "$BRIDGE_NETWORKS" ]; then 20 | K3S_NODE_NETWORK=$(echo "$BRIDGE_NETWORKS" | head -n 1) 21 | echo "Using network $K3S_NODE_NETWORK" 22 | else 23 | K3S_NODE_NETWORK=bridge 24 | fi 25 | # Using k3s with embedded helm controller disabled 26 | docker pull rancher/k3s:$K3S_VERSION 27 | docker run --detach --privileged --rm --network="$K3S_NODE_NETWORK" -p 6443 --name $K3S_NODE_NAME --hostname $K3S_NODE_NAME rancher/k3s:$K3S_VERSION server --disable-helm-controller --disable=metrics-server,traefik 28 | K3S_NODE_IP=$(docker inspect $K3S_NODE_NAME --format="{{index .NetworkSettings.Networks \"$K3S_NODE_NETWORK\" \"IPAddress\"}}") 29 | sleep $K3S_WAIT_TIME 30 | docker exec $K3S_NODE_NAME sed "s/127.0.0.1/$K3S_NODE_IP/" /etc/rancher/k3s/k3s.yaml > $PWD/kube_config_cluster.yml 31 | } 32 | 33 | teardown_k8s(){ 34 | docker rm -f $K3S_NODE_NAME 35 | } 36 | 37 | load_helm_image(){ 38 | docker image save $HELM_CONTROLLER_IMAGE | docker exec -i $K3S_NODE_NAME ctr --namespace k8s.io image import - 39 | } 40 | 41 | trap teardown_k8s EXIT 42 | setup_k8s 43 | export KUBECONFIG=$PWD/kube_config_cluster.yml 44 | export HELM_CONTROLLER_IMAGE=$(cat bin/helm-controller-image.txt) 45 | load_helm_image 46 | go test -cover -tags=test -v ./test/... 47 | -------------------------------------------------------------------------------- /scripts/package: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source $(dirname $0)/version 5 | 6 | cd $(dirname $0)/.. 7 | 8 | if [[ ! -f 'bin/helm-controller' ]]; then 9 | echo "Run 'make build' first." 10 | exit 1 11 | fi 12 | 13 | mkdir -p dist/artifacts 14 | cp bin/helm-controller dist/artifacts/helm-controller${SUFFIX} 15 | 16 | reset-kustomization() { 17 | git checkout kustomization.yaml 18 | } 19 | 20 | if [ "$ARCH" = "amd64" ]; then 21 | trap reset-kustomization EXIT 22 | go run hack/crdgen.go > manifests/crd.yaml 23 | kustomize edit set image "rancher/helm-controller=${REPO}/helm-controller:${VERSION}" 24 | kustomize build > ./dist/artifacts/deploy-cluster-scoped.yaml 25 | 26 | cat <> kustomization.yaml 27 | patches: 28 | - patch: |- 29 | apiVersion: apps/v1 30 | kind: Deployment 31 | metadata: 32 | name: helm-controller 33 | namespace: helm-controller 34 | spec: 35 | template: 36 | spec: 37 | containers: 38 | - name: helm-controller 39 | env: 40 | - name: NAMESPACE 41 | valueFrom: 42 | fieldRef: 43 | fieldPath: metadata.namespace 44 | EOF 45 | kustomize build > ./dist/artifacts/deploy-namespaced.yaml 46 | fi 47 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd $(dirname $0)/.. 5 | 6 | echo Running tests 7 | go test ./pkg/... -cover -tags=test 8 | -------------------------------------------------------------------------------- /scripts/validate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd $(dirname $0)/.. 5 | 6 | if ! command -v golangci-lint; then 7 | echo Skipping validation: no golangci-lint available 8 | exit 9 | fi 10 | 11 | echo Running validation 12 | 13 | echo Running: golangci-lint 14 | golangci-lint run 15 | 16 | echo Running: go fmt 17 | test -z "$(go fmt ${PACKAGES} | tee /dev/stderr)" 18 | -------------------------------------------------------------------------------- /scripts/version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ARCH=${ARCH:-$(go env GOHOSTARCH)} 4 | SUFFIX="-${ARCH}" 5 | 6 | if [ -n "$(git status --porcelain --untracked-files=no)" ]; then 7 | DIRTY="-dirty" 8 | fi 9 | 10 | COMMIT=$(git rev-parse --short HEAD) 11 | GIT_TAG=${DRONE_TAG:-$(git tag -l --contains HEAD | head -n 1)} 12 | 13 | if [[ -z "$DIRTY" && -n "$GIT_TAG" ]]; then 14 | VERSION=$GIT_TAG 15 | else 16 | VERSION="${COMMIT}${DIRTY}" 17 | fi 18 | 19 | # For docker image 20 | TAG=${TAG:-${VERSION}${SUFFIX}} 21 | REPO=${REPO:-rancher} 22 | 23 | # If tag contains dirty, set to dev 24 | if echo $TAG | grep -q dirty; then 25 | TAG=dev 26 | fi 27 | -------------------------------------------------------------------------------- /test/framework/controller.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "time" 7 | 8 | "github.com/k3s-io/helm-controller/pkg/controllers/chart" 9 | "github.com/sirupsen/logrus" 10 | v1 "k8s.io/api/rbac/v1" 11 | "k8s.io/apimachinery/pkg/api/errors" 12 | "k8s.io/apimachinery/pkg/util/wait" 13 | 14 | appsv1 "k8s.io/api/apps/v1" 15 | corev1 "k8s.io/api/core/v1" 16 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 | ) 18 | 19 | func (f *Framework) setupController(ctx context.Context) error { 20 | _, err := f.ClientSet.CoreV1().Namespaces().Create(ctx, f.getNS(), metav1.CreateOptions{}) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | _, err = f.ClientSet.RbacV1().ClusterRoles().Create(ctx, f.getCr(), metav1.CreateOptions{}) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | _, err = f.ClientSet.RbacV1().ClusterRoleBindings().Create(ctx, f.getCrb(), metav1.CreateOptions{}) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | if err := f.crdFactory.BatchCreateCRDs(ctx, f.crds...).BatchWait(); err != nil { 36 | return err 37 | } 38 | 39 | _, err = f.ClientSet.CoreV1().ServiceAccounts(f.Namespace).Create(ctx, f.getSa(), metav1.CreateOptions{}) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | err = wait.Poll(time.Second, 15*time.Second, func() (bool, error) { 45 | _, err := f.ClientSet.CoreV1().ServiceAccounts(f.Namespace).Get(ctx, f.Name, metav1.GetOptions{}) 46 | if err == nil { 47 | return true, nil 48 | } 49 | 50 | if errors.IsNotFound(err) { 51 | return false, nil 52 | } 53 | 54 | logrus.Printf("Waiting for SA to be ready: %+v\n", err) 55 | return false, err 56 | }) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | _, err = f.ClientSet.AppsV1().Deployments(f.Namespace).Create(context.TODO(), f.getDeployment(), metav1.CreateOptions{}) 62 | return err 63 | } 64 | 65 | func (f *Framework) getNS() *corev1.Namespace { 66 | return &corev1.Namespace{ 67 | ObjectMeta: metav1.ObjectMeta{ 68 | Name: f.Name, 69 | Namespace: f.Namespace, 70 | }, 71 | } 72 | } 73 | 74 | func (f *Framework) getDeployment() *appsv1.Deployment { 75 | return &appsv1.Deployment{ 76 | ObjectMeta: metav1.ObjectMeta{ 77 | Name: f.Name, 78 | Namespace: f.Namespace, 79 | Labels: map[string]string{ 80 | "app": f.Name, 81 | }, 82 | }, 83 | Spec: appsv1.DeploymentSpec{ 84 | Selector: &metav1.LabelSelector{ 85 | MatchLabels: map[string]string{ 86 | "app": f.Name, 87 | }, 88 | }, 89 | Template: corev1.PodTemplateSpec{ 90 | ObjectMeta: metav1.ObjectMeta{ 91 | Labels: map[string]string{ 92 | "app": f.Name, 93 | }, 94 | }, 95 | Spec: corev1.PodSpec{ 96 | ServiceAccountName: f.Name, 97 | Containers: []corev1.Container{ 98 | { 99 | Name: f.Name, 100 | Image: getImage(), 101 | Command: []string{"helm-controller"}, 102 | Args: []string{ 103 | "--namespace", "helm-controller", 104 | "--job-cluster-role", f.Name, 105 | }, 106 | }, 107 | }, 108 | }, 109 | }, 110 | }, 111 | } 112 | } 113 | 114 | func getImage() string { 115 | if img, ok := os.LookupEnv("HELM_CONTROLLER_IMAGE"); ok { 116 | return img 117 | } 118 | return "rancher/helm-controller:latest" 119 | } 120 | 121 | func (f *Framework) getCr() *v1.ClusterRole { 122 | return &v1.ClusterRole{ 123 | ObjectMeta: metav1.ObjectMeta{ 124 | Name: f.Name, 125 | }, 126 | Rules: []v1.PolicyRule{ 127 | { 128 | APIGroups: []string{"*"}, 129 | Resources: []string{"*"}, 130 | Verbs: []string{"*"}, 131 | }, 132 | { 133 | NonResourceURLs: []string{"*"}, 134 | Verbs: []string{"*"}, 135 | }, 136 | }, 137 | } 138 | } 139 | 140 | func (f *Framework) getCrb() *v1.ClusterRoleBinding { 141 | return &v1.ClusterRoleBinding{ 142 | ObjectMeta: metav1.ObjectMeta{ 143 | Name: f.Name, 144 | }, 145 | RoleRef: v1.RoleRef{ 146 | APIGroup: "rbac.authorization.k8s.io", 147 | Kind: "ClusterRole", 148 | Name: f.Name, 149 | }, 150 | Subjects: []v1.Subject{ 151 | { 152 | Kind: "ServiceAccount", 153 | Name: f.Name, 154 | Namespace: f.Namespace, 155 | }, 156 | }, 157 | } 158 | } 159 | 160 | func (f *Framework) getSa() *corev1.ServiceAccount { 161 | return &corev1.ServiceAccount{ 162 | ObjectMeta: metav1.ObjectMeta{ 163 | Name: f.Name, 164 | Namespace: f.Namespace, 165 | }, 166 | } 167 | } 168 | 169 | func (f *Framework) teardownController(ctx context.Context) error { 170 | charts, err := f.ListHelmCharts("helm-test=true", f.Namespace) 171 | if err != nil { 172 | return err 173 | } 174 | for _, item := range charts.Items { 175 | // refresh object before updating; it may have changed since listing 176 | rItem, err := f.GetHelmChart(item.Name, item.Namespace) 177 | if err != nil { 178 | return err 179 | } 180 | 181 | rItem.Finalizers = []string{} 182 | _, err = f.UpdateHelmChart(rItem, f.Namespace) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | err = f.DeleteHelmChart(item.Name, item.Namespace) 188 | if err != nil && !errors.IsNotFound(err) { 189 | return err 190 | } 191 | } 192 | 193 | err = f.ClientSet.RbacV1().ClusterRoleBindings().Delete(ctx, f.Name, metav1.DeleteOptions{}) 194 | if err != nil && !errors.IsNotFound(err) { 195 | return err 196 | } 197 | 198 | err = f.crdFactory.CRDClient.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, chart.CRDName, metav1.DeleteOptions{}) 199 | if err != nil && !errors.IsNotFound(err) { 200 | return err 201 | } 202 | 203 | err = f.ClientSet.CoreV1().Namespaces().Delete(ctx, f.Namespace, metav1.DeleteOptions{}) 204 | if err != nil && !errors.IsNotFound(err) { 205 | return err 206 | } 207 | 208 | return nil 209 | } 210 | -------------------------------------------------------------------------------- /test/framework/framework.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/base64" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "os" 11 | 12 | "k8s.io/client-go/util/retry" 13 | 14 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 15 | "github.com/k3s-io/helm-controller/pkg/controllers/common" 16 | helmcrd "github.com/k3s-io/helm-controller/pkg/crd" 17 | helmcln "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned" 18 | "github.com/onsi/ginkgo/v2" 19 | "github.com/rancher/wrangler/v3/pkg/condition" 20 | "github.com/rancher/wrangler/v3/pkg/crd" 21 | "github.com/sirupsen/logrus" 22 | batchv1 "k8s.io/api/batch/v1" 23 | corev1 "k8s.io/api/core/v1" 24 | apierrors "k8s.io/apimachinery/pkg/api/errors" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/labels" 27 | "k8s.io/apimachinery/pkg/util/intstr" 28 | "k8s.io/client-go/kubernetes" 29 | "k8s.io/client-go/tools/clientcmd" 30 | ) 31 | 32 | var ( 33 | conditionComplete = condition.Cond(batchv1.JobComplete) 34 | conditionFailed = condition.Cond(batchv1.JobFailed) 35 | ) 36 | 37 | type Framework struct { 38 | HelmClientSet *helmcln.Clientset 39 | ClientSet *kubernetes.Clientset 40 | crdFactory *crd.Factory 41 | crds []crd.CRD 42 | Kubeconfig string 43 | Name string 44 | Namespace string 45 | PID int 46 | } 47 | 48 | func New() (*Framework, error) { 49 | framework := &Framework{} 50 | ginkgo.BeforeAll(framework.BeforeAll) 51 | ginkgo.AfterAll(framework.AfterAll) 52 | return framework, nil 53 | } 54 | 55 | func (f *Framework) BeforeAll() { 56 | f.beforeFramework() 57 | err := f.setupController(context.TODO()) 58 | if err != nil { 59 | errExit("Failed to set up helm controller", err) 60 | } 61 | } 62 | 63 | func (f *Framework) AfterAll() { 64 | if ginkgo.CurrentSpecReport().Failed() { 65 | podList, _ := f.ClientSet.CoreV1().Pods(f.Namespace).List(context.Background(), metav1.ListOptions{}) 66 | for _, pod := range podList.Items { 67 | containerNames := []string{} 68 | for _, container := range pod.Spec.InitContainers { 69 | containerNames = append(containerNames, container.Name) 70 | } 71 | for _, container := range pod.Spec.Containers { 72 | containerNames = append(containerNames, container.Name) 73 | } 74 | for _, container := range containerNames { 75 | reportName := fmt.Sprintf("podlogs-%s-%s", pod.Name, container) 76 | logs := f.ClientSet.CoreV1().Pods(f.Namespace).GetLogs(pod.Name, &corev1.PodLogOptions{Container: container}) 77 | if logStreamer, err := logs.Stream(context.Background()); err == nil { 78 | if podLogs, err := io.ReadAll(logStreamer); err == nil { 79 | ginkgo.AddReportEntry(reportName, string(podLogs)) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | if err := f.teardownController(context.TODO()); err != nil { 86 | errExit("Failed to teardown helm controller", err) 87 | } 88 | } 89 | 90 | func (f *Framework) beforeFramework() { 91 | ginkgo.By("Creating a kubernetes client") 92 | f.Kubeconfig = os.Getenv("KUBECONFIG") 93 | config, err := clientcmd.BuildConfigFromFlags("", f.Kubeconfig) 94 | errExit("Failed to build a rest config from file", err) 95 | helmcln, err := helmcln.NewForConfig(config) 96 | errExit("Failed to initiate helm client", err) 97 | clientset, err := kubernetes.NewForConfig(config) 98 | errExit("Failed to initiate a client set", err) 99 | crdFactory, err := crd.NewFactoryFromClient(config) 100 | errExit("Failed initiate factory client", err) 101 | f.crds = helmcrd.List() 102 | errExit("Failed to construct helm crd", err) 103 | 104 | f.HelmClientSet = helmcln 105 | f.ClientSet = clientset 106 | f.crdFactory = crdFactory 107 | f.Name = common.Name 108 | f.Namespace = common.Name 109 | 110 | } 111 | 112 | func errExit(msg string, err error) { 113 | if err == nil { 114 | return 115 | } 116 | logrus.Panicf("%s: %v", msg, err) 117 | } 118 | 119 | func (f *Framework) NewHelmChart(name, chart, version, helmVersion, valuesContent string, set map[string]intstr.IntOrString) *v1.HelmChart { 120 | return &v1.HelmChart{ 121 | ObjectMeta: metav1.ObjectMeta{ 122 | Name: name, 123 | Namespace: f.Namespace, 124 | Labels: map[string]string{ 125 | "helm-test": "true", 126 | }, 127 | }, 128 | Spec: v1.HelmChartSpec{ 129 | Chart: chart, 130 | Version: version, 131 | Repo: "", 132 | ValuesContent: valuesContent, 133 | Set: set, 134 | HelmVersion: helmVersion, 135 | }, 136 | } 137 | } 138 | 139 | func (f *Framework) NewHelmChartConfig(name, valuesContent string) *v1.HelmChartConfig { 140 | return &v1.HelmChartConfig{ 141 | ObjectMeta: metav1.ObjectMeta{ 142 | Name: name, 143 | Namespace: f.Namespace, 144 | Labels: map[string]string{ 145 | "helm-test": "true", 146 | }, 147 | }, 148 | Spec: v1.HelmChartConfigSpec{ 149 | ValuesContent: valuesContent, 150 | }, 151 | } 152 | } 153 | 154 | func (f *Framework) ListReleases(chart *v1.HelmChart) ([]corev1.Secret, error) { 155 | labelSelector := labels.SelectorFromSet(labels.Set{ 156 | "owner": "helm", 157 | "name": chart.Name, 158 | }) 159 | namespace := chart.Namespace 160 | if chart.Spec.TargetNamespace != "" { 161 | namespace = chart.Spec.TargetNamespace 162 | } 163 | 164 | secretList, err := f.ClientSet.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()}) 165 | 166 | if err != nil { 167 | if apierrors.IsNotFound(err) { 168 | return nil, nil 169 | } 170 | return nil, err 171 | } 172 | 173 | return secretList.Items, nil 174 | } 175 | 176 | func (f *Framework) CreateHelmChart(chart *v1.HelmChart, namespace string) (*v1.HelmChart, error) { 177 | return f.HelmClientSet.HelmV1().HelmCharts(namespace).Create(context.TODO(), chart, metav1.CreateOptions{}) 178 | } 179 | 180 | func (f *Framework) UpdateHelmChart(chart *v1.HelmChart, namespace string) (updated *v1.HelmChart, err error) { 181 | hcs := f.HelmClientSet.HelmV1() 182 | if err = retry.RetryOnConflict(retry.DefaultRetry, func() error { 183 | updated, err = hcs.HelmCharts(namespace).Get(context.TODO(), chart.Name, metav1.GetOptions{}) 184 | if err != nil { 185 | return err 186 | } 187 | updated.Spec = chart.Spec 188 | _, err = hcs.HelmCharts(namespace).Update(context.TODO(), updated, metav1.UpdateOptions{}) 189 | return err 190 | }); err != nil { 191 | updated = nil 192 | } 193 | return 194 | } 195 | 196 | func (f *Framework) DeleteHelmChart(name, namespace string) error { 197 | return f.HelmClientSet.HelmV1().HelmCharts(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) 198 | } 199 | 200 | func (f *Framework) GetHelmChart(name, namespace string) (*v1.HelmChart, error) { 201 | r, err := f.HelmClientSet.HelmV1().HelmCharts(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 202 | if err != nil { 203 | return nil, err 204 | } 205 | return r, nil 206 | } 207 | 208 | func (f *Framework) ListHelmCharts(labelSelector, namespace string) (*v1.HelmChartList, error) { 209 | return f.HelmClientSet.HelmV1().HelmCharts(namespace).List(context.TODO(), metav1.ListOptions{ 210 | LabelSelector: labelSelector, 211 | }) 212 | } 213 | 214 | func (f *Framework) CreateHelmChartConfig(chartConfig *v1.HelmChartConfig, namespace string) (*v1.HelmChartConfig, error) { 215 | return f.HelmClientSet.HelmV1().HelmChartConfigs(namespace).Create(context.TODO(), chartConfig, metav1.CreateOptions{}) 216 | } 217 | 218 | func (f *Framework) DeleteHelmChartConfig(name, namespace string) error { 219 | return f.HelmClientSet.HelmV1().HelmChartConfigs(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) 220 | } 221 | 222 | func (f *Framework) ListChartPods(chart *v1.HelmChart, appName string) ([]corev1.Pod, error) { 223 | labelSelector := labels.SelectorFromSet(labels.Set{ 224 | "app": appName, 225 | "release": chart.Name, 226 | }) 227 | 228 | namespace := chart.Namespace 229 | if chart.Spec.TargetNamespace != "" { 230 | namespace = chart.Spec.TargetNamespace 231 | } 232 | 233 | podList, err := f.ClientSet.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()}) 234 | 235 | if err != nil { 236 | if apierrors.IsNotFound(err) { 237 | return nil, nil 238 | } 239 | return nil, err 240 | } 241 | 242 | return podList.Items, nil 243 | } 244 | 245 | func (f *Framework) GetJob(chart *v1.HelmChart) (*batchv1.Job, error) { 246 | if chart.Status.JobName == "" { 247 | return nil, fmt.Errorf("waiting for job name to be populated") 248 | } 249 | r, err := f.ClientSet.BatchV1().Jobs(chart.Namespace).Get(context.TODO(), chart.Status.JobName, metav1.GetOptions{}) 250 | if err != nil { 251 | return nil, err 252 | } 253 | return r, nil 254 | } 255 | 256 | // GetChartContent returns the base64-encoded chart tarball, 257 | // downloaded from the specified URL. 258 | func (f *Framework) GetChartContent(url string) (string, error) { 259 | resp, err := http.Get(url) 260 | if err != nil { 261 | return "", err 262 | } 263 | if resp.StatusCode != 200 { 264 | return "", fmt.Errorf("unexpected HTTP response: %s", resp.Status) 265 | } 266 | 267 | b := &bytes.Buffer{} 268 | w := base64.NewEncoder(base64.StdEncoding, b) 269 | if _, err := io.Copy(w, resp.Body); err != nil { 270 | return "", err 271 | } 272 | if err := w.Close(); err != nil { 273 | return "", err 274 | } 275 | return string(b.Bytes()), nil 276 | } 277 | 278 | // GetHelmChartCondition returns true if there is a condition on the chart matching the selected type, status, and reason 279 | func (f *Framework) GetHelmChartCondition(chart *v1.HelmChart, condition v1.HelmChartConditionType, status corev1.ConditionStatus, reason string) bool { 280 | for _, v := range chart.Status.Conditions { 281 | if v.Type == condition && v.Status == status && v.Reason == reason { 282 | return true 283 | } 284 | } 285 | return false 286 | } 287 | -------------------------------------------------------------------------------- /test/suite/helm_test.go: -------------------------------------------------------------------------------- 1 | package suite_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | batchv1 "k8s.io/api/batch/v1" 11 | 12 | v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" 13 | "github.com/k3s-io/helm-controller/test/framework" 14 | corev1 "k8s.io/api/core/v1" 15 | apierrors "k8s.io/apimachinery/pkg/api/errors" 16 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 | "k8s.io/apimachinery/pkg/util/intstr" 18 | "k8s.io/utils/ptr" 19 | ) 20 | 21 | var _ = Describe("HelmChart Controller Tests", Ordered, func() { 22 | framework, _ := framework.New() 23 | 24 | Context("When a HelmChart is created", func() { 25 | var ( 26 | err error 27 | chart *v1.HelmChart 28 | ) 29 | BeforeAll(func() { 30 | chart = framework.NewHelmChart("traefik-example", 31 | "stable/traefik", 32 | "1.86.1", 33 | "v3", 34 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 35 | map[string]intstr.IntOrString{ 36 | "rbac.enabled": { 37 | Type: intstr.String, 38 | StrVal: "true", 39 | }, 40 | "ssl.enabled": { 41 | Type: intstr.String, 42 | StrVal: "true", 43 | }, 44 | }) 45 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 46 | Expect(err).ToNot(HaveOccurred()) 47 | }) 48 | 49 | It("Should create a release for the chart", func() { 50 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 51 | }) 52 | 53 | AfterAll(func() { 54 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 55 | Expect(err).ToNot(HaveOccurred()) 56 | 57 | Eventually(func(g Gomega) { 58 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 59 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 60 | 61 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 62 | }) 63 | }) 64 | 65 | Context("When a HelmChart version is updated", func() { 66 | var ( 67 | err error 68 | chart *v1.HelmChart 69 | ) 70 | BeforeAll(func() { 71 | chart = framework.NewHelmChart("traefik-update-example", 72 | "stable/traefik", 73 | "1.86.1", 74 | "v3", 75 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 76 | map[string]intstr.IntOrString{ 77 | "rbac.enabled": { 78 | Type: intstr.String, 79 | StrVal: "true", 80 | }, 81 | "ssl.enabled": { 82 | Type: intstr.String, 83 | StrVal: "true", 84 | }, 85 | }) 86 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 87 | Expect(err).ToNot(HaveOccurred()) 88 | }) 89 | It("Should create a release for the chart", func() { 90 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 91 | }) 92 | It("Should create a new release when the version is changed", func() { 93 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 94 | chart.Spec.Version = "1.86.2" 95 | chart, err = framework.UpdateHelmChart(chart, chart.Namespace) 96 | Expect(err).ToNot(HaveOccurred()) 97 | Expect(chart.Spec.Version).To(Equal("1.86.2")) 98 | 99 | // check for 2 releases, and pod with image specified by new chart version 100 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(2)) 101 | Eventually(framework.ListChartPods, 120*time.Second, 5*time.Second).WithArguments(chart, "traefik").Should( 102 | ContainElement(HaveField("Status.ContainerStatuses", ContainElements(HaveField("Image", ContainSubstring("docker.io/rancher/library-traefik:1.7.20"))))), 103 | ) 104 | }) 105 | AfterAll(func() { 106 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 107 | Expect(err).ToNot(HaveOccurred()) 108 | 109 | Eventually(func(g Gomega) { 110 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 111 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 112 | 113 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 114 | }) 115 | }) 116 | 117 | Context("When a HelmChart version is changed", func() { 118 | var ( 119 | err error 120 | chart *v1.HelmChart 121 | ) 122 | BeforeAll(func() { 123 | chart = framework.NewHelmChart("traefik-update-example-values", 124 | "stable/traefik", 125 | "1.86.1", 126 | "v3", 127 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 128 | map[string]intstr.IntOrString{ 129 | "rbac.enabled": { 130 | Type: intstr.String, 131 | StrVal: "true", 132 | }, 133 | "ssl.enabled": { 134 | Type: intstr.String, 135 | StrVal: "true", 136 | }, 137 | }) 138 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 139 | Expect(err).ToNot(HaveOccurred()) 140 | }) 141 | It("Should create a release for the chart", func() { 142 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 143 | }) 144 | It("Should create a new release when the values are changed", func() { 145 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 146 | chart.Spec.Set["replicas"] = intstr.FromString("3") 147 | chart, err = framework.UpdateHelmChart(chart, framework.Namespace) 148 | Expect(err).ToNot(HaveOccurred()) 149 | Expect(chart.Spec.Set["replicas"]).To(Equal(intstr.FromString("3"))) 150 | 151 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(2)) 152 | }) 153 | AfterAll(func() { 154 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 155 | Expect(err).ToNot(HaveOccurred()) 156 | 157 | Eventually(func(g Gomega) { 158 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 159 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 160 | 161 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 162 | }) 163 | }) 164 | 165 | Context("When a HelmChart specifies a timeout", func() { 166 | var ( 167 | err error 168 | chart *v1.HelmChart 169 | job *batchv1.Job 170 | ) 171 | BeforeAll(func() { 172 | chart = framework.NewHelmChart("traefik-example-timeout", 173 | "stable/traefik", 174 | "1.86.1", 175 | "v3", 176 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 177 | map[string]intstr.IntOrString{ 178 | "rbac.enabled": { 179 | Type: intstr.String, 180 | StrVal: "true", 181 | }, 182 | "ssl.enabled": { 183 | Type: intstr.String, 184 | StrVal: "true", 185 | }, 186 | }) 187 | chart.Spec.Timeout = &metav1.Duration{Duration: time.Minute * 15} 188 | 189 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 190 | Expect(err).ToNot(HaveOccurred()) 191 | }) 192 | It("Should create a release for the chart", func() { 193 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 194 | }) 195 | It("Should have the correct timeout", func() { 196 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 197 | Expect(err).ToNot(HaveOccurred()) 198 | 199 | job, err = framework.GetJob(chart) 200 | Expect(err).ToNot(HaveOccurred()) 201 | Expect(job.Spec.Template.Spec.Containers[0].Env).To(ContainElement( 202 | And( 203 | HaveField("Name", "TIMEOUT"), 204 | HaveField("Value", chart.Spec.Timeout.Duration.String()), 205 | ), 206 | )) 207 | }) 208 | AfterAll(func() { 209 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 210 | Expect(err).ToNot(HaveOccurred()) 211 | 212 | Eventually(func(g Gomega) { 213 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 214 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 215 | 216 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 217 | }) 218 | }) 219 | 220 | Context("When a HelmChart specifies ChartContent", func() { 221 | var ( 222 | err error 223 | chart *v1.HelmChart 224 | ) 225 | BeforeAll(func() { 226 | chart = framework.NewHelmChart("traefik-example-chartcontent", 227 | "", 228 | "1.86.1", 229 | "v3", 230 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 231 | map[string]intstr.IntOrString{ 232 | "rbac.enabled": { 233 | Type: intstr.String, 234 | StrVal: "true", 235 | }, 236 | "ssl.enabled": { 237 | Type: intstr.String, 238 | StrVal: "true", 239 | }, 240 | }) 241 | chart.Spec.ChartContent, err = framework.GetChartContent("https://charts.helm.sh/stable/packages/traefik-1.86.1.tgz") 242 | Expect(err).ToNot(HaveOccurred()) 243 | 244 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 245 | Expect(err).ToNot(HaveOccurred()) 246 | }) 247 | It("Should create a release for the chart", func() { 248 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 249 | }) 250 | AfterAll(func() { 251 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 252 | Expect(err).ToNot(HaveOccurred()) 253 | 254 | Eventually(func(g Gomega) { 255 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 256 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 257 | 258 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 259 | }) 260 | }) 261 | 262 | Context("When a HelmChart has HelmChartConfig", func() { 263 | var ( 264 | err error 265 | chart *v1.HelmChart 266 | chartConfig *v1.HelmChartConfig 267 | ) 268 | BeforeAll(func() { 269 | chart = framework.NewHelmChart("traefik-example", 270 | "stable/traefik", 271 | "1.86.1", 272 | "v3", 273 | "", 274 | nil) 275 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 276 | Expect(err).ToNot(HaveOccurred()) 277 | }) 278 | It("Should create a release for the chart", func() { 279 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 280 | }) 281 | It("Should create a new release when the HelmChartConfig is created", func() { 282 | chartConfig = framework.NewHelmChartConfig(chart.Name, "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n") 283 | chartConfig, err = framework.CreateHelmChartConfig(chartConfig, framework.Namespace) 284 | Expect(err).ToNot(HaveOccurred()) 285 | 286 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(2)) 287 | }) 288 | AfterAll(func() { 289 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 290 | Expect(err).ToNot(HaveOccurred()) 291 | 292 | err = framework.DeleteHelmChartConfig(chart.Name, chart.Namespace) 293 | Expect(err).ToNot(HaveOccurred()) 294 | 295 | Eventually(func(g Gomega) { 296 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 297 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 298 | 299 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 300 | }) 301 | }) 302 | 303 | Context("When a HelmChart has ValuesSecrets", func() { 304 | var ( 305 | err error 306 | chart *v1.HelmChart 307 | userSecret *corev1.Secret 308 | ) 309 | BeforeAll(func() { 310 | userSecret = &corev1.Secret{ 311 | ObjectMeta: metav1.ObjectMeta{ 312 | Name: "my-traefik-values", 313 | Namespace: framework.Namespace, 314 | }, 315 | StringData: map[string]string{ 316 | "values.yaml": "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 317 | }, 318 | } 319 | userSecret, err = framework.ClientSet.CoreV1().Secrets(userSecret.Namespace).Create(context.TODO(), userSecret, metav1.CreateOptions{}) 320 | Expect(err).ToNot(HaveOccurred()) 321 | 322 | chart = framework.NewHelmChart("traefik-example", 323 | "stable/traefik", 324 | "1.86.1", 325 | "v3", 326 | "", 327 | nil) 328 | 329 | chart.Spec.ValuesSecrets = []v1.SecretSpec{ 330 | { 331 | Name: userSecret.Name, 332 | Keys: []string{"values.yaml"}, 333 | }, 334 | } 335 | 336 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 337 | Expect(err).ToNot(HaveOccurred()) 338 | }) 339 | It("Should create a release for the chart", func() { 340 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 341 | }) 342 | It("Should create a new release when the secret is modified", func() { 343 | userSecret.Data = nil 344 | userSecret.StringData = map[string]string{ 345 | "values.yaml": "metrics:\n prometheus:\n enabled: false\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 346 | } 347 | userSecret, err = framework.ClientSet.CoreV1().Secrets(userSecret.Namespace).Update(context.TODO(), userSecret, metav1.UpdateOptions{}) 348 | Expect(err).ToNot(HaveOccurred()) 349 | 350 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(2)) 351 | }) 352 | AfterAll(func() { 353 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 354 | Expect(err).ToNot(HaveOccurred()) 355 | 356 | Eventually(func(g Gomega) { 357 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 358 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 359 | 360 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 361 | 362 | err = framework.ClientSet.CoreV1().Secrets(userSecret.Namespace).Delete(context.TODO(), userSecret.Name, metav1.DeleteOptions{}) 363 | Expect(err).ToNot(HaveOccurred()) 364 | }) 365 | }) 366 | 367 | Context("When a HelmChart has HelmChartConfig ValuesSecrets", func() { 368 | var ( 369 | err error 370 | chart *v1.HelmChart 371 | chartConfig *v1.HelmChartConfig 372 | userSecret *corev1.Secret 373 | ) 374 | BeforeAll(func() { 375 | chart = framework.NewHelmChart("traefik-example", 376 | "stable/traefik", 377 | "1.86.1", 378 | "v3", 379 | "", 380 | nil) 381 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 382 | Expect(err).ToNot(HaveOccurred()) 383 | }) 384 | It("Should create a release for the chart", func() { 385 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 386 | }) 387 | It("Should create a new release when the HelmChartConfig is created", func() { 388 | userSecret = &corev1.Secret{ 389 | ObjectMeta: metav1.ObjectMeta{ 390 | Name: "my-traefik-values", 391 | Namespace: framework.Namespace, 392 | }, 393 | StringData: map[string]string{ 394 | "values.yaml": "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 395 | }, 396 | } 397 | 398 | userSecret, err = framework.ClientSet.CoreV1().Secrets(userSecret.Namespace).Create(context.TODO(), userSecret, metav1.CreateOptions{}) 399 | Expect(err).ToNot(HaveOccurred()) 400 | 401 | chartConfig = framework.NewHelmChartConfig(chart.Name, "") 402 | chartConfig.Spec.ValuesSecrets = []v1.SecretSpec{ 403 | { 404 | Name: userSecret.Name, 405 | Keys: []string{"values.yaml"}, 406 | }, 407 | } 408 | 409 | chartConfig, err = framework.CreateHelmChartConfig(chartConfig, framework.Namespace) 410 | Expect(err).ToNot(HaveOccurred()) 411 | 412 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(2)) 413 | }) 414 | It("Should create a new release when the secret is modified", func() { 415 | userSecret.Data = nil 416 | userSecret.StringData = map[string]string{ 417 | "values.yaml": "metrics:\n prometheus:\n enabled: false\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 418 | } 419 | userSecret, err = framework.ClientSet.CoreV1().Secrets(userSecret.Namespace).Update(context.TODO(), userSecret, metav1.UpdateOptions{}) 420 | Expect(err).ToNot(HaveOccurred()) 421 | 422 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(3)) 423 | }) 424 | 425 | AfterAll(func() { 426 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 427 | Expect(err).ToNot(HaveOccurred()) 428 | 429 | err = framework.DeleteHelmChartConfig(chart.Name, chart.Namespace) 430 | Expect(err).ToNot(HaveOccurred()) 431 | 432 | Eventually(func(g Gomega) { 433 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 434 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 435 | 436 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 437 | }) 438 | 439 | }) 440 | 441 | Context("When a HelmChart creates a namespace", func() { 442 | var ( 443 | err error 444 | chart *v1.HelmChart 445 | ) 446 | BeforeAll(func() { 447 | chart = framework.NewHelmChart("traefik-ns-example", 448 | "stable/traefik", 449 | "1.86.1", 450 | "v3", 451 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 452 | map[string]intstr.IntOrString{ 453 | "rbac.enabled": { 454 | Type: intstr.String, 455 | StrVal: "true", 456 | }, 457 | "ssl.enabled": { 458 | Type: intstr.String, 459 | StrVal: "true", 460 | }, 461 | }) 462 | chart.Spec.TargetNamespace = chart.Name 463 | chart.Spec.CreateNamespace = true 464 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 465 | Expect(err).ToNot(HaveOccurred()) 466 | }) 467 | It("Should create a release for the chart in the target namespace", func() { 468 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should( 469 | And( 470 | HaveLen(1), 471 | ContainElement(HaveField("ObjectMeta.Namespace", Equal(chart.Spec.TargetNamespace))), 472 | )) 473 | }) 474 | AfterAll(func() { 475 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 476 | Expect(err).ToNot(HaveOccurred()) 477 | 478 | Eventually(func(g Gomega) { 479 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 480 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 481 | 482 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 483 | }) 484 | }) 485 | 486 | Context("When a HelmChart V2 is created", func() { 487 | var ( 488 | err error 489 | chart *v1.HelmChart 490 | ) 491 | BeforeAll(func() { 492 | chart = framework.NewHelmChart("traefik-example-v2", 493 | "stable/traefik", 494 | "1.86.1", 495 | "v2", 496 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 497 | map[string]intstr.IntOrString{ 498 | "rbac.enabled": { 499 | Type: intstr.String, 500 | StrVal: "true", 501 | }, 502 | "ssl.enabled": { 503 | Type: intstr.String, 504 | StrVal: "true", 505 | }, 506 | }) 507 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 508 | Expect(err).ToNot(HaveOccurred()) 509 | }) 510 | It("Should have failed condition", func() { 511 | Eventually(func() error { 512 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 513 | if err != nil { 514 | return err 515 | } 516 | if !framework.GetHelmChartCondition(chart, v1.HelmChartFailed, corev1.ConditionTrue, "Unsupported version") { 517 | return fmt.Errorf("expected condition %v=%v not found", v1.HelmChartFailed, corev1.ConditionTrue) 518 | } 519 | return nil 520 | }, 120*time.Second).ShouldNot(HaveOccurred()) 521 | }) 522 | AfterAll(func() { 523 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 524 | Expect(err).ToNot(HaveOccurred()) 525 | 526 | Eventually(func(g Gomega) { 527 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 528 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 529 | 530 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 531 | }) 532 | }) 533 | 534 | Context("When a custom backoffLimit is specified", func() { 535 | var ( 536 | err error 537 | chart *v1.HelmChart 538 | job *batchv1.Job 539 | backOffLimit int32 540 | ) 541 | BeforeAll(func() { 542 | backOffLimit = 10 543 | chart = framework.NewHelmChart("traefik-example-custom-backoff", 544 | "stable/traefik", 545 | "1.86.1", 546 | "v3", 547 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 548 | map[string]intstr.IntOrString{ 549 | "rbac.enabled": { 550 | Type: intstr.String, 551 | StrVal: "true", 552 | }, 553 | "ssl.enabled": { 554 | Type: intstr.String, 555 | StrVal: "true", 556 | }, 557 | }) 558 | chart.Spec.BackOffLimit = &backOffLimit 559 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 560 | Expect(err).ToNot(HaveOccurred()) 561 | }) 562 | It("Should create a release for the chart", func() { 563 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 564 | }) 565 | It("Should have correct job backOff Limit", func() { 566 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 567 | Expect(err).ToNot(HaveOccurred()) 568 | 569 | job, err = framework.GetJob(chart) 570 | Expect(err).ToNot(HaveOccurred()) 571 | Expect(*job.Spec.BackoffLimit).To(Equal(backOffLimit)) 572 | }) 573 | AfterAll(func() { 574 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 575 | Expect(err).ToNot(HaveOccurred()) 576 | 577 | Eventually(func(g Gomega) { 578 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 579 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 580 | 581 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 582 | }) 583 | }) 584 | 585 | Context("When a no backoffLimit is specified", func() { 586 | var ( 587 | err error 588 | chart *v1.HelmChart 589 | job *batchv1.Job 590 | ) 591 | const ( 592 | defaultBackOffLimit = int32(1000) 593 | ) 594 | BeforeAll(func() { 595 | chart = framework.NewHelmChart("traefik-example-default-backoff", 596 | "stable/traefik", 597 | "1.86.1", 598 | "v3", 599 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 600 | map[string]intstr.IntOrString{ 601 | "rbac.enabled": { 602 | Type: intstr.String, 603 | StrVal: "true", 604 | }, 605 | "ssl.enabled": { 606 | Type: intstr.String, 607 | StrVal: "true", 608 | }, 609 | }) 610 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 611 | Expect(err).ToNot(HaveOccurred()) 612 | }) 613 | It("Should create a release for the chart", func() { 614 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 615 | }) 616 | It("Should have correct job backOff Limit", func() { 617 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 618 | Expect(err).ToNot(HaveOccurred()) 619 | 620 | job, err = framework.GetJob(chart) 621 | Expect(err).ToNot(HaveOccurred()) 622 | Expect(*job.Spec.BackoffLimit).To(Equal(defaultBackOffLimit)) 623 | }) 624 | AfterAll(func() { 625 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 626 | Expect(err).ToNot(HaveOccurred()) 627 | 628 | Eventually(func(g Gomega) { 629 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 630 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 631 | 632 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 633 | }) 634 | }) 635 | 636 | Context("When a custom podSecurityContext is specified", func() { 637 | var ( 638 | err error 639 | chart *v1.HelmChart 640 | job *batchv1.Job 641 | expectedPodSecurityContext = &corev1.PodSecurityContext{ 642 | RunAsNonRoot: ptr.To(false), 643 | } 644 | ) 645 | BeforeAll(func() { 646 | chart = framework.NewHelmChart("traefik-example-custom-podsecuritycontext", 647 | "stable/traefik", 648 | "1.86.1", 649 | "v3", 650 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 651 | map[string]intstr.IntOrString{ 652 | "rbac.enabled": { 653 | Type: intstr.String, 654 | StrVal: "true", 655 | }, 656 | "ssl.enabled": { 657 | Type: intstr.String, 658 | StrVal: "true", 659 | }, 660 | }) 661 | chart.Spec.PodSecurityContext = &corev1.PodSecurityContext{ 662 | RunAsNonRoot: ptr.To(false), 663 | } 664 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 665 | Expect(err).ToNot(HaveOccurred()) 666 | }) 667 | It("Should create a release for the chart", func() { 668 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 669 | }) 670 | It("Should have correct pod securityContext", func() { 671 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 672 | Expect(err).ToNot(HaveOccurred()) 673 | 674 | job, err = framework.GetJob(chart) 675 | Expect(err).ToNot(HaveOccurred()) 676 | Expect(*job.Spec.Template.Spec.SecurityContext).To(Equal(*expectedPodSecurityContext)) 677 | }) 678 | AfterAll(func() { 679 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 680 | Expect(err).ToNot(HaveOccurred()) 681 | 682 | Eventually(func(g Gomega) { 683 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 684 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 685 | 686 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 687 | }) 688 | }) 689 | 690 | Context("When a no podSecurityContext is specified", func() { 691 | var ( 692 | err error 693 | chart *v1.HelmChart 694 | job *batchv1.Job 695 | defaultPodSecurityContext = &corev1.PodSecurityContext{ 696 | RunAsNonRoot: ptr.To(true), 697 | SeccompProfile: &corev1.SeccompProfile{ 698 | Type: "RuntimeDefault", 699 | }, 700 | } 701 | ) 702 | BeforeAll(func() { 703 | chart = framework.NewHelmChart("traefik-example-default-podsecuritycontext", 704 | "stable/traefik", 705 | "1.86.1", 706 | "v3", 707 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 708 | map[string]intstr.IntOrString{ 709 | "rbac.enabled": { 710 | Type: intstr.String, 711 | StrVal: "true", 712 | }, 713 | "ssl.enabled": { 714 | Type: intstr.String, 715 | StrVal: "true", 716 | }, 717 | }) 718 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 719 | Expect(err).ToNot(HaveOccurred()) 720 | }) 721 | It("Should create a release for the chart", func() { 722 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 723 | }) 724 | It("Should have correct pod securityContext", func() { 725 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 726 | Expect(err).ToNot(HaveOccurred()) 727 | job, err = framework.GetJob(chart) 728 | 729 | Expect(err).ToNot(HaveOccurred()) 730 | Expect(*job.Spec.Template.Spec.SecurityContext).To(Equal(*defaultPodSecurityContext)) 731 | }) 732 | AfterAll(func() { 733 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 734 | Expect(err).ToNot(HaveOccurred()) 735 | 736 | Eventually(func(g Gomega) { 737 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 738 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 739 | 740 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 741 | }) 742 | }) 743 | 744 | Context("When a custom securityContext is specified", func() { 745 | var ( 746 | err error 747 | chart *v1.HelmChart 748 | job *batchv1.Job 749 | expectedSecurityContext = &corev1.SecurityContext{ 750 | AllowPrivilegeEscalation: ptr.To(true), 751 | } 752 | ) 753 | BeforeAll(func() { 754 | chart = framework.NewHelmChart("traefik-example-custom-securitycontext", 755 | "stable/traefik", 756 | "1.86.1", 757 | "v3", 758 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 759 | map[string]intstr.IntOrString{ 760 | "rbac.enabled": { 761 | Type: intstr.String, 762 | StrVal: "true", 763 | }, 764 | "ssl.enabled": { 765 | Type: intstr.String, 766 | StrVal: "true", 767 | }, 768 | }) 769 | chart.Spec.SecurityContext = &corev1.SecurityContext{ 770 | AllowPrivilegeEscalation: ptr.To(true), 771 | } 772 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 773 | Expect(err).ToNot(HaveOccurred()) 774 | }) 775 | It("Should create a release for the chart", func() { 776 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 777 | }) 778 | It("Should have correct container securityContext", func() { 779 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 780 | Expect(err).ToNot(HaveOccurred()) 781 | 782 | job, err = framework.GetJob(chart) 783 | Expect(err).ToNot(HaveOccurred()) 784 | Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext).To(Equal(*expectedSecurityContext)) 785 | }) 786 | AfterAll(func() { 787 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 788 | Expect(err).ToNot(HaveOccurred()) 789 | 790 | Eventually(func(g Gomega) { 791 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 792 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 793 | 794 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 795 | }) 796 | }) 797 | 798 | Context("When a no securityContext is specified", func() { 799 | var ( 800 | err error 801 | chart *v1.HelmChart 802 | job *batchv1.Job 803 | defaultSecurityContext = &corev1.SecurityContext{ 804 | AllowPrivilegeEscalation: ptr.To(false), 805 | Capabilities: &corev1.Capabilities{ 806 | Drop: []corev1.Capability{ 807 | "ALL", 808 | }, 809 | }, 810 | ReadOnlyRootFilesystem: ptr.To(true), 811 | } 812 | ) 813 | BeforeAll(func() { 814 | chart = framework.NewHelmChart("traefik-example-default-securitycontext", 815 | "stable/traefik", 816 | "1.86.1", 817 | "v3", 818 | "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", 819 | map[string]intstr.IntOrString{ 820 | "rbac.enabled": { 821 | Type: intstr.String, 822 | StrVal: "true", 823 | }, 824 | "ssl.enabled": { 825 | Type: intstr.String, 826 | StrVal: "true", 827 | }, 828 | }) 829 | chart, err = framework.CreateHelmChart(chart, framework.Namespace) 830 | Expect(err).ToNot(HaveOccurred()) 831 | }) 832 | It("Should create a release for the chart", func() { 833 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(1)) 834 | }) 835 | It("Should have correct container securityContext", func() { 836 | chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) 837 | Expect(err).ToNot(HaveOccurred()) 838 | 839 | job, err = framework.GetJob(chart) 840 | Expect(err).ToNot(HaveOccurred()) 841 | Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext).To(Equal(*defaultSecurityContext)) 842 | }) 843 | AfterAll(func() { 844 | err = framework.DeleteHelmChart(chart.Name, chart.Namespace) 845 | Expect(err).ToNot(HaveOccurred()) 846 | 847 | Eventually(func(g Gomega) { 848 | g.Expect(framework.GetHelmChart(chart.Name, chart.Namespace)).Error().Should(MatchError(apierrors.IsNotFound, "IsNotFound")) 849 | }, 120*time.Second, 5*time.Second).Should(Succeed()) 850 | 851 | Eventually(framework.ListReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) 852 | }) 853 | }) 854 | }) 855 | -------------------------------------------------------------------------------- /test/suite/zz_suite_test.go: -------------------------------------------------------------------------------- 1 | package suite_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestSuite(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Helm Suite") 13 | } 14 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/updatecli.yml: -------------------------------------------------------------------------------- 1 | # This small test makes sure that updatecli is working properly on a repo. 2 | # In the future, more useful files should be added to this directory. 3 | --- 4 | name: "Introduce updatecli to repo and validate basic functionality" 5 | # Make sure we can pull in github repos from multiple orgs 6 | scms: 7 | helm-controller: 8 | kind: "github" 9 | spec: 10 | user: "{{ .github.user }}" 11 | email: "{{ .github.email }}" 12 | username: "{{ requiredEnv .github.username }}" 13 | token: '{{ requiredEnv .github.token }}' 14 | owner: "k3s-io" 15 | repository: "helm-controller" 16 | branch: "master" 17 | go: 18 | kind: "github" 19 | spec: 20 | user: "{{ .github.user }}" 21 | email: "{{ .github.email }}" 22 | username: "{{ requiredEnv .github.username }}" 23 | token: '{{ requiredEnv .github.token }}' 24 | owner: "golang" 25 | repository: "go" 26 | branch: "master" 27 | 28 | sources: 29 | # validate gittag parsing external public repos 30 | goTag: 31 | name: "Get Go 1.20.2 tag" 32 | kind: "gittag" 33 | scmid: "go" 34 | spec: 35 | versionfilter: 36 | kind: "regex" 37 | pattern: '^go1\.20\.2$' 38 | 39 | # Validate read access to local repo 40 | ## continue to targets if the go version in the validate file doesn't match the goTag source 41 | conditions: 42 | testVersionShouldMatchGoTag: 43 | name: test version should match go tag 44 | kind: yaml 45 | sourceid: goTag 46 | spec: 47 | file: "updatecli/validate.yml" 48 | key: version 49 | failwhen: true #if set to true, continue to targets when condition is true rather than false 50 | 51 | # Validate the ability to generate branches, commits, what the commits look like, and what branches look like 52 | ## allow validation of workflow to delete unused branch after merge 53 | ## generate a commit on a branch named updatecli_<256 sha of change> 54 | ## the commit message will be automatically generated by updatecli based on the change 55 | targets: 56 | updateValidateFile: 57 | name: "Update the version in the validate file" 58 | kind: "yaml" 59 | scmid: "helm-controller" 60 | sourceid: goTag 61 | spec: 62 | file: "updatecli/validate.yml" 63 | key: version 64 | 65 | # Validate generating a pull request 66 | actions: 67 | # create a pull request which is not allowed to automerge 68 | # the title matches the commit message 69 | github: 70 | kind: "github/pullrequest" 71 | scmid: "helm-controller" 72 | spec: 73 | automerge: false 74 | draft: false 75 | mergemethod: squash 76 | parent: false # this would allow for making a PR to an upstream fork, if we ran updatecli from a fork 77 | -------------------------------------------------------------------------------- /updatecli/validate.yml: -------------------------------------------------------------------------------- 1 | version: go1.20.2 2 | -------------------------------------------------------------------------------- /updatecli/values.yaml: -------------------------------------------------------------------------------- 1 | github: 2 | user: "github-actions[bot]" 3 | email: "41898282+github-actions[bot]@users.noreply.github.com" 4 | username: "UPDATECLI_GITHUB_ACTOR" 5 | token: "UPDATECLI_GITHUB_TOKEN" 6 | --------------------------------------------------------------------------------