├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── chart-release.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DCO ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── chart └── helm-operator │ ├── CHANGELOG.md │ ├── Chart.yaml │ ├── README.md │ ├── crds │ └── helmrelease.yaml │ ├── dashboards │ └── helm-operator.json │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── configmap-dashboards.yaml │ ├── crd.yaml │ ├── deployment.yaml │ ├── git-secret.yaml │ ├── gitconfig.yaml │ ├── helm-repositories.yaml │ ├── helm-tls.yaml │ ├── kube.yaml │ ├── psp.yaml │ ├── rbac-role.yaml │ ├── rbac.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ ├── servicemonitor.yaml │ └── ssh.yaml │ └── values.yaml ├── cmd └── helm-operator │ └── main.go ├── crd-v1-testing ├── README.md ├── chart-museum-values.yaml ├── crd-test-helmrelease.yaml ├── crd-test │ ├── .helmignore │ ├── Chart.yaml │ ├── crds │ │ └── crd.yaml │ ├── templates │ │ └── crd.yaml │ └── values.yaml ├── k3d-default.yaml ├── kind-config.yaml └── podinfo-helmrelease.yaml ├── deploy ├── crds.yaml ├── deployment.yaml ├── kustomization.yaml ├── namespace.yaml ├── rbac.yaml └── weave-cloud-deployment.yaml ├── docker ├── Dockerfile.helm-operator ├── helm-repositories.yaml ├── helm2.version ├── helm3.version ├── image-tag ├── known_hosts.sh ├── kubectl.version └── ssh_config ├── docs ├── README.md ├── _files │ └── fluxcd-helm-operator-diagram.png ├── contributing │ ├── building.md │ └── get-started-developing.md ├── faq.md ├── get-started │ ├── quickstart.md │ ├── using-helm.md │ ├── using-kustomize.md │ └── using-yamls.md ├── helmrelease-guide │ ├── automation.md │ ├── chart-sources.md │ ├── debugging.md │ ├── introduction.md │ ├── reconciliation-and-upgrades.md │ ├── release-configuration.md │ ├── rollbacks.md │ ├── tests.md │ └── values.md ├── how-to │ ├── upgrade-to-beta.md │ └── upgrade-to-ga.md ├── references │ ├── helmrelease-custom-resource.md │ ├── monitoring.md │ └── operator.md ├── troubleshooting.md └── tutorials │ ├── get-started-kustomize.md │ └── get-started.md ├── go.mod ├── go.sum ├── hack ├── crd-reference-doc-gen │ ├── config.json │ └── template │ │ ├── members.tpl │ │ ├── pkg.tpl │ │ └── type.tpl ├── go_container.sh ├── tools │ ├── boilerplate.go.txt │ ├── go.mod │ ├── go.sum │ └── tools.go └── update │ ├── deps.sh │ ├── generate-all.sh │ ├── generate-codegen.sh │ ├── generate-crds.sh │ ├── k8s-version.sh │ └── verify.sh ├── internal └── lockedfile │ ├── internal │ └── filelock │ │ ├── filelock.go │ │ └── filelock_unix.go │ ├── lockedfile.go │ ├── lockedfile_filelock.go │ └── mutex.go ├── internal_docs └── releasing.md ├── pkg ├── api │ └── api.go ├── apis │ └── helm.fluxcd.io │ │ ├── register.go │ │ └── v1 │ │ ├── doc.go │ │ ├── register.go │ │ ├── types.go │ │ ├── types_helmrelease.go │ │ ├── types_helmrelease_test.go │ │ └── zz_generated.deepcopy.go ├── chartsync │ ├── download.go │ ├── errors.go │ └── git.go ├── client │ ├── clientset │ │ └── versioned │ │ │ ├── clientset.go │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── clientset_generated.go │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ ├── scheme │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ └── typed │ │ │ └── helm.fluxcd.io │ │ │ └── v1 │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_helm.fluxcd.io_client.go │ │ │ └── fake_helmrelease.go │ │ │ ├── generated_expansion.go │ │ │ ├── helm.fluxcd.io_client.go │ │ │ └── helmrelease.go │ ├── informers │ │ └── externalversions │ │ │ ├── factory.go │ │ │ ├── generic.go │ │ │ ├── helm.fluxcd.io │ │ │ ├── interface.go │ │ │ └── v1 │ │ │ │ ├── helmrelease.go │ │ │ │ └── interface.go │ │ │ └── internalinterfaces │ │ │ └── factory_interfaces.go │ └── listers │ │ └── helm.fluxcd.io │ │ └── v1 │ │ ├── expansion_generated.go │ │ └── helmrelease.go ├── helm │ ├── helm.go │ ├── options.go │ ├── release.go │ ├── utils.go │ ├── v2 │ │ ├── chart.go │ │ ├── dependency.go │ │ ├── get.go │ │ ├── helm.go │ │ ├── history.go │ │ ├── pull.go │ │ ├── release.go │ │ ├── repository.go │ │ ├── rollback.go │ │ ├── test.go │ │ ├── uninstall.go │ │ └── upgrade.go │ ├── v3 │ │ ├── chart.go │ │ ├── converter.go │ │ ├── dependency.go │ │ ├── get.go │ │ ├── helm.go │ │ ├── history.go │ │ ├── pull.go │ │ ├── release.go │ │ ├── repository.go │ │ ├── rollback.go │ │ ├── test.go │ │ ├── uninstall.go │ │ └── upgrade.go │ └── values.go ├── http │ ├── daemon │ │ └── server.go │ ├── routes.go │ └── transport.go ├── install │ ├── generate.go │ ├── generated_templates.gogen.go │ ├── go.mod │ ├── go.sum │ ├── install.go │ ├── install_test.go │ └── templates │ │ ├── crds.yaml.tmpl │ │ ├── deployment.yaml.tmpl │ │ └── rbac.yaml.tmpl ├── operator │ ├── metrics.go │ └── operator.go ├── release │ ├── annotator.go │ ├── errors.go │ ├── metrics.go │ ├── release.go │ ├── values.go │ └── values_test.go ├── status │ ├── conditions.go │ ├── metrics.go │ └── status.go └── utils │ └── logwriter.go ├── test ├── bin │ └── .gitkeep └── e2e │ ├── 10_helm_chart.bats │ ├── 15_upgrade.bats │ ├── 20_rollbacks.bats │ ├── 25_max_history.bats │ ├── 30_values_from.bats │ ├── 35_skip_crds.bats │ ├── 40_tests.bats │ ├── 45_convert_2to3.bats │ ├── 50_deletes.bats │ ├── fixtures │ ├── charts │ │ └── nested-helmrelease │ │ │ ├── Chart.yaml │ │ │ ├── templates │ │ │ └── helmrelease.yaml │ │ │ └── values.yaml │ ├── gitconfig │ ├── kustom │ │ └── base │ │ │ ├── chartmuseum │ │ │ ├── chartmuseum.yaml │ │ │ └── kustomization.yaml │ │ │ └── gitsrv │ │ │ ├── gitsrv.yaml │ │ │ └── kustomization.yaml │ ├── releases │ │ ├── convert-2to3-v2.yaml │ │ ├── convert-2to3-v3-upgrade.yaml │ │ ├── convert-2to3-v3.yaml │ │ ├── git.yaml │ │ ├── helm-repository-no-rollback.yaml │ │ ├── helm-repository.yaml │ │ ├── nested-helmrelease.yaml │ │ ├── skip-crd.yaml │ │ ├── takeover.yaml │ │ └── test │ │ │ ├── fail-ignored.yaml │ │ │ ├── fail.yaml │ │ │ ├── success.yaml │ │ │ └── timeout.yaml │ └── values_from │ │ ├── chartfile.yaml │ │ ├── configmap.yaml │ │ ├── externalsource.yaml │ │ └── secret.yaml │ ├── lib │ ├── defer.bash │ ├── env.bash │ ├── helm.bash │ ├── install.bash │ ├── poll.bash │ └── template.bash │ └── run.bash └── tools.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve the Helm Operator 4 | title: '' 5 | labels: blocked needs validation, bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 28 | 29 | **Describe the bug** 30 | 31 | A clear and concise description of what the bug is. 32 | 33 | **To Reproduce** 34 | 35 | Steps to reproduce the behaviour: 36 | 1. Provide the Helm Operator install instructions 37 | 2. Provide a HelmRelease example 38 | 3. Post the HelmRelease status, you can get this by running `kubectl describe helmrelease ` 39 | 40 | **Expected behavior** 41 | 42 | A clear and concise description of what you expected to happen. 43 | 44 | **Logs** 45 | 46 | If applicable, please provide logs. In a standard stand-alone installation, you'd get this by running `kubectl logs deploy/flux-helm-operator -n fluxcd`. 47 | 48 | **Additional context** 49 | 50 | - Helm Operator version: 51 | - Kubernetes version: 52 | - Git provider: 53 | - Helm repository provider: 54 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /.github/workflows/chart-release.yml: -------------------------------------------------------------------------------- 1 | name: release-chart 2 | on: 3 | push: 4 | tags: 'chart-*' 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Publish Helm chart 12 | uses: stefanprodan/helm-gh-pages@master 13 | with: 14 | token: ${{ secrets.BOT_GITHUB_TOKEN }} 15 | charts_dir: chart 16 | charts_url: https://charts.fluxcd.io 17 | owner: fluxcd 18 | repository: charts 19 | branch: gh-pages 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Tool related configurations 27 | .ackrc 28 | .envrc 29 | 30 | ### Package builds (e.g. snap of fluxctl) 31 | fluxctl_*_*.snap 32 | fluxctl_*_*.snap.xdelta3 33 | parts 34 | prime 35 | stage 36 | 37 | 38 | # Specific to this project 39 | bin/ 40 | _tmp/ 41 | vendor/* 42 | !vendor/manifest 43 | build/ 44 | /cache/* 45 | testdata/helloworld/helloworld-linux-amd64 46 | testdata/helloworld/.helloworld 47 | testdata/sidecar/sidecar-linux-amd64 48 | testdata/sidecar/.sidecar 49 | docker/fluxy-dumbconf.priv 50 | test/profiles 51 | test/bin/ 52 | test/e2e/bats 53 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Flux CD Community Code of Conduct 2 | 3 | Flux CD follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 660 York Street, Suite 102, 6 | San Francisco, CA 94110 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | 12 | Developer's Certificate of Origin 1.1 13 | 14 | By making a contribution to this project, I certify that: 15 | 16 | (a) The contribution was created in whole or in part by me and I 17 | have the right to submit it under the open source license 18 | indicated in the file; or 19 | 20 | (b) The contribution is based upon previous work that, to the best 21 | of my knowledge, is covered under an appropriate open source 22 | license and I have the right under that license to submit that 23 | work with modifications, whether created in whole or in part 24 | by me, under the same open source license (unless I am 25 | permitted to submit under a different license), as indicated 26 | in the file; or 27 | 28 | (c) The contribution was provided directly to me by some other 29 | person who certified (a), (b) or (c) and I have not modified 30 | it. 31 | 32 | (d) I understand and agree that this project and the contribution 33 | are public and that a record of the contribution (including all 34 | personal information I submit with it, including my sign-off) is 35 | maintained indefinitely and may be redistributed consistent with 36 | this project or the open source license(s) involved. 37 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | The maintainers are generally available in Slack at 2 | https://cloud-native.slack.com in #flux (https://cloud-native.slack.com/messages/CLAJ40HV3) 3 | (obtain an invitation at https://slack.cncf.io/). 4 | 5 | In alphabetical order: 6 | 7 | Hidde Beydals, Weaveworks (github: @hiddeco, slack: hidde) 8 | Michael Bridgen, Weaveworks (github: @squaremo, slack: Michael Bridgen) 9 | Stefan Prodan, Weaveworks (github: @stefanprodan, slack: stefanprodan) 10 | 11 | Retired maintainers: 12 | 13 | - Alfonso Acosta 14 | 15 | Thank you for your involvement, and let us not say "farewell" ... 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Helm Operator 2 | 3 | This repository contains the source code of the Helm Operator, part of Flux 4 | Legacy (v1). 5 | 6 | Flux v1 has reached **end of life** and has been replaced by [fluxcd/flux2](https://github.com/fluxcd/flux2) 7 | and its controllers entirely. 8 | 9 | If you are on Flux Legacy, please see the [migration guide](https://fluxcd.io/flux/migration). 10 | If you need hands-on help migrating, you can contact one of the companies 11 | [listed here](https://fluxcd.io/support/#my-employer-needs-additional-help). 12 | 13 | ## History 14 | 15 | The Helm Operator was initially developed by [Weaveworks](https://weave.works) 16 | as an extension to Flux, to allow for the declarative management of [Helm](https://helm.sh) 17 | releases. 18 | 19 | For an extensive historical overview of Flux, please refer to 20 | . 21 | -------------------------------------------------------------------------------- /chart/helm-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.4.4" 3 | version: 1.4.4 4 | kubeVersion: ">=1.16.0-0" 5 | name: helm-operator 6 | description: Flux Helm Operator is a CRD controller for declarative helming 7 | home: https://fluxcd.io 8 | sources: 9 | - https://github.com/fluxcd/helm-operator 10 | maintainers: 11 | - name: stefanprodan 12 | email: stefan@weave.works 13 | engine: gotpl 14 | icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png 15 | keywords: 16 | - gitops 17 | - helm 18 | -------------------------------------------------------------------------------- /chart/helm-operator/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Flux Helm Operator docs https://fluxcd.io/legacy/helm-operator 2 | 3 | Example: 4 | 5 | AUTH_VALUES=$(cat <<-END 6 | usePassword: true 7 | password: "redis_pass" 8 | usePasswordFile: true 9 | END 10 | ) 11 | 12 | kubectl create secret generic redis-auth --from-literal=values.yaml="$AUTH_VALUES" 13 | 14 | cat < 27 | curl http://localhost:9898/ 28 | ``` 29 | 30 | # Verification in upgraded cluster 31 | 32 | Create a new cluster with v1beta1 CRD enabled. 33 | ``` 34 | kind create cluster` 35 | ``` 36 | 37 | Install Helm Operator. 38 | ``` 39 | helm repo add fluxcd https://charts.fluxcd.io 40 | kubectl create ns flux 41 | helm upgrade -i helm-operator fluxcd/helm-operator --namespace flux --set helm.versions=v3 42 | ``` 43 | 44 | Install chart museum. 45 | ``` 46 | helm repo add stable https://charts.helm.sh/stable 47 | helm upgrade -i chartmuseum stable/chartmuseum -f crd-v1-testing/chart-museum-values.yaml 48 | kubectl -n default get pods 49 | ``` 50 | 51 | Build and upload helm package. 52 | ``` 53 | kubectl -n default port-forward pod/ 8080:8080 54 | helm plugin install https://github.com/chartmuseum/helm-push.git 55 | helm push crd-v1-testing/crd-test chartmuseum 56 | ``` 57 | 58 | Apply crd-test helm release 59 | ``` 60 | kubectl apply -f crd-v1-testing/crd-test-helmrelease.yaml 61 | ``` 62 | 63 | Exec into the docker container running the node. 64 | ``` 65 | docker ps 66 | docker exec -i bash 67 | ``` 68 | 69 | Get the kubeadm config. 70 | ``` 71 | kubeadm config view > kubeadm-config.yaml 72 | ``` 73 | 74 | Update the config with runtime config. 75 | ``` 76 | extraArgs: 77 | authorization-mode: Node,RBAC 78 | runtime-config: "" 79 | ``` 80 | 81 | ``` 82 | extraArgs: 83 | authorization-mode: Node,RBAC 84 | runtime-config: "apiextensions.k8s.io/v1beta1=false" 85 | ``` 86 | 87 | Upgrade the cluster with the new settings. 88 | ``` 89 | kubeadm upgrade diff --config kubeadm-config.yaml 90 | kubeadm upgrade apply --config kubeadm-config.yaml --ignore-preflight-errors all --force --v=5 91 | ``` 92 | -------------------------------------------------------------------------------- /crd-v1-testing/chart-museum-values.yaml: -------------------------------------------------------------------------------- 1 | env: 2 | open: 3 | DISABLE_API: false 4 | -------------------------------------------------------------------------------- /crd-v1-testing/crd-test-helmrelease.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: crd-test 5 | namespace: default 6 | spec: 7 | chart: 8 | repository: http://chartmuseum-chartmuseum.default.svc.cluster.local:8080 9 | name: crd-test 10 | version: 0.1.0 11 | -------------------------------------------------------------------------------- /crd-v1-testing/crd-test/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /crd-v1-testing/crd-test/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: crd-test 3 | description: A Helm chart for Kubernetes 4 | type: application 5 | version: 0.1.0 6 | appVersion: "1.16.0" 7 | -------------------------------------------------------------------------------- /crd-v1-testing/crd-test/crds/crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: crddirs.crdtest.example.com 5 | spec: 6 | group: crdtest.example.com 7 | versions: 8 | - name: v1 9 | served: true 10 | storage: true 11 | version: v1 12 | scope: Namespaced 13 | names: 14 | plural: crddirs 15 | singular: crddir 16 | kind: CrdDir 17 | shortNames: 18 | - t 19 | -------------------------------------------------------------------------------- /crd-v1-testing/crd-test/templates/crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: templatedirs.crdtest.example.com 5 | spec: 6 | group: crdtest.example.com 7 | versions: 8 | - name: v1 9 | served: true 10 | storage: true 11 | version: v1 12 | scope: Namespaced 13 | names: 14 | plural: templatedirs 15 | singular: templatedir 16 | kind: TemplateDir 17 | shortNames: 18 | - t 19 | -------------------------------------------------------------------------------- /crd-v1-testing/crd-test/values.yaml: -------------------------------------------------------------------------------- 1 | nameOverride: "" 2 | fullnameOverride: "" 3 | -------------------------------------------------------------------------------- /crd-v1-testing/k3d-default.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: k3d.io/v1alpha2 3 | kind: Simple 4 | name: k3s-default 5 | servers: 1 6 | agents: 0 7 | image: docker.io/rancher/k3s:v1.20.4-k3s1 8 | -------------------------------------------------------------------------------- /crd-v1-testing/kind-config.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | kubeadmConfigPatches: 6 | - | 7 | apiVersion: kubeadm.k8s.io/v1beta2 8 | kind: ClusterConfiguration 9 | kubernetesVersion: v1.20.0 10 | apiServer: 11 | extraArgs: 12 | #runtime-config: "apiextensions.k8s.io/v1beta1=false" 13 | -------------------------------------------------------------------------------- /crd-v1-testing/podinfo-helmrelease.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: podinfo 5 | namespace: default 6 | spec: 7 | chart: 8 | repository: https://stefanprodan.github.io/podinfo 9 | name: podinfo 10 | version: 3.2.0 11 | -------------------------------------------------------------------------------- /deploy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - namespace.yaml 3 | - crds.yaml 4 | - rbac.yaml 5 | - deployment.yaml 6 | -------------------------------------------------------------------------------- /deploy/namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: flux 6 | -------------------------------------------------------------------------------- /deploy/rbac.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | labels: 6 | name: helm-operator 7 | name: helm-operator 8 | namespace: flux 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | labels: 14 | name: helm-operator 15 | name: helm-operator 16 | rules: 17 | - apiGroups: ['*'] 18 | resources: ['*'] 19 | verbs: ['*'] 20 | - nonResourceURLs: ['*'] 21 | verbs: ['*'] 22 | --- 23 | apiVersion: rbac.authorization.k8s.io/v1 24 | kind: ClusterRoleBinding 25 | metadata: 26 | labels: 27 | name: helm-operator 28 | name: helm-operator 29 | roleRef: 30 | apiGroup: rbac.authorization.k8s.io 31 | kind: ClusterRole 32 | name: helm-operator 33 | subjects: 34 | - kind: ServiceAccount 35 | name: helm-operator 36 | namespace: flux 37 | -------------------------------------------------------------------------------- /deploy/weave-cloud-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: helm-operator 5 | namespace: weave 6 | labels: 7 | app: helm-operator 8 | weave-cloud-component: helm-operator 9 | spec: 10 | strategy: 11 | type: Recreate 12 | selector: 13 | matchLabels: 14 | app: helm-operator 15 | template: 16 | metadata: 17 | annotations: 18 | prometheus.io/scrape: "false" 19 | labels: 20 | app: helm-operator 21 | spec: 22 | serviceAccountName: weave-flux 23 | volumes: 24 | - name: git-key 25 | secret: 26 | defaultMode: 256 27 | secretName: flux-git-deploy 28 | containers: 29 | - name: helm-operator 30 | image: docker.io/fluxcd/helm-operator:1.4.0 31 | imagePullPolicy: IfNotPresent 32 | args: 33 | - --git-timeout=20s 34 | - --charts-sync-interval=3m 35 | - --update-chart-deps=true 36 | - --tiller-namespace=kube-system 37 | volumeMounts: 38 | - name: git-key 39 | mountPath: /etc/fluxd/ssh 40 | resources: 41 | limits: 42 | cpu: 1000m 43 | memory: 512Mi 44 | requests: 45 | cpu: 50m 46 | memory: 64Mi 47 | -------------------------------------------------------------------------------- /docker/Dockerfile.helm-operator: -------------------------------------------------------------------------------- 1 | FROM alpine:3.15 2 | 3 | WORKDIR /home/flux 4 | 5 | RUN apk add --no-cache openssh-client ca-certificates tini 'git>=2.12.0' socat curl bash 6 | 7 | # Add git hosts to known hosts file so we can use 8 | # StrickHostKeyChecking with git+ssh 9 | ADD ./known_hosts.sh /home/flux/known_hosts.sh 10 | RUN sh /home/flux/known_hosts.sh /etc/ssh/ssh_known_hosts && \ 11 | rm /home/flux/known_hosts.sh 12 | 13 | # Add default SSH config, which points at the private key we'll mount 14 | COPY ./ssh_config /etc/ssh/ssh_config 15 | 16 | COPY ./kubectl /usr/local/bin/ 17 | # The Helm clients are included as a convenience for troubleshooting 18 | COPY ./helm2 /usr/local/bin/ 19 | COPY ./helm3 /usr/local/bin/ 20 | 21 | # These are pretty static 22 | LABEL maintainer="Flux CD " \ 23 | org.opencontainers.image.title="helm-operator" \ 24 | org.opencontainers.image.description="The Helm operator for Kubernetes" \ 25 | org.opencontainers.image.url="https://github.com/fluxcd/helm-operator" \ 26 | org.opencontainers.image.source="git@github.com:fluxcd/helm-operator" \ 27 | org.opencontainers.image.vendor="Flux CD" \ 28 | org.label-schema.schema-version="1.0" \ 29 | org.label-schema.name="helm-operator" \ 30 | org.label-schema.description="The Helm operator for Kubernetes" \ 31 | org.label-schema.url="https://github.com/fluxcd/helm-operator" \ 32 | org.label-schema.vcs-url="git@github.com:fluxcd/helm-operator" \ 33 | org.label-schema.vendor="Flux CD" 34 | 35 | ENTRYPOINT [ "/sbin/tini", "--", "helm-operator" ] 36 | 37 | ENV HELM_HOME=/var/fluxd/helm 38 | COPY ./helm-repositories.yaml /var/fluxd/helm/repository/repositories.yaml 39 | RUN mkdir -p /var/fluxd/helm/repository/cache/ 40 | 41 | COPY ./helm-operator /usr/local/bin/ 42 | 43 | ARG BUILD_DATE 44 | ARG VCS_REF 45 | 46 | # These will change for every build 47 | LABEL org.opencontainers.image.revision="$VCS_REF" \ 48 | org.opencontainers.image.created="$BUILD_DATE" \ 49 | org.label-schema.vcs-ref="$VCS_REF" \ 50 | org.label-schema.build-date="$BUILD_DATE" 51 | -------------------------------------------------------------------------------- /docker/helm-repositories.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | repositories: 3 | - caFile: "" 4 | cache: /var/fluxd/helm/repository/cache/stable-index.yaml 5 | certFile: "" 6 | keyFile: "" 7 | name: stable 8 | password: "" 9 | url: https://charts.helm.sh/stable 10 | username: "" 11 | - caFile: "" 12 | cache: /var/fluxd/helm/repository/cache/fluxcd-index.yaml 13 | certFile: "" 14 | keyFile: "" 15 | name: fluxcd 16 | password: "" 17 | url: https://charts.fluxcd.io 18 | username: "" 19 | - caFile: "" 20 | cache: /var/fluxd/helm/repository/cache/flagger-index.yaml 21 | certFile: "" 22 | keyFile: "" 23 | name: flagger 24 | password: "" 25 | url: https://flagger.app 26 | username: "" 27 | - caFile: "" 28 | cache: /var/fluxd/helm/repository/cache/bitnami-index.yaml 29 | certFile: "" 30 | keyFile: "" 31 | name: bitnami 32 | password: "" 33 | url: https://charts.bitnami.com/bitnami 34 | username: "" 35 | - caFile: "" 36 | cache: /var/fluxd/helm/repository/cache/azure-marketplace-index.yaml 37 | certFile: "" 38 | keyFile: "" 39 | name: azure-marketplace 40 | password: "" 41 | url: https://marketplace.azurecr.io/helm/v1/repo 42 | username: "" 43 | - caFile: "" 44 | cache: /var/fluxd/helm/repository/cache/openfaas-index.yaml 45 | certFile: "" 46 | keyFile: "" 47 | name: openfaas 48 | password: "" 49 | url: https://openfaas.github.io/faas-netes 50 | username: "" 51 | - caFile: "" 52 | cache: /var/fluxd/helm/repository/cache/eks-index.yaml 53 | certFile: "" 54 | keyFile: "" 55 | name: eks 56 | password: "" 57 | url: https://aws.github.io/eks-charts 58 | username: "" 59 | -------------------------------------------------------------------------------- /docker/helm2.version: -------------------------------------------------------------------------------- 1 | HELM2_VERSION=2.17.0 2 | HELM2_CHECKSUM_amd64=f3bec3c7c55f6a9eb9e6586b8c503f370af92fe987fcbf741f37707606d70296 3 | HELM2_CHECKSUM_arm=bf972150ba0b950119a3fe7ac9ed19d467c703fa552ba4ac79a0ad7f1f9e70c4 4 | HELM2_CHECKSUM_arm64=c3ebe8fa04b4e235eb7a9ab030a98d3002f93ecb842f0a8741f98383a9493d7f 5 | -------------------------------------------------------------------------------- /docker/helm3.version: -------------------------------------------------------------------------------- 1 | HELM3_VERSION=3.9.2 2 | HELM3_CHECKSUM_amd64=3f5be38068a1829670440ccf00b3b6656fd90d0d9cfd4367539f3b13e4c20531 3 | HELM3_CHECKSUM_arm=fb9f0c1c9475c66c2b3579b908c181d519761bbfae963ffac860bc683a2253de 4 | HELM3_CHECKSUM_arm64=e4e2f9aad786042d903534e3131bc5300d245c24bbadf64fc46cca1728051dbc 5 | -------------------------------------------------------------------------------- /docker/image-tag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | OUTPUT=--quiet 8 | if [ "${1:-}" = '--show-diff' ]; then 9 | OUTPUT= 10 | fi 11 | 12 | # If a tagged version, just print that tag 13 | HEAD_TAG=$(git tag --points-at HEAD | head -n1) 14 | if [ -n "${HEAD_TAG}" ] ; then 15 | # remove `v` prefix from release name 16 | if echo "${HEAD_TAG}" | grep -Eq "^v[0-9]+(\.[0-9]+)*(-[a-z0-9]+)?$"; then 17 | HEAD_TAG=$(echo "$HEAD_TAG" | cut -c 2-) 18 | fi 19 | echo ${HEAD_TAG} 20 | exit 0 21 | fi 22 | 23 | 24 | WORKING_SUFFIX=$(if ! git diff --exit-code ${OUTPUT} HEAD >&2; \ 25 | then echo "-wip"; \ 26 | else echo ""; \ 27 | fi) 28 | BRANCH_PREFIX=$(git rev-parse --abbrev-ref HEAD) 29 | 30 | # replace spaces with dash 31 | BRANCH_PREFIX=${BRANCH_PREFIX// /-} 32 | # next, replace slashes with dash 33 | BRANCH_PREFIX=${BRANCH_PREFIX//[\/\\]/-} 34 | # now, clean out anything that's not alphanumeric or an dash 35 | BRANCH_PREFIX=${BRANCH_PREFIX//[^a-zA-Z0-9-]/} 36 | # finally, lowercase with TR 37 | BRANCH_PREFIX=`echo -n $BRANCH_PREFIX | tr A-Z a-z` 38 | 39 | echo "$BRANCH_PREFIX-$(git rev-parse --short HEAD)$WORKING_SUFFIX" 40 | -------------------------------------------------------------------------------- /docker/known_hosts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | known_hosts_file=${1} 6 | known_hosts_file=${known_hosts_file:-/etc/ssh/ssh_known_hosts} 7 | hosts="github.com gitlab.com bitbucket.org ssh.dev.azure.com vs-ssh.visualstudio.com" 8 | hosts_2022="source.developers.google.com" 9 | 10 | # The heredoc below was generated by constructing a known_hosts using 11 | # 12 | # ssh-keyscan github.com gitlab.com bitbucket.org ssh.dev.azure.com vs-ssh.visualstudio.com > ./known_hosts 13 | # 14 | # then generating the sorted fingerprints with 15 | # 16 | # ssh-keygen -l -f ./known_hosts | LC_ALL=C sort 17 | # 18 | # then checking against the published fingerprints from: 19 | # - github.com: https://help.github.com/articles/github-s-ssh-key-fingerprints/ 20 | # - gitlab.com: https://docs.gitlab.com/ee/user/gitlab_com/#ssh-host-keys-fingerprints 21 | # - bitbucket.org: https://confluence.atlassian.com/bitbucket/ssh-keys-935365775.html 22 | # - ssh.dev.azure.com & vs-ssh.visualstudio.com: sign in, then go to User settings -> SSH Public Keys 23 | # (this is where the public key fingerprint is shown; it's not a setting) 24 | # - source.developers.google.com: https://cloud.google.com/source-repositories/docs/cloning-repositories 25 | 26 | fingerprints=$(mktemp -t) 27 | cleanup() { 28 | rm -f "$fingerprints" 29 | } 30 | trap cleanup EXIT 31 | 32 | # make sure sorting is in the same locale as the heredoc 33 | export LC_ALL=C 34 | 35 | generate() { 36 | ssh-keyscan ${hosts} > ${known_hosts_file} 37 | ssh-keyscan -p 2022 ${hosts_2022} >> ${known_hosts_file} 38 | } 39 | 40 | validate() { 41 | ssh-keygen -l -f ${known_hosts_file} | sort > "$fingerprints" 42 | 43 | diff - "$fingerprints" < **🛑 Upgrade Advisory** 4 | > 5 | > This documentation is for Helm Operator (v1) which has [reached its end-of-life in November 2022](https://fluxcd.io/blog/2022/10/september-2022-update/#flux-legacy-v1-retirement-plan). 6 | > 7 | > We strongly recommend you familiarise yourself with the newest Flux and [migrate as soon as possible](https://fluxcd.io/flux/migration/). 8 | > 9 | > For documentation regarding the latest Flux, please refer to [this section](https://fluxcd.io/flux/). 10 | 11 | You'll need a working `go` environment version >= 1.11 (official releases are built against `1.14.x`). 12 | It's also expected that you have a Docker daemon for building images. 13 | 14 | Clone the repository. The project uses [Go Modules](https://github.com/golang/go/wiki/Modules), 15 | so if you explicitly define `$GOPATH` you should clone somewhere else. 16 | 17 | Then, from the root directory: 18 | 19 | ```sh 20 | make 21 | ``` 22 | 23 | This makes Docker images, and installs binaries to `$GOBIN` (if you define it) or `$(go env GOPATH)/bin`. 24 | 25 | > **Note:** the default target architecture is amd64. If you would like 26 | > to try to build Docker images and binaries for a different 27 | > architecture you will have to set ARCH variable: 28 | > 29 | > ```sh 30 | > $ make ARCH= 31 | > ``` 32 | 33 | ## Running tests 34 | 35 | ```sh 36 | # Unit tests 37 | make test 38 | 39 | # End-to-end tests, acceptable Helm versions are v2,v3 40 | make e2e HELM_VERSION= 41 | 42 | # Run specific end-to-end test 43 | E2E_TESTS=./10_helm_chart.bats HELM_VERSION=v2 make e2e 44 | ``` 45 | 46 | For e2e tests to work on macOS, you may need to install some dependencies 47 | 48 | ```sh 49 | brew install bash 50 | brew install parallel 51 | brew install coreutils 52 | ``` -------------------------------------------------------------------------------- /docs/get-started/using-yamls.md: -------------------------------------------------------------------------------- 1 | # Get started using YAMLs 2 | 3 | > **🛑 Upgrade Advisory** 4 | > 5 | > This documentation is for Helm Operator (v1) which has [reached its end-of-life in November 2022](https://fluxcd.io/blog/2022/10/september-2022-update/#flux-legacy-v1-retirement-plan). 6 | > 7 | > We strongly recommend you familiarise yourself with the newest Flux and [migrate as soon as possible](https://fluxcd.io/flux/migration/). 8 | > 9 | > For documentation regarding the latest Flux, please refer to [this section](https://fluxcd.io/flux/). 10 | 11 | This guide walks you through setting up the Helm Operator using 12 | [deployment YAMLs](https://github.com/fluxcd/helm-operator/tree/1.4.4/deploy). 13 | 14 | ## Prerequisites 15 | 16 | - Kubernetes cluster **>=1.1.3.0** 17 | - `kubectl` 18 | - _(Optional)_ 19 | Tiller [(secure setup)](https://v2.helm.sh/docs/securing_installation/) 20 | 21 | ## Install the Helm Operator 22 | 23 | First, install the `HelmRelease` Custom Resource Definition. By adding this CRD 24 | it will be possible to define `HelmRelease` resources on the cluster: 25 | 26 | ```sh 27 | kubectl apply -f https://raw.githubusercontent.com/fluxcd/helm-operator/1.4.4/deploy/crds.yaml 28 | ``` 29 | 30 | Proceed to create the `flux` namespace, this is the namespace the Helm Operator 31 | will be deployed to: 32 | 33 | ```sh 34 | kubectl create namespace flux 35 | ``` 36 | 37 | Apply the `ServiceAccount`, `ClusterRole` and `ClusterRoleBinding` so that the 38 | Helm Operator can access cluster resources: 39 | 40 | ```sh 41 | kubectl apply -f https://raw.githubusercontent.com/fluxcd/helm-operator/1.4.4/deploy/rbac.yaml 42 | ``` 43 | 44 | Apply the Helm Operator deployment itself: 45 | 46 | ```sh 47 | kubectl deploy -f https://raw.githubusercontent.com/fluxcd/helm-operator/1.4.4/deploy/deployment.yaml 48 | ``` 49 | 50 | ### Helm 3 51 | 52 | The default deployment of the Helm Operator comes with support for Helm 2 and 3 53 | enabled. To disable support for Helm 2 (and recover from the Tiller connection 54 | failure), patch the resource to set `--enabled-helm-versions=v3`: 55 | 56 | ``` 57 | kubectl deploy -f https://raw.githubusercontent.com/fluxcd/helm-operator/1.4.4/deploy/deployment.yaml \ 58 | --type='json' \ 59 | -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value":"--enabled-helm-versions=v3"}]' 60 | ``` 61 | 62 | ### Helm 2 63 | 64 | The default deployment of the Helm Operator does enable support for Helm 2 but 65 | does not take any custom configurations like Tiller TLS settings into account. 66 | If your Tiller is e.g. in a different namespace than `kube-system` or 67 | [securely setup](https://v2.helm.sh/docs/securing_installation/), take a look 68 | at the available [Tiller configuration flags](../references/operator.md#tiller-configuration) 69 | and [commented out sections](https://github.com/fluxcd/helm-operator/blob/1.4.4/deploy/deployment.yaml) 70 | in the example deployment to further tweak your Helm Operator installation. 71 | 72 | ## Next 73 | 74 | - Learn all about the available configuration options in the [operator 75 | reference](../references/operator.md). 76 | - Continue learning about `HelmRelease` resources [in the 77 | guide](../helmrelease-guide/introduction.md). 78 | -------------------------------------------------------------------------------- /docs/helmrelease-guide/automation.md: -------------------------------------------------------------------------------- 1 | # Automation 2 | 3 | > **🛑 Upgrade Advisory** 4 | > 5 | > This documentation is for Helm Operator (v1) which has [reached its end-of-life in November 2022](https://fluxcd.io/blog/2022/10/september-2022-update/#flux-legacy-v1-retirement-plan). 6 | > 7 | > We strongly recommend you familiarise yourself with the newest Flux and [migrate as soon as possible](https://fluxcd.io/flux/migration/). 8 | > 9 | > For documentation regarding the latest Flux, please refer to [this section](https://fluxcd.io/flux/). 10 | 11 | This section of the guide is mostly a clarification of some common 12 | misconceptions about (non-existent) automation features of the Helm Operator. 13 | 14 | ## Image updates 15 | 16 | Because the Helm Operator is a [Flux umbrella project](https://fluxcd.io), 17 | occasionally people assume it is capable of updating image references in 18 | the `HelmRelease` and/or associated charts. This feature is however baked 19 | in to [Flux](https://github.com/fluxcd/flux), and not the Helm Operator 20 | itself due to it having no knowledge of available images or the origin of the 21 | `HelmRelease`. 22 | 23 | For more details about this Flux feature, please refer to the [documentation 24 | for the `HelmRelease` integration](https://github.com/fluxcd/flux/blob/master/docs/references/helm-operator-integration.md). 25 | 26 | ## Helm repository chart updates 27 | 28 | Another much requested feature is automated updates for charts from [Helm 29 | repository chart sources](chart-sources.md#helm-repositories). The 30 | development of this feature is currently blocked until the automation logic 31 | has been untangled from Flux. To keep up-to-date about new developments of 32 | this feature you can subscribe to 33 | [fluxcd/helm-operator#12](https://github.com/fluxcd/helm-operator/issues/12). 34 | 35 | It is possible to get a similar functionality by making use of an 36 | [umbrella chart](https://helm.sh/docs/howto/charts_tips_and_tricks/#complex-charts-with-many-dependencies) 37 | from a [Git repository chart source](chart-sources.md#git-repositories) with 38 | a [version range set](https://helm.sh/docs/topics/chart_best_practices/dependencies/#versions), 39 | as for charts from Git repository sources, a [dependency update is performed by 40 | default](chart-sources.md#dependency-updates), and that will download the 41 | latest available version within the defined range. 42 | 43 | For example, to make the Helm Operator install the latest `1.2.x` patch release 44 | for `foo-chart`, you would define the following in the `dependencies` of your 45 | (dummy) umbrella chart in Git: 46 | 47 | ```yaml 48 | dependencies: 49 | - name: 50 | version: ~1.4.4 51 | repository: https://charts.example.com 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/helmrelease-guide/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | > **🛑 Upgrade Advisory** 4 | > 5 | > This documentation is for Helm Operator (v1) which has [reached its end-of-life in November 2022](https://fluxcd.io/blog/2022/10/september-2022-update/#flux-legacy-v1-retirement-plan). 6 | > 7 | > We strongly recommend you familiarise yourself with the newest Flux and [migrate as soon as possible](https://fluxcd.io/flux/migration/). 8 | > 9 | > For documentation regarding the latest Flux, please refer to [this section](https://fluxcd.io/flux/). 10 | 11 | Even after having read everything this guide has to offer it is possible that a 12 | `HelmRelease` fails and you want to debug it to get to the cause. This may be 13 | a bit harder at first than you were used to while working with just `helm` 14 | because you are no longer in direct control but the Helm Operator is doing the 15 | work for you. 16 | 17 | This last section of the guide will give you some pointers on how to debug a 18 | failing `HelmRelease` resource. 19 | 20 | ## Getting the reason of failure 21 | 22 | If a release fails the reason of failure will be logged in the Helm Operator's 23 | logs _and_ recorded as a condition on the `HelmRelease` resource. You can view 24 | this condition by describing the `HelmRelease` resource using `kubectl`: 25 | 26 | ```console 27 | $ kubectl describe -n helmrelease/ 28 | ... 29 | Events: 30 | Type Reason Age From Message 31 | ---- ------ ---- ---- ------- 32 | Normal ReleaseSynced 55s helm-operator managed release 'default-podinfo-0' in namespace 'default' synchronized 33 | Warning FailedReleaseSync 18s helm-operator synchronization of release 'default-podinfo-0' in namespace 'default' failed: upgrade failed: "" is invalid: patch: [...] 34 | ``` 35 | 36 | In case of a release upgrade failure, the error as returned by Helm will be 37 | recorded in the message of `FailedReleaseSync`. If this does not give a 38 | conclusive answer the logs will likely contain more information about what 39 | happened during the release process: 40 | 41 | ```console 42 | kubectl logs deploy/flux-helm-operator 43 | ``` 44 | 45 | ## Manually performing a release to debug 46 | 47 | When describing the `HelmRelease` and the logs did not give any clues, it may 48 | help to perform the release manually using the same values as specified in the 49 | `HelmRelease` resource. When no `.valuesFrom` are defined, this can be done 50 | by making use of [`yq`](https://github.com/kislyuk/yq) (an extension to `jq`) 51 | and `kubectl`: 52 | 53 | ```console 54 | kubectl get helmrelease/ -n -o yaml | yq .spec.values -y | helm upgrade -i -f - 55 | ``` 56 | 57 | ## Getting help 58 | 59 | If you still have any questions about the Helm Operator: 60 | 61 | - Invite yourself to the CNCF community 62 | slack and ask a question on the [#flux](https://cloud-native.slack.com/messages/flux/) 63 | channel. 64 | - To be part of the conversation about Helm Operator's development, join the 65 | [flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev). 66 | - [File an issue.](https://github.com/fluxcd/helm-operator/issues/new) 67 | 68 | Your feedback is always welcome! -------------------------------------------------------------------------------- /docs/helmrelease-guide/tests.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | > **🛑 Upgrade Advisory** 4 | > 5 | > This documentation is for Helm Operator (v1) which has [reached its end-of-life in November 2022](https://fluxcd.io/blog/2022/10/september-2022-update/#flux-legacy-v1-retirement-plan). 6 | > 7 | > We strongly recommend you familiarise yourself with the newest Flux and [migrate as soon as possible](https://fluxcd.io/flux/migration/). 8 | > 9 | > For documentation regarding the latest Flux, please refer to [this section](https://fluxcd.io/flux/). 10 | 11 | [Helm tests](https://helm.sh/docs/topics/chart_tests/) are a useful validation 12 | mechanism for Helm Releases, and thus are supported by the Helm Operator. 13 | 14 | ## Enabling tests 15 | 16 | When tests for a `HelmRelease` are enabled, the Helm Operator will run them 17 | after any successful installation or upgrade attempt. In the case of a test 18 | failure, the prior installation or upgrade will be treated as failed, resulting 19 | in the release being purged or rolled back [if enabled](rollbacks.md#enabling-rollbacks). 20 | 21 | Tests can be enabled by setting `.test.enable`: 22 | 23 | ```yaml 24 | spec: 25 | test: 26 | enable: true 27 | ``` 28 | 29 | ## Wait interaction 30 | 31 | When tests are enabled, [resource waiting](release-configuration.md#wait-for-resources-to-be-ready) 32 | defaults to `true` since this is likely needed for test pre-conditions to be satisfied. 33 | 34 | ## Uninstall or rollback release on test failure 35 | 36 | The `spec.test.ignoreFailures` allows the `HelmRelease` to be left in a released state if the tests fail. 37 | Setting `ignoreFailures` to `false` will automatically uninstall or rollback the `HelmRelease` if any of the tests fail. 38 | If the tests are ignored, the `Released` condition will be left as `true` and `Tested` will be `false`. 39 | 40 | ```yaml 41 | spec: 42 | test: 43 | enable: true 44 | ignoreFailures: false 45 | ``` 46 | 47 | ## Test timeout 48 | 49 | Test timeout can be set via the `.test.timeout` option. 50 | 51 | ```yaml 52 | spec: 53 | test: 54 | enable: true 55 | timeout: 600 56 | ``` 57 | 58 | It is defined as the time to wait for any individual Kubernetes operation during 59 | tests in seconds. Defaults to `300` when omitted. 60 | 61 | ## Helm 2 vs 3 62 | 63 | In Helm 3, test-specific funcationality was removed in favor of a generic `test` 64 | hook no different than any other hook. The Helm Operator takes this into account 65 | as detailed below. 66 | 67 | ### Test cleanup 68 | 69 | Helm 3 removed the `helm test --cleanup` flag in favor of [hook delete policies](https://helm.sh/docs/topics/chart_tests/#notes). 70 | For `HelmRelease`s targeting Helm 2, test cleanup is enabled by default since 71 | upgrades are highly likely to cause test pod name conflicts without it. 72 | This flag currently deletes test pods immediately after they are run, but the only 73 | guarantee is that tests are cleaned up before running a subsequent test for the 74 | same `HelmRelease`, as delaying the deletion would allow time to debug failures, 75 | and thus may be implemented in the future. Test cleanup can be disabled by setting 76 | `.test.cleanup` to `false`. 77 | 78 | ```yaml 79 | spec: 80 | helmVersion: v2 81 | test: 82 | enable: true 83 | cleanup: false 84 | ``` 85 | 86 | ### Test parallelism 87 | 88 | Helm 2 supported `helm test --parallel --max 10` to run tests in parallel. Helm 3 will 89 | likely [expand this functionality to all hooks](https://github.com/helm/helm/issues/7763). Once the Helm 3 implementation is available 90 | this can be integrated into the Helm Operator, and translated into the equivalent 91 | Helm 2 options for test parallelism as well. 92 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | > **🛑 Upgrade Advisory** 4 | > 5 | > This documentation is for Helm Operator (v1) which has [reached its end-of-life in November 2022](https://fluxcd.io/blog/2022/10/september-2022-update/#flux-legacy-v1-retirement-plan). 6 | > 7 | > We strongly recommend you familiarise yourself with the newest Flux and [migrate as soon as possible](https://fluxcd.io/flux/migration/). 8 | > 9 | > For documentation regarding the latest Flux, please refer to [this section](https://fluxcd.io/flux/). 10 | 11 | Also see the [issues labeled with 12 | `question`](https://github.com/fluxcd/helm-operator/labels/question), which often 13 | explain workarounds. 14 | 15 | ## `failed to load chart from path [/tmp/flux-working012345678/chart/path] for release [x]: stat /tmp/flux-working012345678/chart/path: no such file or directory` 16 | 17 | The `.chart.path` of your Git sourced chart in the `HelmRelease` is likely 18 | incorrect. It is expected to be a path to the chart folder relative to the 19 | root directory of the Git repository. 20 | 21 | ## `Error creating helm client: failed to append certificates from file: /etc/fluxd/helm-ca/ca.crt` 22 | 23 | Your CA certificate content is not set correctly, check if your ConfigMap contains the correct values. Example: 24 | 25 | ```bash 26 | $ kubectl get configmaps flux-helm-tls-ca-config -o yaml 27 | apiVersion: v1 28 | data: 29 | ca.crt: | 30 | -----BEGIN CERTIFICATE----- 31 | .... 32 | -----END CERTIFICATE----- 33 | kind: ConfigMap 34 | metadata: 35 | creationTimestamp: 2018-07-04T15:27:25Z 36 | name: flux-helm-tls-ca-config 37 | namespace: helm-system 38 | resourceVersion: "1267257" 39 | selfLink: /api/v1/namespaces/helm-system/configmaps/flux-helm-tls-ca-config 40 | uid: c106f866-7f9e-11e8-904a-025000000001 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/tutorials/get-started-kustomize.md: -------------------------------------------------------------------------------- 1 | The contents of this document have been moved to [`get-started`](../get-started/using-kustomize.md). 2 | -------------------------------------------------------------------------------- /docs/tutorials/get-started.md: -------------------------------------------------------------------------------- 1 | The contents of this document have been moved to [`get-started`](../get-started/quickstart.md). 2 | -------------------------------------------------------------------------------- /hack/crd-reference-doc-gen/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hideMemberFields": [ 3 | "TypeMeta", 4 | "ValueFileSecrets" 5 | ], 6 | "hideTypePatterns": [ 7 | "ParseError$", 8 | "List$" 9 | ], 10 | "externalPackages": [ 11 | { 12 | "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$", 13 | "docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration" 14 | }, 15 | { 16 | "typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/", 17 | "docsURLTemplate": "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}" 18 | } 19 | ], 20 | "typeDisplayNamePrefixOverrides": { 21 | "k8s.io/api/": "Kubernetes ", 22 | "k8s.io/apimachinery/pkg/apis/": "Kubernetes " 23 | }, 24 | "markdownDisabled": false 25 | } 26 | -------------------------------------------------------------------------------- /hack/crd-reference-doc-gen/template/members.tpl: -------------------------------------------------------------------------------- 1 | {{ define "members" }} 2 | {{ range .Members }} 3 | {{ if not (hiddenMember .)}} 4 | 5 | 6 | {{ fieldName . }}
7 | 8 | {{ if linkForType .Type }} 9 | 10 | {{ typeDisplayName .Type }} 11 | 12 | {{ else }} 13 | {{ typeDisplayName .Type }} 14 | {{ end }} 15 | 16 | 17 | 18 | {{ if fieldEmbedded . }} 19 |

20 | (Members of {{ fieldName . }} are embedded into this type.) 21 |

22 | {{ end}} 23 | 24 | {{ if isOptionalMember .}} 25 | (Optional) 26 | {{ end }} 27 | 28 | {{ safe (renderComments .CommentLines) }} 29 | 30 | {{ if and (eq (.Type.Name.Name) "ObjectMeta") }} 31 | Refer to the Kubernetes API documentation for the fields of the 32 | metadata field. 33 | {{ end }} 34 | 35 | {{ if or (eq (fieldName .) "spec") (eq (fieldName .) "status") (eq (fieldName .) "chart") }} 36 | 37 | {{ template "members" .Type }} 38 |
39 | {{ end }} 40 | 41 | 42 | {{ end }} 43 | {{ end }} 44 | {{ end }} 45 | -------------------------------------------------------------------------------- /hack/crd-reference-doc-gen/template/pkg.tpl: -------------------------------------------------------------------------------- 1 | {{ define "packages" }} 2 | # `HelmRelease` Custom Resource 3 | 4 | 5 | {{ with .packages}} 6 |

Packages:

7 | 14 | {{ end}} 15 | 16 | {{ range .packages }} 17 |

18 | {{- packageDisplayName . -}} 19 |

20 | 21 | {{ with (index .GoPackages 0 )}} 22 | {{ with .DocComments }} 23 | {{ safe (renderComments .) }} 24 | {{ end }} 25 | {{ end }} 26 | 27 | Resource Types: 28 | 29 |
    30 | {{- range (visibleTypes (sortedTypes .Types)) -}} 31 | {{ if isExportedType . -}} 32 |
  • 33 | {{ typeDisplayName . }} 34 |
  • 35 | {{- end }} 36 | {{- end -}} 37 |
38 | 39 | {{ range (visibleTypes (sortedTypes .Types))}} 40 | {{ template "type" . }} 41 | {{ end }} 42 | {{ end }} 43 | 44 |
45 |

This page was automatically generated with gen-crd-api-reference-docs

46 |
47 | {{ end }} 48 | -------------------------------------------------------------------------------- /hack/crd-reference-doc-gen/template/type.tpl: -------------------------------------------------------------------------------- 1 | {{ define "type" }} 2 |

3 | {{- .Name.Name }} 4 | {{ if eq .Kind "Alias" }}({{.Underlying}} alias){{ end -}} 5 |

6 | 7 | {{ with (typeReferences .) }} 8 |

9 | (Appears on: 10 | {{- $prev := "" -}} 11 | {{- range . -}} 12 | {{- if $prev -}}, {{ end -}} 13 | {{ $prev = . }} 14 | {{ typeDisplayName . }} 15 | {{- end -}} 16 | ) 17 |

18 | {{ end }} 19 | 20 | {{ with .CommentLines }} 21 | {{ safe (renderComments .) }} 22 | {{ end }} 23 | 24 | {{ if .Members }} 25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {{ if isExportedType . }} 36 | 37 | 40 | 43 | 44 | 45 | 49 | 52 | 53 | {{ end }} 54 | {{ template "members" . }} 55 | 56 |
FieldDescription
38 | apiVersion
39 | string
41 | {{ apiGroup . }} 42 |
46 | kind
47 | string 48 |
50 | {{ .Name.Name }} 51 |
57 |
58 |
59 | {{ end }} 60 | {{ end }} 61 | -------------------------------------------------------------------------------- /hack/tools/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 | -------------------------------------------------------------------------------- /hack/tools/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fluxcd/helm-operator/hack/tools 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/ahmetb/gen-crd-api-reference-docs v0.2.0 7 | golang.org/x/tools v0.0.0-20190927052746-69890759d905 // indirect 8 | k8s.io/code-generator v0.17.2 9 | k8s.io/gengo v0.0.0-20190907103519-ebc107f98eab // indirect 10 | sigs.k8s.io/controller-tools v0.2.5 11 | ) 12 | -------------------------------------------------------------------------------- /hack/tools/tools.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package tools is used to track binary dependencies with go modules 3 | https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 4 | */ 5 | package tools 6 | 7 | // +build tools 8 | 9 | import ( 10 | _ "github.com/ahmetb/gen-crd-api-reference-docs" 11 | _ "k8s.io/code-generator/cmd/client-gen" 12 | _ "k8s.io/code-generator/cmd/deepcopy-gen" 13 | _ "k8s.io/code-generator/cmd/informer-gen" 14 | _ "k8s.io/code-generator/cmd/lister-gen" 15 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen" 16 | ) 17 | -------------------------------------------------------------------------------- /hack/update/deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Kindly borrowed from Kind 3 | # https://github.com/kubernetes-sigs/kind/blob/c5298b2/hack/update/deps.sh 4 | # 5 | # Copyright 2018 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 | # Runs go mod tidy 20 | # 21 | # Usage: 22 | # deps.sh 23 | set -o errexit -o nounset -o pipefail 24 | 25 | # cd to the repo root 26 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" 27 | cd "${REPO_ROOT}" 28 | 29 | # tidy all modules 30 | hack/go_container.sh go mod tidy 31 | SOURCE_DIR="${REPO_ROOT}/hack/tools" hack/go_container.sh go mod tidy 32 | -------------------------------------------------------------------------------- /hack/update/generate-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" 20 | 21 | "${REPO_ROOT}/hack/update/generate-codegen.sh" 22 | "${REPO_ROOT}/hack/update/generate-crds.sh" 23 | -------------------------------------------------------------------------------- /hack/update/generate-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Kindly borrowed from Kind 3 | # https://github.com/kubernetes-sigs/kind/blob/c5298b2/hack/update/generated.sh 4 | # 5 | # Copyright 2018 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 | # 'go generate's kind, using tools from vendor (go-bindata) 20 | set -o errexit -o nounset -o pipefail 21 | 22 | # cd to the repo root 23 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" 24 | cd "${REPO_ROOT}" 25 | 26 | # build the generators using the tools module 27 | cd "hack/tools" 28 | "${REPO_ROOT}/hack/go_container.sh" go build -o /out/deepcopy-gen k8s.io/code-generator/cmd/deepcopy-gen 29 | "${REPO_ROOT}/hack/go_container.sh" go build -o /out/client-gen k8s.io/code-generator/cmd/client-gen 30 | "${REPO_ROOT}/hack/go_container.sh" go build -o /out/lister-gen k8s.io/code-generator/cmd/lister-gen 31 | "${REPO_ROOT}/hack/go_container.sh" go build -o /out/informer-gen k8s.io/code-generator/cmd/informer-gen 32 | # go back to the root 33 | cd "${REPO_ROOT}" 34 | 35 | # turn off module mode before running the generators 36 | # https://github.com/kubernetes/code-generator/issues/69 37 | # we also need to populate vendor 38 | hack/go_container.sh go mod tidy 39 | hack/go_container.sh go mod vendor 40 | export GO111MODULE="off" 41 | 42 | # fake being in a gopath 43 | FAKE_GOPATH="$(mktemp -d)" 44 | trap 'rm -rf ${FAKE_GOPATH}' EXIT 45 | 46 | FAKE_REPOPATH="${FAKE_GOPATH}/src/github.com/fluxcd/helm-operator" 47 | mkdir -p "$(dirname "${FAKE_REPOPATH}")" && ln -s "${REPO_ROOT}" "${FAKE_REPOPATH}" 48 | 49 | export GOPATH="${FAKE_GOPATH}" 50 | cd "${FAKE_REPOPATH}" 51 | 52 | # run the generators 53 | echo "Generating deepcopy funcs" 54 | bin/deepcopy-gen \ 55 | --input-dirs "./pkg/apis/helm.fluxcd.io/v1" \ 56 | -O zz_generated.deepcopy \ 57 | --bounding-dirs "./pkg/apis" \ 58 | --go-header-file hack/tools/boilerplate.go.txt 59 | 60 | echo "Generating clientset" 61 | bin/client-gen \ 62 | --clientset-name "versioned" \ 63 | --input-base "" \ 64 | --input-dirs "./pkg/apis/helm.fluxcd.io/v1" \ 65 | --output-package "./pkg/client/clientset" \ 66 | --go-header-file hack/tools/boilerplate.go.txt 67 | 68 | echo "Generating listers" 69 | bin/lister-gen \ 70 | --input-dirs "./pkg/apis/helm.fluxcd.io/v1" \ 71 | --output-package "./pkg/client/listers" \ 72 | --go-header-file hack/tools/boilerplate.go.txt 73 | 74 | echo "Generating informers" 75 | bin/informer-gen \ 76 | --input-dirs "./pkg/apis/helm.fluxcd.io/v1" \ 77 | --versioned-clientset-package "./pkg/client/clientset/versioned" \ 78 | --listers-package "./pkg/client/listers" \ 79 | --output-package "./pkg/client/informers" \ 80 | --go-header-file hack/tools/boilerplate.go.txt 81 | 82 | # set module mode back, return to repo root 83 | export GO111MODULE="on" 84 | cd "${REPO_ROOT}" 85 | -------------------------------------------------------------------------------- /hack/update/generate-crds.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Kindly borrowed from Kind 3 | # https://github.com/kubernetes-sigs/kind/blob/c5298b2/hack/update/generated.sh 4 | # 5 | # Copyright 2018 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 | # 'go generate's kind, using tools from vendor (go-bindata) 20 | set -o errexit -o nounset -o pipefail 21 | 22 | # cd to the repo root 23 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" 24 | cd "${REPO_ROOT}" 25 | 26 | # build the generators using the tools module 27 | cd "hack/tools" 28 | "${REPO_ROOT}/hack/go_container.sh" go build -o /out/controller-gen sigs.k8s.io/controller-tools/cmd/controller-gen 29 | "${REPO_ROOT}/hack/go_container.sh" go build -o /out/crd-reference-gen github.com/ahmetb/gen-crd-api-reference-docs 30 | cd "${REPO_ROOT}" 31 | 32 | # turn off module mode before running the generators 33 | # https://github.com/kubernetes/code-generator/issues/69 34 | hack/go_container.sh go mod tidy 35 | hack/go_container.sh go mod vendor 36 | export GO111MODULE="off" 37 | 38 | # fake being in a gopath 39 | FAKE_GOPATH="$(mktemp -d)" 40 | trap 'rm -rf ${FAKE_GOPATH}' EXIT 41 | 42 | FAKE_REPOPATH="${FAKE_GOPATH}/src/github.com/fluxcd/helm-operator" 43 | mkdir -p "$(dirname "${FAKE_REPOPATH}")" && ln -s "${REPO_ROOT}" "${FAKE_REPOPATH}" 44 | 45 | export GOPATH="${FAKE_GOPATH}" 46 | cd "${FAKE_REPOPATH}" 47 | 48 | # run the generators 49 | CRD_DIR="./chart/helm-operator/crds" 50 | echo "Generating OpenAPI v3 schemas for chart CRDs" 51 | bin/controller-gen \ 52 | schemapatch:manifests="${CRD_DIR}" \ 53 | output:stdout \ 54 | crd:crdVersions=v1 \ 55 | paths=./pkg/apis/... | tail -n+3 - > ${CRD_DIR}/helmrelease.yaml 56 | 57 | echo "Forging CRD template for \`pkg/install\` from generated chart CRDs" 58 | out="./pkg/install/templates/crds.yaml.tmpl" 59 | rm "$out" || true 60 | touch "$out" 61 | for file in $(find "${CRD_DIR}" -type f | sort -V); do 62 | # concatenate all files while removing blank (^$) lines 63 | printf -- "---\n" >> "$out" 64 | < "$file" sed '/^$$/d' >> "$out" 65 | done 66 | chmod 644 "$out" 67 | 68 | echo "Generating CRD reference documentation" 69 | bin/crd-reference-gen \ 70 | -api-dir=./pkg/apis/helm.fluxcd.io/v1 \ 71 | -config=./hack/crd-reference-doc-gen/config.json \ 72 | -template-dir=./hack/crd-reference-doc-gen/template \ 73 | -out-file=./docs/references/helmrelease-custom-resource.md 74 | 75 | # set module mode back, return to repo root 76 | export GO111MODULE="on" 77 | cd "${REPO_ROOT}" 78 | -------------------------------------------------------------------------------- /hack/update/k8s-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -euo pipefail 3 | 4 | VERSION=${1#"v"} 5 | if [ -z "$VERSION" ]; then 6 | echo "Must specify version!" 7 | exit 1 8 | fi 9 | 10 | # cd to the repo root 11 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" 12 | cd "${REPO_ROOT}" 13 | 14 | MODS=($( 15 | curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod | 16 | sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p' 17 | )) 18 | for MOD in "${MODS[@]}"; do 19 | V=$( 20 | go mod download -json "${MOD}@kubernetes-${VERSION}" | 21 | sed -n 's|.*"Version": "\(.*\)".*|\1|p' 22 | ) 23 | go mod edit "-replace=${MOD}=${MOD}@${V}" 24 | done 25 | go get "k8s.io/kubernetes@v${VERSION}" 26 | -------------------------------------------------------------------------------- /hack/update/verify.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" 20 | 21 | TMP_DIFFROOT="${REPO_ROOT}/_tmp" 22 | DIFFROOT="${REPO_ROOT}" 23 | 24 | cleanup() { 25 | rm -rf "${TMP_DIFFROOT}" 26 | } 27 | trap "cleanup" EXIT SIGINT 28 | 29 | cleanup 30 | 31 | mkdir -p "${TMP_DIFFROOT}" 32 | cp -a "${DIFFROOT}"/{pkg,docs,deploy} "${TMP_DIFFROOT}" 33 | 34 | "${REPO_ROOT}/hack/update/generate-all.sh" 35 | 36 | echo "diffing ${DIFFROOT} against freshly generated files" 37 | ret=0 38 | for i in {pkg,docs,deploy}; do 39 | diff -Naupr --no-dereference "${TMP_DIFFROOT}/${i}" "${DIFFROOT}/${i}" || ret=$? 40 | done 41 | cp -a "${TMP_DIFFROOT}"/{pkg,docs,deploy} "${DIFFROOT}" 42 | if [[ $ret -eq 0 ]] 43 | then 44 | echo "${DIFFROOT} up to date." 45 | else 46 | echo "${DIFFROOT} is out of date. Please run hack/update/generate-all.sh" 47 | exit 1 48 | fi 49 | -------------------------------------------------------------------------------- /internal/lockedfile/internal/filelock/filelock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package filelock provides a platform-independent API for advisory file 6 | // locking. Calls to functions in this package on platforms that do not support 7 | // advisory locks will return errors for which IsNotSupported returns true. 8 | package filelock 9 | 10 | import ( 11 | "errors" 12 | "os" 13 | ) 14 | 15 | // A File provides the minimal set of methods required to lock an open file. 16 | // File implementations must be usable as map keys. 17 | // The usual implementation is *os.File. 18 | type File interface { 19 | // Name returns the name of the file. 20 | Name() string 21 | 22 | // Fd returns a valid file descriptor. 23 | // (If the File is an *os.File, it must not be closed.) 24 | Fd() uintptr 25 | 26 | // Stat returns the FileInfo structure describing file. 27 | Stat() (os.FileInfo, error) 28 | } 29 | 30 | // Lock places an advisory write lock on the file, blocking until it can be 31 | // locked. 32 | // 33 | // If Lock returns nil, no other process will be able to place a read or write 34 | // lock on the file until this process exits, closes f, or calls Unlock on it. 35 | // 36 | // If f's descriptor is already read- or write-locked, the behavior of Lock is 37 | // unspecified. 38 | // 39 | // Closing the file may or may not release the lock promptly. Callers should 40 | // ensure that Unlock is always called when Lock succeeds. 41 | func Lock(f File) error { 42 | return lock(f, writeLock) 43 | } 44 | 45 | // RLock places an advisory read lock on the file, blocking until it can be locked. 46 | // 47 | // If RLock returns nil, no other process will be able to place a write lock on 48 | // the file until this process exits, closes f, or calls Unlock on it. 49 | // 50 | // If f is already read- or write-locked, the behavior of RLock is unspecified. 51 | // 52 | // Closing the file may or may not release the lock promptly. Callers should 53 | // ensure that Unlock is always called if RLock succeeds. 54 | func RLock(f File) error { 55 | return lock(f, readLock) 56 | } 57 | 58 | // Unlock removes an advisory lock placed on f by this process. 59 | // 60 | // The caller must not attempt to unlock a file that is not locked. 61 | func Unlock(f File) error { 62 | return unlock(f) 63 | } 64 | 65 | // String returns the name of the function corresponding to lt 66 | // (Lock, RLock, or Unlock). 67 | func (lt lockType) String() string { 68 | switch lt { 69 | case readLock: 70 | return "RLock" 71 | case writeLock: 72 | return "Lock" 73 | default: 74 | return "Unlock" 75 | } 76 | } 77 | 78 | // IsNotSupported returns a boolean indicating whether the error is known to 79 | // report that a function is not supported (possibly for a specific input). 80 | // It is satisfied by ErrNotSupported as well as some syscall errors. 81 | func IsNotSupported(err error) bool { 82 | return isNotSupported(underlyingError(err)) 83 | } 84 | 85 | var ErrNotSupported = errors.New("operation not supported") 86 | 87 | // underlyingError returns the underlying error for known os error types. 88 | func underlyingError(err error) error { 89 | switch err := err.(type) { 90 | case *os.PathError: 91 | return err.Err 92 | case *os.LinkError: 93 | return err.Err 94 | case *os.SyscallError: 95 | return err.Err 96 | } 97 | return err 98 | } 99 | -------------------------------------------------------------------------------- /internal/lockedfile/internal/filelock/filelock_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin dragonfly freebsd linux netbsd openbsd 6 | 7 | package filelock 8 | 9 | import ( 10 | "os" 11 | "syscall" 12 | ) 13 | 14 | type lockType int16 15 | 16 | const ( 17 | readLock lockType = syscall.LOCK_SH 18 | writeLock lockType = syscall.LOCK_EX 19 | ) 20 | 21 | func lock(f File, lt lockType) (err error) { 22 | for { 23 | err = syscall.Flock(int(f.Fd()), int(lt)) 24 | if err != syscall.EINTR { 25 | break 26 | } 27 | } 28 | if err != nil { 29 | return &os.PathError{ 30 | Op: lt.String(), 31 | Path: f.Name(), 32 | Err: err, 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | func unlock(f File) error { 39 | return lock(f, syscall.LOCK_UN) 40 | } 41 | 42 | func isNotSupported(err error) bool { 43 | return err == syscall.ENOSYS || err == syscall.ENOTSUP || err == syscall.EOPNOTSUPP || err == ErrNotSupported 44 | } 45 | -------------------------------------------------------------------------------- /internal/lockedfile/lockedfile_filelock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !plan9 6 | 7 | package lockedfile 8 | 9 | import ( 10 | "os" 11 | 12 | "github.com/fluxcd/helm-operator/internal/lockedfile/internal/filelock" 13 | ) 14 | 15 | func openFile(name string, flag int, perm os.FileMode) (*os.File, error) { 16 | // On BSD systems, we could add the O_SHLOCK or O_EXLOCK flag to the OpenFile 17 | // call instead of locking separately, but we have to support separate locking 18 | // calls for Linux and Windows anyway, so it's simpler to use that approach 19 | // consistently. 20 | 21 | f, err := os.OpenFile(name, flag&^os.O_TRUNC, perm) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) { 27 | case os.O_WRONLY, os.O_RDWR: 28 | err = filelock.Lock(f) 29 | default: 30 | err = filelock.RLock(f) 31 | } 32 | if err != nil { 33 | f.Close() 34 | return nil, err 35 | } 36 | 37 | if flag&os.O_TRUNC == os.O_TRUNC { 38 | if err := f.Truncate(0); err != nil { 39 | // The documentation for os.O_TRUNC says “if possible, truncate file when 40 | // opened”, but doesn't define “possible” (golang.org/issue/28699). 41 | // We'll treat regular files (and symlinks to regular files) as “possible” 42 | // and ignore errors for the rest. 43 | if fi, statErr := f.Stat(); statErr != nil || fi.Mode().IsRegular() { 44 | filelock.Unlock(f) 45 | f.Close() 46 | return nil, err 47 | } 48 | } 49 | } 50 | 51 | return f, nil 52 | } 53 | 54 | func closeFile(f *os.File) error { 55 | // Since locking syscalls operate on file descriptors, we must unlock the file 56 | // while the descriptor is still valid — that is, before the file is closed — 57 | // and avoid unlocking files that are already closed. 58 | err := filelock.Unlock(f) 59 | 60 | if closeErr := f.Close(); err == nil { 61 | err = closeErr 62 | } 63 | return err 64 | } 65 | -------------------------------------------------------------------------------- /internal/lockedfile/mutex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package lockedfile 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "sync" 11 | ) 12 | 13 | // A Mutex provides mutual exclusion within and across processes by locking a 14 | // well-known file. Such a file generally guards some other part of the 15 | // filesystem: for example, a Mutex file in a directory might guard access to 16 | // the entire tree rooted in that directory. 17 | // 18 | // Mutex does not implement sync.Locker: unlike a sync.Mutex, a lockedfile.Mutex 19 | // can fail to lock (e.g. if there is a permission error in the filesystem). 20 | // 21 | // Like a sync.Mutex, a Mutex may be included as a field of a larger struct but 22 | // must not be copied after first use. The Path field must be set before first 23 | // use and must not be change thereafter. 24 | type Mutex struct { 25 | Path string // The path to the well-known lock file. Must be non-empty. 26 | mu sync.Mutex // A redundant mutex. The race detector doesn't know about file locking, so in tests we may need to lock something that it understands. 27 | } 28 | 29 | // MutexAt returns a new Mutex with Path set to the given non-empty path. 30 | func MutexAt(path string) *Mutex { 31 | if path == "" { 32 | panic("lockedfile.MutexAt: path must be non-empty") 33 | } 34 | return &Mutex{Path: path} 35 | } 36 | 37 | func (mu *Mutex) String() string { 38 | return fmt.Sprintf("lockedfile.Mutex(%s)", mu.Path) 39 | } 40 | 41 | // Lock attempts to lock the Mutex. 42 | // 43 | // If successful, Lock returns a non-nil unlock function: it is provided as a 44 | // return-value instead of a separate method to remind the caller to check the 45 | // accompanying error. (See https://golang.org/issue/20803.) 46 | func (mu *Mutex) Lock() (unlock func(), err error) { 47 | if mu.Path == "" { 48 | panic("lockedfile.Mutex: missing Path during Lock") 49 | } 50 | 51 | // We could use either O_RDWR or O_WRONLY here. If we choose O_RDWR and the 52 | // file at mu.Path is write-only, the call to OpenFile will fail with a 53 | // permission error. That's actually what we want: if we add an RLock method 54 | // in the future, it should call OpenFile with O_RDONLY and will require the 55 | // files must be readable, so we should not let the caller make any 56 | // assumptions about Mutex working with write-only files. 57 | f, err := OpenFile(mu.Path, os.O_RDWR|os.O_CREATE, 0666) 58 | if err != nil { 59 | return nil, err 60 | } 61 | mu.mu.Lock() 62 | 63 | return func() { 64 | mu.mu.Unlock() 65 | f.Close() 66 | }, nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // Server is the interface that must be satisfied in order to serve 4 | // HTTP API requests. 5 | type Server interface { 6 | SyncMirrors() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/apis/helm.fluxcd.io/register.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | const ( 4 | GroupName = "helm.fluxcd.io" 5 | ) 6 | -------------------------------------------------------------------------------- /pkg/apis/helm.fluxcd.io/v1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:deepcopy-gen=package,register 2 | // +groupName=helm.fluxcd.io 3 | // Package v1 is the v1 version of the API. The prior 4 | // version was flux.weave.works/v1beta1. 5 | package v1 6 | -------------------------------------------------------------------------------- /pkg/apis/helm.fluxcd.io/v1/register.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | 8 | "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io" 9 | ) 10 | 11 | const Version = "v1" 12 | 13 | // SchemeGroupVersion is group version used to register these objects 14 | var SchemeGroupVersion = schema.GroupVersion{Group: helm.GroupName, Version: Version} 15 | 16 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 17 | func Resource(resource string) schema.GroupResource { 18 | return SchemeGroupVersion.WithResource(resource).GroupResource() 19 | } 20 | 21 | var ( 22 | // SchemeBuilder will stay in k8s.io/kubernetes. 23 | SchemeBuilder runtime.SchemeBuilder 24 | localSchemeBuilder = &SchemeBuilder 25 | // AddToScheme will stay in k8s.io/kubernetes. 26 | AddToScheme = localSchemeBuilder.AddToScheme 27 | ) 28 | 29 | func init() { 30 | // We only register manually written functions here. The registration of the 31 | // generated functions takes place in the generated files. The separation 32 | // makes the code compile even when the generated files are missing. 33 | localSchemeBuilder.Register(addKnownTypes) 34 | } 35 | 36 | // Adds the list of known types to api.Scheme. 37 | func addKnownTypes(scheme *runtime.Scheme) error { 38 | scheme.AddKnownTypes(SchemeGroupVersion, 39 | &HelmRelease{}, 40 | &HelmReleaseList{}, 41 | ) 42 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/apis/helm.fluxcd.io/v1/types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | // +kubebuilder:validation:Enum="True";"False";"Unknown" 4 | type ConditionStatus string 5 | 6 | // These are valid condition statuses. 7 | // "ConditionTrue" means a resource is in the condition, 8 | // "ConditionFalse" means a resource is not in the condition, 9 | // "ConditionUnknown" means the operator can't decide if a 10 | // resource is in the condition or not. 11 | const ( 12 | ConditionTrue ConditionStatus = "True" 13 | ConditionFalse ConditionStatus = "False" 14 | ConditionUnknown ConditionStatus = "Unknown" 15 | ) 16 | 17 | type LocalObjectReference struct { 18 | Name string `json:"name"` 19 | } 20 | 21 | type ObjectReference struct { 22 | LocalObjectReference `json:",inline"` 23 | Namespace string `json:"namespace,omitempty"` 24 | } 25 | 26 | type ConfigMapKeySelector struct { 27 | LocalObjectReference `json:",inline"` 28 | // +optional 29 | Namespace string `json:"namespace,omitempty"` 30 | // +optional 31 | Key string `json:"key,omitempty"` 32 | } 33 | 34 | type OptionalConfigMapKeySelector struct { 35 | ConfigMapKeySelector `json:",inline"` 36 | // +optional 37 | Optional bool `json:"optional,omitempty"` 38 | } 39 | 40 | type SecretKeySelector struct { 41 | LocalObjectReference `json:",inline"` 42 | // +optional 43 | Namespace string `json:"namespace,omitempty"` 44 | // +optional 45 | Key string `json:"key,omitempty"` 46 | } 47 | 48 | type OptionalSecretKeySelector struct { 49 | SecretKeySelector `json:",inline"` 50 | // +optional 51 | Optional bool `json:"optional,omitempty"` 52 | } 53 | -------------------------------------------------------------------------------- /pkg/apis/helm.fluxcd.io/v1/types_helmrelease_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRefOrDefault(t *testing.T) { 10 | testCases := []struct { 11 | chartSource GitChartSource 12 | potentialDefault string 13 | expected string 14 | }{ 15 | { 16 | chartSource: GitChartSource{ 17 | Ref: "master", 18 | }, 19 | potentialDefault: "dev", 20 | expected: "master", 21 | }, 22 | { 23 | chartSource: GitChartSource{}, 24 | potentialDefault: "dev", 25 | expected: "dev", 26 | }, 27 | } 28 | 29 | for _, tc := range testCases { 30 | got := tc.chartSource.RefOrDefault(tc.potentialDefault) 31 | assert.Equal(t, tc.expected, got) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/chartsync/download.go: -------------------------------------------------------------------------------- 1 | package chartsync 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | 10 | helmfluxv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" 11 | "github.com/fluxcd/helm-operator/pkg/helm" 12 | ) 13 | 14 | // EnsureChartFetched returns the path to a downloaded chart, fetching 15 | // it first if necessary. It returns the (expected) path to the chart, 16 | // a boolean indicating a fetch, and either an error or nil. 17 | func EnsureChartFetched(client helm.Client, base string, source *helmfluxv1.RepoChartSource) (string, bool, error) { 18 | repoPath, filename, err := makeChartPath(base, client.Version(), source) 19 | if err != nil { 20 | return "", false, ChartUnavailableError{err} 21 | } 22 | chartPath := filepath.Join(repoPath, filename) 23 | stat, err := os.Stat(chartPath) 24 | switch { 25 | case os.IsNotExist(err): 26 | chartPath, err = downloadChart(client, repoPath, source) 27 | if err != nil { 28 | return chartPath, false, ChartUnavailableError{err} 29 | } 30 | return chartPath, true, nil 31 | case err != nil: 32 | return chartPath, false, ChartUnavailableError{err} 33 | case stat.IsDir(): 34 | return chartPath, false, ChartUnavailableError{errors.New("path to chart exists but is a directory")} 35 | } 36 | return chartPath, false, nil 37 | } 38 | 39 | // makeChartPath gives the expected filesystem location for a chart, 40 | // without testing whether the file exists or not. 41 | func makeChartPath(base string, clientVersion string, source *helmfluxv1.RepoChartSource) (string, string, error) { 42 | // We don't need to obscure the location of the charts in the 43 | // filesystem; but we do need a stable, filesystem-friendly path 44 | // to them that is based on the URL and the client version. 45 | repoPath := filepath.Join(base, clientVersion, base64.URLEncoding.EncodeToString([]byte(source.CleanRepoURL()))) 46 | if err := os.MkdirAll(repoPath, 00750); err != nil { 47 | return "", "", err 48 | } 49 | filename := fmt.Sprintf("%s-%s.tgz", source.Name, source.Version) 50 | return repoPath, filename, nil 51 | } 52 | 53 | // downloadChart attempts to pull a chart tarball, given the name, 54 | // version and repo URL in `source`, and the path to write the file 55 | // to in `destFolder`. 56 | func downloadChart(helm helm.Client, destFolder string, source *helmfluxv1.RepoChartSource) (string, error) { 57 | return helm.PullWithRepoURL(source.RepoURL, source.Name, source.Version, destFolder) 58 | } 59 | -------------------------------------------------------------------------------- /pkg/chartsync/errors.go: -------------------------------------------------------------------------------- 1 | package chartsync 2 | 3 | // ChartUnavailableError is returned when the requested chart is 4 | // unavailable, and the reason is known and finite. 5 | type ChartUnavailableError struct { 6 | Err error 7 | } 8 | 9 | func (err ChartUnavailableError) Unwrap() error { 10 | return err.Err 11 | } 12 | 13 | func (err ChartUnavailableError) Error() string { 14 | return "chart unavailable: " + err.Err.Error() 15 | } 16 | 17 | // ChartNotReadyError is returned when the requested chart is 18 | // unavailable at the moment, but may become at available a later stage 19 | // without any interference from a human. 20 | type ChartNotReadyError struct { 21 | Err error 22 | } 23 | 24 | func (err ChartNotReadyError) Unwrap() error { 25 | return err.Err 26 | } 27 | 28 | func (err ChartNotReadyError) Error() string { 29 | return "chart not ready: " + err.Err.Error() 30 | } 31 | 32 | // GitAuthError presents a error that has occured when handling 33 | // the git auth details 34 | type GitAuthError struct { 35 | Err error 36 | } 37 | 38 | func (err GitAuthError) Unwrap() error { 39 | return err.Err 40 | } 41 | 42 | func (err GitAuthError) Error() string { 43 | return "git auth error: " + err.Err.Error() 44 | } 45 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | package versioned 20 | 21 | import ( 22 | helmv1 "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/typed/helm.fluxcd.io/v1" 23 | discovery "k8s.io/client-go/discovery" 24 | rest "k8s.io/client-go/rest" 25 | flowcontrol "k8s.io/client-go/util/flowcontrol" 26 | ) 27 | 28 | type Interface interface { 29 | Discovery() discovery.DiscoveryInterface 30 | HelmV1() helmv1.HelmV1Interface 31 | } 32 | 33 | // Clientset contains the clients for groups. Each group has exactly one 34 | // version included in a Clientset. 35 | type Clientset struct { 36 | *discovery.DiscoveryClient 37 | helmV1 *helmv1.HelmV1Client 38 | } 39 | 40 | // HelmV1 retrieves the HelmV1Client 41 | func (c *Clientset) HelmV1() helmv1.HelmV1Interface { 42 | return c.helmV1 43 | } 44 | 45 | // Discovery retrieves the DiscoveryClient 46 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 47 | if c == nil { 48 | return nil 49 | } 50 | return c.DiscoveryClient 51 | } 52 | 53 | // NewForConfig creates a new Clientset for the given config. 54 | func NewForConfig(c *rest.Config) (*Clientset, error) { 55 | configShallowCopy := *c 56 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 57 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 58 | } 59 | var cs Clientset 60 | var err error 61 | cs.helmV1, err = helmv1.NewForConfig(&configShallowCopy) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return &cs, nil 71 | } 72 | 73 | // NewForConfigOrDie creates a new Clientset for the given config and 74 | // panics if there is an error in the config. 75 | func NewForConfigOrDie(c *rest.Config) *Clientset { 76 | var cs Clientset 77 | cs.helmV1 = helmv1.NewForConfigOrDie(c) 78 | 79 | cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) 80 | return &cs 81 | } 82 | 83 | // New creates a new Clientset for the given RESTClient. 84 | func New(c rest.Interface) *Clientset { 85 | var cs Clientset 86 | cs.helmV1 = helmv1.New(c) 87 | 88 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 89 | return &cs 90 | } 91 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated clientset. 20 | package versioned 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | clientset "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned" 23 | helmv1 "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/typed/helm.fluxcd.io/v1" 24 | fakehelmv1 "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/typed/helm.fluxcd.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 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 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 37 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 38 | for _, obj := range objects { 39 | if err := o.Add(obj); err != nil { 40 | panic(err) 41 | } 42 | } 43 | 44 | cs := &Clientset{tracker: o} 45 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 46 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 47 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 48 | gvr := action.GetResource() 49 | ns := action.GetNamespace() 50 | watch, err := o.Watch(gvr, ns) 51 | if err != nil { 52 | return false, nil, err 53 | } 54 | return true, watch, nil 55 | }) 56 | 57 | return cs 58 | } 59 | 60 | // Clientset implements clientset.Interface. Meant to be embedded into a 61 | // struct to get a default implementation. This makes faking out just the method 62 | // you want to test easier. 63 | type Clientset struct { 64 | testing.Fake 65 | discovery *fakediscovery.FakeDiscovery 66 | tracker testing.ObjectTracker 67 | } 68 | 69 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 70 | return c.discovery 71 | } 72 | 73 | func (c *Clientset) Tracker() testing.ObjectTracker { 74 | return c.tracker 75 | } 76 | 77 | var _ clientset.Interface = &Clientset{} 78 | 79 | // HelmV1 retrieves the HelmV1Client 80 | func (c *Clientset) HelmV1() helmv1.HelmV1Interface { 81 | return &fakehelmv1.FakeHelmV1{Fake: &c.Fake} 82 | } 83 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated fake clientset. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | helmv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.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/client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | // This package contains the scheme of the automatically generated clientset. 20 | package scheme 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | package scheme 20 | 21 | import ( 22 | helmv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.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/client/clientset/versioned/typed/helm.fluxcd.io/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/helm.fluxcd.io/v1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/helm.fluxcd.io/v1/fake/fake_helm.fluxcd.io_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | v1 "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/typed/helm.fluxcd.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) HelmReleases(namespace string) v1.HelmReleaseInterface { 32 | return &FakeHelmReleases{c, namespace} 33 | } 34 | 35 | // RESTClient returns a RESTClient that is used to communicate 36 | // with API server by this client implementation. 37 | func (c *FakeHelmV1) RESTClient() rest.Interface { 38 | var ret *rest.RESTClient 39 | return ret 40 | } 41 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/helm.fluxcd.io/v1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | type HelmReleaseExpansion interface{} 22 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/helm.fluxcd.io/v1/helm.fluxcd.io_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 client-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" 23 | "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/scheme" 24 | rest "k8s.io/client-go/rest" 25 | ) 26 | 27 | type HelmV1Interface interface { 28 | RESTClient() rest.Interface 29 | HelmReleasesGetter 30 | } 31 | 32 | // HelmV1Client is used to interact with features provided by the helm.fluxcd.io group. 33 | type HelmV1Client struct { 34 | restClient rest.Interface 35 | } 36 | 37 | func (c *HelmV1Client) HelmReleases(namespace string) HelmReleaseInterface { 38 | return newHelmReleases(c, namespace) 39 | } 40 | 41 | // NewForConfig creates a new HelmV1Client for the given config. 42 | func NewForConfig(c *rest.Config) (*HelmV1Client, error) { 43 | config := *c 44 | if err := setConfigDefaults(&config); err != nil { 45 | return nil, err 46 | } 47 | client, err := rest.RESTClientFor(&config) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &HelmV1Client{client}, nil 52 | } 53 | 54 | // NewForConfigOrDie creates a new HelmV1Client for the given config and 55 | // panics if there is an error in the config. 56 | func NewForConfigOrDie(c *rest.Config) *HelmV1Client { 57 | client, err := NewForConfig(c) 58 | if err != nil { 59 | panic(err) 60 | } 61 | return client 62 | } 63 | 64 | // New creates a new HelmV1Client for the given RESTClient. 65 | func New(c rest.Interface) *HelmV1Client { 66 | return &HelmV1Client{c} 67 | } 68 | 69 | func setConfigDefaults(config *rest.Config) error { 70 | gv := v1.SchemeGroupVersion 71 | config.GroupVersion = &gv 72 | config.APIPath = "/apis" 73 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 74 | 75 | if config.UserAgent == "" { 76 | config.UserAgent = rest.DefaultKubernetesUserAgent() 77 | } 78 | 79 | return nil 80 | } 81 | 82 | // RESTClient returns a RESTClient that is used to communicate 83 | // with API server by this client implementation. 84 | func (c *HelmV1Client) RESTClient() rest.Interface { 85 | if c == nil { 86 | return nil 87 | } 88 | return c.restClient 89 | } 90 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 informer-gen. DO NOT EDIT. 18 | 19 | package externalversions 20 | 21 | import ( 22 | "fmt" 23 | 24 | v1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | cache "k8s.io/client-go/tools/cache" 27 | ) 28 | 29 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 30 | // sharedInformers based on type 31 | type GenericInformer interface { 32 | Informer() cache.SharedIndexInformer 33 | Lister() cache.GenericLister 34 | } 35 | 36 | type genericInformer struct { 37 | informer cache.SharedIndexInformer 38 | resource schema.GroupResource 39 | } 40 | 41 | // Informer returns the SharedIndexInformer. 42 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 43 | return f.informer 44 | } 45 | 46 | // Lister returns the GenericLister. 47 | func (f *genericInformer) Lister() cache.GenericLister { 48 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 49 | } 50 | 51 | // ForResource gives generic access to a shared informer of the matching type 52 | // TODO extend this to unknown resources with a client pool 53 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 54 | switch resource { 55 | // Group=helm.fluxcd.io, Version=v1 56 | case v1.SchemeGroupVersion.WithResource("helmreleases"): 57 | return &genericInformer{resource: resource.GroupResource(), informer: f.Helm().V1().HelmReleases().Informer()}, nil 58 | 59 | } 60 | 61 | return nil, fmt.Errorf("no informer found for %v", resource) 62 | } 63 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/helm.fluxcd.io/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 informer-gen. DO NOT EDIT. 18 | 19 | package helm 20 | 21 | import ( 22 | v1 "github.com/fluxcd/helm-operator/pkg/client/informers/externalversions/helm.fluxcd.io/v1" 23 | internalinterfaces "github.com/fluxcd/helm-operator/pkg/client/informers/externalversions/internalinterfaces" 24 | ) 25 | 26 | // Interface provides access to each of this group's versions. 27 | type Interface interface { 28 | // V1 provides access to shared informers for resources in V1. 29 | V1() v1.Interface 30 | } 31 | 32 | type group struct { 33 | factory internalinterfaces.SharedInformerFactory 34 | namespace string 35 | tweakListOptions internalinterfaces.TweakListOptionsFunc 36 | } 37 | 38 | // New returns a new Interface. 39 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 40 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 41 | } 42 | 43 | // V1 returns a new v1.Interface. 44 | func (g *group) V1() v1.Interface { 45 | return v1.New(g.factory, g.namespace, g.tweakListOptions) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/helm.fluxcd.io/v1/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 informer-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | internalinterfaces "github.com/fluxcd/helm-operator/pkg/client/informers/externalversions/internalinterfaces" 23 | ) 24 | 25 | // Interface provides access to all the informers in this group version. 26 | type Interface interface { 27 | // HelmReleases returns a HelmReleaseInformer. 28 | HelmReleases() HelmReleaseInformer 29 | } 30 | 31 | type version struct { 32 | factory internalinterfaces.SharedInformerFactory 33 | namespace string 34 | tweakListOptions internalinterfaces.TweakListOptionsFunc 35 | } 36 | 37 | // New returns a new Interface. 38 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 39 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 40 | } 41 | 42 | // HelmReleases returns a HelmReleaseInformer. 43 | func (v *version) HelmReleases() HelmReleaseInformer { 44 | return &helmReleaseInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 45 | } 46 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 informer-gen. DO NOT EDIT. 18 | 19 | package internalinterfaces 20 | 21 | import ( 22 | time "time" 23 | 24 | versioned "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | runtime "k8s.io/apimachinery/pkg/runtime" 27 | cache "k8s.io/client-go/tools/cache" 28 | ) 29 | 30 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 31 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 32 | 33 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 34 | type SharedInformerFactory interface { 35 | Start(stopCh <-chan struct{}) 36 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 37 | } 38 | 39 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 40 | type TweakListOptionsFunc func(*v1.ListOptions) 41 | -------------------------------------------------------------------------------- /pkg/client/listers/helm.fluxcd.io/v1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | // HelmReleaseListerExpansion allows custom methods to be added to 22 | // HelmReleaseLister. 23 | type HelmReleaseListerExpansion interface{} 24 | 25 | // HelmReleaseNamespaceListerExpansion allows custom methods to be added to 26 | // HelmReleaseNamespaceLister. 27 | type HelmReleaseNamespaceListerExpansion interface{} 28 | -------------------------------------------------------------------------------- /pkg/client/listers/helm.fluxcd.io/v1/helmrelease.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018-2019 The Flux CD contributors. 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 lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" 23 | "k8s.io/apimachinery/pkg/api/errors" 24 | "k8s.io/apimachinery/pkg/labels" 25 | "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // HelmReleaseLister helps list HelmReleases. 29 | type HelmReleaseLister interface { 30 | // List lists all HelmReleases in the indexer. 31 | List(selector labels.Selector) (ret []*v1.HelmRelease, err error) 32 | // HelmReleases returns an object that can list and get HelmReleases. 33 | HelmReleases(namespace string) HelmReleaseNamespaceLister 34 | HelmReleaseListerExpansion 35 | } 36 | 37 | // helmReleaseLister implements the HelmReleaseLister interface. 38 | type helmReleaseLister struct { 39 | indexer cache.Indexer 40 | } 41 | 42 | // NewHelmReleaseLister returns a new HelmReleaseLister. 43 | func NewHelmReleaseLister(indexer cache.Indexer) HelmReleaseLister { 44 | return &helmReleaseLister{indexer: indexer} 45 | } 46 | 47 | // List lists all HelmReleases in the indexer. 48 | func (s *helmReleaseLister) List(selector labels.Selector) (ret []*v1.HelmRelease, err error) { 49 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 50 | ret = append(ret, m.(*v1.HelmRelease)) 51 | }) 52 | return ret, err 53 | } 54 | 55 | // HelmReleases returns an object that can list and get HelmReleases. 56 | func (s *helmReleaseLister) HelmReleases(namespace string) HelmReleaseNamespaceLister { 57 | return helmReleaseNamespaceLister{indexer: s.indexer, namespace: namespace} 58 | } 59 | 60 | // HelmReleaseNamespaceLister helps list and get HelmReleases. 61 | type HelmReleaseNamespaceLister interface { 62 | // List lists all HelmReleases in the indexer for a given namespace. 63 | List(selector labels.Selector) (ret []*v1.HelmRelease, err error) 64 | // Get retrieves the HelmRelease from the indexer for a given namespace and name. 65 | Get(name string) (*v1.HelmRelease, error) 66 | HelmReleaseNamespaceListerExpansion 67 | } 68 | 69 | // helmReleaseNamespaceLister implements the HelmReleaseNamespaceLister 70 | // interface. 71 | type helmReleaseNamespaceLister struct { 72 | indexer cache.Indexer 73 | namespace string 74 | } 75 | 76 | // List lists all HelmReleases in the indexer for a given namespace. 77 | func (s helmReleaseNamespaceLister) List(selector labels.Selector) (ret []*v1.HelmRelease, err error) { 78 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { 79 | ret = append(ret, m.(*v1.HelmRelease)) 80 | }) 81 | return ret, err 82 | } 83 | 84 | // Get retrieves the HelmRelease from the indexer for a given namespace and name. 85 | func (s helmReleaseNamespaceLister) Get(name string) (*v1.HelmRelease, error) { 86 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) 87 | if err != nil { 88 | return nil, err 89 | } 90 | if !exists { 91 | return nil, errors.NewNotFound(v1.Resource("helmrelease"), name) 92 | } 93 | return obj.(*v1.HelmRelease), nil 94 | } 95 | -------------------------------------------------------------------------------- /pkg/helm/helm.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | import "sync" 4 | 5 | // Client is the generic interface for Helm (v2 and v3) clients. 6 | type Client interface { 7 | Get(releaseName string, opts GetOptions) (*Release, error) 8 | Status(releaseName string, opts StatusOptions) (Status, error) 9 | UpgradeFromPath(chartPath string, releaseName string, values []byte, opts UpgradeOptions) (*Release, error) 10 | History(releaseName string, opts HistoryOptions) ([]*Release, error) 11 | Rollback(releaseName string, opts RollbackOptions) (*Release, error) 12 | Test(releaseName string, opts TestOptions) error 13 | DependencyUpdate(chartPath string) error 14 | RepositoryIndex() error 15 | RepositoryAdd(name, url, username, password, certFile, keyFile, caFile string) error 16 | RepositoryRemove(name string) error 17 | RepositoryImport(path string) error 18 | Pull(ref, version, dest string) (string, error) 19 | PullWithRepoURL(repoURL, name, version, dest string) (string, error) 20 | Uninstall(releaseName string, opts UninstallOptions) error 21 | GetChartRevision(chartPath string) (string, error) 22 | Version() string 23 | } 24 | 25 | // Clients is the storage for clients, indexed by version. 26 | type Clients struct { 27 | sm sync.Map 28 | } 29 | 30 | func (cs *Clients) Add(version string, client Client) { 31 | cs.sm.Store(version, client) 32 | } 33 | 34 | func (cs *Clients) Load(version string) (Client, bool) { 35 | i, ok := cs.sm.Load(version) 36 | if !ok { 37 | return nil, false 38 | } 39 | c, ok := i.(Client) 40 | if !ok { 41 | return nil, false 42 | } 43 | return c, true 44 | } 45 | -------------------------------------------------------------------------------- /pkg/helm/options.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | import "time" 4 | 5 | // GetOptions holds the options available for Helm get 6 | // operations, the version implementation _must_ implement all 7 | // fields supported by that version but can (silently) ignore 8 | // unsupported set values. 9 | type GetOptions struct { 10 | Namespace string 11 | Version int 12 | } 13 | 14 | // StatusOptions holds the options available for Helm status 15 | // operations, the version implementation _must_ implement all 16 | // fields supported by that version but can (silently) ignore 17 | // unsupported set values. 18 | type StatusOptions struct { 19 | Namespace string 20 | Version int 21 | } 22 | 23 | // UpgradeOptions holds the options available for Helm upgrade 24 | // operations, the version implementation _must_ implement all 25 | // fields supported by that version but can (silently) ignore 26 | // unsupported set values. 27 | type UpgradeOptions struct { 28 | Namespace string 29 | Timeout time.Duration 30 | Wait bool 31 | Install bool 32 | DisableHooks bool 33 | DryRun bool 34 | ClientOnly bool 35 | Force bool 36 | ResetValues bool 37 | SkipCRDs bool 38 | ReuseValues bool 39 | Recreate bool 40 | MaxHistory int 41 | Atomic bool 42 | DisableValidation bool 43 | } 44 | 45 | // RollbackOptions holds the options available for Helm rollback 46 | // operations, the version implementation _must_ implement all 47 | // fields supported by that version but can (silently) ignore 48 | // unsupported set values. 49 | type RollbackOptions struct { 50 | Namespace string 51 | Version int 52 | Timeout time.Duration 53 | Wait bool 54 | DisableHooks bool 55 | DryRun bool 56 | Recreate bool 57 | Force bool 58 | } 59 | 60 | // TestOptions holds the options available for Helm test 61 | // operations, the version implementation _must_ implement all 62 | // fields supported by that version but can (silently) ignore 63 | // unsupported set values. 64 | type TestOptions struct { 65 | Namespace string 66 | Cleanup bool 67 | Timeout time.Duration 68 | } 69 | 70 | // UninstallOptions holds the options available for Helm uninstall 71 | // operations, the version implementation _must_ implement all 72 | // fields supported by that version but can (silently) ignore 73 | // unsupported set values. 74 | type UninstallOptions struct { 75 | Namespace string 76 | DisableHooks bool 77 | DryRun bool 78 | KeepHistory bool 79 | Timeout time.Duration 80 | } 81 | 82 | // HistoryOption holds the options available for Helm history 83 | // operations, the version implementation _must_ implement all 84 | // fields supported by that version but can (silently) ignore 85 | // unsupported set values. 86 | type HistoryOptions struct { 87 | Namespace string 88 | Max int 89 | } 90 | -------------------------------------------------------------------------------- /pkg/helm/release.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | import "time" 4 | 5 | // Define release statuses 6 | const ( 7 | // StatusUnknown indicates that a release is in an uncertain state 8 | StatusUnknown Status = "unknown" 9 | // StatusDeployed indicates that the release has been pushed to Kubernetes 10 | StatusDeployed Status = "deployed" 11 | // StatusUninstalled indicates that a release has been uninstalled from Kubernetes 12 | StatusUninstalled Status = "uninstalled" 13 | // StatusSuperseded indicates that this release object is outdated and a newer one exists 14 | StatusSuperseded Status = "superseded" 15 | // StatusFailed indicates that the release was not successfully deployed 16 | StatusFailed Status = "failed" 17 | // StatusUninstalling indicates that a uninstall operation is underway 18 | StatusUninstalling Status = "uninstalling" 19 | // StatusPendingInstall indicates that an install operation is underway 20 | StatusPendingInstall Status = "pending-install" 21 | // StatusPendingUpgrade indicates that an upgrade operation is underway 22 | StatusPendingUpgrade Status = "pending-upgrade" 23 | // StatusPendingRollback indicates that an rollback operation is underway 24 | StatusPendingRollback Status = "pending-rollback" 25 | ) 26 | 27 | // Release describes a generic chart deployment 28 | type Release struct { 29 | Name string 30 | Namespace string 31 | Chart *Chart 32 | Info *Info 33 | Values map[string]interface{} 34 | Manifest string 35 | Version int 36 | } 37 | 38 | // Info holds metadata of a chart deployment 39 | type Info struct { 40 | LastDeployed time.Time 41 | Description string 42 | Status Status 43 | } 44 | 45 | // Chart describes the chart for a release 46 | type Chart struct { 47 | Name string 48 | Version string 49 | AppVersion string 50 | Values Values 51 | Templates []*File 52 | } 53 | 54 | // File represents a file as a name/value pair. 55 | // The name is a relative path within the scope 56 | // of the chart's base directory. 57 | type File struct { 58 | Name string 59 | Data []byte 60 | } 61 | 62 | // Status holds the status of a release 63 | type Status string 64 | 65 | // AllowsUpgrade returns true if the status allows the release 66 | // to be upgraded. This is currently only the case if it equals 67 | // `StatusDeployed`. 68 | func (s Status) AllowsUpgrade() bool { 69 | return s == StatusDeployed 70 | } 71 | 72 | // String returns the Status as a string 73 | func (s Status) String() string { 74 | return string(s) 75 | } 76 | -------------------------------------------------------------------------------- /pkg/helm/utils.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | ) 8 | 9 | func Diff(j *Release, k *Release) string { 10 | opt := cmp.FilterValues(func(x, y interface{}) bool { 11 | isNumeric := func(v interface{}) bool { 12 | return v != nil && reflect.TypeOf(v).ConvertibleTo(reflect.TypeOf(float64(0))) 13 | } 14 | return isNumeric(x) && isNumeric(y) 15 | }, cmp.Transformer("T", func(v interface{}) float64 { 16 | return reflect.ValueOf(v).Convert(reflect.TypeOf(float64(0))).Float() 17 | })) 18 | 19 | return cmp.Diff(j.Values, k.Values, opt) + cmp.Diff(j.Chart, k.Chart, opt) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/helm/v2/chart.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "k8s.io/helm/pkg/chartutil" 7 | ) 8 | 9 | func (h *HelmV2) GetChartRevision(chartPath string) (string, error) { 10 | chartRequested, err := chartutil.Load(chartPath) 11 | if err != nil { 12 | return "", fmt.Errorf("failed to load chart to determine revision: %w", err) 13 | } 14 | return chartRequested.Metadata.Version, nil 15 | } 16 | -------------------------------------------------------------------------------- /pkg/helm/v2/dependency.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "k8s.io/helm/pkg/downloader" 5 | 6 | "github.com/fluxcd/helm-operator/pkg/utils" 7 | ) 8 | 9 | func (h *HelmV2) DependencyUpdate(chartPath string) error { 10 | out := utils.NewLogWriter(h.logger) 11 | man := downloader.Manager{ 12 | Out: out, 13 | ChartPath: chartPath, 14 | HelmHome: helmHome(), 15 | Getters: getterProviders(), 16 | } 17 | return man.Update() 18 | } 19 | -------------------------------------------------------------------------------- /pkg/helm/v2/get.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "strings" 5 | 6 | helmv2 "k8s.io/helm/pkg/helm" 7 | 8 | "github.com/fluxcd/helm-operator/pkg/helm" 9 | ) 10 | 11 | func (h *HelmV2) Get(releaseName string, opts helm.GetOptions) (*helm.Release, error) { 12 | res, err := h.client.ReleaseContent(releaseName, helmv2.ContentReleaseVersion(int32(opts.Version))) 13 | if err != nil { 14 | err = statusMessageErr(err) 15 | if strings.Contains(err.Error(), "not found") { 16 | return nil, nil 17 | } 18 | return nil, err 19 | } 20 | return releaseToGenericRelease(res.Release), nil 21 | } 22 | 23 | func (h *HelmV2) Status(releaseName string, opts helm.StatusOptions) (helm.Status, error) { 24 | res, err := h.client.ReleaseStatus(releaseName, helmv2.StatusReleaseVersion(int32(opts.Version))) 25 | if err != nil { 26 | err = statusMessageErr(err) 27 | if strings.Contains(err.Error(), "not found") { 28 | return "", nil 29 | } 30 | return "", err 31 | } 32 | return lookUpGenericStatus(res.Info.Status.Code), nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/helm/v2/history.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | helmv2 "k8s.io/helm/pkg/helm" 5 | 6 | "github.com/fluxcd/helm-operator/pkg/helm" 7 | ) 8 | 9 | func (h *HelmV2) History(releaseName string, opts helm.HistoryOptions) ([]*helm.Release, error) { 10 | max := helmv2.WithMaxHistory(256) 11 | if opts.Max != 0 { 12 | max = helmv2.WithMaxHistory(int32(opts.Max)) 13 | } 14 | res, err := h.client.ReleaseHistory(releaseName, max) 15 | if err != nil { 16 | return nil, err 17 | } 18 | var rels []*helm.Release 19 | for _, r := range res.Releases { 20 | rels = append(rels, releaseToGenericRelease(r)) 21 | } 22 | return rels, nil 23 | } 24 | -------------------------------------------------------------------------------- /pkg/helm/v2/pull.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "sync" 7 | 8 | "k8s.io/helm/pkg/downloader" 9 | "k8s.io/helm/pkg/repo" 10 | "k8s.io/helm/pkg/urlutil" 11 | 12 | "github.com/fluxcd/helm-operator/pkg/utils" 13 | ) 14 | 15 | func (h *HelmV2) Pull(ref, version, dest string) (string, error) { 16 | repositoryConfigLock.RLock() 17 | defer repositoryConfigLock.RUnlock() 18 | 19 | out := utils.NewLogWriter(h.logger) 20 | c := downloader.ChartDownloader{ 21 | Out: out, 22 | HelmHome: helmHome(), 23 | Verify: downloader.VerifyNever, 24 | Getters: getterProviders(), 25 | } 26 | d, _, err := c.DownloadTo(ref, version, dest) 27 | return d, err 28 | } 29 | 30 | func (h *HelmV2) PullWithRepoURL(repoURL, name, version, dest string) (string, error) { 31 | // This first attempts to look up the repository name by the given 32 | // `repoURL`, if found the repository name and given chart name 33 | // are used to construct a `chartRef` Helm understands. 34 | // 35 | // If no repository is found it attempts to resolve the absolute 36 | // URL to the chart by making a request to the given `repoURL`, 37 | // this absolute URL is then used to instruct Helm to pull the 38 | // chart. 39 | 40 | repositoryConfigLock.RLock() 41 | repoFile, err := loadRepositoryConfig() 42 | repositoryConfigLock.RUnlock() 43 | if err != nil { 44 | return "", err 45 | } 46 | 47 | // Here we attempt to find an entry for the repository. If found the 48 | // entry's name is used to construct a `chartRef` Helm understands. 49 | var chartRef string 50 | for _, entry := range repoFile.Repositories { 51 | if urlutil.Equal(repoURL, entry.URL) { 52 | chartRef = entry.Name + "/" + name 53 | // Ensure we have the repository index as this is 54 | // later used by Helm. 55 | if r, err := repo.NewChartRepository(entry, getterProviders()); err == nil { 56 | r.DownloadIndexFile(repositoryCache) 57 | } 58 | break 59 | } 60 | } 61 | 62 | if chartRef == "" { 63 | // We were unable to find an entry so we need to make a request 64 | // to the repository to get the absolute URL of the chart. 65 | chartRef, err = repo.FindChartInRepoURL(repoURL, name, version, "", "", "", getterProviders()) 66 | if err != nil { 67 | return "", err 68 | } 69 | 70 | // As Helm also attempts to find credentials for the absolute URL 71 | // we give to it, and does not ignore missing index files, we need 72 | // to be sure all indexes files are present, and we are only able 73 | // to do so by updating our indexes. 74 | err := downloadMissingRepositoryIndexes(repoFile.Repositories) 75 | if err != nil { 76 | return "", err 77 | } 78 | } 79 | 80 | return h.Pull(chartRef, version, dest) 81 | } 82 | 83 | func downloadMissingRepositoryIndexes(repositories []*repo.Entry) error { 84 | 85 | var wg sync.WaitGroup 86 | for _, c := range repositories { 87 | r, err := repo.NewChartRepository(c, getterProviders()) 88 | if err != nil { 89 | return err 90 | } 91 | wg.Add(1) 92 | go func(r *repo.ChartRepository) { 93 | f := r.Config.Cache 94 | if !filepath.IsAbs(f) { 95 | f = filepath.Join(repositoryCache, f) 96 | } 97 | if _, err := os.Stat(f); os.IsNotExist(err) { 98 | r.DownloadIndexFile(repositoryCache) 99 | } 100 | wg.Done() 101 | }(r) 102 | } 103 | wg.Wait() 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /pkg/helm/v2/repository.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | 8 | "github.com/pkg/errors" 9 | 10 | "k8s.io/helm/pkg/repo" 11 | ) 12 | 13 | var repositoryConfigLock sync.RWMutex 14 | 15 | func (h *HelmV2) RepositoryIndex() error { 16 | repositoryConfigLock.RLock() 17 | f, err := loadRepositoryConfig() 18 | repositoryConfigLock.RUnlock() 19 | if err != nil { 20 | return err 21 | } 22 | 23 | var wg sync.WaitGroup 24 | for _, c := range f.Repositories { 25 | r, err := repo.NewChartRepository(c, getterProviders()) 26 | if err != nil { 27 | return err 28 | } 29 | wg.Add(1) 30 | go func(r *repo.ChartRepository) { 31 | if err := r.DownloadIndexFile(repositoryCache); err != nil { 32 | h.logger.Log("error", "unable to get an update from the chart repository", "url", r.Config.URL, "err", err) 33 | } 34 | wg.Done() 35 | }(r) 36 | } 37 | wg.Wait() 38 | return nil 39 | } 40 | 41 | func (h *HelmV2) RepositoryAdd(name, url, username, password, certFile, keyFile, caFile string) error { 42 | repositoryConfigLock.Lock() 43 | defer repositoryConfigLock.Unlock() 44 | 45 | f, err := loadRepositoryConfig() 46 | if err != nil { 47 | return err 48 | } 49 | if f.Has(name) { 50 | return fmt.Errorf("chart repository with name '%s' already exists", name) 51 | } 52 | 53 | c := &repo.Entry{ 54 | Name: name, 55 | URL: url, 56 | Username: username, 57 | Password: password, 58 | CertFile: certFile, 59 | KeyFile: keyFile, 60 | CAFile: caFile, 61 | } 62 | f.Add(c) 63 | 64 | r, err := repo.NewChartRepository(c, getterProviders()) 65 | if err != nil { 66 | return err 67 | } 68 | if err = r.DownloadIndexFile(repositoryCache); err != nil { 69 | return err 70 | } 71 | 72 | return f.WriteFile(repositoryConfig, 0644) 73 | } 74 | 75 | func (h *HelmV2) RepositoryRemove(name string) error { 76 | repositoryConfigLock.Lock() 77 | defer repositoryConfigLock.Unlock() 78 | 79 | f, err := repo.LoadRepositoriesFile(repositoryConfig) 80 | if err != nil { 81 | return err 82 | } 83 | f.Remove(name) 84 | 85 | return f.WriteFile(repositoryConfig, 0644) 86 | } 87 | 88 | func (h *HelmV2) RepositoryImport(path string) error { 89 | s, err := repo.LoadRepositoriesFile(path) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | repositoryConfigLock.Lock() 95 | defer repositoryConfigLock.Unlock() 96 | 97 | t, err := loadRepositoryConfig() 98 | if err != nil { 99 | return err 100 | } 101 | 102 | for _, c := range s.Repositories { 103 | if t.Has(c.Name) { 104 | h.logger.Log("error", "repository with name already exists", "name", c.Name, "url", c.URL) 105 | continue 106 | } 107 | r, err := repo.NewChartRepository(c, getterProviders()) 108 | if err != nil { 109 | h.logger.Log("error", err, "name", c.Name, "url", c.URL) 110 | continue 111 | } 112 | if err := r.DownloadIndexFile(repositoryCache); err != nil { 113 | h.logger.Log("error", err, "name", c.Name, "url", c.URL) 114 | continue 115 | } 116 | 117 | t.Add(c) 118 | h.logger.Log("info", "successfully imported repository", "name", c.Name, "url", c.URL) 119 | } 120 | 121 | return t.WriteFile(repositoryConfig, 0644) 122 | } 123 | 124 | func loadRepositoryConfig() (*repo.RepoFile, error) { 125 | r, err := repo.LoadRepositoriesFile(repositoryConfig) 126 | if err != nil && !os.IsNotExist(errors.Cause(err)) { 127 | return nil, err 128 | } 129 | return r, nil 130 | } 131 | -------------------------------------------------------------------------------- /pkg/helm/v2/rollback.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | helmv2 "k8s.io/helm/pkg/helm" 5 | 6 | "github.com/fluxcd/helm-operator/pkg/helm" 7 | ) 8 | 9 | func (h *HelmV2) Rollback(releaseName string, opts helm.RollbackOptions) (*helm.Release, error) { 10 | res, err := h.client.RollbackRelease( 11 | releaseName, 12 | helmv2.RollbackVersion(int32(opts.Version)), 13 | helmv2.RollbackTimeout(int64(opts.Timeout.Seconds())), 14 | helmv2.RollbackWait(opts.Wait), 15 | helmv2.RollbackDisableHooks(opts.DisableHooks), 16 | helmv2.RollbackDryRun(opts.DryRun), 17 | helmv2.RollbackRecreate(opts.Recreate), 18 | helmv2.RollbackForce(opts.Force), 19 | ) 20 | if err != nil { 21 | return nil, statusMessageErr(err) 22 | } 23 | return releaseToGenericRelease(res.Release), nil 24 | } 25 | -------------------------------------------------------------------------------- /pkg/helm/v2/test.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "fmt" 5 | 6 | helmv2 "k8s.io/helm/pkg/helm" 7 | "k8s.io/helm/pkg/proto/hapi/release" 8 | 9 | "github.com/fluxcd/helm-operator/pkg/helm" 10 | ) 11 | 12 | func (h *HelmV2) Test(releaseName string, opts helm.TestOptions) error { 13 | c, errc := h.client.RunReleaseTest( 14 | releaseName, 15 | helmv2.ReleaseTestCleanup(opts.Cleanup), 16 | helmv2.ReleaseTestTimeout(int64(opts.Timeout.Seconds())), 17 | ) 18 | 19 | testErr := &testErr{} 20 | 21 | for { 22 | select { 23 | case err := <-errc: 24 | if err != nil { 25 | return statusMessageErr(err) 26 | } 27 | if testErr.failed > 0 { 28 | return testErr.Error() 29 | } 30 | return nil 31 | case res, ok := <-c: 32 | if !ok { 33 | break 34 | } 35 | 36 | if res.Status == release.TestRun_FAILURE { 37 | testErr.failed++ 38 | } 39 | 40 | h.logger.Log("info", res.Msg) 41 | } 42 | } 43 | } 44 | 45 | type testErr struct { 46 | failed int 47 | } 48 | 49 | func (err *testErr) Error() error { 50 | return fmt.Errorf("%v test(s) failed", err.failed) 51 | } 52 | -------------------------------------------------------------------------------- /pkg/helm/v2/uninstall.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | helmv2 "k8s.io/helm/pkg/helm" 5 | 6 | "github.com/fluxcd/helm-operator/pkg/helm" 7 | ) 8 | 9 | func (h *HelmV2) Uninstall(releaseName string, opts helm.UninstallOptions) error { 10 | if _, err := h.client.DeleteRelease( 11 | releaseName, 12 | helmv2.DeleteDisableHooks(opts.DisableHooks), 13 | helmv2.DeleteDryRun(opts.DryRun), 14 | helmv2.DeletePurge(!opts.KeepHistory), 15 | helmv2.DeleteTimeout(int64(opts.Timeout.Seconds())), 16 | ); err != nil { 17 | return statusMessageErr(err) 18 | } 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /pkg/helm/v2/upgrade.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "k8s.io/helm/pkg/chartutil" 7 | helmv2 "k8s.io/helm/pkg/helm" 8 | "k8s.io/helm/pkg/proto/hapi/release" 9 | 10 | "github.com/fluxcd/helm-operator/pkg/helm" 11 | ) 12 | 13 | type releaseResponse interface { 14 | GetRelease() *release.Release 15 | } 16 | 17 | func (h *HelmV2) UpgradeFromPath(chartPath string, releaseName string, values []byte, 18 | opts helm.UpgradeOptions) (*helm.Release, error) { 19 | // Load the chart from the given path 20 | chartRequested, err := chartutil.Load(chartPath) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | var res releaseResponse 26 | if opts.Install { 27 | res, err = h.client.InstallReleaseFromChart( 28 | chartRequested, 29 | opts.Namespace, 30 | helmv2.ReleaseName(releaseName), 31 | helmv2.ValueOverrides(values), 32 | helmv2.InstallDisableHooks(opts.DisableHooks), 33 | helmv2.InstallDryRun(opts.DryRun), 34 | helmv2.InstallWait(opts.Wait || opts.Atomic), 35 | helmv2.InstallTimeout(int64(opts.Timeout.Seconds())), 36 | ) 37 | if err != nil && opts.Atomic { 38 | h.logger.Log("warning", "installation failed with atomic flag set, uninstalling release") 39 | _, dErr := h.client.DeleteRelease(releaseName, 40 | helmv2.DeletePurge(true), helmv2.DeleteDisableHooks(opts.DisableHooks)) 41 | if dErr != nil { 42 | return nil, fmt.Errorf("%s, original installation error: %w", statusMessageErr(dErr), statusMessageErr(err)) 43 | } 44 | } 45 | } else { 46 | res, err = h.client.UpdateReleaseFromChart( 47 | releaseName, 48 | chartRequested, 49 | helmv2.UpdateValueOverrides(values), 50 | helmv2.UpgradeDisableHooks(opts.DisableHooks), 51 | helmv2.UpgradeDryRun(opts.DryRun), 52 | helmv2.UpgradeForce(opts.Force), 53 | helmv2.UpgradeRecreate(opts.Recreate), 54 | helmv2.ResetValues(opts.ResetValues), 55 | helmv2.ReuseValues(opts.ReuseValues), 56 | helmv2.UpgradeTimeout(int64(opts.Timeout.Seconds())), 57 | helmv2.UpgradeWait(opts.Wait || opts.Atomic), 58 | ) 59 | if err != nil && opts.Atomic { 60 | h.logger.Log("warning", "upgrade failed with atomic flag set, rolling back release") 61 | _, rErr := h.client.RollbackRelease(releaseName, 62 | helmv2.RollbackTimeout(int64(opts.Timeout.Seconds())), 63 | helmv2.RollbackWait(opts.Wait), 64 | helmv2.RollbackDisableHooks(opts.DisableHooks), 65 | helmv2.RollbackDryRun(opts.DryRun), 66 | helmv2.RollbackRecreate(opts.Recreate), 67 | helmv2.RollbackForce(opts.Force)) 68 | return nil, fmt.Errorf("%s, original installation error: %w", statusMessageErr(rErr), statusMessageErr(err)) 69 | } 70 | } 71 | if err != nil { 72 | return nil, statusMessageErr(err) 73 | } 74 | return releaseToGenericRelease(res.GetRelease()), nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/helm/v3/chart.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "fmt" 5 | 6 | "helm.sh/helm/v3/pkg/chart/loader" 7 | ) 8 | 9 | func (h *HelmV3) GetChartRevision(chartPath string) (string, error) { 10 | chartRequested, err := loader.Load(chartPath) 11 | if err != nil { 12 | return "", fmt.Errorf("failed to load chart to determine revision: %w", err) 13 | } 14 | return chartRequested.Metadata.Version, nil 15 | } 16 | -------------------------------------------------------------------------------- /pkg/helm/v3/converter.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/helm/helm-2to3/pkg/common" 7 | helm2 "github.com/helm/helm-2to3/pkg/v2" 8 | helm3 "github.com/helm/helm-2to3/pkg/v3" 9 | ) 10 | 11 | // Converter Converts a given helm 2 release with all its release versions to helm 3 format and deletes the old release from tiller 12 | type Converter struct { 13 | TillerNamespace string 14 | KubeConfig string // file path to kubeconfig 15 | TillerOutCluster bool 16 | StorageType string 17 | } 18 | 19 | // V2ReleaseExists helps you check if a helm v2 release exists or not 20 | func (c Converter) V2ReleaseExists(releaseName string) (bool, error) { 21 | retrieveOpts := helm2.RetrieveOptions{ 22 | ReleaseName: releaseName, 23 | TillerNamespace: c.TillerNamespace, 24 | TillerOutCluster: c.TillerOutCluster, 25 | StorageType: c.StorageType, 26 | } 27 | kubeConfig := common.KubeConfig{ 28 | File: c.KubeConfig, 29 | } 30 | v2Releases, err := helm2.GetReleaseVersions(retrieveOpts, kubeConfig) 31 | 32 | // We check for the error message content because 33 | // Helm 2to3 returns an error if it doesn't find release versions 34 | if err != nil && !strings.Contains(err.Error(), "has no deployed releases") { 35 | return false, err 36 | } 37 | return len(v2Releases) > 0, nil 38 | } 39 | 40 | // Convert attempts to convert the given release name from v2 to v3. 41 | func (c Converter) Convert(releaseName string, dryRun bool) error { 42 | retrieveOpts := helm2.RetrieveOptions{ 43 | ReleaseName: releaseName, 44 | TillerNamespace: c.TillerNamespace, 45 | } 46 | kubeConfig := common.KubeConfig{ 47 | File: c.KubeConfig, 48 | } 49 | v2Releases, err := helm2.GetReleaseVersions(retrieveOpts, kubeConfig) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | if !dryRun { 55 | for _, v2Release := range v2Releases { 56 | v3Release, err := helm3.CreateRelease(v2Release) 57 | if err != nil { 58 | return err 59 | } 60 | if err := helm3.StoreRelease(v3Release, kubeConfig); err != nil { 61 | return err 62 | } 63 | } 64 | } 65 | 66 | if err := helm2.DeleteAllReleaseVersions(retrieveOpts, kubeConfig, dryRun); err != nil { 67 | return err 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /pkg/helm/v3/dependency.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | "time" 8 | 9 | "helm.sh/helm/v3/pkg/downloader" 10 | 11 | "github.com/fluxcd/helm-operator/pkg/utils" 12 | ) 13 | 14 | func (h *HelmV3) DependencyUpdate(chartPath string) error { 15 | // Garbage collect before the dependency update so that 16 | // anonymous files from previous runs are cleared, with 17 | // a safe guard time offset to not touch any files in 18 | // use. 19 | garbageCollect(repositoryCache, time.Second * 300) 20 | out := utils.NewLogWriter(h.logger) 21 | man := &downloader.Manager{ 22 | Out: out, 23 | ChartPath: chartPath, 24 | RepositoryConfig: repositoryConfig, 25 | RepositoryCache: repositoryCache, 26 | Getters: getterProviders(), 27 | } 28 | return man.Update() 29 | } 30 | 31 | // garbageCollect walks over the files in the given path and deletes 32 | // any anonymous index file with a mod time older than the given 33 | // duration. 34 | func garbageCollect(path string, olderThan time.Duration) { 35 | now := time.Now() 36 | filepath.Walk(path, func(p string, f os.FileInfo, err error) error { 37 | if err != nil || f.IsDir() { 38 | return nil 39 | } 40 | if strings.HasSuffix(f.Name(), "=-index.yaml") || strings.HasSuffix(f.Name(), "=-charts.txt") { 41 | if now.Sub(f.ModTime()) > olderThan { 42 | return os.Remove(p) 43 | } 44 | } 45 | return nil 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/helm/v3/get.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "helm.sh/helm/v3/pkg/action" 5 | "helm.sh/helm/v3/pkg/storage/driver" 6 | 7 | "github.com/fluxcd/helm-operator/pkg/helm" 8 | ) 9 | 10 | type ( 11 | getOptions helm.GetOptions 12 | statusOptions helm.GetOptions 13 | ) 14 | 15 | func (h *HelmV3) Get(releaseName string, opts helm.GetOptions) (*helm.Release, error) { 16 | cfg, err := newActionConfig(h.kubeConfig, h.infoLogFunc(opts.Namespace, releaseName), opts.Namespace, "") 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | get := action.NewGet(cfg) 22 | getOptions(opts).configure(get) 23 | 24 | res, err := get.Run(releaseName) 25 | switch err { 26 | case nil: 27 | return releaseToGenericRelease(res), nil 28 | case driver.ErrReleaseNotFound: 29 | return nil, nil 30 | default: 31 | return nil, err 32 | } 33 | } 34 | 35 | func (opts getOptions) configure(action *action.Get) { 36 | action.Version = opts.Version 37 | } 38 | 39 | func (h *HelmV3) Status(releaseName string, opts helm.StatusOptions) (helm.Status, error) { 40 | cfg, err := newActionConfig(h.kubeConfig, h.infoLogFunc(opts.Namespace, releaseName), opts.Namespace, "") 41 | if err != nil { 42 | return "", err 43 | } 44 | 45 | status := action.NewStatus(cfg) 46 | statusOptions(opts).configure(status) 47 | 48 | res, err := status.Run(releaseName) 49 | switch err { 50 | case nil: 51 | return lookUpGenericStatus(res.Info.Status), nil 52 | default: 53 | return "", err 54 | } 55 | } 56 | 57 | func (opts statusOptions) configure(action *action.Status) { 58 | action.Version = opts.Version 59 | } 60 | -------------------------------------------------------------------------------- /pkg/helm/v3/history.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "helm.sh/helm/v3/pkg/action" 5 | "helm.sh/helm/v3/pkg/releaseutil" 6 | 7 | "github.com/fluxcd/helm-operator/pkg/helm" 8 | ) 9 | 10 | func (h *HelmV3) History(releaseName string, opts helm.HistoryOptions) ([]*helm.Release, error) { 11 | cfg, err := newActionConfig(h.kubeConfig, h.infoLogFunc(opts.Namespace, releaseName), opts.Namespace, "") 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | history := action.NewHistory(cfg) 17 | historyOptions(opts).configure(history) 18 | 19 | hist, err := history.Run(releaseName) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | releaseutil.Reverse(hist, releaseutil.SortByRevision) 25 | 26 | var rels []*helm.Release 27 | for i := 0; i < min(len(hist), history.Max); i++ { 28 | rels = append(rels, releaseToGenericRelease(hist[i])) 29 | } 30 | return rels, nil 31 | } 32 | 33 | type historyOptions helm.HistoryOptions 34 | 35 | func (opts historyOptions) configure(action *action.History) { 36 | action.Max = opts.Max 37 | } 38 | 39 | func min(x, y int) int { 40 | if x < y { 41 | return x 42 | } 43 | return y 44 | } 45 | -------------------------------------------------------------------------------- /pkg/helm/v3/pull.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "sync" 7 | 8 | "k8s.io/helm/pkg/urlutil" 9 | 10 | "helm.sh/helm/v3/pkg/downloader" 11 | "helm.sh/helm/v3/pkg/helmpath" 12 | "helm.sh/helm/v3/pkg/repo" 13 | 14 | "github.com/fluxcd/helm-operator/pkg/utils" 15 | ) 16 | 17 | func (h *HelmV3) Pull(ref, version, dest string) (string, error) { 18 | repositoryConfigLock.RLock() 19 | defer repositoryConfigLock.RUnlock() 20 | 21 | out := utils.NewLogWriter(h.logger) 22 | c := downloader.ChartDownloader{ 23 | Out: out, 24 | Verify: downloader.VerifyNever, 25 | RepositoryConfig: repositoryConfig, 26 | RepositoryCache: repositoryCache, 27 | Getters: getterProviders(), 28 | } 29 | d, _, err := c.DownloadTo(ref, version, dest) 30 | return d, err 31 | } 32 | 33 | func (h *HelmV3) PullWithRepoURL(repoURL, name, version, dest string) (string, error) { 34 | // This first attempts to look up the repository name by the given 35 | // `repoURL`, if found the repository name and given chart name 36 | // are used to construct a `chartRef` Helm understands. 37 | // 38 | // If no repository is found it attempts to resolve the absolute 39 | // URL to the chart by making a request to the given `repoURL`, 40 | // this absolute URL is then used to instruct Helm to pull the 41 | // chart. 42 | 43 | repositoryConfigLock.RLock() 44 | repoFile, err := loadRepositoryConfig() 45 | repositoryConfigLock.RUnlock() 46 | if err != nil { 47 | return "", err 48 | } 49 | 50 | // Here we attempt to find an entry for the repository. If found the 51 | // entry's name is used to construct a `chartRef` Helm understands. 52 | var chartRef string 53 | for _, entry := range repoFile.Repositories { 54 | if urlutil.Equal(repoURL, entry.URL) { 55 | chartRef = entry.Name + "/" + name 56 | // Ensure we have the repository index as this is 57 | // later used by Helm. 58 | if r, err := newChartRepository(entry); err == nil { 59 | r.DownloadIndexFile() 60 | } 61 | break 62 | } 63 | } 64 | 65 | if chartRef == "" { 66 | // We were unable to find an entry so we need to make a request 67 | // to the repository to get the absolute URL of the chart. 68 | chartRef, err = repo.FindChartInRepoURL(repoURL, name, version, "", "", "", getterProviders()) 69 | if err != nil { 70 | return "", err 71 | } 72 | 73 | // As Helm also attempts to find credentials for the absolute URL 74 | // we give to it, and does not ignore missing index files, we need 75 | // to be sure all indexes files are present, and we are only able 76 | // to do so by updating our indexes. 77 | if err := downloadMissingRepositoryIndexes(repoFile.Repositories); err != nil { 78 | return "", err 79 | } 80 | } 81 | 82 | return h.Pull(chartRef, version, dest) 83 | } 84 | 85 | func downloadMissingRepositoryIndexes(repositories []*repo.Entry) error { 86 | var wg sync.WaitGroup 87 | for _, c := range repositories { 88 | r, err := newChartRepository(c) 89 | if err != nil { 90 | return err 91 | } 92 | wg.Add(1) 93 | go func(r *repo.ChartRepository) { 94 | f := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) 95 | if _, err := os.Stat(f); os.IsNotExist(err) { 96 | r.DownloadIndexFile() 97 | } 98 | wg.Done() 99 | }(r) 100 | } 101 | wg.Wait() 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /pkg/helm/v3/rollback.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "helm.sh/helm/v3/pkg/action" 5 | 6 | "github.com/fluxcd/helm-operator/pkg/helm" 7 | ) 8 | 9 | func (h *HelmV3) Rollback(releaseName string, opts helm.RollbackOptions) (*helm.Release, error) { 10 | cfg, err := newActionConfig(h.kubeConfig, h.infoLogFunc(opts.Namespace, releaseName), opts.Namespace, "") 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | rollback := action.NewRollback(cfg) 16 | rollbackOptions(opts).configure(rollback) 17 | 18 | if err := rollback.Run(releaseName); err != nil { 19 | return nil, err 20 | } 21 | 22 | // As rolling back does no longer return information about 23 | // the release in v3, we need to make an additional call to 24 | // get the release we rolled back to. 25 | return h.Get(releaseName, helm.GetOptions{Namespace: opts.Namespace}) 26 | } 27 | 28 | type rollbackOptions helm.RollbackOptions 29 | 30 | func (opts rollbackOptions) configure(action *action.Rollback) { 31 | action.Timeout = opts.Timeout 32 | action.Version = opts.Version 33 | action.Wait = opts.Wait 34 | action.DisableHooks = opts.DisableHooks 35 | action.DryRun = opts.DryRun 36 | action.Recreate = opts.Recreate 37 | } 38 | -------------------------------------------------------------------------------- /pkg/helm/v3/test.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "helm.sh/helm/v3/pkg/action" 5 | 6 | "github.com/fluxcd/helm-operator/pkg/helm" 7 | ) 8 | 9 | func (h *HelmV3) Test(releaseName string, opts helm.TestOptions) error { 10 | cfg, err := newActionConfig(h.kubeConfig, h.infoLogFunc(opts.Namespace, releaseName), opts.Namespace, "") 11 | if err != nil { 12 | return err 13 | } 14 | 15 | test := action.NewReleaseTesting(cfg) 16 | testOptions(opts).configure(test) 17 | 18 | if _, err := test.Run(releaseName); err != nil { 19 | return err 20 | } 21 | 22 | return nil 23 | } 24 | 25 | type testOptions helm.TestOptions 26 | 27 | func (opts testOptions) configure(action *action.ReleaseTesting) { 28 | action.Timeout = opts.Timeout 29 | } 30 | -------------------------------------------------------------------------------- /pkg/helm/v3/uninstall.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "helm.sh/helm/v3/pkg/action" 5 | 6 | "github.com/fluxcd/helm-operator/pkg/helm" 7 | ) 8 | 9 | func (h *HelmV3) Uninstall(releaseName string, opts helm.UninstallOptions) error { 10 | cfg, err := newActionConfig(h.kubeConfig, h.infoLogFunc(opts.Namespace, releaseName), opts.Namespace, "") 11 | if err != nil { 12 | return err 13 | } 14 | 15 | uninstall := action.NewUninstall(cfg) 16 | uninstallOptions(opts).configure(uninstall) 17 | 18 | _, err = uninstall.Run(releaseName) 19 | return err 20 | } 21 | 22 | type uninstallOptions helm.UninstallOptions 23 | 24 | func (opts uninstallOptions) configure(action *action.Uninstall) { 25 | action.DisableHooks = opts.DisableHooks 26 | action.DryRun = opts.DryRun 27 | action.KeepHistory = opts.KeepHistory 28 | action.Timeout = opts.Timeout 29 | } 30 | -------------------------------------------------------------------------------- /pkg/helm/v3/upgrade.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "helm.sh/helm/v3/pkg/action" 5 | "helm.sh/helm/v3/pkg/chart/loader" 6 | "helm.sh/helm/v3/pkg/chartutil" 7 | "helm.sh/helm/v3/pkg/release" 8 | 9 | "github.com/fluxcd/helm-operator/pkg/helm" 10 | ) 11 | 12 | func (h *HelmV3) UpgradeFromPath(chartPath string, releaseName string, values []byte, 13 | opts helm.UpgradeOptions) (*helm.Release, error) { 14 | 15 | cfg, err := newActionConfig(h.kubeConfig, h.infoLogFunc(opts.Namespace, releaseName), opts.Namespace, "") 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | // Load the chart from the given path, this also ensures that 21 | // all chart dependencies are present 22 | chartRequested, err := loader.Load(chartPath) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | // Read and set values 28 | val, err := chartutil.ReadValues(values) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | var res *release.Release 34 | if opts.Install { 35 | install := action.NewInstall(cfg) 36 | installOptions(opts).configure(install, releaseName) 37 | res, err = install.Run(chartRequested, val.AsMap()) 38 | } else { 39 | upgrade := action.NewUpgrade(cfg) 40 | upgradeOptions(opts).configure(upgrade) 41 | res, err = upgrade.Run(releaseName, chartRequested, val.AsMap()) 42 | } 43 | 44 | if err != nil { 45 | return nil, err 46 | } 47 | return releaseToGenericRelease(res), err 48 | } 49 | 50 | type installOptions helm.UpgradeOptions 51 | 52 | func (opts installOptions) configure(action *action.Install, releaseName string) { 53 | action.Namespace = opts.Namespace 54 | action.ReleaseName = releaseName 55 | action.Atomic = opts.Atomic 56 | action.DisableHooks = opts.DisableHooks 57 | action.DryRun = opts.DryRun 58 | action.ClientOnly = opts.ClientOnly 59 | action.Timeout = opts.Timeout 60 | action.Wait = opts.Wait 61 | action.SkipCRDs = opts.SkipCRDs 62 | action.DisableOpenAPIValidation = opts.DisableValidation 63 | } 64 | 65 | type upgradeOptions helm.UpgradeOptions 66 | 67 | func (opts upgradeOptions) configure(action *action.Upgrade) { 68 | action.Namespace = opts.Namespace 69 | action.Atomic = opts.Atomic 70 | action.DisableHooks = opts.DisableHooks 71 | action.DryRun = opts.DryRun 72 | action.Force = opts.Force 73 | action.MaxHistory = opts.MaxHistory 74 | action.ResetValues = opts.ResetValues 75 | action.ReuseValues = opts.ReuseValues 76 | action.Timeout = opts.Timeout 77 | action.Wait = opts.Wait 78 | } 79 | -------------------------------------------------------------------------------- /pkg/helm/values.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | 7 | "sigs.k8s.io/yaml" 8 | ) 9 | 10 | // Values represents a collection of (Helm) values. 11 | // We define our own type to avoid working with two `chartutil` 12 | // versions. 13 | type Values map[string]interface{} 14 | 15 | // YAML encodes the values into YAML bytes. 16 | func (v Values) YAML() ([]byte, error) { 17 | b, err := yaml.Marshal(v) 18 | return b, err 19 | } 20 | 21 | // Checksum calculates and returns the SHA256 checksum of the YAML 22 | // encoded values. 23 | func (v Values) Checksum() string { 24 | b, _ := v.YAML() 25 | 26 | hasher := sha256.New() 27 | hasher.Write(b) 28 | return hex.EncodeToString(hasher.Sum(nil)) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/http/daemon/server.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | _ "net/http/pprof" 8 | "sync/atomic" 9 | "time" 10 | 11 | "github.com/fluxcd/helm-operator/pkg/api" 12 | transport "github.com/fluxcd/helm-operator/pkg/http" 13 | "github.com/go-kit/kit/log" 14 | "github.com/gorilla/mux" 15 | "github.com/prometheus/client_golang/prometheus/promhttp" 16 | ) 17 | 18 | // ListenAndServe starts a HTTP server instrumented with Prometheus metrics, 19 | // health and API endpoints on the specified address. 20 | func ListenAndServe(listenAddr string, apiServer api.Server, logger log.Logger, stopCh <-chan struct{}) { 21 | mux := http.DefaultServeMux 22 | 23 | // setup metrics and health endpoints 24 | mux.Handle("/metrics", promhttp.Handler()) 25 | mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { 26 | w.WriteHeader(http.StatusOK) 27 | w.Write([]byte("OK")) 28 | }) 29 | 30 | // setup api endpoints 31 | handler := NewHandler(apiServer, transport.NewRouter()) 32 | mux.Handle("/api/", http.StripPrefix("/api", handler)) 33 | 34 | srv := &http.Server{ 35 | Addr: listenAddr, 36 | Handler: mux, 37 | ReadTimeout: 5 * time.Second, 38 | WriteTimeout: 1 * time.Minute, 39 | IdleTimeout: 15 * time.Second, 40 | } 41 | 42 | logger.Log("info", fmt.Sprintf("starting HTTP server on %s", listenAddr)) 43 | 44 | // run server in background 45 | go func() { 46 | if err := srv.ListenAndServe(); err != http.ErrServerClosed { 47 | logger.Log("error", fmt.Sprintf("HTTP server crashed %v", err)) 48 | } 49 | }() 50 | 51 | // wait for close signal and attempt graceful shutdown 52 | <-stopCh 53 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 54 | defer cancel() 55 | 56 | if err := srv.Shutdown(ctx); err != nil { 57 | logger.Log("warn", fmt.Sprintf("HTTP server graceful shutdown failed %v", err)) 58 | } else { 59 | logger.Log("info", "HTTP server stopped") 60 | } 61 | } 62 | 63 | // NewHandler registers handlers on the given router. 64 | func NewHandler(s api.Server, r *mux.Router) http.Handler { 65 | handle := &APIServer{server: s} 66 | r.Get(transport.SyncGit).HandlerFunc(handle.SyncGit) 67 | return r 68 | } 69 | 70 | type APIServer struct { 71 | server api.Server 72 | syncingGit uint32 73 | } 74 | 75 | // SyncGit starts a goroutine in the background to sync all git mirrors 76 | // _if there is not one running at time of request_. It writes back a 77 | // HTTP 200 status header and 'OK' body to inform the request was 78 | // successful. 79 | // TODO(hidde): in the future we may want to give users the option to 80 | // request the status after it has been started. The Flux (daemon) API 81 | // achieves this by working with jobs whos IDs can be tracked. 82 | func (s *APIServer) SyncGit(w http.ResponseWriter, r *http.Request) { 83 | if atomic.CompareAndSwapUint32(&s.syncingGit, 0, 1) { 84 | go func() { 85 | s.server.SyncMirrors() 86 | atomic.StoreUint32(&s.syncingGit, 0) 87 | }() 88 | } 89 | 90 | w.WriteHeader(http.StatusOK) 91 | w.Write([]byte("OK")) 92 | } 93 | -------------------------------------------------------------------------------- /pkg/http/routes.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | const ( 4 | SyncGit = "SyncGit" 5 | ) 6 | -------------------------------------------------------------------------------- /pkg/http/transport.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | ) 6 | 7 | // NewRouter creates a new router instance, registers all API routes 8 | // and returns it. 9 | func NewRouter() *mux.Router { 10 | r := mux.NewRouter() 11 | r.NewRoute().Name(SyncGit).Methods("POST").Path("/v1/sync-git") 12 | return r 13 | } 14 | -------------------------------------------------------------------------------- /pkg/install/generate.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "time" 12 | 13 | "github.com/shurcooL/vfsgen" 14 | 15 | "github.com/fluxcd/helm-operator/pkg/install" 16 | ) 17 | 18 | func main() { 19 | usage := func() { 20 | fmt.Fprintf(os.Stderr, "usage: %s {embedded-templates,deploy}\n", os.Args[0]) 21 | os.Exit(1) 22 | } 23 | if len(os.Args) != 2 { 24 | usage() 25 | } 26 | switch os.Args[1] { 27 | case "embedded-templates": 28 | var fs http.FileSystem = modTimeFS{ 29 | fs: http.Dir("templates/"), 30 | } 31 | err := vfsgen.Generate(fs, vfsgen.Options{ 32 | Filename: "generated_templates.gogen.go", 33 | PackageName: "install", 34 | VariableName: "templates", 35 | }) 36 | if err != nil { 37 | log.Fatalln(err) 38 | } 39 | case "deploy": 40 | manifests, err := install.FillInTemplates(install.TemplateParameters{Namespace: "flux"}) 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "error: failed to fill in templates: %s\n", err) 43 | os.Exit(1) 44 | } 45 | for fileName, contents := range manifests { 46 | if err := ioutil.WriteFile(fileName, contents, 0600); err != nil { 47 | fmt.Fprintf(os.Stderr, "error: failed to write deploy file %s: %s\n", fileName, err) 48 | os.Exit(1) 49 | } 50 | } 51 | 52 | default: 53 | usage() 54 | } 55 | } 56 | 57 | // modTimeFS is a wrapper that rewrites all mod times to Unix epoch. 58 | // This is to ensure `generated_templates.gogen.go` only changes when 59 | // the folder and/or file contents change. 60 | type modTimeFS struct { 61 | fs http.FileSystem 62 | } 63 | 64 | func (fs modTimeFS) Open(name string) (http.File, error) { 65 | f, err := fs.fs.Open(name) 66 | if err != nil { 67 | return nil, err 68 | } 69 | return modTimeFile{f}, nil 70 | } 71 | 72 | type modTimeFile struct { 73 | http.File 74 | } 75 | 76 | func (f modTimeFile) Stat() (os.FileInfo, error) { 77 | fi, err := f.File.Stat() 78 | if err != nil { 79 | return nil, err 80 | } 81 | return modTimeFileInfo{fi}, nil 82 | } 83 | 84 | type modTimeFileInfo struct { 85 | os.FileInfo 86 | } 87 | 88 | func (modTimeFileInfo) ModTime() time.Time { 89 | return time.Unix(0, 0) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/install/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fluxcd/helm-operator/pkg/install 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/instrumenta/kubeval v0.0.0-20190918223246-8d013ec9fc56 7 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 8 | github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect 9 | github.com/stretchr/testify v1.4.0 10 | golang.org/x/tools v0.0.0-20200121210457-b3205ff6fffe // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/install/install.go: -------------------------------------------------------------------------------- 1 | package install 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "strings" 10 | "text/template" 11 | 12 | "github.com/shurcooL/httpfs/vfsutil" 13 | ) 14 | 15 | const ( 16 | defaultNamespace = "default" 17 | defaultTillerNamespace = "kube-system" 18 | ) 19 | 20 | type TemplateParameters struct { 21 | Namespace string 22 | TillerNamespace string 23 | SSHSecretName string 24 | HelmVersions string 25 | AdditionalArgs []string 26 | } 27 | 28 | func FillInTemplates(params TemplateParameters) (map[string][]byte, error) { 29 | if params.Namespace == "" { 30 | // Set the default namespace 31 | params.Namespace = defaultNamespace 32 | } 33 | if params.TillerNamespace == "" { 34 | // Set the default Tiller namespace 35 | params.TillerNamespace = defaultTillerNamespace 36 | } 37 | result := map[string][]byte{} 38 | err := vfsutil.WalkFiles(templates, "/", func(path string, info os.FileInfo, rs io.ReadSeeker, err error) error { 39 | if err != nil { 40 | return fmt.Errorf("cannot walk embedded files: %s", err) 41 | } 42 | if info.IsDir() { 43 | return nil 44 | } 45 | manifestTemplateBytes, err := ioutil.ReadAll(rs) 46 | if err != nil { 47 | return fmt.Errorf("cannot read embedded file %q: %s", info.Name(), err) 48 | } 49 | indent := func(n int, s string) string { 50 | lines := strings.Split(s, "\n") 51 | for i, l := range lines { 52 | lines[i] = strings.Repeat(" ", n) + l 53 | } 54 | return strings.Join(lines, "\n") 55 | } 56 | manifestTemplate, err := template.New(info.Name()). 57 | Funcs(template.FuncMap{"indent": indent}). 58 | Parse(string(manifestTemplateBytes)) 59 | if err != nil { 60 | return fmt.Errorf("cannot parse embedded file %q: %s", info.Name(), err) 61 | } 62 | out := bytes.NewBuffer(nil) 63 | if err := manifestTemplate.Execute(out, params); err != nil { 64 | return fmt.Errorf("cannot execute template for embedded file %q: %s", info.Name(), err) 65 | } 66 | if len(out.Bytes()) <= 1 { // empty file 67 | return nil 68 | } 69 | result[strings.TrimSuffix(info.Name(), ".tmpl")] = out.Bytes() 70 | return nil 71 | }) 72 | if err != nil { 73 | return nil, fmt.Errorf("internal error filling embedded installation templates: %s", err) 74 | } 75 | return result, nil 76 | } 77 | -------------------------------------------------------------------------------- /pkg/install/install_test.go: -------------------------------------------------------------------------------- 1 | package install 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/instrumenta/kubeval/kubeval" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func testFillInTemplates(t *testing.T, params TemplateParameters, expectedManifestNum int) { 11 | manifests, err := FillInTemplates(params) 12 | assert.NoError(t, err) 13 | assert.Len(t, manifests, expectedManifestNum) 14 | 15 | config := &kubeval.Config{ 16 | IgnoreMissingSchemas: true, 17 | KubernetesVersion: "master", 18 | } 19 | for fileName, contents := range manifests { 20 | config.FileName = fileName 21 | validationResults, err := kubeval.Validate(contents, config) 22 | assert.NoError(t, err, "contents: %s", string(contents)) 23 | for _, result := range validationResults { 24 | if len(result.Errors) > 0 { 25 | t.Errorf("found problems with manifest %s (Kind %s):\ncontent:\n%s\nerrors: %s", 26 | fileName, 27 | result.Kind, 28 | string(contents), 29 | result.Errors) 30 | } 31 | } 32 | } 33 | } 34 | 35 | func TestFillInTemplates(t *testing.T) { 36 | testFillInTemplates(t, TemplateParameters{ 37 | Namespace: "flux", 38 | TillerNamespace: "tiller", 39 | SSHSecretName: "mysshsecretname", 40 | }, 3) 41 | } 42 | 43 | func TestFillInTemplatesEmpty(t *testing.T) { 44 | testFillInTemplates(t, TemplateParameters{}, 3) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/install/templates/rbac.yaml.tmpl: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | labels: 6 | name: helm-operator 7 | name: helm-operator 8 | namespace: {{ .Namespace }} 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | labels: 14 | name: helm-operator 15 | name: helm-operator 16 | rules: 17 | - apiGroups: ['*'] 18 | resources: ['*'] 19 | verbs: ['*'] 20 | - nonResourceURLs: ['*'] 21 | verbs: ['*'] 22 | --- 23 | apiVersion: rbac.authorization.k8s.io/v1 24 | kind: ClusterRoleBinding 25 | metadata: 26 | labels: 27 | name: helm-operator 28 | name: helm-operator 29 | roleRef: 30 | apiGroup: rbac.authorization.k8s.io 31 | kind: ClusterRole 32 | name: helm-operator 33 | subjects: 34 | - kind: ServiceAccount 35 | name: helm-operator 36 | namespace: {{ .Namespace }} 37 | -------------------------------------------------------------------------------- /pkg/operator/metrics.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "github.com/go-kit/kit/metrics/prometheus" 5 | stdprometheus "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | var ( 9 | releaseQueueLength = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ 10 | Namespace: "flux", 11 | Subsystem: "helm_operator", 12 | Name: "release_queue_length_count", 13 | Help: "Count of release jobs waiting in the queue to be processed.", 14 | }, []string{}) 15 | releaseCount = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ 16 | Namespace: "flux", 17 | Subsystem: "helm_operator", 18 | Name: "release_count", 19 | Help: "Count of releases managed by the operator.", 20 | }, []string{}) 21 | ) 22 | -------------------------------------------------------------------------------- /pkg/release/errors.go: -------------------------------------------------------------------------------- 1 | package release 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type errCollection []error 8 | 9 | func (err errCollection) Error() string { 10 | var errs []string 11 | for i := len(err)-1; i >= 0; i-- { 12 | errs = append(errs, err[i].Error()) 13 | } 14 | return strings.Join(errs, ", previous error:") 15 | } 16 | 17 | func (err errCollection) Empty() bool { 18 | return len(err) == 0 19 | } 20 | -------------------------------------------------------------------------------- /pkg/release/metrics.go: -------------------------------------------------------------------------------- 1 | package release 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/go-kit/kit/metrics/prometheus" 8 | stdprometheus "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | const ( 12 | LabelSuccess = "success" 13 | LabelNamespace = "namespace" 14 | LabelTargetNamespace = "target_namespace" 15 | LabelReleaseName = "release_name" 16 | LabelAction = "action" 17 | ) 18 | 19 | var ( 20 | durationBuckets = []float64{1, 5, 10, 30, 60, 120, 180, 300, 600, 1800} 21 | // Deprecated 22 | releaseDuration = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ 23 | Namespace: "flux", 24 | Subsystem: "helm_operator", 25 | Name: "release_duration_seconds", 26 | Help: "Release synchronization duration in seconds.", 27 | Buckets: durationBuckets, 28 | }, []string{LabelSuccess, LabelNamespace, LabelReleaseName}) 29 | // Deprecated 30 | releasePhaseDuration = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ 31 | Namespace: "flux", 32 | Subsystem: "helm_operator", 33 | Name: "release_phase_duration_seconds", 34 | Help: "Release synchronization phase duration in seconds.", 35 | ConstLabels: nil, 36 | Buckets: durationBuckets, 37 | }, []string{LabelAction, LabelSuccess, LabelNamespace, LabelReleaseName}) 38 | releaseActionDuration = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ 39 | Namespace: "flux", 40 | Subsystem: "helm_operator", 41 | Name: "release_action_duration_seconds", 42 | Help: "Release synchronization action duration in seconds.", 43 | ConstLabels: nil, 44 | Buckets: durationBuckets, 45 | }, []string{LabelAction, LabelSuccess, LabelTargetNamespace, LabelReleaseName}) 46 | syncAction = "sync" 47 | ) 48 | 49 | func ObserveRelease(start time.Time, success bool, namespace, releaseName string) { 50 | releaseDuration.With( 51 | LabelSuccess, fmt.Sprint(success), 52 | LabelNamespace, namespace, 53 | LabelReleaseName, releaseName, 54 | ).Observe(time.Since(start).Seconds()) 55 | releaseActionDuration.With( 56 | LabelAction, syncAction, 57 | LabelSuccess, fmt.Sprint(success), 58 | LabelTargetNamespace, namespace, 59 | LabelReleaseName, releaseName, 60 | ).Observe(time.Since(start).Seconds()) 61 | } 62 | 63 | func ObserveReleaseAction(start time.Time, action action, success bool, namespace, releaseName string) { 64 | releasePhaseDuration.With( 65 | LabelAction, string(action), 66 | LabelSuccess, fmt.Sprint(success), 67 | LabelNamespace, namespace, 68 | LabelReleaseName, releaseName, 69 | ).Observe(time.Since(start).Seconds()) 70 | releaseActionDuration.With( 71 | LabelAction, string(action), 72 | LabelSuccess, fmt.Sprint(success), 73 | LabelTargetNamespace, namespace, 74 | LabelReleaseName, releaseName, 75 | ).Observe(time.Since(start).Seconds()) 76 | } 77 | -------------------------------------------------------------------------------- /pkg/status/metrics.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | v1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" 5 | stdprometheus "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | const ( 9 | LabelTargetNamespace = "target_namespace" 10 | LabelReleaseName = "release_name" 11 | LabelCondition = "condition" 12 | ) 13 | 14 | var ( 15 | conditionStatusToGaugeValue = map[v1.ConditionStatus]float64{ 16 | v1.ConditionFalse: -1, 17 | v1.ConditionUnknown: 0, 18 | v1.ConditionTrue: 1, 19 | } 20 | releaseCondition = stdprometheus.NewGaugeVec(stdprometheus.GaugeOpts{ 21 | Namespace: "flux", 22 | Subsystem: "helm_operator", 23 | Name: "release_condition_info", 24 | Help: "Current HelmRelease condition status. Values are -1 (false), 0 (unknown or absent), 1 (true)", 25 | }, []string{LabelTargetNamespace, LabelReleaseName, LabelCondition}) 26 | ) 27 | 28 | func init() { 29 | stdprometheus.MustRegister(releaseCondition) 30 | } 31 | 32 | func ObserveReleaseConditions(old *v1.HelmRelease, new *v1.HelmRelease) { 33 | conditions := make(map[v1.HelmReleaseConditionType]*v1.ConditionStatus) 34 | 35 | for _, condition := range old.Status.Conditions { 36 | conditions[condition.Type] = nil 37 | } 38 | 39 | if new != nil { 40 | for _, condition := range new.Status.Conditions { 41 | conditions[condition.Type] = &condition.Status 42 | } 43 | } 44 | 45 | for conditionType, conditionStatus := range conditions { 46 | if conditionStatus == nil { 47 | releaseCondition.Delete(labelsForRelease(old, conditionType)) 48 | } else { 49 | releaseCondition.With(labelsForRelease(new, conditionType)).Set(conditionStatusToGaugeValue[*conditionStatus]) 50 | } 51 | } 52 | } 53 | 54 | func labelsForRelease(hr *v1.HelmRelease, conditionType v1.HelmReleaseConditionType) stdprometheus.Labels { 55 | return stdprometheus.Labels{ 56 | LabelTargetNamespace: hr.GetTargetNamespace(), 57 | LabelReleaseName: hr.GetReleaseName(), 58 | LabelCondition: string(conditionType), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/utils/logwriter.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/go-kit/kit/log" 8 | ) 9 | 10 | // logWriter wraps a `log.Logger` so it can be used as an `io.Writer`. 11 | type logWriter struct { 12 | log.Logger 13 | } 14 | 15 | // NewLogWriter returns an `io.Writer` for the given logger. 16 | func NewLogWriter(logger log.Logger) io.Writer { 17 | return &logWriter{logger} 18 | } 19 | 20 | // Write simply logs the given byes as a 'write' operation, the only 21 | // modification it makes before logging the given bytes is the removal 22 | // of a terminating newline if present. 23 | func (l *logWriter) Write(p []byte) (n int, err error) { 24 | origLen := len(p) 25 | if len(p) > 0 && p[len(p)-1] == '\n' { 26 | p = p[:len(p)-1] // Cut terminating newline 27 | } 28 | l.Log("info", fmt.Sprintf("%s", p)) 29 | return origLen, nil 30 | } 31 | -------------------------------------------------------------------------------- /test/bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluxcd/helm-operator/a58c2676bce8bea794769506dd7bff7ff4924e5e/test/bin/.gitkeep -------------------------------------------------------------------------------- /test/e2e/10_helm_chart.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | function setup() { 4 | # Load libraries in setup() to access BATS_* variables 5 | load lib/env 6 | load lib/install 7 | load lib/poll 8 | 9 | kubectl create namespace "$E2E_NAMESPACE" 10 | install_gitsrv 11 | install_tiller 12 | install_helm_operator_with_helm 13 | kubectl create namespace "$DEMO_NAMESPACE" 14 | } 15 | 16 | @test "Helm chart installation smoke test" { 17 | # The gitconfig secret must exist and have the right value 18 | poll_until_equals "gitconfig secret" "$GITCONFIG" "kubectl get secrets -n $E2E_NAMESPACE gitconfig -ojsonpath={..data.gitconfig} | base64 --decode" 19 | 20 | # Apply the HelmRelease fixtures 21 | kubectl apply -f "$FIXTURES_DIR/releases/git.yaml" >&3 22 | kubectl apply -f "$FIXTURES_DIR/releases/helm-repository.yaml" >&3 23 | 24 | poll_until_equals 'helm repository chart deployed release status' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-helm-repository -o 'custom-columns=status:status.releaseStatus' --no-headers" 25 | poll_until_equals 'git deployed chart release status' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o 'custom-columns=status:status.releaseStatus' --no-headers" 26 | 27 | # Assert `Released` and `Deployed` conditions are `True` 28 | poll_until_equals 'helm repository chart released condition true' 'True' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-helm-repository -o jsonpath='{.status.conditions[?(@.type==\"Released\")].status}'" 29 | run kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-helm-repository -o jsonpath='{.status.conditions[?(@.type=="Deployed")].status}' 30 | [ "$output" = 'True' ] 31 | poll_until_equals 'git chart released condition true' 'True' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.conditions[?(@.type==\"Released\")].status}'" 32 | run kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.conditions[?(@.type=="Deployed")].status}' 33 | [ "$output" = 'True' ] 34 | 35 | poll_no_restarts 36 | } 37 | 38 | function teardown() { 39 | # Teardown is verbose when a test fails, and this will help most of the time 40 | # to determine _why_ it failed. 41 | echo "" 42 | echo "### Previous container:" 43 | kubectl logs -n "$E2E_NAMESPACE" deploy/helm-operator -p 44 | echo "" 45 | echo "### Current container:" 46 | kubectl logs -n "$E2E_NAMESPACE" deploy/helm-operator 47 | 48 | # Removing the operator also takes care of the global resources it installs. 49 | uninstall_helm_operator_with_helm 50 | uninstall_tiller 51 | # Removing the namespace also takes care of removing gitsrv. 52 | kubectl delete namespace "$E2E_NAMESPACE" 53 | # Only remove the demo workloads after the operator, so that they cannot be recreated. 54 | kubectl delete namespace "$DEMO_NAMESPACE" 55 | } 56 | -------------------------------------------------------------------------------- /test/e2e/35_skip_crds.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | function setup() { 4 | # Load libraries in setup() to access BATS_* variables 5 | load lib/env 6 | load lib/install 7 | load lib/poll 8 | 9 | kubectl create namespace "$E2E_NAMESPACE" 10 | install_gitsrv 11 | install_tiller 12 | install_helm_operator_with_helm 13 | kubectl create namespace "$DEMO_NAMESPACE" 14 | } 15 | 16 | @test "When skipCRDs is set" { 17 | if [ "$HELM_VERSION" != "v3" ]; then 18 | skip 19 | fi 20 | 21 | # Apply the HelmRelease 22 | kubectl apply -f "$FIXTURES_DIR/releases/skip-crd.yaml" >&3 23 | poll_until_equals 'skip-crd HelmRelease' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/skip-crd -o 'custom-columns=status:status.releaseStatus' --no-headers" 24 | 25 | # Assert no CRDs were installed 26 | count=$(kubectl get crd --no-headers | grep 'konghq.com' | wc -l) 27 | [ "$count" -eq 0 ] 28 | 29 | poll_no_restarts 30 | } 31 | 32 | function teardown() { 33 | # Teardown is verbose when a test fails, and this will help most of the time 34 | # to determine _why_ it failed. 35 | echo "" 36 | echo "### Previous container:" 37 | kubectl logs -n "$E2E_NAMESPACE" deploy/helm-operator -p 38 | echo "" 39 | echo "### Current container:" 40 | kubectl logs -n "$E2E_NAMESPACE" deploy/helm-operator 41 | 42 | # Removing the operator also takes care of the global resources it installs. 43 | uninstall_helm_operator_with_helm 44 | uninstall_tiller 45 | # Removing the namespace also takes care of removing gitsrv. 46 | kubectl delete namespace "$E2E_NAMESPACE" 47 | # Only remove the demo workloads after the operator, so that they cannot be recreated. 48 | kubectl delete namespace "$DEMO_NAMESPACE" 49 | } 50 | -------------------------------------------------------------------------------- /test/e2e/45_convert_2to3.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | function setup() { 4 | # Load libraries in setup() to access BATS_* variables 5 | load lib/env 6 | load lib/helm 7 | load lib/install 8 | load lib/poll 9 | 10 | kubectl create namespace "$E2E_NAMESPACE" 11 | install_gitsrv 12 | install_tiller 13 | # for this test, we need the operator to be able to handle both v2 and v3 manifests 14 | HELM_ENABLED_VERSIONS="v2\,v3" install_helm_operator_with_helm 15 | kubectl create namespace "$DEMO_NAMESPACE" 16 | } 17 | 18 | @test "Migration succeeds from v2 to v3" { 19 | # Apply the HelmRelease 20 | kubectl apply -f "$FIXTURES_DIR/releases/convert-2to3-v2.yaml" >&3 21 | 22 | # Wait for it to be released 23 | poll_until_equals 'release deploy' 'True' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-helm-repository -o jsonpath='{.status.conditions[?(@.type==\"Released\")].status}'" 24 | 25 | poll_until_equals 'helm2 shows helm release' 'podinfo-helm-repository' "HELM_VERSION=v2 helm ls | grep podinfo-helm-repository | awk '{print \$1}'" 26 | 27 | kubectl apply -f "$FIXTURES_DIR/releases/convert-2to3-v3.yaml" >&3 28 | poll_until_equals 'helm2 no longer shows helm release' '0' "HELM_VERSION=v2 helm ls | grep podinfo-helm-repository | wc -l | awk '{\$1=\$1};1'" 29 | poll_until_equals 'helm3 shows helm release' 'podinfo-helm-repository' "HELM_VERSION=v3 helm ls -n $DEMO_NAMESPACE | grep podinfo-helm-repository | awk '{print \$1}'" 30 | poll_until_equals 'release migrated' 'True' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-helm-repository -o jsonpath='{.status.conditions[?(@.type==\"Released\")].status}'" 31 | 32 | kubectl apply -f "$FIXTURES_DIR/releases/convert-2to3-v3-upgrade.yaml" >&3 33 | poll_until_equals 'upgrades work after migration' '1' "kubectl get deploy/podinfo-helm-repository -n "$DEMO_NAMESPACE" -o jsonpath='{.spec.replicas}'" 34 | poll_no_restarts 35 | } 36 | 37 | @test "Migration is skipped and install works when no v2 release exists" { 38 | kubectl apply -f "$FIXTURES_DIR/releases/convert-2to3-v3.yaml" >&3 39 | poll_until_equals 'helm3 shows helm release' 'podinfo-helm-repository' "HELM_VERSION=v3 helm ls -n $DEMO_NAMESPACE | grep podinfo-helm-repository | awk '{print \$1}'" 40 | poll_until_equals 'install successful' 'True' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-helm-repository -o jsonpath='{.status.conditions[?(@.type==\"Released\")].status}'" 41 | poll_no_restarts 42 | } 43 | 44 | function teardown() { 45 | # Teardown is verbose when a test fails, and this will help most of the time 46 | # to determine _why_ it failed. 47 | echo "" 48 | echo "### Previous container:" 49 | kubectl logs -n "$E2E_NAMESPACE" deploy/helm-operator -p 50 | echo "" 51 | echo "### Current container:" 52 | kubectl logs -n "$E2E_NAMESPACE" deploy/helm-operator 53 | 54 | # Removing the operator also takes care of the global resources it installs. 55 | uninstall_helm_operator_with_helm 56 | uninstall_tiller 57 | # Removing the namespace also takes care of removing gitsrv. 58 | kubectl delete namespace "$E2E_NAMESPACE" 59 | # Only remove the demo workloads after the operator, so that they cannot be recreated. 60 | kubectl delete namespace "$DEMO_NAMESPACE" 61 | } 62 | -------------------------------------------------------------------------------- /test/e2e/50_deletes.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | function setup() { 4 | # Load libraries in setup() to access BATS_* variables 5 | load lib/env 6 | load lib/helm 7 | load lib/install 8 | load lib/poll 9 | 10 | kubectl create namespace "$E2E_NAMESPACE" 11 | install_gitsrv 12 | install_tiller 13 | install_helm_operator_with_helm 14 | kubectl create namespace "$DEMO_NAMESPACE" 15 | } 16 | 17 | @test "Deletes cleanup resources successfully" { 18 | # Apply the HelmRelease 19 | kubectl apply -f "$FIXTURES_DIR/releases/helm-repository.yaml" >&3 20 | 21 | # Wait for it to be released 22 | poll_until_equals 'release deploy' 'True' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-helm-repository -o jsonpath='{.status.conditions[?(@.type==\"Released\")].status}'" 23 | 24 | kubectl delete -f "$FIXTURES_DIR/releases/helm-repository.yaml" >&3 25 | 26 | poll_until_true 'deployment is deleted' "kubectl get deploy -n $DEMO_NAMESPACE podinfo-helm-repository 2>&1 | grep 'NotFound'" 27 | 28 | poll_no_restarts 29 | } 30 | 31 | function teardown() { 32 | # Teardown is verbose when a test fails, and this will help most of the time 33 | # to determine _why_ it failed. 34 | echo "" 35 | echo "### Previous container:" 36 | kubectl logs -n "$E2E_NAMESPACE" deploy/helm-operator -p 37 | echo "" 38 | echo "### Current container:" 39 | kubectl logs -n "$E2E_NAMESPACE" deploy/helm-operator 40 | 41 | # Removing the operator also takes care of the global resources it installs. 42 | uninstall_helm_operator_with_helm 43 | uninstall_tiller 44 | # Removing the namespace also takes care of removing gitsrv. 45 | kubectl delete namespace "$E2E_NAMESPACE" 46 | # Only remove the demo workloads after the operator, so that they cannot be recreated. 47 | kubectl delete namespace "$DEMO_NAMESPACE" 48 | } 49 | -------------------------------------------------------------------------------- /test/e2e/fixtures/charts/nested-helmrelease/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: Nested HelmRelease chart 3 | name: nested-helmrelease 4 | version: 0.1.0 5 | -------------------------------------------------------------------------------- /test/e2e/fixtures/charts/nested-helmrelease/templates/helmrelease.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: {{ .Release.Name }}-child 6 | namespace: {{ .Release.Namespace }} 7 | spec: 8 | releaseName: {{ .Release.Name }}-child 9 | chart: 10 | repository: https://stefanprodan.github.io/podinfo 11 | version: 3.2.0 12 | name: podinfo 13 | values: 14 | {{- toYaml .Values.nested.deeper.deepest | nindent 4 }} 15 | -------------------------------------------------------------------------------- /test/e2e/fixtures/charts/nested-helmrelease/values.yaml: -------------------------------------------------------------------------------- 1 | nested: 2 | deeper: 3 | deepest: 4 | image: 5 | name: some-other-image 6 | tag: 1.0.1 7 | -------------------------------------------------------------------------------- /test/e2e/fixtures/gitconfig: -------------------------------------------------------------------------------- 1 | [core] 2 | editor=vim 3 | -------------------------------------------------------------------------------- /test/e2e/fixtures/kustom/base/chartmuseum/chartmuseum.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | name: chartmuseum 7 | name: chartmuseum 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | name: chartmuseum 13 | template: 14 | metadata: 15 | labels: 16 | name: chartmuseum 17 | spec: 18 | containers: 19 | - name: chartmuseum 20 | image: chartmuseum/chartmuseum:v0.11.0 21 | imagePullPolicy: IfNotPresent 22 | env: 23 | - name: "LOG_JSON" 24 | value: "true" 25 | - name: "STORAGE" 26 | value: "local" 27 | args: 28 | - --port=8080 29 | - --storage-local-rootdir=/storage 30 | livenessProbe: 31 | httpGet: 32 | path: /health 33 | port: http 34 | failureThreshold: 3 35 | initialDelaySeconds: 5 36 | periodSeconds: 10 37 | successThreshold: 1 38 | timeoutSeconds: 1 39 | readinessProbe: 40 | httpGet: 41 | path: /health 42 | port: http 43 | failureThreshold: 3 44 | initialDelaySeconds: 5 45 | periodSeconds: 10 46 | successThreshold: 1 47 | timeoutSeconds: 1 48 | ports: 49 | - containerPort: 8080 50 | name: http 51 | protocol: TCP 52 | volumeMounts: 53 | - mountPath: /storage 54 | name: storage-volume 55 | securityContext: 56 | fsGroup: 1000 57 | volumes: 58 | - name: storage-volume 59 | emptyDir: {} 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | labels: 65 | name: chartmuseum 66 | name: chartmuseum 67 | spec: 68 | ports: 69 | - name: http 70 | port: 8080 71 | protocol: TCP 72 | targetPort: http 73 | selector: 74 | name: chartmuseum 75 | type: ClusterIP 76 | -------------------------------------------------------------------------------- /test/e2e/fixtures/kustom/base/chartmuseum/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - chartmuseum.yaml 3 | -------------------------------------------------------------------------------- /test/e2e/fixtures/kustom/base/gitsrv/gitsrv.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | name: gitsrv 7 | name: gitsrv 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | name: gitsrv 13 | template: 14 | metadata: 15 | labels: 16 | name: gitsrv 17 | spec: 18 | containers: 19 | - image: fluxcd/gitsrv:v1.0.0 20 | name: git 21 | env: 22 | - name: REPO 23 | value: "cluster.git" 24 | - name: TAR_URL 25 | value: "https://github.com/stefanprodan/podinfo/archive/3.2.0.tar.gz" 26 | ports: 27 | - containerPort: 22 28 | name: ssh 29 | protocol: TCP 30 | volumeMounts: 31 | - mountPath: /git-server/repos 32 | name: git-server-data 33 | - mountPath: /git-server/keys 34 | name: flux-git-deploy 35 | volumes: 36 | - name: flux-git-deploy 37 | secret: 38 | secretName: flux-git-deploy 39 | - name: git-server-data 40 | emptyDir: {} 41 | --- 42 | apiVersion: v1 43 | kind: Service 44 | metadata: 45 | labels: 46 | name: gitsrv 47 | name: gitsrv 48 | spec: 49 | ports: 50 | - name: ssh 51 | port: 22 52 | protocol: TCP 53 | targetPort: ssh 54 | selector: 55 | name: gitsrv 56 | type: ClusterIP 57 | -------------------------------------------------------------------------------- /test/e2e/fixtures/kustom/base/gitsrv/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - gitsrv.yaml 3 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/convert-2to3-v2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-helm-repository 9 | timeout: 30 10 | test: 11 | enable: true 12 | timeout: 30 13 | rollback: 14 | enable: true 15 | wait: true 16 | chart: 17 | repository: https://stefanprodan.github.io/podinfo 18 | name: podinfo 19 | version: 4.0.1 20 | values: 21 | replicaCount: 2 22 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/convert-2to3-v3-upgrade.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | annotations: 8 | helm.fluxcd.io/migrate: "true" 9 | spec: 10 | helmVersion: v3 11 | releaseName: podinfo-helm-repository 12 | timeout: 30 13 | test: 14 | enable: true 15 | timeout: 30 16 | rollback: 17 | enable: true 18 | wait: true 19 | chart: 20 | repository: https://stefanprodan.github.io/podinfo 21 | name: podinfo 22 | version: 4.0.1 23 | values: 24 | replicaCount: 1 25 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/convert-2to3-v3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | annotations: 8 | helm.fluxcd.io/migrate: "true" 9 | spec: 10 | helmVersion: v3 11 | releaseName: podinfo-helm-repository 12 | timeout: 30 13 | test: 14 | enable: true 15 | timeout: 30 16 | rollback: 17 | enable: true 18 | wait: true 19 | chart: 20 | repository: https://stefanprodan.github.io/podinfo 21 | name: podinfo 22 | version: 4.0.1 23 | values: 24 | replicaCount: 2 25 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/git.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-git 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-git 9 | timeout: 30 10 | rollback: 11 | enable: true 12 | wait: true 13 | chart: 14 | git: ssh://git@gitsrv/git-server/repos/cluster.git 15 | ref: master 16 | path: charts/podinfo 17 | values: 18 | replicaCount: 2 19 | 20 | faults: 21 | # Setting this to 'true' will make the wait fail 22 | unready: false 23 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/helm-repository-no-rollback.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-helm-repository 9 | timeout: 30 10 | wait: true 11 | chart: 12 | repository: https://stefanprodan.github.io/podinfo 13 | name: podinfo 14 | version: 3.2.0 15 | values: 16 | replicaCount: 2 17 | 18 | faults: 19 | # Setting this to 'true' will make the wait fail 20 | unready: false 21 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/helm-repository.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-helm-repository 9 | timeout: 30 10 | rollback: 11 | enable: true 12 | wait: true 13 | chart: 14 | repository: https://stefanprodan.github.io/podinfo 15 | name: podinfo 16 | version: 3.2.0 17 | values: 18 | replicaCount: 2 19 | 20 | faults: 21 | # Setting this to 'true' will make the wait fail 22 | unready: false 23 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/nested-helmrelease.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: nested-helmrelease 6 | namespace: demo 7 | spec: 8 | releaseName: nested-helmrelease 9 | chart: 10 | repository: http://chartmuseum:8080 11 | name: nested-helmrelease 12 | version: 0.1.0 13 | values: 14 | nested: 15 | deeper: 16 | deepest: 17 | image: 18 | name: some-other-image 19 | tag: 1.0.1 20 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/skip-crd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: skip-crd 6 | namespace: demo 7 | spec: 8 | releaseName: skip-crd 9 | skipCRDs: true 10 | chart: 11 | repository: https://charts.konghq.com/ 12 | name: kong 13 | version: 1.1.1 14 | values: 15 | ingressController: 16 | installCRDs: false 17 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/takeover.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-takeover 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-takeover 9 | timeout: 30 10 | resetValues: false 11 | chart: 12 | repository: https://stefanprodan.github.io/podinfo 13 | name: podinfo 14 | version: 3.2.2 15 | values: {} 16 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/test/fail-ignored.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-helm-repository 9 | timeout: 30 10 | test: 11 | enable: true 12 | ignoreFailures: true 13 | timeout: 30 14 | rollback: 15 | enable: true 16 | wait: true 17 | chart: 18 | repository: https://stefanprodan.github.io/podinfo 19 | name: podinfo 20 | version: 4.0.1 21 | values: 22 | replicaCount: 2 23 | faults: 24 | testFail: true 25 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/test/fail.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-helm-repository 9 | timeout: 30 10 | test: 11 | enable: true 12 | ignoreFailures: false 13 | timeout: 30 14 | rollback: 15 | enable: true 16 | wait: true 17 | chart: 18 | repository: https://stefanprodan.github.io/podinfo 19 | name: podinfo 20 | version: 4.0.1 21 | values: 22 | replicaCount: 2 23 | faults: 24 | testFail: true 25 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/test/success.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-helm-repository 9 | timeout: 30 10 | test: 11 | enable: true 12 | timeout: 30 13 | rollback: 14 | enable: true 15 | wait: true 16 | chart: 17 | repository: https://stefanprodan.github.io/podinfo 18 | name: podinfo 19 | version: 4.0.1 20 | values: 21 | replicaCount: 2 22 | -------------------------------------------------------------------------------- /test/e2e/fixtures/releases/test/timeout.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-helm-repository 6 | namespace: demo 7 | spec: 8 | releaseName: podinfo-helm-repository 9 | timeout: 30 10 | test: 11 | enable: true 12 | timeout: 1 13 | rollback: 14 | enable: true 15 | wait: true 16 | chart: 17 | repository: https://stefanprodan.github.io/podinfo 18 | name: podinfo 19 | version: 4.0.1 20 | values: 21 | replicaCount: 2 22 | faults: 23 | testTimeout: true 24 | -------------------------------------------------------------------------------- /test/e2e/fixtures/values_from/chartfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: chartfile-values 6 | spec: 7 | releaseName: chartfile-values 8 | chart: 9 | git: ssh://git@gitsrv/git-server/repos/cluster.git 10 | ref: master 11 | path: charts/podinfo 12 | valuesFrom: 13 | - chartFileRef: 14 | path: values.yaml 15 | values: {} 16 | -------------------------------------------------------------------------------- /test/e2e/fixtures/values_from/configmap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: configmap-values 6 | spec: 7 | releaseName: configmap-values 8 | chart: 9 | repository: https://stefanprodan.github.io/podinfo 10 | name: podinfo 11 | version: 3.2.0 12 | valuesFrom: 13 | - configMapKeyRef: 14 | name: podinfo-values 15 | values: {} 16 | -------------------------------------------------------------------------------- /test/e2e/fixtures/values_from/externalsource.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: external-source-values 6 | spec: 7 | releaseName: external-source-values 8 | chart: 9 | repository: https://stefanprodan.github.io/podinfo 10 | name: podinfo 11 | version: 3.2.0 12 | valuesFrom: 13 | - externalSourceRef: 14 | url: https://raw.githubusercontent.com/stefanprodan/podinfo/3.2.0/charts/podinfo/values.yaml 15 | values: {} 16 | -------------------------------------------------------------------------------- /test/e2e/fixtures/values_from/secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: secret-values 6 | spec: 7 | releaseName: secret-values 8 | chart: 9 | repository: https://stefanprodan.github.io/podinfo 10 | name: podinfo 11 | version: 3.2.0 12 | valuesFrom: 13 | - secretKeyRef: 14 | name: podinfo-values 15 | values: {} 16 | -------------------------------------------------------------------------------- /test/e2e/lib/defer.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This lets you call `defer` to record an action to do later; 4 | # `run_deferred` should be called in an EXIT trap, either explicitly: 5 | # 6 | # trap run_deferred EXIT 7 | # 8 | # or when using with tests, by calling it in the teardown function 9 | # (which bats will arrange to run). 10 | 11 | declare -a on_exit_items 12 | 13 | function run_deferred() { 14 | if [ "${#on_exit_items[@]}" -gt 0 ]; then 15 | echo -e '\nRunning deferred items, please do not interrupt until they are done:' 16 | fi 17 | for I in "${on_exit_items[@]}"; do 18 | echo "deferred: ${I}" 19 | eval "${I}" 20 | done 21 | } 22 | 23 | function defer() { 24 | on_exit_items=("$*" "${on_exit_items[@]}") 25 | } 26 | -------------------------------------------------------------------------------- /test/e2e/lib/env.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export E2E_NAMESPACE=helm-operator-e2e 4 | export DEMO_NAMESPACE=demo 5 | ROOT_DIR=$(git rev-parse --show-toplevel) 6 | export ROOT_DIR 7 | export E2E_DIR="${ROOT_DIR}/test/e2e" 8 | export FIXTURES_DIR="${E2E_DIR}/fixtures" 9 | GITCONFIG=$(cat "${FIXTURES_DIR}/gitconfig") 10 | export GITCONFIG 11 | export HELM_VERSION=${HELM_VERSION} 12 | export GITSRV_VERSION=v1.0.0 13 | export GITSRV_KNOWN_HOSTS="${ROOT_DIR}/cache/known_hosts_${GITSRV_VERSION}" 14 | 15 | # Wire the test to the right cluster when tests are run in parallel 16 | if eval [ -n '$KUBECONFIG_SLOT_'"${BATS_JOB_SLOT}" ]; then 17 | eval export KUBECONFIG='$KUBECONFIG_SLOT_'"${BATS_JOB_SLOT}" 18 | fi 19 | -------------------------------------------------------------------------------- /test/e2e/lib/helm.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # shellcheck disable=SC1090 4 | source "${E2E_DIR}/lib/defer.bash" 5 | 6 | function helm() { 7 | case ${HELM_VERSION} in 8 | v2) 9 | helm2 --tiller-namespace "$E2E_NAMESPACE" "$@" 10 | ;; 11 | v3) 12 | helm3 "$@" 13 | ;; 14 | *) 15 | echo "No Helm binary found for version $HELM_VERSION" >&2 16 | return 1 17 | esac 18 | } 19 | 20 | function package_and_upload_chart() { 21 | local chart=${1} 22 | local chart_repository=${2} 23 | 24 | gen_dir=$(mktemp -d) 25 | defer rm -rf "'$gen_dir'" 26 | 27 | helm package --destination "$gen_dir" "$chart" 28 | 29 | # Upload 30 | chart_tarbal=$(find "$gen_dir" -type f -name "*.tgz" | head -n1) 31 | curl --data-binary "@$chart_tarbal" "$chart_repository/api/charts" 32 | } 33 | -------------------------------------------------------------------------------- /test/e2e/lib/poll.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function poll_until_equals() { 4 | local what="$1" 5 | local expected="$2" 6 | local check_cmd="$3" 7 | local retries="$4" 8 | local wait_period="$5" 9 | poll_until_true "$what" "[ '$expected' = \"\$( $check_cmd )\" ]" "$retries" "$wait_period" 10 | } 11 | 12 | function poll_until_true() { 13 | local what="$1" 14 | local check_cmd="$2" 15 | # timeout after $retries * $wait_period seconds 16 | local retries=${3:-24} 17 | local wait_period=${4:-3} 18 | echo -n ">>> Waiting for $what " >&3 19 | count=0 20 | until eval "$check_cmd"; do 21 | echo -n '.' >&3 22 | sleep "$wait_period" 23 | count=$((count + 1)) 24 | if [[ ${count} -eq ${retries} ]]; then 25 | echo ' No more retries left!' >&3 26 | return 1 # fail 27 | fi 28 | done 29 | echo ' done' >&3 30 | } 31 | 32 | function poll_no_restarts() { 33 | sleep 5 # allow enough time for the operator to react to previous operation 34 | poll_until_equals 'no helm-operator restarts' '0' "kubectl get pod -n $E2E_NAMESPACE -l app=helm-operator -o jsonpath='{.items[*].status.containerStatuses[*].restartCount}'" '1' 35 | } 36 | -------------------------------------------------------------------------------- /test/e2e/lib/template.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function fill_in_place_recursively() { 4 | local -n key_values=$1 # pass an associate array as a nameref 5 | local target_directory=${2:-.} 6 | (# use a subshell to expose key-values as variables for envsubst to use 7 | for key in "${!key_values[@]}"; do 8 | export "$key"="${key_values[$key]}" 9 | done 10 | # Use find with zero-ended strings and read to avoid problems 11 | # with spaces in paths 12 | while IFS= read -r -d '' file; do 13 | # Use a command group to ensure "$file" is not 14 | # deleted before being written to. 15 | # shellcheck disable=SC2094 16 | { 17 | rm "$file" 18 | envsubst > "$file" 19 | } < "$file" 20 | done < <(find "$target_directory" -type f -print0) 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /test/e2e/run.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | 5 | # This script runs the bats tests, first ensuring there is a kubernetes 6 | # cluster available, with a namespace and a git secret ready to use 7 | 8 | # Directory paths we need to be aware of 9 | ROOT_DIR="$(git rev-parse --show-toplevel)" 10 | E2E_DIR="${ROOT_DIR}/test/e2e" 11 | CACHE_DIR="${ROOT_DIR}/cache/$CURRENT_OS_ARCH" 12 | 13 | KIND_VERSION=v0.11.1 14 | KUBE_VERSION=v1.20.2 15 | KIND_CACHE_PATH="${CACHE_DIR}/kind-$KIND_VERSION" 16 | BATS_EXTRA_ARGS="" 17 | 18 | # shellcheck disable=SC1090 19 | source "${E2E_DIR}/lib/defer.bash" 20 | trap run_deferred EXIT 21 | 22 | function install_kind() { 23 | if [ ! -f "${KIND_CACHE_PATH}" ]; then 24 | echo '>>> Downloading Kind' 25 | mkdir -p "${CACHE_DIR}" 26 | curl -sL "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-${CURRENT_OS_ARCH}" -o "${KIND_CACHE_PATH}" 27 | fi 28 | cp "${KIND_CACHE_PATH}" "${ROOT_DIR}/test/bin/kind" 29 | chmod +x "${ROOT_DIR}/test/bin/kind" 30 | } 31 | 32 | # Let users specify how many, e.g. with E2E_KIND_CLUSTER_NUM=3 make e2e 33 | E2E_KIND_CLUSTER_NUM=${E2E_KIND_CLUSTER_NUM:-1} 34 | KIND_CLUSTER_PREFIX=${KIND_CLUSTER_PREFIX:-helm-operator-e2e} 35 | 36 | # Check if there is a kubernetes cluster running, otherwise use Kind 37 | if ! kubectl version > /dev/null 2>&1; then 38 | install_kind 39 | 40 | # We require GNU Parallel, but some systems come with Tollef's parallel (moreutils) 41 | if ! parallel -h | grep -q "GNU Parallel"; then 42 | echo "GNU Parallel is not available on your system"; exit 1 43 | fi 44 | 45 | echo '>>> Creating Kind Kubernetes cluster(s)' 46 | KIND_CONFIG_PREFIX="${HOME}/.kube/kind-config-${KIND_CLUSTER_PREFIX}" 47 | for I in $(seq 1 "${E2E_KIND_CLUSTER_NUM}"); do 48 | defer kind --name "${KIND_CLUSTER_PREFIX}-${I}" delete cluster > /dev/null 2>&1 || true 49 | defer rm -rf "${KIND_CONFIG_PREFIX}-${I}" 50 | # Wire tests with the right cluster based on their BATS_JOB_SLOT env variable 51 | eval export "KUBECONFIG_SLOT_${I}=${KIND_CONFIG_PREFIX}-${I}" 52 | done 53 | # Due to https://github.com/kubernetes-sigs/kind/issues/1288 54 | # limit parallel creation of clusters to 1 job. 55 | seq 1 "${E2E_KIND_CLUSTER_NUM}" | time parallel -j 1 -- env KUBECONFIG="${KIND_CONFIG_PREFIX}-{}" kind create cluster --name "${KIND_CLUSTER_PREFIX}-{}" --wait 5m --image kindest/node:${KUBE_VERSION} 56 | 57 | echo '>>> Loading images into the Kind cluster(s)' 58 | seq 1 "${E2E_KIND_CLUSTER_NUM}" | time parallel -- kind --name "${KIND_CLUSTER_PREFIX}-{}" load docker-image 'docker.io/fluxcd/helm-operator:latest' 59 | if [ "${E2E_KIND_CLUSTER_NUM}" -gt 1 ]; then 60 | BATS_EXTRA_ARGS="--jobs ${E2E_KIND_CLUSTER_NUM}" 61 | fi 62 | fi 63 | 64 | echo '>>> Running the tests' 65 | # Run all tests by default but let users specify which ones to run, e.g. with E2E_TESTS='11_*' make e2e 66 | E2E_TESTS=${E2E_TESTS:-.} 67 | HELM_VERSION=${HELM_VERSION:-} 68 | ( 69 | cd "${E2E_DIR}" 70 | export HELM_VERSION=${HELM_VERSION} 71 | # shellcheck disable=SC2086 72 | "${E2E_DIR}/bats/bin/bats" -t ${BATS_EXTRA_ARGS} ${E2E_TESTS} 73 | ) 74 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // This file just exists to ensure we download the tools we need for building 5 | // See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 6 | 7 | package helm_operator 8 | 9 | import ( 10 | _ "github.com/fluxcd/helm-operator/pkg/install" 11 | _ "github.com/shurcooL/vfsgen" 12 | ) 13 | --------------------------------------------------------------------------------