From e31a5352cc1eff6f3ca52171c249f4a9bd33c834 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 12:18:47 -0400 Subject: [PATCH 01/48] rename example chart to be more specific --- .github/workflows/e2e/scripts/create-projecthelmchart.sh | 8 ++++---- .github/workflows/e2e/scripts/delete-projecthelmchart.sh | 6 +++--- charts/helm-project-operator/README.md | 4 ++-- .../Chart.yaml | 4 ++-- .../{example-chart => project-operator-example}/README.md | 2 +- .../questions.yaml | 0 .../templates/configmaps.yaml | 0 .../templates/dashboard-roles.yaml | 0 .../templates/dashboard-values-configmap.yaml | 0 .../values.yaml | 0 docs/design.md | 4 ++-- docs/developing.md | 8 ++++---- docs/experimental/e2e.md | 2 +- docs/gettingstarted.md | 2 +- dummy.go | 8 ++++---- examples/ci-example.yaml | 2 +- scripts/build-chart | 2 +- 17 files changed, 26 insertions(+), 26 deletions(-) rename charts/{example-chart => project-operator-example}/Chart.yaml (74%) rename charts/{example-chart => project-operator-example}/README.md (84%) rename charts/{example-chart => project-operator-example}/questions.yaml (100%) rename charts/{example-chart => project-operator-example}/templates/configmaps.yaml (100%) rename charts/{example-chart => project-operator-example}/templates/dashboard-roles.yaml (100%) rename charts/{example-chart => project-operator-example}/templates/dashboard-values-configmap.yaml (100%) rename charts/{example-chart => project-operator-example}/values.yaml (100%) diff --git a/.github/workflows/e2e/scripts/create-projecthelmchart.sh b/.github/workflows/e2e/scripts/create-projecthelmchart.sh index 00a20ca..0956bc1 100755 --- a/.github/workflows/e2e/scripts/create-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/create-projecthelmchart.sh @@ -8,7 +8,7 @@ cd $(dirname $0)/../../../.. kubectl apply -f ./examples/ci-example.yaml sleep ${DEFAULT_SLEEP_TIMEOUT_SECONDS}; -if ! kubectl get -n cattle-helm-system job/helm-install-project-example-chart-dummy; then +if ! kubectl get -n cattle-helm-system job/helm-install-project-project-operator-example-dummy; then echo "ERROR: Helm Install Job for Example Chart was never created after ${KUBECTL_WAIT_TIMEOUT} seconds" echo "PROJECT HELM CHARTS:" kubectl get projecthelmchart -n cattle-project-p-example -o yaml @@ -21,11 +21,11 @@ if ! kubectl get -n cattle-helm-system job/helm-install-project-example-chart-du exit 1 fi -if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-install-project-example-chart-dummy; then +if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-install-project-project-operator-example-dummy; then echo "ERROR: Helm Install Job for Example Chart never completed after ${KUBECTL_WAIT_TIMEOUT} seconds" - kubectl logs job/helm-install-project-example-chart-dummy -n cattle-helm-system + kubectl logs job/helm-install-project-project-operator-example-dummy -n cattle-helm-system exit 1 fi -kubectl logs job/helm-install-project-example-chart-dummy -n cattle-helm-system +kubectl logs job/helm-install-project-project-operator-example-dummy -n cattle-helm-system echo "PASS: Adding ProjectHelmChart successfully installed Example Chart" diff --git a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh index 712fc0c..d2a9195 100755 --- a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh @@ -6,10 +6,10 @@ source $(dirname $0)/entry cd $(dirname $0)/../../../.. kubectl delete -f ./examples/ci-example.yaml -if kubectl get -n cattle-helm-system job/helm-delete-project-example-chart-dummy --ignore-not-found; then - if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-delete-project-example-chart-dummy; then +if kubectl get -n cattle-helm-system job/helm-delete-project-project-operator-example-dummy --ignore-not-found; then + if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-delete-project-project-operator-example-dummy; then echo "ERROR: Helm Uninstall Job for Example Chart never completed after ${KUBECTL_WAIT_TIMEOUT}" - kubectl logs job/helm-delete-project-example-chart-dummy -n cattle-helm-system + kubectl logs job/helm-delete-project-project-operator-example-dummy -n cattle-helm-system exit 1 fi fi diff --git a/charts/helm-project-operator/README.md b/charts/helm-project-operator/README.md index fc1d39e..9623ff2 100644 --- a/charts/helm-project-operator/README.md +++ b/charts/helm-project-operator/README.md @@ -21,7 +21,7 @@ Generally, the best way to think about the ProjectHelmChart model is by comparin ### Configuring the Helm release created by a ProjectHelmChart The `spec.values` of this ProjectHelmChart resources will correspond to the `values.yaml` override to be supplied to the underlying Helm chart deployed by the operator on the user's behalf; to see the underlying chart's `values.yaml` spec, either: -- View to the chart's definition located at [`rancher/helm-project-operator` under `charts/example-chart`](https://github.com/rancher/helm-project-operator/blob/main/charts/example-chart) (where the chart version will be tied to the version of this operator) +- View to the chart's definition located at [`rancher/helm-project-operator` under `charts/project-operator-example`](https://github.com/rancher/helm-project-operator/blob/main/charts/project-operator-example) (where the chart version will be tied to the version of this operator) - Look for the ConfigMap named `dummy.cattle.io.v1alpha1` that is automatically created in each Project Registration Namespace, which will contain both the `values.yaml` and `questions.yaml` that was used to configure the chart (which was embedded directly into the `helm-project-operator` binary). ### Namespaces @@ -60,7 +60,7 @@ If the `roleRef` matches, the Helm Project Operator will filter the `subjects` o - `helm.cattle.io/project-helm-chart-role: {{ .Release.Name }}` - `helm.cattle.io/project-helm-chart-role-aggregate-from: ` -By default, the `example-chart` (the underlying chart deployed by Helm Project Operator) does not create any default roles; however, if a Cluster Admin would like to assign additional permissions to certain users, they can either directly assign RoleBindings in the Project Release Namespace to certain users or created Roles with the above two labels on them to allow Project Owners to control assigning those RBAC roles to users in their Project Registration namespaces. +By default, the `project-operator-example` (the underlying chart deployed by Helm Project Operator) does not create any default roles; however, if a Cluster Admin would like to assign additional permissions to certain users, they can either directly assign RoleBindings in the Project Release Namespace to certain users or created Roles with the above two labels on them to allow Project Owners to control assigning those RBAC roles to users in their Project Registration namespaces. ### Advanced Helm Project Operator Configuration diff --git a/charts/example-chart/Chart.yaml b/charts/project-operator-example/Chart.yaml similarity index 74% rename from charts/example-chart/Chart.yaml rename to charts/project-operator-example/Chart.yaml index 82ffe69..cb2d771 100644 --- a/charts/example-chart/Chart.yaml +++ b/charts/project-operator-example/Chart.yaml @@ -1,11 +1,11 @@ apiVersion: v2 -name: example-chart +name: project-operator-example description: Example Helm Project Operator chart version: 0.0.0 appVersion: 0.0.0 annotations: catalog.cattle.io/certified: rancher catalog.cattle.io/hidden: "true" - catalog.cattle.io/release-name: example-chart + catalog.cattle.io/release-name: project-operator-example catalog.cattle.io/os: linux,windows catalog.cattle.io/permits-os: linux,windows diff --git a/charts/example-chart/README.md b/charts/project-operator-example/README.md similarity index 84% rename from charts/example-chart/README.md rename to charts/project-operator-example/README.md index c25c95c..6f12dc0 100644 --- a/charts/example-chart/README.md +++ b/charts/project-operator-example/README.md @@ -1,4 +1,4 @@ -# example-chart +# project-operator-example This chart is a dummy chart that is deployed on behalf of the default Helm Project Operator. diff --git a/charts/example-chart/questions.yaml b/charts/project-operator-example/questions.yaml similarity index 100% rename from charts/example-chart/questions.yaml rename to charts/project-operator-example/questions.yaml diff --git a/charts/example-chart/templates/configmaps.yaml b/charts/project-operator-example/templates/configmaps.yaml similarity index 100% rename from charts/example-chart/templates/configmaps.yaml rename to charts/project-operator-example/templates/configmaps.yaml diff --git a/charts/example-chart/templates/dashboard-roles.yaml b/charts/project-operator-example/templates/dashboard-roles.yaml similarity index 100% rename from charts/example-chart/templates/dashboard-roles.yaml rename to charts/project-operator-example/templates/dashboard-roles.yaml diff --git a/charts/example-chart/templates/dashboard-values-configmap.yaml b/charts/project-operator-example/templates/dashboard-values-configmap.yaml similarity index 100% rename from charts/example-chart/templates/dashboard-values-configmap.yaml rename to charts/project-operator-example/templates/dashboard-values-configmap.yaml diff --git a/charts/example-chart/values.yaml b/charts/project-operator-example/values.yaml similarity index 100% rename from charts/example-chart/values.yaml rename to charts/project-operator-example/values.yaml diff --git a/docs/design.md b/docs/design.md index f0b0529..85cff75 100644 --- a/docs/design.md +++ b/docs/design.md @@ -21,7 +21,7 @@ Generally, the best way to think about the ProjectHelmChart model is by comparin ### Configuring the Helm release created by a ProjectHelmChart The `spec.values` of this ProjectHelmChart resources will correspond to the `values.yaml` override to be supplied to the underlying Helm chart deployed by the operator on the user's behalf; to see the underlying chart's `values.yaml` spec, either: -- View to the chart's definition located at [`rancher/helm-project-operator` under `charts/example-chart`](https://github.com/rancher/helm-project-operator/blob/main/charts/example-chart) (where the chart version will be tied to the version of this operator) +- View to the chart's definition located at [`rancher/helm-project-operator` under `charts/project-operator-example`](https://github.com/rancher/helm-project-operator/blob/main/charts/project-operator-example) (where the chart version will be tied to the version of this operator) - Look for the ConfigMap named `dummy.cattle.io.v1alpha1` that is automatically created in each Project Registration Namespace, which will contain both the `values.yaml` and `questions.yaml` that was used to configure the chart (which was embedded directly into the `helm-project-operator` binary). ### Namespaces @@ -60,7 +60,7 @@ If the `roleRef` matches, the Helm Project Operator will filter the `subjects` o - `helm.cattle.io/project-helm-chart-role: {{ .Release.Name }}` - `helm.cattle.io/project-helm-chart-role-aggregate-from: ` -By default, the `example-chart` (the underlying chart deployed by Helm Project Operator) does not create any default roles; however, if a Cluster Admin would like to assign additional permissions to certain users, they can either directly assign RoleBindings in the Project Release Namespace to certain users or created Roles with the above two labels on them to allow Project Owners to control assigning those RBAC roles to users in their Project Registration namespaces. +By default, the `project-operator-example` (the underlying chart deployed by Helm Project Operator) does not create any default roles; however, if a Cluster Admin would like to assign additional permissions to certain users, they can either directly assign RoleBindings in the Project Release Namespace to certain users or created Roles with the above two labels on them to allow Project Owners to control assigning those RBAC roles to users in their Project Registration namespaces. ### Advanced Helm Project Operator Configuration diff --git a/docs/developing.md b/docs/developing.md index 9ca4419..d7d07c9 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -4,7 +4,7 @@ ```bash ## This directory contains a Helm chart that can be used to deploy Helm Project Operator in a Kubernetes cluster in the cattle-helm-system namespace, -## which deploys example-chart (located under charts/example-chart) on seeing a ProjectHelmChart with spec.helmApiVersion: dummy.cattle.io/v1alpha1. +## which deploys project-operator-example (located under charts/project-operator-example) on seeing a ProjectHelmChart with spec.helmApiVersion: dummy.cattle.io/v1alpha1. charts/ ## The main chart that deploys Helm Project Operator in the cluster. helm-project-operator/ @@ -13,12 +13,12 @@ charts/ ## a Project Registration Namespace with spec.helmApiVersion set to dummy.cattle.io/v1alpha1) ## ## This chart is not expected to ever be deployed standalone; it is embedded into the Helm Project Operator binary itself. - example-chart/ + project-operator-example/ ## This directory will contain additional docs to assist users in getting started with using Helm Project Operator. docs/ -## This directory contains example ProjectHelmCharts that can be deployed that work on the default example-chart packaged with the Helm Project Operator +## This directory contains example ProjectHelmCharts that can be deployed that work on the default project-operator-example packaged with the Helm Project Operator examples/ ## This directory contains the image that is used to build rancher/helm-project-operator, which is hosted on hub.docker.com. @@ -124,5 +124,5 @@ Once the image is successfully packaged, simply run `docker push ${REPO}/helm-pr 1. Ensure that your `KUBECONFIG` environment variable is pointing to your cluster (e.g. `export KUBECONFIG=; kubectl get nodes` should show the nodes of your cluster) and pull in this repository locally 2. Go to the root of your local copy of this repository and deploy the Helm Project Operator chart as a Helm 3 chart onto your cluster after overriding the image and tag values with your Docker repository and tag: run `helm upgrade --install --set image.repository="${REPO}/helm-project-operator" --set image.tag="${TAG}" --set image.pullPolicy=Always helm-project-operator -n cattle-helm-system charts/helm-project-operator` -> Note: Why do we set the Image Pull Policy to `Always`? If you update the Docker image on your fork, setting the Image Pull Policy to `Always` ensures that running `kubectl rollout restart -n cattle-helm-system deployment/helm-project-operator` is all you need to do to update your running deployment to the new image, since this would ensure redeploying a deployment triggers a image pull that uses your most up-to-date Docker image. Also, since the underlying Helm chart deployed by the operator (e.g. `example-chart`) is directly embedded into the Helm Project Operator image, you also do not need to update the Deployment object itself to see all the HelmCharts in your cluster automatically be updated to the latest embedded version of the chart. +> Note: Why do we set the Image Pull Policy to `Always`? If you update the Docker image on your fork, setting the Image Pull Policy to `Always` ensures that running `kubectl rollout restart -n cattle-helm-system deployment/helm-project-operator` is all you need to do to update your running deployment to the new image, since this would ensure redeploying a deployment triggers a image pull that uses your most up-to-date Docker image. Also, since the underlying Helm chart deployed by the operator (e.g. `project-operator-example`) is directly embedded into the Helm Project Operator image, you also do not need to update the Deployment object itself to see all the HelmCharts in your cluster automatically be updated to the latest embedded version of the chart. 3. Profit! \ No newline at end of file diff --git a/docs/experimental/e2e.md b/docs/experimental/e2e.md index 6f863a7..32c9756 100644 --- a/docs/experimental/e2e.md +++ b/docs/experimental/e2e.md @@ -2,7 +2,7 @@ ## What does E2E CI do? -The E2E CI described in [.github/scripts/](../../.github/workflows/e2e-ci.yaml) checks out the current Git repository, builds a Docker image using the repository's build scripts, sets up a [k3d](https://k3d.io) cluster, imports the built `helm-project-operator` image into the cluster (which automatically uses the latest `example-chart` chart since it is embedded into the binary as part of the build process), and then uses Helm to install `helm-project-operator` (using the Helm chart contained in the repository). +The E2E CI described in [.github/scripts/](../../.github/workflows/e2e-ci.yaml) checks out the current Git repository, builds a Docker image using the repository's build scripts, sets up a [k3d](https://k3d.io) cluster, imports the built `helm-project-operator` image into the cluster (which automatically uses the latest `project-operator-example` chart since it is embedded into the binary as part of the build process), and then uses Helm to install `helm-project-operator` (using the Helm chart contained in the repository). Once it is installed, it will run checks to ensure that all workloads are up and running in the Helm install and then mimic creating a Project (by creating a namespace with a particular label on it). diff --git a/docs/gettingstarted.md b/docs/gettingstarted.md index 9086854..9e7bb13 100644 --- a/docs/gettingstarted.md +++ b/docs/gettingstarted.md @@ -24,7 +24,7 @@ helm install -n cattle-helm-system helm-project-operator charts/helm-project-ope 4. Find the Job in the Operator / System (`cattle-helm-system`) namespace tied to the HelmChart object to view the Helm operation logs that were performed on behalf of the HelmChart resource created; these logs should show as successful. 5. Check to see if a HelmRelease CR was created on behalf of that ProjectHelmChart in the Operator / System (`cattle-helm-system`) namespace 6. Ensure that the status of the HelmRelease CR shows that it has successfully found the Helm release secret for the Helm chart deployed by the HelmChart CR. -7. Locate the Project Release Namespace (see [design.md](design.md) for more information on how to identify this) and ensure that the ConfigMaps contained within `charts/example-chart` were deployed onto the cluster. +7. Locate the Project Release Namespace (see [design.md](design.md) for more information on how to identify this) and ensure that the ConfigMaps contained within `charts/project-operator-example` were deployed onto the cluster. 8. Try to modify or delete the resources; you should see that they are instantly recreated or fixed back into place. 9. Try supplying overrides to the deployed Helm chart by modifying `spec.values` on the ProjectHelmChart resource the `data` value to any YAML you want; on supplying new YAML to the ProjectHelmChart, you should see the Helm Operator Job (deployed on behalf of the HelmChart resource) be modified and you should observe that the HelmRelease CR emits an event (observable by running `kubectl describe -n cattle-helm-system ` on the HelmRelease object) that indicates that it is Transitioning and then Locked; the release number will also be updated. diff --git a/dummy.go b/dummy.go index 1eaf01e..0582854 100644 --- a/dummy.go +++ b/dummy.go @@ -17,18 +17,18 @@ import ( ) const ( - // DummyHelmAPIVersion is the spec.helmApiVersion corresponding to the dummy example-chart + // DummyHelmAPIVersion is the spec.helmApiVersion corresponding to the dummy project-operator-example chart DummyHelmAPIVersion = "dummy.cattle.io/v1alpha1" - // DummyReleaseName is the release name corresponding to the operator that deploys the dummy example-chart + // DummyReleaseName is the release name corresponding to the operator that deploys the dummy project-operator-example chart DummyReleaseName = "dummy" ) var ( - // DummySystemNamespaces is the system namespaces scoped for the dummy example-chart. + // DummySystemNamespaces is the system namespaces scoped for the dummy project-operator-example chart. DummySystemNamespaces = []string{"kube-system"} - //go:embed bin/example-chart/example-chart.tgz.base64 + //go:embed bin/project-operator-example/project-operator-example.tgz.base64 base64TgzChart string debugConfig command.DebugConfig diff --git a/examples/ci-example.yaml b/examples/ci-example.yaml index 8491059..887392b 100644 --- a/examples/ci-example.yaml +++ b/examples/ci-example.yaml @@ -6,7 +6,7 @@ apiVersion: helm.cattle.io/v1alpha1 kind: ProjectHelmChart metadata: - name: project-example-chart + name: project-operator-example-chart namespace: cattle-project-p-example spec: helmApiVersion: dummy.cattle.io/v1alpha1 diff --git a/scripts/build-chart b/scripts/build-chart index e7c41e7..08cd6db 100755 --- a/scripts/build-chart +++ b/scripts/build-chart @@ -5,7 +5,7 @@ source $(dirname $0)/version cd $(dirname $0)/.. -CHART=example-chart +CHART=project-operator-example VERSION=0.0.0 helm package charts/${CHART} --destination bin/${CHART} From 8b98ad950d745c55a228120946e27439d4d7f778 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 12:27:09 -0400 Subject: [PATCH 02/48] Add helm-locker docs --- docs/helm-locker/developing.md | 94 ++++++++++++++++++++++++++++++ docs/helm-locker/gettingstarted.md | 41 +++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 docs/helm-locker/developing.md create mode 100644 docs/helm-locker/gettingstarted.md diff --git a/docs/helm-locker/developing.md b/docs/helm-locker/developing.md new file mode 100644 index 0000000..8f2bc0b --- /dev/null +++ b/docs/helm-locker/developing.md @@ -0,0 +1,94 @@ +# Developing Helm Locker + +## Repository Structure + +```bash +## This directory contains Helm charts that can be used to deploy Helm Locker in a Kubernetes cluster in the cattle-helm-system namespace +charts/ + + ## The main chart that deploys Helm Locker in the cluster. + helm-locker/ + + ## A dummy chart that can be deployed as a Helm release in the cluster under the release name 'helm-locker-example' and the namespace 'cattle-helm-system' + ## + ## By default, it deploys with a HelmRelease CR that targets itself. + ## + ## Depends on 'helm-locker' being deployed onto the cluster first. + helm-locker-example/ + +## This directory will contain additional docs to assist users in getting started with using Helm Locker +docs/ + +## This directory contains the image that is used to build rancher/helm-locker, which is hosted on hub.docker.com +package/ + Dockerfile + +## The main source directory for the code. See below for more details. +pkg/ + +## The Dockerfile used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) +Dockerfile.dapper + +## The file that contains the underlying actions that 'go generate' needs to execute on a call to it. Includes the logic for generating controllers and updating crds.yaml under the crds/ directory +generate.go + +## The main entrypoint into HelmLocker +main.go +``` + +## Making changes to the codebase (`pkg`) + +Most of the code for Helm Locker is contained in the `pkg` directory, which has the following structure: + +```bash +## This directory contains the definition of a HelmRelease CR under release.go; if you need to add new fields to HelmRelease CRs, this is where you would make the change +apis/ + +## These directories manage all the logic around 'go generate', including the creation of the 'generated/' directory that contains all the underlying controllers that are auto-generated based on the API definition of the HelmRelease CR defined under 'apis/' +codegen/ +crd/ +version/ +generated/ + +## These directories are the core controller directories that manage how the operator watches HelmReleases and executes operations on the underlying in-memory ObjectSet LockableRegister (Lock, Unlock, Set, Delete) +controllers/ + ## This directory is where logic is defined for watching Helm Release Secrets targeted by HelmReleases and automatically keeping resources locked or unlocked + release/ + ## This is where the underlying context used by all controllers of this operator are registered, all using the same underlying SharedControllerFactory + controller.go +## A utility package to help wrap getting Helm releases via Helm library calls +releases/ + +## These directories implement an object that satisfies the LockableRegister interface; it is used as an underlying set of libraries that Helm Locker calls upon to achieve locking or unlocking HelmReleases (tracked as ObjectSets, or a []runtime.Object) and dynamically starting controllers based on GVKs observed in tracked object sets +gvk/ +informerfactory/ +objectset/ +``` + +## Once you have made a change + +If you modified `pkg/apis` or `generate.go`, make sure you run `go generate`. + +Also, make sure you run `go mod tidy`. + +## Creating a Docker image based off of your changes + +To test your changes and create a Docker image to a specific Docker repository with a given tag, you should run `REPO= TAG= make` (e.g. `REPO=arvindiyengar TAG=dev make`), which will run the `./scripts/ci` script that builds, tests, validates, and packages your changes into a local Docker image (if you run `docker images`, it should show up as an image in the format `${REPO}/helm-locker:${TAG}`). + +If you don't want to run all the steps in CI every time you make a change, you could also run the following one-liner to build and package the image: + +```bash +REPO= +TAG= + +GOOS=linux CGO_ENABLED=0 go build -ldflags "-extldflags -static -s" -o bin/helm-locker && REPO=${REPO} TAG=${TAG} make package +``` + +Once the image is successfully packaged, simply run `docker push ${REPO}/helm-locker:${TAG}` to push your image to your Docker repository. + +## Testing a custom Docker image build + +1. Ensure that your `KUBECONFIG` environment variable is pointing to your cluster (e.g. `export KUBECONFIG=; kubectl get nodes` should show the nodes of your cluster) and pull in this repository locally +2. Go to the root of your local copy of this repository and deploy the Helm Locker chart as a Helm 3 chart onto your cluster after overriding the image and tag values with your Docker repository and tag: run `helm upgrade --install --set image.repository="${REPO}/helm-locker" --set image.tag="${TAG}" --set image.pullPolicy=Always helm-locker -n cattle-helm-system charts/helm-locker` +> Note: Why do we set the Image Pull Policy to `Always`? If you update the Docker image on your fork, setting the Image Pull Policy to `Always` ensures that running `kubectl rollout restart -n cattle-helm-system deployment/helm-locker` is all you need to do to update your running deployment to the new image, since this would ensure redeploying a deployment triggers a image pull that uses your most up-to-date Docker image. +3. Profit! \ No newline at end of file diff --git a/docs/helm-locker/gettingstarted.md b/docs/helm-locker/gettingstarted.md new file mode 100644 index 0000000..8414f35 --- /dev/null +++ b/docs/helm-locker/gettingstarted.md @@ -0,0 +1,41 @@ +# Getting Started + +## Simple Installation + +### In Rancher (via Apps & Marketplace) + +1. Navigate to `Apps & Marketplace -> Repositories` in your target downstream cluster and create a Repository that points to a `Git repository containing Helm chart or cluster template definitions` where the `Git Repo URL` is `https://github.com/rancher/helm-locker` and the `Git Branch` is `main` +2. Navigate to `Apps & Marketplace -> Charts`; you should see two charts under the new Repository you created: `Helm Locker` and `Helm Locker Example Chart`. +3. Install `Helm Locker` first +4. Install `Helm Locker Example Chart` + +### In a normal Kubernetes cluster (via running Helm 3 locally) + +1. Install `helm-locker` onto your cluster via Helm to install the Helm Locker Operator + +``` +helm install -n cattle-helm-system helm-locker charts/helm-locker +``` + +2. Install `helm-locker-example` to check out a simple Helm chart containing a ConfigMap and a HelmRelease CR that targets the release itself and keeps it locked into place + +```bash +helm install -n cattle-helm-system helm-locker-example charts/helm-locker-example +``` + +### Checking if the HelmRelease works + +1. Ensure that the logs of `helm-locker` in the `cattle-helm-system` namespace show that the controller was able to acquire a lock and has started in that namespace +2. Try to delete or modify the ConfigMaps deployed by the `helm-locker-example` chart (`cattle-helm-system/my-config-map` and `cattle-helm-system/my-config-map-2`); any changes should automatically be overwritten and a log will show up in the Helm Locker logs that showed which ConfigMap it detected a change in +3. Run `kubectl describe helmreleases -n cattle-helm-system helm-locker-example`; you should be able to see events that have been triggered on changes. +4. Upgrade the `helm-locker-example` values to change the contents of the ConfigMap; you should see the modifications show up in the ConfigMap deployed in the cluster as well as events that have been triggered on Helm Locker noticing that change (i.e. you should see a `Transitioning` event that is emitted). + +## Uninstalling Helm Locker + +After deleting the Helm Charts, you may want to manually uninstall the CRDs from the cluster to clean them up: + +```bash +kubectl delete crds helmreleases.helm.cattle.io +``` + +> Note: Why aren't we packaging Helm Locker CRDs in a CRD chart? Since Helm Locker CRDs can be used for other projects (e.g. [rancher/helm-project-operator](https://github.com/rancher/helm-project-operator), [rancher/prometheus-federator](https://github.com/rancher/prometheus-federator), etc.) and Helm Locker itself can be deployed multiple times to the same cluster, the ownership model of having a single CRD chart that manages installing, upgrading, and uninstalling Helm Locker CRDs isn't a good model for managing CRDs. Instead, it's left as an explicit action that the user should take in order to delete the Helm Locker CRDs from the cluster with caution that it could affect other deployments reliant on those CRDs. \ No newline at end of file From b9a50bf704d614c3ad29cf3f447a4889cd912931 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 12:41:58 -0400 Subject: [PATCH 03/48] Reorg more files for project merge --- CONTRIBUTING.md | 2 +- README.md | 6 +++--- docs/{ => helm-project-operator}/design.md | 0 docs/{ => helm-project-operator}/developing.md | 0 docs/{ => helm-project-operator}/experimental/e2e.md | 6 +++--- docs/{ => helm-project-operator}/gettingstarted.md | 0 examples/{ => helm-project-operator}/ci-example.yaml | 0 examples/{ => helm-project-operator}/example.yaml | 0 .../{ => helm-project-operator}/project-label-example.yaml | 0 9 files changed, 7 insertions(+), 7 deletions(-) rename docs/{ => helm-project-operator}/design.md (100%) rename docs/{ => helm-project-operator}/developing.md (100%) rename docs/{ => helm-project-operator}/experimental/e2e.md (78%) rename docs/{ => helm-project-operator}/gettingstarted.md (100%) rename examples/{ => helm-project-operator}/ci-example.yaml (100%) rename examples/{ => helm-project-operator}/example.yaml (100%) rename examples/{ => helm-project-operator}/project-label-example.yaml (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a083678..c088c4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,7 @@ Scan through our [existing issues](https://github.com/rancher/helm-project-opera - Using the command line: - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them. -3. Install or update to **Go 1.17**. For more information, see [the development guide](docs/developing.md). +3. Install or update to **Go 1.17**. For more information, see [the development guide](docs/helm-project-operator/developing.md). 4. Create a working branch and start with your changes! diff --git a/README.md b/README.md index ecfcb26..60f2cfe 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It is intended to be implemented by a Project Operator (e.g. [`rancher/prometheu ## Getting Started -For more information, see the [Getting Started guide](docs/gettingstarted.md). +For more information, see the [Getting Started guide](docs/helm-project-operator/gettingstarted.md). ## Developing @@ -17,7 +17,7 @@ For more information, see the [Getting Started guide](docs/gettingstarted.md). Helm Project Operator is built and released off the contents of the `main` branch. To make a contribution, open up a PR to the `main` branch. -For more information, see the [Developing guide](docs/developing.md). +For more information, see the [Developing guide](docs/helm-project-operator/developing.md). ## Design @@ -25,7 +25,7 @@ Helm Project Operator is built on top of [k3s-io/helm-controller](https://github For an example of how Helm Project Operator can be implemented, please see [`rancher/prometheus-federator`](https://github.com/rancher/prometheus-federator). -For more information in general, please see [docs/design.md](docs/design.md). +For more information in general, please see [docs/design.md](docs/helm-project-operator/design.md). ## Building diff --git a/docs/design.md b/docs/helm-project-operator/design.md similarity index 100% rename from docs/design.md rename to docs/helm-project-operator/design.md diff --git a/docs/developing.md b/docs/helm-project-operator/developing.md similarity index 100% rename from docs/developing.md rename to docs/helm-project-operator/developing.md diff --git a/docs/experimental/e2e.md b/docs/helm-project-operator/experimental/e2e.md similarity index 78% rename from docs/experimental/e2e.md rename to docs/helm-project-operator/experimental/e2e.md index 32c9756..21cebec 100644 --- a/docs/experimental/e2e.md +++ b/docs/helm-project-operator/experimental/e2e.md @@ -2,7 +2,7 @@ ## What does E2E CI do? -The E2E CI described in [.github/scripts/](../../.github/workflows/e2e-ci.yaml) checks out the current Git repository, builds a Docker image using the repository's build scripts, sets up a [k3d](https://k3d.io) cluster, imports the built `helm-project-operator` image into the cluster (which automatically uses the latest `project-operator-example` chart since it is embedded into the binary as part of the build process), and then uses Helm to install `helm-project-operator` (using the Helm chart contained in the repository). +The E2E CI described in [.github/scripts/](../../../.github/workflows/e2e-ci.yaml) checks out the current Git repository, builds a Docker image using the repository's build scripts, sets up a [k3d](https://k3d.io) cluster, imports the built `helm-project-operator` image into the cluster (which automatically uses the latest `project-operator-example` chart since it is embedded into the binary as part of the build process), and then uses Helm to install `helm-project-operator` (using the Helm chart contained in the repository). Once it is installed, it will run checks to ensure that all workloads are up and running in the Helm install and then mimic creating a Project (by creating a namespace with a particular label on it). @@ -14,7 +14,7 @@ Finally, it deletes the ProjectHelmChart, asserts the helm uninstall Job on the To run the end-to-end GitHub Workflow CI locally to test whether your changes work, it's recommended to install [`nektos/act`](https://github.com/nektos/act). -An slim image has been defined in [`.github/workflows/e2e/package/Dockerfile`](../../.github/workflows/e2e/package/Dockerfile) that has the necessary dependencies to be used as a Runner for act for this GitHub Workflow. To build the image, run the following commmand (make sure you re-run it if you make any changes to add dependencies): +An slim image has been defined in [`.github/workflows/e2e/package/Dockerfile`](../../../.github/workflows/e2e/package/Dockerfile) that has the necessary dependencies to be used as a Runner for act for this GitHub Workflow. To build the image, run the following commmand (make sure you re-run it if you make any changes to add dependencies): ```bash docker build -f ./.github/workflows/e2e/package/Dockerfile -t rancher/helm-project-operator-e2e:latest . @@ -30,7 +30,7 @@ act pull_request -j e2e-helm-project-operator -P ubuntu-latest=rancher/helm-proj ## Running E2E Tests on an already provisioned cluster -To verify that the functionality of Helm Project Operator on a live cluster that you have already configured your `KUBECONFIG` environment variable to point to, you can use the utility script found in [script/e2e-ci](../../scripts/e2e-ci) to run the relevant CI commands to install Monitoring, install Helm Project Operator using your forked image, and run the remaining CI steps. +To verify that the functionality of Helm Project Operator on a live cluster that you have already configured your `KUBECONFIG` environment variable to point to, you can use the utility script found in [script/e2e-ci](../../../scripts/e2e-ci) to run the relevant CI commands to install Monitoring, install Helm Project Operator using your forked image, and run the remaining CI steps. > **Note:** For now, this script only works on k3s, RKE1, and RKE2 clusters but it can be easily extended to work on different cluster types by supplying the right values in `install-helm-project-operator.sh` to enable and verify the correct cluster-type specific testing. Contributions are welcome! diff --git a/docs/gettingstarted.md b/docs/helm-project-operator/gettingstarted.md similarity index 100% rename from docs/gettingstarted.md rename to docs/helm-project-operator/gettingstarted.md diff --git a/examples/ci-example.yaml b/examples/helm-project-operator/ci-example.yaml similarity index 100% rename from examples/ci-example.yaml rename to examples/helm-project-operator/ci-example.yaml diff --git a/examples/example.yaml b/examples/helm-project-operator/example.yaml similarity index 100% rename from examples/example.yaml rename to examples/helm-project-operator/example.yaml diff --git a/examples/project-label-example.yaml b/examples/helm-project-operator/project-label-example.yaml similarity index 100% rename from examples/project-label-example.yaml rename to examples/helm-project-operator/project-label-example.yaml From bc925c2232efd3d8c874ef2be1a4674f65c23bde Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 12:42:19 -0400 Subject: [PATCH 04/48] Add helm-locker charts --- charts/helm-locker-example/Chart.yaml | 18 +++ .../templates/configmap.yaml | 17 +++ .../templates/example.yaml | 10 ++ .../templates/validate-install-crd.yaml | 14 ++ charts/helm-locker-example/values.yaml | 2 + charts/helm-locker/Chart.yaml | 18 +++ charts/helm-locker/README.md | 60 +++++++++ charts/helm-locker/templates/NOTES.txt | 3 + charts/helm-locker/templates/_helpers.tpl | 66 ++++++++++ charts/helm-locker/templates/clusterrole.yaml | 58 +++++++++ charts/helm-locker/templates/deployment.yaml | 55 ++++++++ charts/helm-locker/templates/hardened.yaml | 121 ++++++++++++++++++ charts/helm-locker/templates/psp.yaml | 68 ++++++++++ charts/helm-locker/templates/rbac.yaml | 27 ++++ charts/helm-locker/values.yaml | 86 +++++++++++++ 15 files changed, 623 insertions(+) create mode 100644 charts/helm-locker-example/Chart.yaml create mode 100644 charts/helm-locker-example/templates/configmap.yaml create mode 100644 charts/helm-locker-example/templates/example.yaml create mode 100644 charts/helm-locker-example/templates/validate-install-crd.yaml create mode 100644 charts/helm-locker-example/values.yaml create mode 100644 charts/helm-locker/Chart.yaml create mode 100644 charts/helm-locker/README.md create mode 100644 charts/helm-locker/templates/NOTES.txt create mode 100644 charts/helm-locker/templates/_helpers.tpl create mode 100644 charts/helm-locker/templates/clusterrole.yaml create mode 100644 charts/helm-locker/templates/deployment.yaml create mode 100644 charts/helm-locker/templates/hardened.yaml create mode 100644 charts/helm-locker/templates/psp.yaml create mode 100644 charts/helm-locker/templates/rbac.yaml create mode 100644 charts/helm-locker/values.yaml diff --git a/charts/helm-locker-example/Chart.yaml b/charts/helm-locker-example/Chart.yaml new file mode 100644 index 0000000..442ad34 --- /dev/null +++ b/charts/helm-locker-example/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: helm-locker-example +description: Helm Locker Example Chart +version: 0.0.1 +appVersion: 0.0.1 +annotations: + catalog.cattle.io/certified: rancher + catalog.cattle.io/display-name: Helm Locker Example Chart + catalog.cattle.io/kube-version: '>=1.16.0-0' + catalog.cattle.io/namespace: cattle-helm-system + catalog.cattle.io/permits-os: linux,windows + catalog.cattle.io/rancher-version: '>= 2.6.0-0 <=2.6.99-0' + catalog.cattle.io/release-name: helm-locker-example + catalog.cattle.io/os: linux,windows +maintainers: +- email: arvind.iyengar@suse.com + name: aiyengar2 + diff --git a/charts/helm-locker-example/templates/configmap.yaml b/charts/helm-locker-example/templates/configmap.yaml new file mode 100644 index 0000000..dfb1826 --- /dev/null +++ b/charts/helm-locker-example/templates/configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config-map + namespace: {{ .Release.Namespace }} +data: + config: |- +{{ .Values.data | toYaml | indent 4 }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config-map-2 + namespace: {{ .Release.Namespace }} +data: + config: |- +{{ .Values.data | toYaml | indent 4 }} \ No newline at end of file diff --git a/charts/helm-locker-example/templates/example.yaml b/charts/helm-locker-example/templates/example.yaml new file mode 100644 index 0000000..3e7e475 --- /dev/null +++ b/charts/helm-locker-example/templates/example.yaml @@ -0,0 +1,10 @@ +apiVersion: helm.cattle.io/v1alpha1 +kind: HelmRelease +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +spec: + release: + # Chart is configured to lock itself + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/charts/helm-locker-example/templates/validate-install-crd.yaml b/charts/helm-locker-example/templates/validate-install-crd.yaml new file mode 100644 index 0000000..22d86cc --- /dev/null +++ b/charts/helm-locker-example/templates/validate-install-crd.yaml @@ -0,0 +1,14 @@ +#{{- if gt (len (lookup "rbac.authorization.k8s.io/v1" "ClusterRole" "" "")) 0 -}} +# {{- $found := dict -}} +# {{- set $found "helm.cattle.io/v1alpha1/HelmRelease" false -}} +# {{- range .Capabilities.APIVersions -}} +# {{- if hasKey $found (toString .) -}} +# {{- set $found (toString .) true -}} +# {{- end -}} +# {{- end -}} +# {{- range $_, $exists := $found -}} +# {{- if (eq $exists false) -}} +# {{- required "Required CRDs are missing. Please install the corresponding CRD chart before installing this chart." "" -}} +# {{- end -}} +# {{- end -}} +#{{- end -}} \ No newline at end of file diff --git a/charts/helm-locker-example/values.yaml b/charts/helm-locker-example/values.yaml new file mode 100644 index 0000000..ee9d3ba --- /dev/null +++ b/charts/helm-locker-example/values.yaml @@ -0,0 +1,2 @@ +data: + hello: rancher \ No newline at end of file diff --git a/charts/helm-locker/Chart.yaml b/charts/helm-locker/Chart.yaml new file mode 100644 index 0000000..512980a --- /dev/null +++ b/charts/helm-locker/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: helm-locker +description: Helm Locker +version: 0.0.2 +appVersion: 0.0.2 +annotations: + catalog.cattle.io/certified: rancher + catalog.cattle.io/display-name: Helm Locker + catalog.cattle.io/kube-version: '>=1.16.0-0' + catalog.cattle.io/namespace: cattle-helm-system + catalog.cattle.io/permits-os: linux,windows + catalog.cattle.io/provides-gvr: helm.cattle.io.helmrelease/v1alpha1 + catalog.cattle.io/rancher-version: '>= 2.6.0-0 <=2.6.99-0' + catalog.cattle.io/release-name: helm-locker + catalog.cattle.io/os: linux,windows +maintainers: +- email: arvind.iyengar@suse.com + name: aiyengar2 \ No newline at end of file diff --git a/charts/helm-locker/README.md b/charts/helm-locker/README.md new file mode 100644 index 0000000..f81c61f --- /dev/null +++ b/charts/helm-locker/README.md @@ -0,0 +1,60 @@ +helm-locker +======== + +Helm Locker is a Kubernetes operator that prevents resource drift on (i.e. "locks") Kubernetes objects that are tracked by Helm 3 releases. + +Once installed, a user can create a `HelmRelease` CR in the `Helm Release Registration Namespace` (default: `cattle-helm-system`) by providing: +1. The name of a Helm 3 release +2. The namespace that contains the Helm Release Secret (supplied as `--namespace` on the `helm install` command that created the release) + +Once created, the Helm Locker controllers will watch all resources tracked by the Helm Release Secret and automatically revert any changes to the persisted resources that were not made through Helm (e.g. changes that were directly applied via `kubectl` or other controllers). + +## Who needs Helm Locker? + +Anyone who would like to declaratively manage resources deployed by existing Helm chart releases. + +## How is this different from projects like `fluxcd/helm-controller`? + +Projects like [`fluxcd/helm-controller`](https://github.com/fluxcd/helm-controller) allow users to declaratively manage **Helm charts from deployment to release**, whereas this project only allows you lock an **existing** Helm chart release; as a result, the scope of this project is much more narrow than what is offered by `fluxcd/helm-controller` and should be integrable with any solution that produces Helm releases. + +If you are looking for a larger, more opinionated solution that also has features around **how** Helm charts should be deployed onto a cluster (e.g. from a `GitRepository` or `Bucket` or `HelmRepository`), this is not the project for you. + +However, if you are looking for something light-weight that simply guarentees that **Helm is the only way to modify resources tracked by Helm releases**, this is a good solution to use. + +## How does Helm Locker know whether a release was changed by Helm or by another source? + +In order to prevent multiple Helm instances from performing the same upgrade at the same time, Helm 3 will always first update the `info.status` field on a Helm Release Secret from `deployed` to another state (e.g. `pending-upgrade`, `pending-install`, `uninstalling`, etc.) before performing the operation; once the operation is complete, the Helm Release Secret is expected to be reverted back to `deployed`. + +Therefore, if Helm Locker observes a Helm Release Secret tied to a `HelmRelease` has been updated, it will check to see what the current status of the release is; if the release is anything but `deployed`, Helm Locker will not perform any operations on the resources tracked by this release, which will allow upgrades to occur as expected. + +However, once a release is `deployed`, if what is tracked in the Helm secret is different than what is currently installed onto the cluster, Helm Locker will revert all resources back to what was tracked by the Helm release (in case a change was made to the resource tracked by the Helm Release while the release was being modified). + +## Debugging + +### How do I manually inspect the content of the Helm Release Secret to debug a possible Helm Locker issue? + +Identify the release namespace (`RELEASE_NAMESPACE`), release name (`RELEASE_NAME`), and release version (`RELEASE_VERSION`) that identifies the Secret used by Helm to store the release data. Then, with access to your Kubernetes cluster via `kubectl`, run the following command (e.g. run base64 decode, base64 decode, gzip decompress the .data.release of the Secret): + +```bash +RELEASE_NAMESPACE=default +RELEASE_NAME=test +RELEASE_VERSION=v1 + +# Magic one-liner! jq call is optional... +kubectl get secrets -n ${RELEASE_NAMESPACE} sh.helm.release.v1.${RELEASE_NAME}.${RELEASE_VERSION} -o=jsonpath='{ .data.release }' | base64 -d | base64 -d | gunzip -c | jq -r '.' +``` + +## License +Copyright (c) 2020 [Rancher Labs, Inc.](http://rancher.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/charts/helm-locker/templates/NOTES.txt b/charts/helm-locker/templates/NOTES.txt new file mode 100644 index 0000000..cf19a8c --- /dev/null +++ b/charts/helm-locker/templates/NOTES.txt @@ -0,0 +1,3 @@ +{{ $.Chart.Name }} has been installed. Check its status by running: + kubectl --namespace {{ template "helm-locker.namespace" . }} get pods -l "release={{ $.Release.Name }}" + diff --git a/charts/helm-locker/templates/_helpers.tpl b/charts/helm-locker/templates/_helpers.tpl new file mode 100644 index 0000000..02f6676 --- /dev/null +++ b/charts/helm-locker/templates/_helpers.tpl @@ -0,0 +1,66 @@ +# Rancher +{{- define "system_default_registry" -}} +{{- if .Values.global.cattle.systemDefaultRegistry -}} +{{- printf "%s/" .Values.global.cattle.systemDefaultRegistry -}} +{{- end -}} +{{- end -}} + +# Windows Support + +{{/* +Windows cluster will add default taint for linux nodes, +add below linux tolerations to workloads could be scheduled to those linux nodes +*/}} + +{{- define "linux-node-tolerations" -}} +- key: "cattle.io/os" + value: "linux" + effect: "NoSchedule" + operator: "Equal" +{{- end -}} + +{{- define "linux-node-selector" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +beta.kubernetes.io/os: linux +{{- else -}} +kubernetes.io/os: linux +{{- end -}} +{{- end -}} + +# Helm Locker + +{{/* vim: set filetype=mustache: */}} +{{/* Expand the name of the chart. This is suffixed with -alertmanager, which means subtract 13 from longest 63 available */}} +{{- define "helm-locker.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 50 | trimSuffix "-" -}} +{{- end }} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts +*/}} +{{- define "helm-locker.namespace" -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} +{{- end -}} + +{{/* Create chart name and version as used by the chart label. */}} +{{- define "helm-locker.chartref" -}} +{{- replace "+" "_" .Chart.Version | printf "%s-%s" .Chart.Name -}} +{{- end }} + +{{/* Generate basic labels */}} +{{- define "helm-locker.labels" }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: "{{ replace "+" "_" .Chart.Version }}" +app.kubernetes.io/part-of: {{ template "helm-locker.name" . }} +chart: {{ template "helm-locker.chartref" . }} +release: {{ $.Release.Name | quote }} +heritage: {{ $.Release.Service | quote }} +{{- if .Values.commonLabels}} +{{ toYaml .Values.commonLabels }} +{{- end }} +{{- end }} diff --git a/charts/helm-locker/templates/clusterrole.yaml b/charts/helm-locker/templates/clusterrole.yaml new file mode 100644 index 0000000..b174e4a --- /dev/null +++ b/charts/helm-locker/templates/clusterrole.yaml @@ -0,0 +1,58 @@ +{{- if and .Values.global.rbac.create .Values.global.rbac.userRoles.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "helm-locker.name" . }}-admin + labels: {{ include "helm-locker.labels" . | nindent 4 }} + {{- if .Values.global.rbac.userRoles.aggregateToDefaultRoles }} + rbac.authorization.k8s.io/aggregate-to-admin: "true" + {{- end }} +rules: +- apiGroups: + - helm.cattle.io + resources: + - helmreleases + - helmreleases/finalizers + - helmreleases/status + verbs: + - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "helm-locker.name" . }}-edit + labels: {{ include "helm-locker.labels" . | nindent 4 }} + {{- if .Values.global.rbac.userRoles.aggregateToDefaultRoles }} + rbac.authorization.k8s.io/aggregate-to-edit: "true" + {{- end }} +rules: +- apiGroups: + - helm.cattle.io + resources: + - helmreleases + - helmreleases/status + verbs: + # Since Helm Locker executes with cluster-admin privileges, only an admin gets mutating permissions for them + - 'get' + - 'list' + - 'watch' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "helm-locker.name" . }}-view + labels: {{ include "helm-locker.labels" . | nindent 4 }} + {{- if .Values.global.rbac.userRoles.aggregateToDefaultRoles }} + rbac.authorization.k8s.io/aggregate-to-view: "true" + {{- end }} +rules: +- apiGroups: + - helm.cattle.io + resources: + - helmreleases + - helmreleases/status + verbs: + - 'get' + - 'list' + - 'watch' +{{- end }} diff --git a/charts/helm-locker/templates/deployment.yaml b/charts/helm-locker/templates/deployment.yaml new file mode 100644 index 0000000..a21edb1 --- /dev/null +++ b/charts/helm-locker/templates/deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "helm-locker.name" . }} + namespace: {{ template "helm-locker.namespace" . }} + labels: {{ include "helm-locker.labels" . | nindent 4 }} + app: {{ template "helm-locker.name" . }} +spec: + {{- if .Values.replicas }} + replicas: {{ .Values.replicas }} + {{- end }} + selector: + matchLabels: + app: {{ template "helm-locker.name" . }} + release: {{ $.Release.Name | quote }} + template: + metadata: + labels: {{ include "helm-locker.labels" . | nindent 8 }} + app: {{ template "helm-locker.name" . }} + spec: + containers: + - name: {{ template "helm-locker.name" . }} + image: "{{ template "system_default_registry" . }}{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + args: + - {{ template "helm-locker.name" . }} + - --namespace={{ template "helm-locker.namespace" . }} + - --controller-name={{ template "helm-locker.name" . }} +{{- if .Values.debug }} + - --debug + - --debug-level={{ .Values.debugLevel }} +{{- end }} +{{- if .Values.additionalArgs }} +{{- toYaml .Values.additionalArgs | nindent 10 }} +{{- end }} + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName +{{- if .Values.resources }} + resources: {{ toYaml .Values.resources | nindent 12 }} +{{- end }} + serviceAccountName: {{ template "helm-locker.name" . }} +{{- if .Values.securityContext }} + securityContext: {{ toYaml .Values.securityContext | indent 8 }} +{{- end }} + nodeSelector: {{ include "linux-node-selector" . | nindent 8 }} +{{- if .Values.nodeSelector }} +{{- toYaml .Values.nodeSelector | nindent 8 }} +{{- end }} + tolerations: {{ include "linux-node-tolerations" . | nindent 8 }} +{{- if .Values.tolerations }} +{{- toYaml .Values.tolerations | nindent 8 }} +{{- end }} \ No newline at end of file diff --git a/charts/helm-locker/templates/hardened.yaml b/charts/helm-locker/templates/hardened.yaml new file mode 100644 index 0000000..1abaeec --- /dev/null +++ b/charts/helm-locker/templates/hardened.yaml @@ -0,0 +1,121 @@ +{{- $namespaces := dict "_0" .Release.Namespace -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "helm-locker.name" . }}-patch-sa + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "helm-locker.name" . }}-patch-sa + annotations: + "helm.sh/hook": post-install, post-upgrade + "helm.sh/hook-delete-policy": hook-succeeded, before-hook-creation +spec: + template: + metadata: + name: {{ template "helm-locker.name" . }}-patch-sa + labels: + app: {{ template "helm-locker.name" . }}-patch-sa + spec: + serviceAccountName: {{ template "helm-locker.name" . }}-patch-sa + securityContext: + runAsNonRoot: true + runAsUser: 1000 + restartPolicy: Never + nodeSelector: {{ include "linux-node-selector" . | nindent 8 }} + tolerations: {{ include "linux-node-tolerations" . | nindent 8 }} + containers: + {{- range $_, $ns := $namespaces }} + - name: patch-sa-{{ $ns }} + image: {{ template "system_default_registry" $ }}{{ $.Values.global.kubectl.repository }}:{{ $.Values.global.kubectl.tag }} + imagePullPolicy: {{ $.Values.global.kubectl.pullPolicy }} + command: ["kubectl", "patch", "serviceaccount", "default", "-p", "{\"automountServiceAccountToken\": false}"] + args: ["-n", "{{ $ns }}"] + {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "helm-locker.name" . }}-patch-sa + labels: + app: {{ template "helm-locker.name" . }}-patch-sa +rules: +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: ['get', 'patch'] +- apiGroups: ['policy'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ template "helm-locker.name" . }}-patch-sa +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "helm-locker.name" . }}-patch-sa + labels: + app: {{ template "helm-locker.name" . }}-patch-sa +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "helm-locker.name" . }}-patch-sa +subjects: +- kind: ServiceAccount + name: {{ template "helm-locker.name" . }}-patch-sa + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "helm-locker.name" . }}-patch-sa + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "helm-locker.name" . }}-patch-sa +--- +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "helm-locker.name" . }}-patch-sa + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "helm-locker.name" . }}-patch-sa +spec: + privileged: false + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'MustRunAsNonRoot' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + readOnlyRootFilesystem: false + volumes: + - 'secret' +{{- range $_, $ns := $namespaces }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-allow-all + namespace: {{ $ns }} +spec: + podSelector: {} + ingress: + - {} + egress: + - {} + policyTypes: + - Ingress + - Egress +{{- end }} diff --git a/charts/helm-locker/templates/psp.yaml b/charts/helm-locker/templates/psp.yaml new file mode 100644 index 0000000..16ff49e --- /dev/null +++ b/charts/helm-locker/templates/psp.yaml @@ -0,0 +1,68 @@ +{{- if .Values.global.rbac.pspEnabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "helm-locker.name" . }}-psp + namespace: {{ template "helm-locker.namespace" . }} + labels: {{ include "helm-locker.labels" . | nindent 4 }} + app: {{ template "helm-locker.name" . }} +{{- if .Values.global.rbac.pspAnnotations }} + annotations: {{ toYaml .Values.global.rbac.pspAnnotations | nindent 4 }} +{{- end }} +spec: + privileged: false + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + # Permits the container to run with root privileges as well. + rule: 'RunAsAny' + seLinux: + # This policy assumes the nodes are using AppArmor rather than SELinux. + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + readOnlyRootFilesystem: false +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "helm-locker.name" . }}-psp + labels: {{ include "helm-locker.labels" . | nindent 4 }} + app: {{ template "helm-locker.name" . }} +rules: +{{- if semverCompare "> 1.15.0-0" .Capabilities.KubeVersion.GitVersion }} +- apiGroups: ['policy'] +{{- else }} +- apiGroups: ['extensions'] +{{- end }} + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ template "helm-locker.name" . }}-psp +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "helm-locker.name" . }}-psp + labels: {{ include "helm-locker.labels" . | nindent 4 }} + app: {{ template "helm-locker.name" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "helm-locker.name" . }}-psp +subjects: + - kind: ServiceAccount + name: {{ template "helm-locker.name" . }} + namespace: {{ template "helm-locker.namespace" . }} +{{- end }} diff --git a/charts/helm-locker/templates/rbac.yaml b/charts/helm-locker/templates/rbac.yaml new file mode 100644 index 0000000..3b55ffb --- /dev/null +++ b/charts/helm-locker/templates/rbac.yaml @@ -0,0 +1,27 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "helm-locker.name" . }} + labels: {{ include "helm-locker.labels" . | nindent 4 }} + app: {{ template "helm-locker.name" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + # By default, Helm Locker does not know what it will need permissions to be able to lock, so it is granted a cluster-admin role by default + name: "cluster-admin" +subjects: +- kind: ServiceAccount + name: {{ template "helm-locker.name" . }} + namespace: {{ template "helm-locker.namespace" . }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "helm-locker.name" . }} + namespace: {{ template "helm-locker.namespace" . }} + labels: {{ include "helm-locker.labels" . | nindent 4 }} + app: {{ template "helm-locker.name" . }} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: {{ toYaml .Values.global.imagePullSecrets | nindent 2 }} +{{- end }} + diff --git a/charts/helm-locker/values.yaml b/charts/helm-locker/values.yaml new file mode 100644 index 0000000..e4a037e --- /dev/null +++ b/charts/helm-locker/values.yaml @@ -0,0 +1,86 @@ +# Default values for helm-locker. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Helm Locker Configuration + +global: + cattle: + systemDefaultRegistry: "" + + kubectl: + repository: rancher/kubectl + tag: v1.20.2 + pullPolicy: IfNotPresent + + rbac: + ## Create RBAC resources for ServiceAccounts and users + ## + create: true + + userRoles: + ## Create default user ClusterRoles to allow users to interact with HelmReleases + create: true + ## Aggregate default user ClusterRoles into default k8s ClusterRoles + aggregateToDefaultRoles: true + + pspEnabled: true + pspAnnotations: {} + ## Specify pod annotations + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#apparmor + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#sysctl + ## + # seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + # seccomp.security.alpha.kubernetes.io/defaultProfileName: 'docker/default' + # apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' + + ## Reference to one or more secrets to be used when pulling images + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + imagePullSecrets: [] + # - name: "image-pull-secret" + +nameOverride: "" + +namespaceOverride: "" + +replicas: 1 + +image: + repository: rancher/helm-locker + tag: v0.0.2 + pullPolicy: IfNotPresent + +# Additional arguments to be passed into the Helm Locker image +additionalArgs: [] + +## Define which Nodes the Pods are scheduled on. +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} + +## Tolerations for use with node taints +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] +# - key: "key" +# operator: "Equal" +# value: "value" +# effect: "NoSchedule" + +resources: {} + # limits: + # memory: 500Mi + # cpu: 1000m + # requests: + # memory: 100Mi + # cpu: 100m + +securityContext: {} + # allowPrivilegeEscalation: false + # readOnlyRootFilesystem: true + +debug: false +debugLevel: 0 + From 9be3772f590e85a392f6a4695abf66a3821b4e36 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 13:01:34 -0400 Subject: [PATCH 05/48] Adjust CRD reference yaml generation Remove redundant prefix since we're already in a CRDs directory --- crds/{crd-helmchartconfigs.yaml => helmchartconfigs.yaml} | 0 crds/{crd-helmcharts.yaml => helmcharts.yaml} | 0 crds/{crd-helmreleases.yaml => helmreleases.yaml} | 0 crds/{crd-projecthelmcharts.yaml => projecthelmcharts.yaml} | 0 pkg/crd/crds.go | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename crds/{crd-helmchartconfigs.yaml => helmchartconfigs.yaml} (100%) rename crds/{crd-helmcharts.yaml => helmcharts.yaml} (100%) rename crds/{crd-helmreleases.yaml => helmreleases.yaml} (100%) rename crds/{crd-projecthelmcharts.yaml => projecthelmcharts.yaml} (100%) diff --git a/crds/crd-helmchartconfigs.yaml b/crds/helmchartconfigs.yaml similarity index 100% rename from crds/crd-helmchartconfigs.yaml rename to crds/helmchartconfigs.yaml diff --git a/crds/crd-helmcharts.yaml b/crds/helmcharts.yaml similarity index 100% rename from crds/crd-helmcharts.yaml rename to crds/helmcharts.yaml diff --git a/crds/crd-helmreleases.yaml b/crds/helmreleases.yaml similarity index 100% rename from crds/crd-helmreleases.yaml rename to crds/helmreleases.yaml diff --git a/crds/crd-projecthelmcharts.yaml b/crds/projecthelmcharts.yaml similarity index 100% rename from crds/crd-projecthelmcharts.yaml rename to crds/projecthelmcharts.yaml diff --git a/pkg/crd/crds.go b/pkg/crd/crds.go index ea54e7f..cf02f73 100644 --- a/pkg/crd/crds.go +++ b/pkg/crd/crds.go @@ -65,7 +65,7 @@ func writeFiles(dirpath string, objs []runtime.Object) error { for key, data := range objMap { go func(key string, data []byte) { defer wg.Done() - f, err := os.Create(filepath.Join(dirpath, fmt.Sprintf("crd-%s.yaml", key))) + f, err := os.Create(filepath.Join(dirpath, fmt.Sprintf("%s.yaml", key))) if err != nil { logrus.Error(err) } From 10028d732c482b62557dbe3c3f6bb0ed7a6b0976 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 13:02:41 -0400 Subject: [PATCH 06/48] Adjust default entry point to use explicit name Will allow alternative targets in the future --- dummy.go => helm-project-operator.go | 0 scripts/build | 8 +++++--- 2 files changed, 5 insertions(+), 3 deletions(-) rename dummy.go => helm-project-operator.go (100%) diff --git a/dummy.go b/helm-project-operator.go similarity index 100% rename from dummy.go rename to helm-project-operator.go diff --git a/scripts/build b/scripts/build index 271d51c..3ddcaa5 100755 --- a/scripts/build +++ b/scripts/build @@ -1,6 +1,8 @@ #!/bin/bash set -e +BUILD_TARGET_CMD=${BUILD_TARGET_CMD:-"./helm-project-operator.go"} + source $(dirname $0)/version cd $(dirname $0)/.. @@ -13,8 +15,8 @@ if [ "$(uname)" = "Linux" ]; then fi LINKFLAGS="-X github.com/rancher/helm-project-operator/pkg/version.Version=$VERSION" LINKFLAGS="-X github.com/rancher/helm-project-operator/pkg/version.GitCommit=$COMMIT $LINKFLAGS" -CGO_ENABLED=0 go build -ldflags "$LINKFLAGS $OTHER_LINKFLAGS" -o bin/helm-project-operator +CGO_ENABLED=0 go build -ldflags "$LINKFLAGS $OTHER_LINKFLAGS" -o bin/helm-project-operator "${BUILD_TARGET_CMD}" if [ "$CROSS" = "true" ] && [ "$ARCH" = "amd64" ]; then - GOOS=darwin go build -ldflags "$LINKFLAGS" -o bin/helm-project-operator-darwin - GOOS=windows go build -ldflags "$LINKFLAGS" -o bin/helm-project-operator-windows + GOOS=darwin go build -ldflags "$LINKFLAGS" -o bin/helm-project-operator-darwin "${BUILD_TARGET_CMD}" + GOOS=windows go build -ldflags "$LINKFLAGS" -o bin/helm-project-operator-windows "${BUILD_TARGET_CMD}" fi From 42eff8b59d115d22d2f52f6d7e1fd8d327be6f82 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 13:02:57 -0400 Subject: [PATCH 07/48] Rename docker file to be specific --- .github/workflows/ci.yaml | 2 +- .github/workflows/publish-image.yaml | 2 +- docs/helm-locker/developing.md | 6 +++--- docs/helm-project-operator/developing.md | 6 +++--- docs/helm-project-operator/experimental/e2e.md | 2 +- package/{Dockerfile => Dockerfile-project-operator} | 0 6 files changed, 9 insertions(+), 9 deletions(-) rename package/{Dockerfile => Dockerfile-project-operator} (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e45d6c8..ba0c499 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,7 +45,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: ./package/Dockerfile + file: ./package/Dockerfile-project-operator push: false tags: ${{ env.IMAGE }} platforms: linux/amd64 \ No newline at end of file diff --git a/.github/workflows/publish-image.yaml b/.github/workflows/publish-image.yaml index 9d685e0..5765411 100644 --- a/.github/workflows/publish-image.yaml +++ b/.github/workflows/publish-image.yaml @@ -37,7 +37,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: ./package/Dockerfile + file: ./package/Dockerfile-project-operator push: true tags: ${{ env.IMAGE }} platforms: linux/amd64,linux/arm64 \ No newline at end of file diff --git a/docs/helm-locker/developing.md b/docs/helm-locker/developing.md index 8f2bc0b..8b568a8 100644 --- a/docs/helm-locker/developing.md +++ b/docs/helm-locker/developing.md @@ -21,13 +21,13 @@ docs/ ## This directory contains the image that is used to build rancher/helm-locker, which is hosted on hub.docker.com package/ - Dockerfile + Dockerfile-project-operator ## The main source directory for the code. See below for more details. pkg/ -## The Dockerfile used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) -Dockerfile.dapper +## The Dockerfile-project-operator used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) +Dockerfile-project-operator.dapper ## The file that contains the underlying actions that 'go generate' needs to execute on a call to it. Includes the logic for generating controllers and updating crds.yaml under the crds/ directory generate.go diff --git a/docs/helm-project-operator/developing.md b/docs/helm-project-operator/developing.md index d7d07c9..59d8a05 100644 --- a/docs/helm-project-operator/developing.md +++ b/docs/helm-project-operator/developing.md @@ -23,13 +23,13 @@ examples/ ## This directory contains the image that is used to build rancher/helm-project-operator, which is hosted on hub.docker.com. package/ - Dockerfile + Dockerfile-project-operator ## The main source directory for the code. See below for more details. pkg/ -## The Dockerfile used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) -Dockerfile.dapper +## The Dockerfile-project-operator used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) +Dockerfile-project-operator.dapper ## The file that contains the underlying actions that 'go generate' needs to execute on a call to it. Includes the logic for generating ## controllers and updating the crds.yaml under the crds/ directory diff --git a/docs/helm-project-operator/experimental/e2e.md b/docs/helm-project-operator/experimental/e2e.md index 21cebec..282c089 100644 --- a/docs/helm-project-operator/experimental/e2e.md +++ b/docs/helm-project-operator/experimental/e2e.md @@ -17,7 +17,7 @@ To run the end-to-end GitHub Workflow CI locally to test whether your changes wo An slim image has been defined in [`.github/workflows/e2e/package/Dockerfile`](../../../.github/workflows/e2e/package/Dockerfile) that has the necessary dependencies to be used as a Runner for act for this GitHub Workflow. To build the image, run the following commmand (make sure you re-run it if you make any changes to add dependencies): ```bash -docker build -f ./.github/workflows/e2e/package/Dockerfile -t rancher/helm-project-operator-e2e:latest . +docker build -f ./.github/workflows/e2e/package/Dockerfile-project-operator -t rancher/helm-project-operator-e2e:latest . ``` Once you have built the image and installed `act`, simply run the following command on the root of this repository and it will run your GitHub workflow within a Docker container: diff --git a/package/Dockerfile b/package/Dockerfile-project-operator similarity index 100% rename from package/Dockerfile rename to package/Dockerfile-project-operator From 78cec107bd2d144330ec3567028d78753ab0b85a Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 13:46:24 -0400 Subject: [PATCH 08/48] Rename docker file to better name --- .github/workflows/ci.yaml | 2 +- .github/workflows/publish-image.yaml | 2 +- docs/helm-locker/developing.md | 6 +++--- docs/helm-project-operator/developing.md | 6 +++--- docs/helm-project-operator/experimental/e2e.md | 2 +- ...le-project-operator => Dockerfile-helm-project-operator} | 0 6 files changed, 9 insertions(+), 9 deletions(-) rename package/{Dockerfile-project-operator => Dockerfile-helm-project-operator} (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ba0c499..469a9e3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,7 +45,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: ./package/Dockerfile-project-operator + file: ./package/Dockerfile-helm-project-operator push: false tags: ${{ env.IMAGE }} platforms: linux/amd64 \ No newline at end of file diff --git a/.github/workflows/publish-image.yaml b/.github/workflows/publish-image.yaml index 5765411..c80d95e 100644 --- a/.github/workflows/publish-image.yaml +++ b/.github/workflows/publish-image.yaml @@ -37,7 +37,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: ./package/Dockerfile-project-operator + file: ./package/Dockerfile-helm-project-operator push: true tags: ${{ env.IMAGE }} platforms: linux/amd64,linux/arm64 \ No newline at end of file diff --git a/docs/helm-locker/developing.md b/docs/helm-locker/developing.md index 8b568a8..a174ec3 100644 --- a/docs/helm-locker/developing.md +++ b/docs/helm-locker/developing.md @@ -21,13 +21,13 @@ docs/ ## This directory contains the image that is used to build rancher/helm-locker, which is hosted on hub.docker.com package/ - Dockerfile-project-operator + Dockerfile-helm-project-operator ## The main source directory for the code. See below for more details. pkg/ -## The Dockerfile-project-operator used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) -Dockerfile-project-operator.dapper +## The Dockerfile-helm-project-operator used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) +Dockerfile-helm-project-operator.dapper ## The file that contains the underlying actions that 'go generate' needs to execute on a call to it. Includes the logic for generating controllers and updating crds.yaml under the crds/ directory generate.go diff --git a/docs/helm-project-operator/developing.md b/docs/helm-project-operator/developing.md index 59d8a05..7b9f356 100644 --- a/docs/helm-project-operator/developing.md +++ b/docs/helm-project-operator/developing.md @@ -23,13 +23,13 @@ examples/ ## This directory contains the image that is used to build rancher/helm-project-operator, which is hosted on hub.docker.com. package/ - Dockerfile-project-operator + Dockerfile-helm-project-operator ## The main source directory for the code. See below for more details. pkg/ -## The Dockerfile-project-operator used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) -Dockerfile-project-operator.dapper +## The Dockerfile-helm-project-operator used to run CI and other scripts executed by make in a Docker container (powered by https://github.com/rancher/dapper) +Dockerfile-helm-project-operator.dapper ## The file that contains the underlying actions that 'go generate' needs to execute on a call to it. Includes the logic for generating ## controllers and updating the crds.yaml under the crds/ directory diff --git a/docs/helm-project-operator/experimental/e2e.md b/docs/helm-project-operator/experimental/e2e.md index 282c089..c9b3fd0 100644 --- a/docs/helm-project-operator/experimental/e2e.md +++ b/docs/helm-project-operator/experimental/e2e.md @@ -17,7 +17,7 @@ To run the end-to-end GitHub Workflow CI locally to test whether your changes wo An slim image has been defined in [`.github/workflows/e2e/package/Dockerfile`](../../../.github/workflows/e2e/package/Dockerfile) that has the necessary dependencies to be used as a Runner for act for this GitHub Workflow. To build the image, run the following commmand (make sure you re-run it if you make any changes to add dependencies): ```bash -docker build -f ./.github/workflows/e2e/package/Dockerfile-project-operator -t rancher/helm-project-operator-e2e:latest . +docker build -f ./.github/workflows/e2e/package/Dockerfile-helm-project-operator -t rancher/helm-project-operator-e2e:latest . ``` Once you have built the image and installed `act`, simply run the following command on the root of this repository and it will run your GitHub workflow within a Docker container: diff --git a/package/Dockerfile-project-operator b/package/Dockerfile-helm-project-operator similarity index 100% rename from package/Dockerfile-project-operator rename to package/Dockerfile-helm-project-operator From 4403c8846b6764d328635b36e5835124f608cc4a Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 13:46:48 -0400 Subject: [PATCH 09/48] Adjust scripts to allow targeting other entrypoints --- package/Dockerfile-helm-project-operator | 4 ++++ scripts/build | 2 +- scripts/build-chart | 2 +- scripts/default | 4 ++++ scripts/package | 6 ++++-- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/package/Dockerfile-helm-project-operator b/package/Dockerfile-helm-project-operator index 2f27807..be475d5 100644 --- a/package/Dockerfile-helm-project-operator +++ b/package/Dockerfile-helm-project-operator @@ -18,6 +18,10 @@ RUN make -C /helm RUN xx-verify --static /helm/bin/helm FROM registry.suse.com/bci/golang:1.22 AS builder + +ARG BUILD_TARGET +ENV BUILD_TARGET=${BUILD_TARGET} + WORKDIR /usr/src/app ENV YQ_VERSION=v4.25.1 RUN zypper -n install git curl wget make diff --git a/scripts/build b/scripts/build index 3ddcaa5..9b00767 100755 --- a/scripts/build +++ b/scripts/build @@ -1,7 +1,7 @@ #!/bin/bash set -e -BUILD_TARGET_CMD=${BUILD_TARGET_CMD:-"./helm-project-operator.go"} +BUILD_TARGET_CMD=${BUILD_TARGET_CMD:-"./${BUILD_TARGET}.go"} source $(dirname $0)/version diff --git a/scripts/build-chart b/scripts/build-chart index 08cd6db..3d1bc01 100755 --- a/scripts/build-chart +++ b/scripts/build-chart @@ -5,7 +5,7 @@ source $(dirname $0)/version cd $(dirname $0)/.. -CHART=project-operator-example +CHART=${CHART:-"project-operator-example"} VERSION=0.0.0 helm package charts/${CHART} --destination bin/${CHART} diff --git a/scripts/default b/scripts/default index af2ad2d..87ac486 100755 --- a/scripts/default +++ b/scripts/default @@ -1,8 +1,12 @@ #!/bin/bash set -e +BUILD_TARGET=${BUILD_TARGET:-"helm-project-operator"} + cd $(dirname $0) +export BUILD_TARGET + ./build ./test ./package diff --git a/scripts/package b/scripts/package index b257c59..830e2b0 100755 --- a/scripts/package +++ b/scripts/package @@ -1,10 +1,12 @@ #!/bin/bash set -e +DOCKER_TARGET=${DOCKER_TARGET:-"-${BUILD_TARGET}"} + source $(dirname $0)/version cd $(dirname $0)/.. echo Building ${IMAGE} ... -DOCKERFILE=package/Dockerfile -docker build -f ${DOCKERFILE} -t ${IMAGE} . +DOCKERFILE=package/Dockerfile${DOCKER_TARGET} +docker build --build-arg BUILD_TARGET=${BUILD_TARGET} -f ${DOCKERFILE} -t ${IMAGE} . echo Built ${IMAGE} From 7b9cc342f9627e0d9fe7ac3e5d62b338ab56bfe7 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 16:20:32 -0400 Subject: [PATCH 10/48] simplify docker bin target --- package/Dockerfile-helm-project-operator | 3 +-- scripts/package | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package/Dockerfile-helm-project-operator b/package/Dockerfile-helm-project-operator index be475d5..e50ac0e 100644 --- a/package/Dockerfile-helm-project-operator +++ b/package/Dockerfile-helm-project-operator @@ -19,8 +19,7 @@ RUN xx-verify --static /helm/bin/helm FROM registry.suse.com/bci/golang:1.22 AS builder -ARG BUILD_TARGET -ENV BUILD_TARGET=${BUILD_TARGET} +ENV BUILD_TARGET="helm-project-operator" WORKDIR /usr/src/app ENV YQ_VERSION=v4.25.1 diff --git a/scripts/package b/scripts/package index 830e2b0..da9f299 100755 --- a/scripts/package +++ b/scripts/package @@ -8,5 +8,5 @@ source $(dirname $0)/version cd $(dirname $0)/.. echo Building ${IMAGE} ... DOCKERFILE=package/Dockerfile${DOCKER_TARGET} -docker build --build-arg BUILD_TARGET=${BUILD_TARGET} -f ${DOCKERFILE} -t ${IMAGE} . +docker build -f ${DOCKERFILE} -t ${IMAGE} . echo Built ${IMAGE} From 6b4cdc3d1e8aa887a0ec9b657504f85d6c63a805 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 17:20:42 -0400 Subject: [PATCH 11/48] move CRDs for repo merge (cherry picked from commit 72dce6e4fbe7b1e8db0ce86c3eeb7b9d505dd7e6) --- crds/{ => helm-project-operator}/helmchartconfigs.yaml | 0 crds/{ => helm-project-operator}/helmcharts.yaml | 0 crds/{ => helm-project-operator}/helmreleases.yaml | 0 crds/{ => helm-project-operator}/projecthelmcharts.yaml | 0 generate.go | 2 +- pkg/codegen/cleanup/main.go | 2 +- 6 files changed, 2 insertions(+), 2 deletions(-) rename crds/{ => helm-project-operator}/helmchartconfigs.yaml (100%) rename crds/{ => helm-project-operator}/helmcharts.yaml (100%) rename crds/{ => helm-project-operator}/helmreleases.yaml (100%) rename crds/{ => helm-project-operator}/projecthelmcharts.yaml (100%) diff --git a/crds/helmchartconfigs.yaml b/crds/helm-project-operator/helmchartconfigs.yaml similarity index 100% rename from crds/helmchartconfigs.yaml rename to crds/helm-project-operator/helmchartconfigs.yaml diff --git a/crds/helmcharts.yaml b/crds/helm-project-operator/helmcharts.yaml similarity index 100% rename from crds/helmcharts.yaml rename to crds/helm-project-operator/helmcharts.yaml diff --git a/crds/helmreleases.yaml b/crds/helm-project-operator/helmreleases.yaml similarity index 100% rename from crds/helmreleases.yaml rename to crds/helm-project-operator/helmreleases.yaml diff --git a/crds/projecthelmcharts.yaml b/crds/helm-project-operator/projecthelmcharts.yaml similarity index 100% rename from crds/projecthelmcharts.yaml rename to crds/helm-project-operator/projecthelmcharts.yaml diff --git a/generate.go b/generate.go index 94e672b..bf39f3b 100644 --- a/generate.go +++ b/generate.go @@ -1,5 +1,5 @@ //go:generate go run pkg/codegen/cleanup/main.go //go:generate go run pkg/codegen/main.go -//go:generate go run ./pkg/codegen crds ./crds ./crds +//go:generate go run ./pkg/codegen crds ./crds/helm-project-operator ./crds/helm-project-operator package main diff --git a/pkg/codegen/cleanup/main.go b/pkg/codegen/cleanup/main.go index 30875bb..6af146d 100644 --- a/pkg/codegen/cleanup/main.go +++ b/pkg/codegen/cleanup/main.go @@ -14,7 +14,7 @@ func main() { if err := os.RemoveAll("./pkg/generated"); err != nil { logrus.Fatal(err) } - if err := os.RemoveAll("./crds"); err != nil { + if err := os.RemoveAll("./crds/helm-project-operator"); err != nil { logrus.Fatal(err) } } From bd7c5b9cbfab3973605b074cc8b9b37b2309075e Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Sep 2024 18:16:25 -0400 Subject: [PATCH 12/48] bring helm-locker code into local repo --- crds/crds.yaml | 85 ++++ crds/helm-locker/crds.yaml | 85 ++++ generate.go | 4 + go.mod | 1 - pkg/controllers/controllers.go | 8 +- pkg/controllers/project/controller.go | 2 +- pkg/controllers/project/resolvers.go | 2 +- pkg/controllers/project/resources.go | 4 +- pkg/crd/crds.go | 2 +- .../apis/helm.cattle.io/v1alpha1/doc.go | 21 + .../apis/helm.cattle.io/v1alpha1/release.go | 59 +++ .../v1alpha1/zz_generated_deepcopy.go | 142 +++++++ .../v1alpha1/zz_generated_list_types.go | 42 ++ .../v1alpha1/zz_generated_register.go | 60 +++ .../helm.cattle.io/zz_generated_register.go | 24 ++ pkg/helm-locker/codegen/cleanup/main.go | 20 + pkg/helm-locker/codegen/main.go | 39 ++ pkg/helm-locker/controllers/controller.go | 176 ++++++++ .../controllers/release/controller.go | 238 +++++++++++ pkg/helm-locker/controllers/release/decode.go | 45 +++ pkg/helm-locker/controllers/release/info.go | 53 +++ pkg/helm-locker/controllers/release/utils.go | 43 ++ pkg/helm-locker/crd/crds.go | 104 +++++ .../controllers/helm.cattle.io/factory.go | 67 ++++ .../controllers/helm.cattle.io/interface.go | 43 ++ .../helm.cattle.io/v1alpha1/helmrelease.go | 376 ++++++++++++++++++ .../helm.cattle.io/v1alpha1/interface.go | 48 +++ pkg/helm-locker/gvk/gvk.go | 141 +++++++ pkg/helm-locker/gvk/lister.go | 44 ++ pkg/helm-locker/gvk/wrapper.go | 24 ++ pkg/helm-locker/informerfactory/wrapper.go | 29 ++ pkg/helm-locker/objectset/cache.go | 370 +++++++++++++++++ pkg/helm-locker/objectset/controller.go | 56 +++ pkg/helm-locker/objectset/handler.go | 103 +++++ pkg/helm-locker/objectset/parser/parse.go | 33 ++ pkg/helm-locker/objectset/state.go | 117 ++++++ pkg/helm-locker/objectset/utils.go | 13 + pkg/helm-locker/objectset/wrapper.go | 23 ++ pkg/helm-locker/releases/releases.go | 47 +++ pkg/helm-locker/remove/handler.go | 39 ++ pkg/helm-locker/version/version.go | 13 + 41 files changed, 2835 insertions(+), 10 deletions(-) create mode 100644 crds/crds.yaml create mode 100644 crds/helm-locker/crds.yaml create mode 100644 pkg/helm-locker/apis/helm.cattle.io/v1alpha1/doc.go create mode 100644 pkg/helm-locker/apis/helm.cattle.io/v1alpha1/release.go create mode 100644 pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_deepcopy.go create mode 100644 pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_list_types.go create mode 100644 pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_register.go create mode 100644 pkg/helm-locker/apis/helm.cattle.io/zz_generated_register.go create mode 100644 pkg/helm-locker/codegen/cleanup/main.go create mode 100644 pkg/helm-locker/codegen/main.go create mode 100644 pkg/helm-locker/controllers/controller.go create mode 100644 pkg/helm-locker/controllers/release/controller.go create mode 100644 pkg/helm-locker/controllers/release/decode.go create mode 100644 pkg/helm-locker/controllers/release/info.go create mode 100644 pkg/helm-locker/controllers/release/utils.go create mode 100644 pkg/helm-locker/crd/crds.go create mode 100644 pkg/helm-locker/generated/controllers/helm.cattle.io/factory.go create mode 100644 pkg/helm-locker/generated/controllers/helm.cattle.io/interface.go create mode 100644 pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1/helmrelease.go create mode 100644 pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1/interface.go create mode 100644 pkg/helm-locker/gvk/gvk.go create mode 100644 pkg/helm-locker/gvk/lister.go create mode 100644 pkg/helm-locker/gvk/wrapper.go create mode 100644 pkg/helm-locker/informerfactory/wrapper.go create mode 100644 pkg/helm-locker/objectset/cache.go create mode 100644 pkg/helm-locker/objectset/controller.go create mode 100644 pkg/helm-locker/objectset/handler.go create mode 100644 pkg/helm-locker/objectset/parser/parse.go create mode 100644 pkg/helm-locker/objectset/state.go create mode 100644 pkg/helm-locker/objectset/utils.go create mode 100644 pkg/helm-locker/objectset/wrapper.go create mode 100644 pkg/helm-locker/releases/releases.go create mode 100644 pkg/helm-locker/remove/handler.go create mode 100644 pkg/helm-locker/version/version.go diff --git a/crds/crds.yaml b/crds/crds.yaml new file mode 100644 index 0000000..7958825 --- /dev/null +++ b/crds/crds.yaml @@ -0,0 +1,85 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: helmreleases.helm.cattle.io +spec: + group: helm.cattle.io + names: + kind: HelmRelease + plural: helmreleases + singular: helmrelease + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.release.name + name: Release Name + type: string + - jsonPath: .spec.release.namespace + name: Release Namespace + type: string + - jsonPath: .status.version + name: Version + type: string + - jsonPath: .status.state + name: State + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + release: + properties: + name: + nullable: true + type: string + namespace: + nullable: true + type: string + type: object + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + nullable: true + type: string + lastUpdateTime: + nullable: true + type: string + message: + nullable: true + type: string + reason: + nullable: true + type: string + status: + nullable: true + type: string + type: + nullable: true + type: string + type: object + nullable: true + type: array + description: + nullable: true + type: string + notes: + nullable: true + type: string + state: + nullable: true + type: string + version: + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/crds/helm-locker/crds.yaml b/crds/helm-locker/crds.yaml new file mode 100644 index 0000000..7958825 --- /dev/null +++ b/crds/helm-locker/crds.yaml @@ -0,0 +1,85 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: helmreleases.helm.cattle.io +spec: + group: helm.cattle.io + names: + kind: HelmRelease + plural: helmreleases + singular: helmrelease + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.release.name + name: Release Name + type: string + - jsonPath: .spec.release.namespace + name: Release Namespace + type: string + - jsonPath: .status.version + name: Version + type: string + - jsonPath: .status.state + name: State + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + release: + properties: + name: + nullable: true + type: string + namespace: + nullable: true + type: string + type: object + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + nullable: true + type: string + lastUpdateTime: + nullable: true + type: string + message: + nullable: true + type: string + reason: + nullable: true + type: string + status: + nullable: true + type: string + type: + nullable: true + type: string + type: object + nullable: true + type: array + description: + nullable: true + type: string + notes: + nullable: true + type: string + state: + nullable: true + type: string + version: + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/generate.go b/generate.go index bf39f3b..81727c3 100644 --- a/generate.go +++ b/generate.go @@ -1,3 +1,7 @@ +//go:generate go run pkg/helm-locker/codegen/cleanup/main.go +//go:generate go run pkg/helm-locker/codegen/main.go +//go:generate go run ./pkg/helm-locker/codegen crds ./crds/helm-locker/crds.yaml + //go:generate go run pkg/codegen/cleanup/main.go //go:generate go run pkg/codegen/main.go //go:generate go run ./pkg/codegen crds ./crds/helm-project-operator ./crds/helm-project-operator diff --git a/go.mod b/go.mod index 523dae8..ab032df 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ replace ( require ( github.com/k3s-io/helm-controller v0.13.1 - github.com/rancher/helm-locker v0.0.0-20220511204622-3b216418e2f4 github.com/rancher/lasso v0.0.0-20220303220127-8cf5555ec03c github.com/rancher/wrangler v0.8.11-0.20220217210408-3ecd23dfea3b github.com/rancher/wrangler-cli v0.0.0-20211112052728-f172e9bf59af diff --git a/pkg/controllers/controllers.go b/pkg/controllers/controllers.go index 2f95b38..750d6b9 100644 --- a/pkg/controllers/controllers.go +++ b/pkg/controllers/controllers.go @@ -9,16 +9,16 @@ import ( "github.com/k3s-io/helm-controller/pkg/controllers/chart" k3shelm "github.com/k3s-io/helm-controller/pkg/generated/controllers/helm.cattle.io" k3shelmcontroller "github.com/k3s-io/helm-controller/pkg/generated/controllers/helm.cattle.io/v1" - "github.com/rancher/helm-locker/pkg/controllers/release" - helmlocker "github.com/rancher/helm-locker/pkg/generated/controllers/helm.cattle.io" - helmlockercontroller "github.com/rancher/helm-locker/pkg/generated/controllers/helm.cattle.io/v1alpha1" - "github.com/rancher/helm-locker/pkg/objectset" "github.com/rancher/helm-project-operator/pkg/controllers/common" "github.com/rancher/helm-project-operator/pkg/controllers/hardened" "github.com/rancher/helm-project-operator/pkg/controllers/namespace" "github.com/rancher/helm-project-operator/pkg/controllers/project" helmproject "github.com/rancher/helm-project-operator/pkg/generated/controllers/helm.cattle.io" helmprojectcontroller "github.com/rancher/helm-project-operator/pkg/generated/controllers/helm.cattle.io/v1alpha1" + "github.com/rancher/helm-project-operator/pkg/helm-locker/controllers/release" + helmlocker "github.com/rancher/helm-project-operator/pkg/helm-locker/generated/controllers/helm.cattle.io" + helmlockercontroller "github.com/rancher/helm-project-operator/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1" + "github.com/rancher/helm-project-operator/pkg/helm-locker/objectset" "github.com/rancher/lasso/pkg/cache" "github.com/rancher/lasso/pkg/client" "github.com/rancher/lasso/pkg/controller" diff --git a/pkg/controllers/project/controller.go b/pkg/controllers/project/controller.go index a14b39f..34bc401 100644 --- a/pkg/controllers/project/controller.go +++ b/pkg/controllers/project/controller.go @@ -6,11 +6,11 @@ import ( "github.com/k3s-io/helm-controller/pkg/controllers/chart" k3shelmcontroller "github.com/k3s-io/helm-controller/pkg/generated/controllers/helm.cattle.io/v1" - helmlockercontroller "github.com/rancher/helm-locker/pkg/generated/controllers/helm.cattle.io/v1alpha1" v1alpha1 "github.com/rancher/helm-project-operator/pkg/apis/helm.cattle.io/v1alpha1" "github.com/rancher/helm-project-operator/pkg/controllers/common" "github.com/rancher/helm-project-operator/pkg/controllers/namespace" helmprojectcontroller "github.com/rancher/helm-project-operator/pkg/generated/controllers/helm.cattle.io/v1alpha1" + helmlockercontroller "github.com/rancher/helm-project-operator/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1" "github.com/rancher/helm-project-operator/pkg/remove" "github.com/rancher/wrangler/pkg/apply" corecontroller "github.com/rancher/wrangler/pkg/generated/controllers/core/v1" diff --git a/pkg/controllers/project/resolvers.go b/pkg/controllers/project/resolvers.go index 0a03da8..a5cfe70 100644 --- a/pkg/controllers/project/resolvers.go +++ b/pkg/controllers/project/resolvers.go @@ -4,8 +4,8 @@ import ( "context" helmcontrollerv1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" - helmlockerv1alpha1 "github.com/rancher/helm-locker/pkg/apis/helm.cattle.io/v1alpha1" "github.com/rancher/helm-project-operator/pkg/controllers/common" + helmlockerv1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" "github.com/rancher/wrangler/pkg/apply" "github.com/rancher/wrangler/pkg/relatedresource" "github.com/sirupsen/logrus" diff --git a/pkg/controllers/project/resources.go b/pkg/controllers/project/resources.go index a1b6dab..ad9eb69 100644 --- a/pkg/controllers/project/resources.go +++ b/pkg/controllers/project/resources.go @@ -3,10 +3,10 @@ package project import ( helmcontrollerv1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1" "github.com/k3s-io/helm-controller/pkg/controllers/chart" - helmlockerv1alpha1 "github.com/rancher/helm-locker/pkg/apis/helm.cattle.io/v1alpha1" - "github.com/rancher/helm-locker/pkg/controllers/release" v1alpha1 "github.com/rancher/helm-project-operator/pkg/apis/helm.cattle.io/v1alpha1" "github.com/rancher/helm-project-operator/pkg/controllers/common" + helmlockerv1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" + "github.com/rancher/helm-project-operator/pkg/helm-locker/controllers/release" v1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/crd/crds.go b/pkg/crd/crds.go index cf02f73..c1bc967 100644 --- a/pkg/crd/crds.go +++ b/pkg/crd/crds.go @@ -10,8 +10,8 @@ import ( "sync" helmcontrollercrd "github.com/k3s-io/helm-controller/pkg/crd" - helmlockercrd "github.com/rancher/helm-locker/pkg/crd" v1alpha1 "github.com/rancher/helm-project-operator/pkg/apis/helm.cattle.io/v1alpha1" + helmlockercrd "github.com/rancher/helm-project-operator/pkg/helm-locker/crd" "github.com/rancher/wrangler/pkg/crd" "github.com/rancher/wrangler/pkg/yaml" "github.com/sirupsen/logrus" diff --git a/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/doc.go b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/doc.go new file mode 100644 index 0000000..20da9d2 --- /dev/null +++ b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +// +k8s:deepcopy-gen=package +// +groupName=helm.cattle.io +package v1alpha1 diff --git a/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/release.go b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/release.go new file mode 100644 index 0000000..d72c4ac --- /dev/null +++ b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/release.go @@ -0,0 +1,59 @@ +package v1alpha1 + +import ( + "github.com/rancher/wrangler/pkg/genericcondition" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // Helm Release Statuses + + // SecretNotFoundState is the state when a Helm release secret has not been found for this HelmRelease + SecretNotFoundState = "SecretNotFound" + + // UnknownState is the state when the Helm release secret reports that it does not know the state of the underlying Helm release + UnknownState = "Unknown" + + // DeployedState is the state where the underlying Helm release has been successfully deployed, indicating Helm Locker should lock the release + DeployedState = "Deployed" + + // UninstalledState is the state when the underlying Helm release is uninstalled but the Helm release secret has not been deleted + UninstalledState = "Uninstalled" + + // ErrorState is a state where Helm Locker has encountered an unexpected bug on trying to parse the underlying Helm release + ErrorState = "Error" + + // FailedState is the state when the underlying Helm release has failed its last Helm operation + FailedState = "Failed" + + // TransitioningState is the transitionary state when a Helm operation is being performed on the release (install, upgrade, uninstall) + TransitioningState = "Transitioning" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type HelmRelease struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec HelmReleaseSpec `json:"spec"` + Status HelmReleaseStatus `json:"status"` +} + +type HelmReleaseSpec struct { + Release ReleaseKey `json:"release,omitempty"` +} + +type ReleaseKey struct { + Name string `json:"name,omitempty"` + Namespace string `json:"namespace,omitempty"` +} + +type HelmReleaseStatus struct { + State string `json:"state,omitempty"` + Version int `json:"version,omitempty"` + Description string `json:"description,omitempty"` + Notes string `json:"notes,omitempty"` + + Conditions []genericcondition.GenericCondition `json:"conditions,omitempty"` +} diff --git a/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_deepcopy.go b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_deepcopy.go new file mode 100644 index 0000000..fbf352e --- /dev/null +++ b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_deepcopy.go @@ -0,0 +1,142 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1alpha1 + +import ( + genericcondition "github.com/rancher/wrangler/pkg/genericcondition" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmRelease) DeepCopyInto(out *HelmRelease) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmRelease. +func (in *HelmRelease) DeepCopy() *HelmRelease { + if in == nil { + return nil + } + out := new(HelmRelease) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HelmRelease) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmReleaseList) DeepCopyInto(out *HelmReleaseList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]HelmRelease, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmReleaseList. +func (in *HelmReleaseList) DeepCopy() *HelmReleaseList { + if in == nil { + return nil + } + out := new(HelmReleaseList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HelmReleaseList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) { + *out = *in + out.Release = in.Release + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmReleaseSpec. +func (in *HelmReleaseSpec) DeepCopy() *HelmReleaseSpec { + if in == nil { + return nil + } + out := new(HelmReleaseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmReleaseStatus) DeepCopyInto(out *HelmReleaseStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]genericcondition.GenericCondition, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmReleaseStatus. +func (in *HelmReleaseStatus) DeepCopy() *HelmReleaseStatus { + if in == nil { + return nil + } + out := new(HelmReleaseStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReleaseKey) DeepCopyInto(out *ReleaseKey) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReleaseKey. +func (in *ReleaseKey) DeepCopy() *ReleaseKey { + if in == nil { + return nil + } + out := new(ReleaseKey) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_list_types.go b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_list_types.go new file mode 100644 index 0000000..135106a --- /dev/null +++ b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_list_types.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +// +k8s:deepcopy-gen=package +// +groupName=helm.cattle.io +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// HelmReleaseList is a list of HelmRelease resources +type HelmReleaseList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []HelmRelease `json:"items"` +} + +func NewHelmRelease(namespace, name string, obj HelmRelease) *HelmRelease { + obj.APIVersion, obj.Kind = SchemeGroupVersion.WithKind("HelmRelease").ToAPIVersionAndKind() + obj.Name = name + obj.Namespace = namespace + return &obj +} diff --git a/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_register.go b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_register.go new file mode 100644 index 0000000..0597f36 --- /dev/null +++ b/pkg/helm-locker/apis/helm.cattle.io/v1alpha1/zz_generated_register.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +// +k8s:deepcopy-gen=package +// +groupName=helm.cattle.io +package v1alpha1 + +import ( + helm "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + HelmReleaseResourceName = "helmreleases" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: helm.GroupName, Version: "v1alpha1"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &HelmRelease{}, + &HelmReleaseList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/helm-locker/apis/helm.cattle.io/zz_generated_register.go b/pkg/helm-locker/apis/helm.cattle.io/zz_generated_register.go new file mode 100644 index 0000000..11c331c --- /dev/null +++ b/pkg/helm-locker/apis/helm.cattle.io/zz_generated_register.go @@ -0,0 +1,24 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package helm + +const ( + // Package-wide consts from generator "zz_generated_register". + GroupName = "helm.cattle.io" +) diff --git a/pkg/helm-locker/codegen/cleanup/main.go b/pkg/helm-locker/codegen/cleanup/main.go new file mode 100644 index 0000000..82b688d --- /dev/null +++ b/pkg/helm-locker/codegen/cleanup/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "os" + + "github.com/rancher/wrangler/pkg/cleanup" + "github.com/sirupsen/logrus" +) + +func main() { + if err := cleanup.Cleanup("./pkg/helm-locker/apis"); err != nil { + logrus.Fatal(err) + } + if err := os.RemoveAll("./pkg/helm-locker/generated"); err != nil { + logrus.Fatal(err) + } + if err := os.RemoveAll("./crds/helm-locker"); err != nil { + logrus.Fatal(err) + } +} diff --git a/pkg/helm-locker/codegen/main.go b/pkg/helm-locker/codegen/main.go new file mode 100644 index 0000000..b5b5827 --- /dev/null +++ b/pkg/helm-locker/codegen/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "os" + + v1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" + "github.com/rancher/helm-project-operator/pkg/helm-locker/crd" + "github.com/sirupsen/logrus" + + controllergen "github.com/rancher/wrangler/pkg/controller-gen" + "github.com/rancher/wrangler/pkg/controller-gen/args" +) + +func main() { + if len(os.Args) > 2 && os.Args[1] == "crds" { + if len(os.Args) != 3 { + logrus.Fatal("usage: ./codegen crds ") + } + logrus.Infof("Writing CRDs to %s", os.Args[2]) + if err := crd.WriteFile(os.Args[2]); err != nil { + panic(err) + } + return + } + + os.Unsetenv("GOPATH") + controllergen.Run(args.Options{ + OutputPackage: "github.com/rancher/helm-project-operator/pkg/helm-locker/generated", + Boilerplate: "scripts/boilerplate.go.txt", + Groups: map[string]args.Group{ + "helm.cattle.io": { + Types: []interface{}{ + v1alpha1.HelmRelease{}, + }, + GenerateTypes: true, + }, + }, + }) +} diff --git a/pkg/helm-locker/controllers/controller.go b/pkg/helm-locker/controllers/controller.go new file mode 100644 index 0000000..d541a45 --- /dev/null +++ b/pkg/helm-locker/controllers/controller.go @@ -0,0 +1,176 @@ +package controllers + +import ( + "context" + "errors" + "time" + + "github.com/rancher/helm-project-operator/pkg/helm-locker/controllers/release" + helmcontroller "github.com/rancher/helm-project-operator/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1" + "github.com/rancher/helm-project-operator/pkg/helm-locker/objectset" + "github.com/rancher/lasso/pkg/cache" + "github.com/rancher/lasso/pkg/client" + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/apply" + "github.com/rancher/wrangler/pkg/generated/controllers/core" + corecontroller "github.com/rancher/wrangler/pkg/generated/controllers/core/v1" + "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/pkg/leader" + "github.com/rancher/wrangler/pkg/ratelimit" + "github.com/rancher/wrangler/pkg/schemes" + "github.com/rancher/wrangler/pkg/start" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/discovery" + "k8s.io/client-go/kubernetes" + typedv1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" +) + +type appContext struct { + helmcontroller.Interface + + K8s kubernetes.Interface + Core corecontroller.Interface + + Apply apply.Apply + + ObjectSetRegister objectset.LockableRegister + ObjectSetHandler *controller.SharedHandler + + EventBroadcaster record.EventBroadcaster + + starters []start.Starter +} + +func (a *appContext) start(ctx context.Context) error { + return start.All(ctx, 50, a.starters...) +} + +func Register(ctx context.Context, systemNamespace, controllerName, nodeName string, cfg clientcmd.ClientConfig) error { + if len(systemNamespace) == 0 { + return errors.New("cannot start controllers on system namespace: system namespace not provided") + } + + appCtx, err := newContext(ctx, systemNamespace, cfg) + if err != nil { + return err + } + + appCtx.EventBroadcaster.StartLogging(logrus.Debugf) + appCtx.EventBroadcaster.StartRecordingToSink(&typedv1.EventSinkImpl{ + Interface: appCtx.K8s.CoreV1().Events(systemNamespace), + }) + recorder := appCtx.EventBroadcaster.NewRecorder(schemes.All, corev1.EventSource{ + Component: "helm-locker", + Host: nodeName, + }) + + if len(controllerName) == 0 { + controllerName = "helm-locker" + } + + // TODO: Register all controllers + release.Register(ctx, + systemNamespace, + controllerName, + appCtx.HelmRelease(), + appCtx.HelmRelease().Cache(), + appCtx.Core.Secret(), + appCtx.Core.Secret().Cache(), + appCtx.K8s, + appCtx.ObjectSetRegister, + appCtx.ObjectSetHandler, + recorder, + ) + + leader.RunOrDie(ctx, systemNamespace, "helm-locker-lock", appCtx.K8s, func(ctx context.Context) { + if err := appCtx.start(ctx); err != nil { + logrus.Fatal(err) + } + logrus.Info("All controllers have been started") + }) + + return nil +} + +func controllerFactory(rest *rest.Config) (controller.SharedControllerFactory, error) { + rateLimit := workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 60*time.Second) + clientFactory, err := client.NewSharedClientFactory(rest, nil) + if err != nil { + return nil, err + } + + cacheFactory := cache.NewSharedCachedFactory(clientFactory, nil) + return controller.NewSharedControllerFactory(cacheFactory, &controller.SharedControllerFactoryOptions{ + DefaultRateLimiter: rateLimit, + DefaultWorkers: 50, + }), nil +} + +func newContext(_ context.Context, systemNamespace string, cfg clientcmd.ClientConfig) (*appContext, error) { + client, err := cfg.ClientConfig() + if err != nil { + return nil, err + } + client.RateLimiter = ratelimit.None + + k8s, err := kubernetes.NewForConfig(client) + if err != nil { + return nil, err + } + + discovery, err := discovery.NewDiscoveryClientForConfig(client) + if err != nil { + return nil, err + } + + scf, err := controllerFactory(client) + if err != nil { + return nil, err + } + + core, err := core.NewFactoryFromConfigWithOptions(client, &generic.FactoryOptions{ + SharedControllerFactory: scf, + }) + if err != nil { + return nil, err + } + corev := core.Core().V1() + + helm, err := helm.NewFactoryFromConfigWithOptions(client, &generic.FactoryOptions{ + Namespace: systemNamespace, + SharedControllerFactory: scf, + }) + if err != nil { + return nil, err + } + helmv := helm.Helm().V1alpha1() + + apply := apply.New(discovery, apply.NewClientFactory(client)) + + objectSet, objectSetRegister, objectSetHandler := objectset.NewLockableRegister("object-set-register", apply, scf, discovery, nil) + + return &appContext{ + Interface: helmv, + + K8s: k8s, + Core: corev, + + Apply: apply, + + ObjectSetRegister: objectSetRegister, + ObjectSetHandler: objectSetHandler, + + EventBroadcaster: record.NewBroadcaster(), + + starters: []start.Starter{ + objectSet, + core, + helm, + }, + }, nil +} diff --git a/pkg/helm-locker/controllers/release/controller.go b/pkg/helm-locker/controllers/release/controller.go new file mode 100644 index 0000000..bbfcc30 --- /dev/null +++ b/pkg/helm-locker/controllers/release/controller.go @@ -0,0 +1,238 @@ +package release + +import ( + "context" + "fmt" + + v1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" + helmcontroller "github.com/rancher/helm-project-operator/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1" + "github.com/rancher/helm-project-operator/pkg/helm-locker/objectset" + "github.com/rancher/helm-project-operator/pkg/helm-locker/objectset/parser" + "github.com/rancher/helm-project-operator/pkg/helm-locker/releases" + "github.com/rancher/helm-project-operator/pkg/helm-locker/remove" + "github.com/rancher/lasso/pkg/controller" + corecontroller "github.com/rancher/wrangler/pkg/generated/controllers/core/v1" + "github.com/rancher/wrangler/pkg/relatedresource" + "github.com/sirupsen/logrus" + "helm.sh/helm/v3/pkg/storage/driver" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/record" +) + +const ( + // HelmReleaseByReleaseKey is the key used to get HelmRelease objects by the namespace/name of the underlying Helm Release it points to + HelmReleaseByReleaseKey = "helm.cattle.io/helm-release-by-release-key" + + // ManagedBy is an annotation attached to HelmRelease objects that indicates that they are managed by this operator + ManagedBy = "helmreleases.cattle.io/managed-by" +) + +type handler struct { + systemNamespace string + managedBy string + + helmReleases helmcontroller.HelmReleaseController + helmReleaseCache helmcontroller.HelmReleaseCache + secrets corecontroller.SecretController + secretCache corecontroller.SecretCache + + releases releases.HelmReleaseGetter + + lockableObjectSetRegister objectset.LockableRegister + recorder record.EventRecorder +} + +func Register( + ctx context.Context, + systemNamespace, managedBy string, + helmReleases helmcontroller.HelmReleaseController, + helmReleaseCache helmcontroller.HelmReleaseCache, + secrets corecontroller.SecretController, + secretCache corecontroller.SecretCache, + k8s kubernetes.Interface, + lockableObjectSetRegister objectset.LockableRegister, + lockableObjectSetHandler *controller.SharedHandler, + recorder record.EventRecorder, +) { + + h := &handler{ + systemNamespace: systemNamespace, + managedBy: managedBy, + + helmReleases: helmReleases, + helmReleaseCache: helmReleaseCache, + secrets: secrets, + secretCache: secretCache, + + releases: releases.NewHelmReleaseGetter(k8s), + + lockableObjectSetRegister: lockableObjectSetRegister, + recorder: recorder, + } + + lockableObjectSetHandler.Register(ctx, "on-objectset-change", controller.SharedControllerHandlerFunc(h.OnObjectSetChange)) + + helmReleaseCache.AddIndexer(HelmReleaseByReleaseKey, helmReleaseToReleaseKey) + + relatedresource.Watch(ctx, "on-helm-secret-change", h.resolveHelmRelease, helmReleases, secrets) + + helmReleases.OnChange(ctx, "apply-lock-on-release", h.OnHelmRelease) + + remove.RegisterScopedOnRemoveHandler(ctx, helmReleases, "on-helm-release-remove", + func(_ string, obj runtime.Object) (bool, error) { + if obj == nil { + return false, nil + } + helmRelease, ok := obj.(*v1alpha1.HelmRelease) + if !ok { + return false, nil + } + return h.shouldManage(helmRelease) + }, + helmcontroller.FromHelmReleaseHandlerToHandler(h.OnHelmReleaseRemove), + ) +} + +func (h *handler) OnObjectSetChange(setID string, obj runtime.Object) (runtime.Object, error) { + helmReleases, err := h.helmReleaseCache.GetByIndex(HelmReleaseByReleaseKey, setID) + if err != nil { + return nil, fmt.Errorf("unable to find HelmReleases for objectset %s to trigger event", setID) + } + for _, helmRelease := range helmReleases { + if helmRelease == nil { + continue + } + if obj != nil { + h.recorder.Eventf(helmRelease, corev1.EventTypeNormal, "Locked", "Applied ObjectSet %s tied to HelmRelease %s/%s to lock into place", setID, helmRelease.Namespace, helmRelease.Name) + } else { + h.recorder.Eventf(helmRelease, corev1.EventTypeNormal, "Untracked", "ObjectSet %s tied to HelmRelease %s/%s is not tracked", setID, helmRelease.Namespace, helmRelease.Name) + } + } + return nil, nil +} + +func helmReleaseToReleaseKey(helmRelease *v1alpha1.HelmRelease) ([]string, error) { + releaseKey := releaseKeyFromRelease(helmRelease) + return []string{releaseKeyToString(releaseKey)}, nil +} + +func (h *handler) resolveHelmRelease(_ /* secretNamespace */, _ /* secretName */ string, obj runtime.Object) ([]relatedresource.Key, error) { + secret, ok := obj.(*corev1.Secret) + if !ok { + return nil, nil + } + releaseKey := releaseKeyFromSecret(secret) + if releaseKey == nil { + // No release found matching this secret + return nil, nil + } + helmReleases, err := h.helmReleaseCache.GetByIndex(HelmReleaseByReleaseKey, releaseKeyToString(*releaseKey)) + if err != nil { + return nil, err + } + + keys := make([]relatedresource.Key, len(helmReleases)) + for i, helmRelease := range helmReleases { + keys[i] = relatedresource.Key{ + Name: helmRelease.Name, + Namespace: helmRelease.Namespace, + } + } + + return keys, nil +} + +// shouldManage determines if this HelmRelease should be handled by this operator +func (h *handler) shouldManage(helmRelease *v1alpha1.HelmRelease) (bool, error) { + if helmRelease == nil { + return false, nil + } + if helmRelease.Namespace != h.systemNamespace { + return false, nil + } + if helmRelease.Annotations != nil { + managedBy, ok := helmRelease.Annotations[ManagedBy] + if ok { + // if the label exists, only handle this if the managedBy label matches that of this controller + return managedBy == h.managedBy, nil + } + } + // The managedBy label does not exist, so we trigger claiming the HelmRelease + // We then return false since this update will automatically retrigger an OnChange operation + helmReleaseCopy := helmRelease.DeepCopy() + if helmReleaseCopy.Annotations == nil { + helmReleaseCopy.SetAnnotations(map[string]string{ + ManagedBy: h.managedBy, + }) + } else { + helmReleaseCopy.Annotations[ManagedBy] = h.managedBy + } + _, err := h.helmReleases.Update(helmReleaseCopy) + return false, err +} + +func (h *handler) OnHelmReleaseRemove(_ string, helmRelease *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) { + if helmRelease == nil { + return nil, nil + } + if helmRelease.Status.State == v1alpha1.SecretNotFoundState || helmRelease.Status.State == v1alpha1.UninstalledState { + // HelmRelease was not tracking any underlying objectSet + return helmRelease, nil + } + // HelmRelease CRs are only pointers to Helm releases... if the HelmRelease CR is removed, we should do nothing, but should warn the user + // that we are leaving behind resources in the cluster + logrus.Warnf("HelmRelease %s/%s was removed, resources tied to Helm release may need to be manually deleted", helmRelease.Namespace, helmRelease.Name) + logrus.Warnf("To delete the contents of a Helm release automatically, delete the Helm release secret before deleting the HelmRelease.") + releaseKey := releaseKeyFromRelease(helmRelease) + h.lockableObjectSetRegister.Delete(releaseKey, false) // remove the objectset, but don't purge the underlying resources + return helmRelease, nil +} + +func (h *handler) OnHelmRelease(_ string, helmRelease *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) { + if shouldManage, err := h.shouldManage(helmRelease); err != nil { + return helmRelease, err + } else if !shouldManage { + return helmRelease, nil + } + if helmRelease.DeletionTimestamp != nil { + return helmRelease, nil + } + releaseKey := releaseKeyFromRelease(helmRelease) + latestRelease, err := h.releases.Last(releaseKey.Namespace, releaseKey.Name) + if err != nil { + if err == driver.ErrReleaseNotFound { + logrus.Warnf("waiting for release %s/%s to be found to reconcile HelmRelease %s, deleting any orphaned resources", releaseKey.Namespace, releaseKey.Name, helmRelease.GetName()) + h.lockableObjectSetRegister.Delete(releaseKey, true) // remove the objectset and purge any untracked resources + helmRelease.Status.Version = 0 + helmRelease.Status.Description = "Could not find Helm Release Secret" + helmRelease.Status.State = v1alpha1.SecretNotFoundState + helmRelease.Status.Notes = "" + return h.helmReleases.UpdateStatus(helmRelease) + } + return helmRelease, fmt.Errorf("unable to find latest Helm Release Secret tied to Helm Release %s: %s", helmRelease.GetName(), err) + } + logrus.Infof("loading latest release version %d of HelmRelease %s", latestRelease.Version, helmRelease.GetName()) + releaseInfo := newReleaseInfo(latestRelease) + helmRelease, err = h.helmReleases.UpdateStatus(releaseInfo.GetUpdatedStatus(helmRelease)) + if err != nil { + return helmRelease, fmt.Errorf("unable to update status of HelmRelease %s: %s", helmRelease.GetName(), err) + } + if !releaseInfo.Locked() { + // TODO: add status + logrus.Infof("detected HelmRelease %s is not deployed or transitioning (state is %s), unlocking release", helmRelease.GetName(), releaseInfo.State) + h.lockableObjectSetRegister.Unlock(releaseKey) + h.recorder.Eventf(helmRelease, corev1.EventTypeNormal, "Transitioning", "Unlocked HelmRelease %s/%s to allow changes while Helm operation is being executed", helmRelease.Namespace, helmRelease.Name) + return helmRelease, nil + } + manifestOS, err := parser.Parse(releaseInfo.Manifest) + if err != nil { + // TODO: add status + return helmRelease, fmt.Errorf("unable to parse objectset from manifest for HelmRelease %s: %s", helmRelease.GetName(), err) + } + logrus.Infof("detected HelmRelease %s is deployed, locking release %s with %d objects", helmRelease.GetName(), releaseKey, len(manifestOS.All())) + locked := true + h.lockableObjectSetRegister.Set(releaseKey, manifestOS, &locked) + return helmRelease, nil +} diff --git a/pkg/helm-locker/controllers/release/decode.go b/pkg/helm-locker/controllers/release/decode.go new file mode 100644 index 0000000..7fdf999 --- /dev/null +++ b/pkg/helm-locker/controllers/release/decode.go @@ -0,0 +1,45 @@ +package release + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "encoding/json" + "io/ioutil" + + rspb "helm.sh/helm/v3/pkg/release" +) + +// decodeRelease decodes the bytes of data into a release +// type. Data must contain a base64 encoded gzipped string of a +// valid release, otherwise an error is returned. +func decodeRelease(data string) (*rspb.Release, error) { + // base64 decode string + b, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return nil, err + } + + // For backwards compatibility with releases that were stored before + // compression was introduced we skip decompression if the + // gzip magic header is not found + if bytes.Equal(b[0:3], []byte{0x1f, 0x8b, 0x08}) { + r, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + defer r.Close() + b2, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + b = b2 + } + + var rls rspb.Release + // unmarshal release object bytes + if err := json.Unmarshal(b, &rls); err != nil { + return nil, err + } + return &rls, nil +} diff --git a/pkg/helm-locker/controllers/release/info.go b/pkg/helm-locker/controllers/release/info.go new file mode 100644 index 0000000..02a2e8a --- /dev/null +++ b/pkg/helm-locker/controllers/release/info.go @@ -0,0 +1,53 @@ +package release + +import ( + v1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" + rspb "helm.sh/helm/v3/pkg/release" +) + +func newReleaseInfo(release *rspb.Release) *releaseInfo { + info := &releaseInfo{} + info.Version = int(release.Version) + info.Manifest = release.Manifest + if release.Info != nil { + info.Description = release.Info.Description + info.Notes = release.Info.Notes + switch release.Info.Status { + case rspb.StatusUnknown: + info.State = v1alpha1.UnknownState + case rspb.StatusDeployed: + info.State = v1alpha1.DeployedState + case rspb.StatusUninstalled: + info.State = v1alpha1.UninstalledState + case rspb.StatusSuperseded: + // note: this should never be the case since we always get the latest secret + info.State = v1alpha1.ErrorState + case rspb.StatusFailed: + info.State = v1alpha1.FailedState + default: + // uninstalling, pending install, pending upgrade, pending rollback + info.State = v1alpha1.TransitioningState + } + } + return info +} + +type releaseInfo struct { + Version int + Manifest string + Description string + Notes string + State string +} + +func (i *releaseInfo) Locked() bool { + return i.State == v1alpha1.DeployedState +} + +func (i *releaseInfo) GetUpdatedStatus(helmRelease *v1alpha1.HelmRelease) *v1alpha1.HelmRelease { + helmRelease.Status.Version = i.Version + helmRelease.Status.Description = i.Description + helmRelease.Status.State = i.State + helmRelease.Status.Notes = i.Notes + return helmRelease +} diff --git a/pkg/helm-locker/controllers/release/utils.go b/pkg/helm-locker/controllers/release/utils.go new file mode 100644 index 0000000..b6b3077 --- /dev/null +++ b/pkg/helm-locker/controllers/release/utils.go @@ -0,0 +1,43 @@ +package release + +import ( + "fmt" + + v1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" + "github.com/rancher/wrangler/pkg/relatedresource" + corev1 "k8s.io/api/core/v1" +) + +const ( + // HelmReleaseSecretType is the type of a secret that is considered a Helm Release secret + HelmReleaseSecretType = "helm.sh/release.v1" +) + +func releaseKeyToString(key relatedresource.Key) string { + return fmt.Sprintf("%s/%s", key.Namespace, key.Name) +} + +func releaseKeyFromRelease(release *v1alpha1.HelmRelease) relatedresource.Key { + return relatedresource.Key{ + Namespace: release.Spec.Release.Namespace, + Name: release.Spec.Release.Name, + } +} + +func releaseKeyFromSecret(secret *corev1.Secret) *relatedresource.Key { + if !isHelmReleaseSecret(secret) { + return nil + } + releaseNameFromLabel, ok := secret.GetLabels()["name"] + if !ok { + return nil + } + return &relatedresource.Key{ + Namespace: secret.GetNamespace(), + Name: releaseNameFromLabel, + } +} + +func isHelmReleaseSecret(secret *corev1.Secret) bool { + return secret.Type == HelmReleaseSecretType +} diff --git a/pkg/helm-locker/crd/crds.go b/pkg/helm-locker/crd/crds.go new file mode 100644 index 0000000..2d8a11a --- /dev/null +++ b/pkg/helm-locker/crd/crds.go @@ -0,0 +1,104 @@ +package crd + +import ( + "context" + "io" + "os" + "path/filepath" + + v1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" + "github.com/rancher/wrangler/pkg/crd" + "github.com/rancher/wrangler/pkg/yaml" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/rest" +) + +// WriteFile writes CRDs to the path specified +func WriteFile(filename string) error { + if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { + return err + } + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + return Print(f) +} + +// Print prints CRDs to out +func Print(out io.Writer) error { + obj, err := Objects(false) + if err != nil { + return err + } + data, err := yaml.Export(obj...) + if err != nil { + return err + } + + _, err = out.Write(data) + return err +} + +// Objects returns runtime.Objects for every CRD +func Objects(v1beta1 bool) (result []runtime.Object, err error) { + for _, crdDef := range List() { + if v1beta1 { + crd, err := crdDef.ToCustomResourceDefinitionV1Beta1() + if err != nil { + return nil, err + } + result = append(result, crd) + } else { + crd, err := crdDef.ToCustomResourceDefinition() + if err != nil { + return nil, err + } + result = append(result, crd) + } + } + return +} + +// List returns the set of CRDs that need to be generated +func List() []crd.CRD { + return []crd.CRD{ + newCRD(&v1alpha1.HelmRelease{}, func(c crd.CRD) crd.CRD { + return c. + WithColumn("Release Name", ".spec.release.name"). + WithColumn("Release Namespace", ".spec.release.namespace"). + WithColumn("Version", ".status.version"). + WithColumn("State", ".status.state") + }), + } +} + +// Create creates the necessary CRDs on starting this program onto the target cluster +func Create(ctx context.Context, cfg *rest.Config) error { + factory, err := crd.NewFactoryFromClient(cfg) + if err != nil { + return err + } + + return factory.BatchCreateCRDs(ctx, List()...).BatchWait() +} + +// newCRD returns the CustomResourceDefinition of an object that is customized +// according to the provided customize function +func newCRD(obj interface{}, customize func(crd.CRD) crd.CRD) crd.CRD { + crd := crd.CRD{ + GVK: schema.GroupVersionKind{ + Group: "helm.cattle.io", + Version: "v1alpha1", + }, + Status: true, + SchemaObject: obj, + } + if customize != nil { + crd = customize(crd) + } + return crd +} diff --git a/pkg/helm-locker/generated/controllers/helm.cattle.io/factory.go b/pkg/helm-locker/generated/controllers/helm.cattle.io/factory.go new file mode 100644 index 0000000..38c58cd --- /dev/null +++ b/pkg/helm-locker/generated/controllers/helm.cattle.io/factory.go @@ -0,0 +1,67 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package helm + +import ( + "github.com/rancher/wrangler/pkg/generic" + "k8s.io/client-go/rest" +) + +type Factory struct { + *generic.Factory +} + +func NewFactoryFromConfigOrDie(config *rest.Config) *Factory { + f, err := NewFactoryFromConfig(config) + if err != nil { + panic(err) + } + return f +} + +func NewFactoryFromConfig(config *rest.Config) (*Factory, error) { + return NewFactoryFromConfigWithOptions(config, nil) +} + +func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) { + return NewFactoryFromConfigWithOptions(config, &FactoryOptions{ + Namespace: namespace, + }) +} + +type FactoryOptions = generic.FactoryOptions + +func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) { + f, err := generic.NewFactoryFromConfigWithOptions(config, opts) + return &Factory{ + Factory: f, + }, err +} + +func NewFactoryFromConfigWithOptionsOrDie(config *rest.Config, opts *FactoryOptions) *Factory { + f, err := NewFactoryFromConfigWithOptions(config, opts) + if err != nil { + panic(err) + } + return f +} + +func (c *Factory) Helm() Interface { + return New(c.ControllerFactory()) +} diff --git a/pkg/helm-locker/generated/controllers/helm.cattle.io/interface.go b/pkg/helm-locker/generated/controllers/helm.cattle.io/interface.go new file mode 100644 index 0000000..0ca3c85 --- /dev/null +++ b/pkg/helm-locker/generated/controllers/helm.cattle.io/interface.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package helm + +import ( + v1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1" + "github.com/rancher/lasso/pkg/controller" +) + +type Interface interface { + V1alpha1() v1alpha1.Interface +} + +type group struct { + controllerFactory controller.SharedControllerFactory +} + +// New returns a new Interface. +func New(controllerFactory controller.SharedControllerFactory) Interface { + return &group{ + controllerFactory: controllerFactory, + } +} + +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.controllerFactory) +} diff --git a/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1/helmrelease.go b/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1/helmrelease.go new file mode 100644 index 0000000..307da41 --- /dev/null +++ b/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1/helmrelease.go @@ -0,0 +1,376 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" + "github.com/rancher/lasso/pkg/client" + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/apply" + "github.com/rancher/wrangler/pkg/condition" + "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" +) + +type HelmReleaseHandler func(string, *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) + +type HelmReleaseController interface { + generic.ControllerMeta + HelmReleaseClient + + OnChange(ctx context.Context, name string, sync HelmReleaseHandler) + OnRemove(ctx context.Context, name string, sync HelmReleaseHandler) + Enqueue(namespace, name string) + EnqueueAfter(namespace, name string, duration time.Duration) + + Cache() HelmReleaseCache +} + +type HelmReleaseClient interface { + Create(*v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) + Update(*v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) + UpdateStatus(*v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) + Delete(namespace, name string, options *metav1.DeleteOptions) error + Get(namespace, name string, options metav1.GetOptions) (*v1alpha1.HelmRelease, error) + List(namespace string, opts metav1.ListOptions) (*v1alpha1.HelmReleaseList, error) + Watch(namespace string, opts metav1.ListOptions) (watch.Interface, error) + Patch(namespace, name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.HelmRelease, err error) +} + +type HelmReleaseCache interface { + Get(namespace, name string) (*v1alpha1.HelmRelease, error) + List(namespace string, selector labels.Selector) ([]*v1alpha1.HelmRelease, error) + + AddIndexer(indexName string, indexer HelmReleaseIndexer) + GetByIndex(indexName, key string) ([]*v1alpha1.HelmRelease, error) +} + +type HelmReleaseIndexer func(obj *v1alpha1.HelmRelease) ([]string, error) + +type helmReleaseController struct { + controller controller.SharedController + client *client.Client + gvk schema.GroupVersionKind + groupResource schema.GroupResource +} + +func NewHelmReleaseController(gvk schema.GroupVersionKind, resource string, namespaced bool, controller controller.SharedControllerFactory) HelmReleaseController { + c := controller.ForResourceKind(gvk.GroupVersion().WithResource(resource), gvk.Kind, namespaced) + return &helmReleaseController{ + controller: c, + client: c.Client(), + gvk: gvk, + groupResource: schema.GroupResource{ + Group: gvk.Group, + Resource: resource, + }, + } +} + +func FromHelmReleaseHandlerToHandler(sync HelmReleaseHandler) generic.Handler { + return func(key string, obj runtime.Object) (ret runtime.Object, err error) { + var v *v1alpha1.HelmRelease + if obj == nil { + v, err = sync(key, nil) + } else { + v, err = sync(key, obj.(*v1alpha1.HelmRelease)) + } + if v == nil { + return nil, err + } + return v, err + } +} + +func (c *helmReleaseController) Updater() generic.Updater { + return func(obj runtime.Object) (runtime.Object, error) { + newObj, err := c.Update(obj.(*v1alpha1.HelmRelease)) + if newObj == nil { + return nil, err + } + return newObj, err + } +} + +func UpdateHelmReleaseDeepCopyOnChange(client HelmReleaseClient, obj *v1alpha1.HelmRelease, handler func(obj *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error)) (*v1alpha1.HelmRelease, error) { + if obj == nil { + return obj, nil + } + + copyObj := obj.DeepCopy() + newObj, err := handler(copyObj) + if newObj != nil { + copyObj = newObj + } + if obj.ResourceVersion == copyObj.ResourceVersion && !equality.Semantic.DeepEqual(obj, copyObj) { + return client.Update(copyObj) + } + + return copyObj, err +} + +func (c *helmReleaseController) AddGenericHandler(ctx context.Context, name string, handler generic.Handler) { + c.controller.RegisterHandler(ctx, name, controller.SharedControllerHandlerFunc(handler)) +} + +func (c *helmReleaseController) AddGenericRemoveHandler(ctx context.Context, name string, handler generic.Handler) { + c.AddGenericHandler(ctx, name, generic.NewRemoveHandler(name, c.Updater(), handler)) +} + +func (c *helmReleaseController) OnChange(ctx context.Context, name string, sync HelmReleaseHandler) { + c.AddGenericHandler(ctx, name, FromHelmReleaseHandlerToHandler(sync)) +} + +func (c *helmReleaseController) OnRemove(ctx context.Context, name string, sync HelmReleaseHandler) { + c.AddGenericHandler(ctx, name, generic.NewRemoveHandler(name, c.Updater(), FromHelmReleaseHandlerToHandler(sync))) +} + +func (c *helmReleaseController) Enqueue(namespace, name string) { + c.controller.Enqueue(namespace, name) +} + +func (c *helmReleaseController) EnqueueAfter(namespace, name string, duration time.Duration) { + c.controller.EnqueueAfter(namespace, name, duration) +} + +func (c *helmReleaseController) Informer() cache.SharedIndexInformer { + return c.controller.Informer() +} + +func (c *helmReleaseController) GroupVersionKind() schema.GroupVersionKind { + return c.gvk +} + +func (c *helmReleaseController) Cache() HelmReleaseCache { + return &helmReleaseCache{ + indexer: c.Informer().GetIndexer(), + resource: c.groupResource, + } +} + +func (c *helmReleaseController) Create(obj *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) { + result := &v1alpha1.HelmRelease{} + return result, c.client.Create(context.TODO(), obj.Namespace, obj, result, metav1.CreateOptions{}) +} + +func (c *helmReleaseController) Update(obj *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) { + result := &v1alpha1.HelmRelease{} + return result, c.client.Update(context.TODO(), obj.Namespace, obj, result, metav1.UpdateOptions{}) +} + +func (c *helmReleaseController) UpdateStatus(obj *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) { + result := &v1alpha1.HelmRelease{} + return result, c.client.UpdateStatus(context.TODO(), obj.Namespace, obj, result, metav1.UpdateOptions{}) +} + +func (c *helmReleaseController) Delete(namespace, name string, options *metav1.DeleteOptions) error { + if options == nil { + options = &metav1.DeleteOptions{} + } + return c.client.Delete(context.TODO(), namespace, name, *options) +} + +func (c *helmReleaseController) Get(namespace, name string, options metav1.GetOptions) (*v1alpha1.HelmRelease, error) { + result := &v1alpha1.HelmRelease{} + return result, c.client.Get(context.TODO(), namespace, name, result, options) +} + +func (c *helmReleaseController) List(namespace string, opts metav1.ListOptions) (*v1alpha1.HelmReleaseList, error) { + result := &v1alpha1.HelmReleaseList{} + return result, c.client.List(context.TODO(), namespace, result, opts) +} + +func (c *helmReleaseController) Watch(namespace string, opts metav1.ListOptions) (watch.Interface, error) { + return c.client.Watch(context.TODO(), namespace, opts) +} + +func (c *helmReleaseController) Patch(namespace, name string, pt types.PatchType, data []byte, subresources ...string) (*v1alpha1.HelmRelease, error) { + result := &v1alpha1.HelmRelease{} + return result, c.client.Patch(context.TODO(), namespace, name, pt, data, result, metav1.PatchOptions{}, subresources...) +} + +type helmReleaseCache struct { + indexer cache.Indexer + resource schema.GroupResource +} + +func (c *helmReleaseCache) Get(namespace, name string) (*v1alpha1.HelmRelease, error) { + obj, exists, err := c.indexer.GetByKey(namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(c.resource, name) + } + return obj.(*v1alpha1.HelmRelease), nil +} + +func (c *helmReleaseCache) List(namespace string, selector labels.Selector) (ret []*v1alpha1.HelmRelease, err error) { + + err = cache.ListAllByNamespace(c.indexer, namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.HelmRelease)) + }) + + return ret, err +} + +func (c *helmReleaseCache) AddIndexer(indexName string, indexer HelmReleaseIndexer) { + utilruntime.Must(c.indexer.AddIndexers(map[string]cache.IndexFunc{ + indexName: func(obj interface{}) (strings []string, e error) { + return indexer(obj.(*v1alpha1.HelmRelease)) + }, + })) +} + +func (c *helmReleaseCache) GetByIndex(indexName, key string) (result []*v1alpha1.HelmRelease, err error) { + objs, err := c.indexer.ByIndex(indexName, key) + if err != nil { + return nil, err + } + result = make([]*v1alpha1.HelmRelease, 0, len(objs)) + for _, obj := range objs { + result = append(result, obj.(*v1alpha1.HelmRelease)) + } + return result, nil +} + +type HelmReleaseStatusHandler func(obj *v1alpha1.HelmRelease, status v1alpha1.HelmReleaseStatus) (v1alpha1.HelmReleaseStatus, error) + +type HelmReleaseGeneratingHandler func(obj *v1alpha1.HelmRelease, status v1alpha1.HelmReleaseStatus) ([]runtime.Object, v1alpha1.HelmReleaseStatus, error) + +func RegisterHelmReleaseStatusHandler(ctx context.Context, controller HelmReleaseController, condition condition.Cond, name string, handler HelmReleaseStatusHandler) { + statusHandler := &helmReleaseStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, FromHelmReleaseHandlerToHandler(statusHandler.sync)) +} + +func RegisterHelmReleaseGeneratingHandler(ctx context.Context, controller HelmReleaseController, apply apply.Apply, + condition condition.Cond, name string, handler HelmReleaseGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &helmReleaseGeneratingHandler{ + HelmReleaseGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterHelmReleaseStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type helmReleaseStatusHandler struct { + client HelmReleaseClient + condition condition.Cond + handler HelmReleaseStatusHandler +} + +func (a *helmReleaseStatusHandler) sync(key string, obj *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type helmReleaseGeneratingHandler struct { + HelmReleaseGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string +} + +func (a *helmReleaseGeneratingHandler) Remove(key string, obj *v1alpha1.HelmRelease) (*v1alpha1.HelmRelease, error) { + if obj != nil { + return obj, nil + } + + obj = &v1alpha1.HelmRelease{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +func (a *helmReleaseGeneratingHandler) Handle(obj *v1alpha1.HelmRelease, status v1alpha1.HelmReleaseStatus) (v1alpha1.HelmReleaseStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.HelmReleaseGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + + return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) +} diff --git a/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1/interface.go b/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1/interface.go new file mode 100644 index 0000000..2e911ae --- /dev/null +++ b/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1/interface.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/rancher/helm-project-operator/pkg/helm-locker/apis/helm.cattle.io/v1alpha1" + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/schemes" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func init() { + schemes.Register(v1alpha1.AddToScheme) +} + +type Interface interface { + HelmRelease() HelmReleaseController +} + +func New(controllerFactory controller.SharedControllerFactory) Interface { + return &version{ + controllerFactory: controllerFactory, + } +} + +type version struct { + controllerFactory controller.SharedControllerFactory +} + +func (c *version) HelmRelease() HelmReleaseController { + return NewHelmReleaseController(schema.GroupVersionKind{Group: "helm.cattle.io", Version: "v1alpha1", Kind: "HelmRelease"}, "helmreleases", true, c.controllerFactory) +} diff --git a/pkg/helm-locker/gvk/gvk.go b/pkg/helm-locker/gvk/gvk.go new file mode 100644 index 0000000..6dc5067 --- /dev/null +++ b/pkg/helm-locker/gvk/gvk.go @@ -0,0 +1,141 @@ +package gvk + +import ( + "context" + "fmt" + "sync" + + "github.com/hashicorp/go-multierror" + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/relatedresource" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Resolver is a relatedresource.Resolver that can work on multiple GVKs +type Resolver func(gvk schema.GroupVersionKind, namespace, name string, _ runtime.Object) ([]relatedresource.Key, error) + +// ForGVK returns the relatedresource.Resolver for a particular GVK +func (r Resolver) ForGVK(gvk schema.GroupVersionKind) relatedresource.Resolver { + return func(namespace, name string, obj runtime.Object) ([]relatedresource.Key, error) { + return r(gvk, namespace, name, obj) + } +} + +// Watcher starts controllers for one or more GVKs using the provided SharedControllerFactory +// After starting a GVK controller, it will register a relatedresource.Watch using the provided +// relatedresource.Enqueuer and Resolver +type Watcher interface { + // Start will run all the watchers that have been registered thus far and deferred from starting + Start(ctx context.Context, workers int) error + // Watch will start a new watcher for a particular GVK; if the Watcher has not started yet, + // watching will be deferred till the first Start call is made. + Watch(gvk schema.GroupVersionKind) error +} + +// NewWatcher returns an object that satisfies the Watcher interface +func NewWatcher(scf controller.SharedControllerFactory, gvkResolver Resolver, enqueuer relatedresource.Enqueuer) Watcher { + return &watcher{ + scf: scf, + gvkResolver: gvkResolver, + enqueuer: enqueuer, + + gvkRegistered: make(map[schema.GroupVersionKind]bool), + gvkStarted: make(map[schema.GroupVersionKind]bool), + } +} + +// watcher is a Watcher based on a provided resolver and enqueuer +type watcher struct { + + // scf is the controller.SharedControllerFactory to use to generate controllers from + scf controller.SharedControllerFactory + + // gvkResolver is the Resolver that is used to register the relatedresource.Watch + gvkResolver Resolver + + // enqueuer is the relatedresource.Enqueuer that is used to register the relatedresource.Watch + enqueuer relatedresource.Enqueuer + + // gvkRegistered is the list of all gvks that have been registered for Watch + // note: the associated gvkControllers will not be started if this Watcher has not been started yet + gvkRegistered map[schema.GroupVersionKind]bool + // gvkStarted is the list of all gvks that have already started watching and triggering enqueues + gvkStarted map[schema.GroupVersionKind]bool + + // started is whether the Watcher has started actually registering relatedresource.Watch + started bool + // controllerCtx is the context provided on start that all watchers will use + controllerCtx context.Context + // controllerWorkers is the number of worker threads each watcher should use to process resources + controllerWorkers int + + // lock ensures concurrent calls to Watch and Start happen atomically + lock sync.RWMutex +} + +// Watch begins watching a GVK or defers its start for after Start is called +func (w *watcher) Watch(gvk schema.GroupVersionKind) error { + w.lock.Lock() + defer w.lock.Unlock() + w.gvkRegistered[gvk] = true + return w.startGVK(gvk) +} + +// Start begins watching all registered GVKs +func (w *watcher) Start(ctx context.Context, workers int) error { + w.lock.Lock() + defer w.lock.Unlock() + w.started = true + w.controllerCtx = ctx + w.controllerWorkers = workers + var multierr error + for gvk := range w.gvkRegistered { + if err := w.startGVK(gvk); err != nil { + multierr = multierror.Append(multierr, err) + } + } + return multierr +} + +// startGVK starts watching a particular GVK if the Watcher has been started +func (w *watcher) startGVK(gvk schema.GroupVersionKind) error { + if !w.started { + return nil + } + if _, ok := w.gvkStarted[gvk]; ok { + // gvk was already started + return nil + } + gvkController, err := w.scf.ForKind(gvk) + if err != nil { + return err + } + + name := fmt.Sprintf("%s Watcher", gvk) + logrus.Infof("Starting %s", name) + + // NOTE: The order here (namely, calling relatedresource.Watch before gvkController.Start) is important. + // + // By default, the controller returned by a shared controller factory is a deferred controller + // that won't populate the actual underlying controller until at least one function is called on + // the controller (e.g. Enqueue, EnqueueAfter, EnqueueKey, Informer, or RegisterHandler) + // + // Therefore, running Start on an empty controller will result in the controller never registering + // the relatedresource.Watch we provide here, since the underlying informer is nil. + + relatedresource.Watch( + w.controllerCtx, + name, + w.gvkResolver.ForGVK(gvk), + w.enqueuer, + wrapController(gvkController), + ) + + if err := gvkController.Start(w.controllerCtx, w.controllerWorkers); err != nil { + return err + } + w.gvkStarted[gvk] = true + return nil +} diff --git a/pkg/helm-locker/gvk/lister.go b/pkg/helm-locker/gvk/lister.go new file mode 100644 index 0000000..bcaa43d --- /dev/null +++ b/pkg/helm-locker/gvk/lister.go @@ -0,0 +1,44 @@ +package gvk + +import ( + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" +) + +// Lister is any object that can list a set of GVKs or return an error +type Lister interface { + List() ([]schema.GroupVersionKind, error) +} + +// NewLister returns an object that implements the Lister interface +func NewLister(discovery discovery.DiscoveryInterface) Lister { + return &lister{ + discovery: discovery, + } +} + +// lister implements the Lister interface given the provided discovery interface +type lister struct { + discovery discovery.DiscoveryInterface +} + +// List returns a list of schema.GroupVersionKinds that you can run informers on +func (l *lister) List() ([]schema.GroupVersionKind, error) { + _, resources, err := l.discovery.ServerGroupsAndResources() + if err != nil { + return nil, err + } + var gvks []schema.GroupVersionKind + for _, resource := range resources { + for _, apiResource := range resource.APIResources { + if strings.Contains(apiResource.Name, "/") { + // Ignore subresources + continue + } + gvks = append(gvks, schema.FromAPIVersionAndKind(resource.GroupVersion, apiResource.Kind)) + } + } + return gvks, nil +} diff --git a/pkg/helm-locker/gvk/wrapper.go b/pkg/helm-locker/gvk/wrapper.go new file mode 100644 index 0000000..497076d --- /dev/null +++ b/pkg/helm-locker/gvk/wrapper.go @@ -0,0 +1,24 @@ +package gvk + +import ( + "context" + + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/pkg/relatedresource" +) + +// sharedControllerToWrapper converts a SharedController to a relatedresource.ControllerWrapper +type sharedControllerToWrapper struct { + controller.SharedController +} + +// AddGenericHandler registers a generic Handler on a SharedController +func (w sharedControllerToWrapper) AddGenericHandler(ctx context.Context, name string, handler generic.Handler) { + w.RegisterHandler(ctx, name, controller.SharedControllerHandlerFunc(handler)) +} + +// wrapController returns a relatedresource.ControllerWrapper on top of a SharedController +func wrapController(c controller.SharedController) relatedresource.ControllerWrapper { + return sharedControllerToWrapper{c} +} diff --git a/pkg/helm-locker/informerfactory/wrapper.go b/pkg/helm-locker/informerfactory/wrapper.go new file mode 100644 index 0000000..bdc60f1 --- /dev/null +++ b/pkg/helm-locker/informerfactory/wrapper.go @@ -0,0 +1,29 @@ +package informerfactory + +import ( + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/apply" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/cache" +) + +// New wraps the provided SharedControllerFactory to satisfy the apply.InformerFactory interface +func New(scf controller.SharedControllerFactory) apply.InformerFactory { + return informerFactoryWrapper{ + SharedControllerFactory: scf, + } +} + +// informerFactoryWrapper satisfies the apply.InformerFactory interface +type informerFactoryWrapper struct { + controller.SharedControllerFactory +} + +// Get returns a cache.SharedIndexInformer for a given GVK from the SharedControllerFactory +func (w informerFactoryWrapper) Get(gvk schema.GroupVersionKind, _ schema.GroupVersionResource) (cache.SharedIndexInformer, error) { + controller, err := w.ForKind(gvk) + if err != nil { + return nil, nil + } + return controller.Informer(), nil +} diff --git a/pkg/helm-locker/objectset/cache.go b/pkg/helm-locker/objectset/cache.go new file mode 100644 index 0000000..8a53321 --- /dev/null +++ b/pkg/helm-locker/objectset/cache.go @@ -0,0 +1,370 @@ +package objectset + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/rancher/helm-project-operator/pkg/helm-locker/gvk" + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/objectset" + "github.com/rancher/wrangler/pkg/relatedresource" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" +) + +// LockableRegister implements Register and Locker +type LockableRegister interface { + Register + Locker +} + +// Register can keep track of sets of ObjectSets +type Register interface { + relatedresource.Enqueuer + + // Set allows you to set and lock an objectset associated with a specific key + // if os or locked are not provided, the currently persisted values will be used + Set(key relatedresource.Key, os *objectset.ObjectSet, locked *bool) + + // Delete allows you to delete an objectset associated with a specific key + Delete(key relatedresource.Key, purge bool) +} + +// Locker can lock or unlock object sets tied to a specific key +type Locker interface { + // Lock allows you to lock an objectset associated with a specific key + Lock(key relatedresource.Key) + + // Unlock allows you to unlock an objectset associated with a specific key + Unlock(key relatedresource.Key) +} + +// newLockableObjectSetRegisterAndCache returns: +// 1) a LockableRegister that allows registering new ObjectSets, locking them, unlocking them, or deleting them +// 2) a cache.SharedIndexInformer that listens to events on objectSetStates that are created from interacting with the provided register +// +// Note: This function is intentionally internal since the cache.SharedIndexInformer responds to an internal runtime.Object type (objectSetState) +func newLockableObjectSetRegisterAndCache(scf controller.SharedControllerFactory, triggerOnDelete func(string, bool)) (LockableRegister, cache.SharedIndexInformer) { + c := lockableObjectSetRegisterAndCache{ + stateByKey: make(map[relatedresource.Key]*objectSetState), + keyByResourceKeyByGVK: make(map[schema.GroupVersionKind]map[relatedresource.Key]relatedresource.Key), + + stateChanges: make(chan watch.Event, 50), + + triggerOnDelete: triggerOnDelete, + } + // initialize watcher that populates watch queue + c.gvkWatcher = gvk.NewWatcher(scf, c.Resolve, &c) + // initialize informer + c.SharedIndexInformer = cache.NewSharedIndexInformer(&c, &objectSetState{}, 10*time.Hour, cache.Indexers{ + cache.NamespaceIndex: cache.MetaNamespaceIndexFunc, + }) + return &c, &c +} + +// lockableObjectSetRegisterAndCache is a cache.SharedIndexInformer that operates on objectSetStates +// and implements the LockableRegister interface via the informer +// +// internal note: also implements cache.ListerWatcher on objectSetStates +// internal note: also implements watch.Interface on objectSetStates +type lockableObjectSetRegisterAndCache struct { + cache.SharedIndexInformer + + // stateChanges is the internal channel tracking events that happen to ObjectSetStates + stateChanges chan watch.Event + // gvkWatcher watches all GVKs tied to resources tracked by any ObjectSet tracked by this register + // It will automatically trigger an Enqueue on seeing changes, which will trigger an event that + // the underlying cache.SharedIndexInformer will process + gvkWatcher gvk.Watcher + // started represents whether the cache has been started yet + started bool + // startLock is a lock that prevents a Watch from occurring before the Informer has been started + startLock sync.RWMutex + + // stateByKey is a map that keeps track of the desired state of the Register + stateByKey map[relatedresource.Key]*objectSetState + // stateMapLock is a lock on the stateByKey map + stateMapLock sync.RWMutex + + // keyByResourceKeyByGVK is a map that keeps track of which resources are tied to a particular ObjectSet + // This is used to make resolving the objectset on seeing changes to underlying resources more efficient + keyByResourceKeyByGVK map[schema.GroupVersionKind]map[relatedresource.Key]relatedresource.Key + // keyMapLock is a lock on the keyByResourceKeyByGVK map + keyMapLock sync.RWMutex + + // triggerOnDelete allows registering a function that gets called on a delete from the cache + // purge indicates whether or not the triggerOnDelete function is expected to purge underlying + // resources on deleting an objectSet + triggerOnDelete func(key string, purge bool) +} + +// init initializes the register and the cache +func (c *lockableObjectSetRegisterAndCache) init() { + c.startLock.Lock() + defer c.startLock.Unlock() + // do not start twice + if !c.started { + c.started = true + } +} + +// Run starts the objectSetState informer and starts watching GVKs tracked by ObjectSets +func (c *lockableObjectSetRegisterAndCache) Run(stopCh <-chan struct{}) { + c.init() + err := c.gvkWatcher.Start(context.TODO(), 50) + if err != nil { + logrus.Errorf("unable to watch gvks: %s", err) + } + + c.SharedIndexInformer.Run(stopCh) +} + +// Stop is a noop +// Allows implementing watch.Interface on objectSetStates +func (c *lockableObjectSetRegisterAndCache) Stop() {} + +// ResultChan returns the channel that watch.Events on objectSetStates are registered on +// Allows implementing watch.Interface on objectSetStates +func (c *lockableObjectSetRegisterAndCache) ResultChan() <-chan watch.Event { + return c.stateChanges +} + +// List returns an objectSetStateList +// Allows implementing cache.ListerWatcher on objectSetStates +func (c *lockableObjectSetRegisterAndCache) List(options metav1.ListOptions) (runtime.Object, error) { + c.stateMapLock.RLock() + defer c.stateMapLock.RUnlock() + objectSetStateList := &objectSetStateList{} + for _, objectSetState := range c.stateByKey { + if objectSetState != nil { + objectSetStateList.Items = append(objectSetStateList.Items, *objectSetState) + } + } + objectSetStateList.ResourceVersion = options.ResourceVersion + return objectSetStateList, nil +} + +// List returns an watch.Interface if the cache has been started that watches for events on objectSetStates +// Allows implementing cache.ListerWatcher on objectSetStates +func (c *lockableObjectSetRegisterAndCache) Watch(_ metav1.ListOptions) (watch.Interface, error) { + c.startLock.RLock() + defer c.startLock.RUnlock() + if !c.started { + return nil, fmt.Errorf("cache is not started yet") + } + return c, nil +} + +// Set allows you to set and lock an objectset associated with a specific key +func (c *lockableObjectSetRegisterAndCache) Set(key relatedresource.Key, os *objectset.ObjectSet, locked *bool) { + logrus.Debugf("set objectset for %s/%s", key.Namespace, key.Name) + c.setState(key, os, locked, false) +} + +// Lock allows you to lock an objectset associated with a specific key +func (c *lockableObjectSetRegisterAndCache) Lock(key relatedresource.Key) { + logrus.Debugf("locking %s/%s", key.Namespace, key.Name) + s, ok := c.getState(key) + if !ok { + // nothing to lock + return + } + if s.ObjectSet == nil { + // nothing to lock + return + } + c.lock(key, s.ObjectSet) +} + +// Unlock allows you to unlock an objectset associated with a specific key +func (c *lockableObjectSetRegisterAndCache) Unlock(key relatedresource.Key) { + logrus.Debugf("unlocking %s/%s", key.Namespace, key.Name) + c.unlock(key) +} + +// Delete allows you to delete an objectset associated with a specific key +func (c *lockableObjectSetRegisterAndCache) Delete(key relatedresource.Key, purge bool) { + logrus.Debugf("deleting %s/%s", key.Namespace, key.Name) + c.deleteState(key) + c.triggerOnDelete(fmt.Sprintf("%s/%s", key.Namespace, key.Name), purge) +} + +// Enqueue allows you to enqueue an objectset associated with a specific key +func (c *lockableObjectSetRegisterAndCache) Enqueue(namespace, name string) { + key := keyFunc(namespace, name) + c.setState(key, nil, nil, true) +} + +// Resolve allows you to resolve an object seen in the cluster to an ObjectSet tracked in this LockableRegister +// Objects will only be resolved if the LockableRegister has locked this ObjectSet +func (c *lockableObjectSetRegisterAndCache) Resolve(gvk schema.GroupVersionKind, namespace, name string, _ runtime.Object) ([]relatedresource.Key, error) { + resourceKey := keyFunc(namespace, name) + + c.keyMapLock.RLock() + defer c.keyMapLock.RUnlock() + keyByResourceKey, ok := c.keyByResourceKeyByGVK[gvk] + if !ok { + // do nothing since we're not watching this GVK anymore + return nil, nil + } + key, ok := keyByResourceKey[resourceKey] + if !ok { + // do nothing since the resource is not tied to a set + return nil, nil + } + logrus.Infof("detected change in %s/%s (%s), enqueuing objectset %s/%s", namespace, name, gvk, key.Namespace, key.Name) + return []relatedresource.Key{key}, nil +} + +// getState returns the underlying objectSetState for a given key +func (c *lockableObjectSetRegisterAndCache) getState(key relatedresource.Key) (*objectSetState, bool) { + c.stateMapLock.RLock() + defer c.stateMapLock.RUnlock() + state, ok := c.stateByKey[key] + return state, ok +} + +// setState allows a user to set the objectSetState for a given key +func (c *lockableObjectSetRegisterAndCache) setState(key relatedresource.Key, os *objectset.ObjectSet, locked *bool, forceEnqueue bool) { + // get old state and use as the base + originalState, modifying := c.getState(key) + var s *objectSetState + + // generate new state to be set + if !modifying { + s = newObjectSetState(key.Namespace, key.Name, objectSetState{}) + } else { + s = originalState.DeepCopy() + s.Generation++ + s.ResourceVersion = fmt.Sprintf("%d", s.Generation) + } + + // apply provided settings or use original state as default + if os != nil { + s.ObjectSet = os + } + if locked != nil { + s.Locked = *locked + } + + // do nothing if the object has not changed + objectChanged := forceEnqueue || !modifying + if modifying { + objectChanged = objectChanged || s.ObjectSet != originalState.ObjectSet || s.Locked != originalState.Locked + } + if !objectChanged { + return + } + + // handle adding events and storing state + c.stateMapLock.Lock() + defer c.stateMapLock.Unlock() + if modifying { + c.stateChanges <- watch.Event{Type: watch.Modified, Object: s} + } else { + c.stateChanges <- watch.Event{Type: watch.Added, Object: s} + } + c.stateByKey[key] = s + logrus.Debugf("set state for %s/%s: locked %t, os %p, objectMeta: %v", s.Namespace, s.Name, s.Locked, s.ObjectSet, s.ObjectMeta) +} + +// deleteState deletes anything on the register for a given key +func (c *lockableObjectSetRegisterAndCache) deleteState(key relatedresource.Key) { + s, exists := c.getState(key) + if !exists { + // nothing to add, event was already processed + return + } + c.stateMapLock.Lock() + delete(c.stateByKey, key) + c.stateMapLock.Unlock() + + s.ObjectSet = nil + s.Locked = false + c.stateChanges <- watch.Event{Type: watch.Deleted, Object: s} +} + +// lock adds entries to the register to ensure that resources tracked by this ObjectSet are resolved to this ObjectSet +func (c *lockableObjectSetRegisterAndCache) lock(key relatedresource.Key, os *objectset.ObjectSet) error { + c.keyMapLock.Lock() + defer c.keyMapLock.Unlock() + + if err := c.canLock(key, os); err != nil { + return err + } + + c.removeAllEntries(key) + + objectsByGVK := os.ObjectsByGVK() + + for gvk, objMap := range objectsByGVK { + keyByResourceKey, ok := c.keyByResourceKeyByGVK[gvk] + if !ok { + keyByResourceKey = make(map[relatedresource.Key]relatedresource.Key) + } + for objKey := range objMap { + resourceKey := keyFunc(objKey.Namespace, objKey.Name) + keyByResourceKey[resourceKey] = key + } + c.keyByResourceKeyByGVK[gvk] = keyByResourceKey + + // ensure that we are watching this new GVK + if err := c.gvkWatcher.Watch(gvk); err != nil { + return err + } + } + + return nil +} + +// unlock removes all entries to the register tied to a particular ObjectSet by key +func (c *lockableObjectSetRegisterAndCache) unlock(key relatedresource.Key) { + c.keyMapLock.Lock() + defer c.keyMapLock.Unlock() + + c.removeAllEntries(key) +} + +// canLock returns whether trynig to lock the provided ObjectSet will result in an error +// One of the few reasons why this is possible is if two registered ObjectSets are attempting to track the same resource +func (c *lockableObjectSetRegisterAndCache) canLock(key relatedresource.Key, os *objectset.ObjectSet) error { + objectsByGVK := os.ObjectsByGVK() + for gvk, objMap := range objectsByGVK { + keyByResourceKey, ok := c.keyByResourceKeyByGVK[gvk] + if !ok { + continue + } + for objKey := range objMap { + resourceKey := keyFunc(objKey.Namespace, objKey.Name) + currKey, ok := keyByResourceKey[resourceKey] + if ok && currKey != key { + // object is already associated with another set + return fmt.Errorf("cannot lock objectset for %s: object %s is already associated with key %s", key, objKey, currKey) + } + } + } + return nil +} + +// removeAllEntries removes all entries to the register tied to a particular ObjectSet by key +// Note: This is a thread-unsafe version of +func (c *lockableObjectSetRegisterAndCache) removeAllEntries(key relatedresource.Key) { + for gvk, keyByResourceKey := range c.keyByResourceKeyByGVK { + for resourceKey, currSetKey := range keyByResourceKey { + if key == currSetKey { + delete(keyByResourceKey, resourceKey) + } + } + if len(keyByResourceKey) == 0 { + delete(c.keyByResourceKeyByGVK, gvk) + } else { + c.keyByResourceKeyByGVK[gvk] = keyByResourceKey + } + } +} diff --git a/pkg/helm-locker/objectset/controller.go b/pkg/helm-locker/objectset/controller.go new file mode 100644 index 0000000..7815a63 --- /dev/null +++ b/pkg/helm-locker/objectset/controller.go @@ -0,0 +1,56 @@ +package objectset + +import ( + "context" + "time" + + "github.com/rancher/helm-project-operator/pkg/helm-locker/gvk" + "github.com/rancher/helm-project-operator/pkg/helm-locker/informerfactory" + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/apply" + "github.com/rancher/wrangler/pkg/start" + "k8s.io/client-go/discovery" + "k8s.io/client-go/util/workqueue" +) + +// NewLockableRegister returns a starter that starts an ObjectSetController listening to events on ObjectSetStates +// and a LockableRegister that allows you to register new states for ObjectSets in memory +func NewLockableRegister(name string, apply apply.Apply, scf controller.SharedControllerFactory, discovery discovery.DiscoveryInterface, opts *controller.Options) (start.Starter, LockableRegister, *controller.SharedHandler) { + // Define a new cache + apply = apply.WithCacheTypeFactory(informerfactory.New(scf)) + + handler := handler{ + apply: apply, + gvkLister: gvk.NewLister(discovery), + sharedHandler: &controller.SharedHandler{}, + } + + lockableObjectSetRegister, objectSetCache := newLockableObjectSetRegisterAndCache(scf, handler.OnRemove) + + handler.locker = lockableObjectSetRegister + + startCache := func(ctx context.Context) error { + go objectSetCache.Run(ctx.Done()) + return nil + } + + // Define a new controller that responds to events from the cache + objectSetController := controller.New(name, objectSetCache, startCache, &handler, applyDefaultOptions(opts)) + + return wrapStarter(objectSetController), lockableObjectSetRegister, handler.sharedHandler +} + +// applyDefaultOptions applies default controller options if none are provided +func applyDefaultOptions(opts *controller.Options) *controller.Options { + var newOpts controller.Options + if opts != nil { + newOpts = *opts + } + if newOpts.RateLimiter == nil { + newOpts.RateLimiter = workqueue.NewMaxOfRateLimiter( + workqueue.NewItemFastSlowRateLimiter(time.Millisecond, 2*time.Minute, 30), + workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 30*time.Second), + ) + } + return &newOpts +} diff --git a/pkg/helm-locker/objectset/handler.go b/pkg/helm-locker/objectset/handler.go new file mode 100644 index 0000000..217cc03 --- /dev/null +++ b/pkg/helm-locker/objectset/handler.go @@ -0,0 +1,103 @@ +package objectset + +import ( + "fmt" + + "github.com/rancher/helm-project-operator/pkg/helm-locker/gvk" + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/apply" + "github.com/rancher/wrangler/pkg/relatedresource" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime" +) + +type handler struct { + apply apply.Apply + gvkLister gvk.Lister + locker Locker + + // allows us to add hooks into triggering certain actions on reconciles, e.g. launching events + sharedHandler *controller.SharedHandler +} + +// configureApply configures the apply object for the provided setID and objectSetState +func (h *handler) configureApply(setID string, oss *objectSetState) apply.Apply { + apply := h.apply. + WithSetID("object-set-applier"). + WithOwnerKey(setID, internalGroupVersion.WithKind("objectSetState")) + + if oss != nil && oss.ObjectSet != nil { + apply = apply.WithGVK(oss.ObjectSet.GVKs()...) + } else { + // if we cannot infer the GVK from the provided object set, include all GVKs in the cache types + gvks, err := h.gvkLister.List() + if err != nil { + logrus.Errorf("unable to list GVKs to apply deletes on objects, objectset %s may require manual cleanup: %s", setID, err) + } else { + apply = apply.WithGVK(gvks...) + } + } + + return apply +} + +// OnChange reconciles the resources tracked by an objectSetState +func (h *handler) OnChange(setID string, obj runtime.Object) error { + logrus.Debugf("on change: %s", setID) + + if obj == nil { + // nothing to do + return nil + } + oss, ok := obj.(*objectSetState) + if !ok { + return fmt.Errorf("expected object of type objectSetState, found %t", obj) + } + + if oss.DeletionTimestamp != nil { + return nil + } + + key := relatedresource.FromString(setID) + h.locker.Unlock(key) // ensure that apply does not trigger locking again + + if !oss.Locked { + // nothing to do + return nil + } + // Run the apply + defer h.locker.Lock(key) + + logrus.Debugf("running apply for %s...", setID) + if err := h.configureApply(setID, oss).Apply(oss.ObjectSet); err != nil { + return fmt.Errorf("failed to apply objectset for %s: %s", setID, err) + } + + logrus.Infof("applied %s", setID) + + go h.sharedHandler.OnChange(setID, obj) + + return nil +} + +// OnRemove cleans up the resources tracked by an objectSetState +func (h *handler) OnRemove(setID string, purge bool) { + logrus.Debugf("on delete: %s", setID) + + key := relatedresource.FromString(setID) + + h.locker.Unlock(key) + + if !purge { + return + } + + logrus.Debugf("running apply for %s...", setID) + if err := h.configureApply(setID, nil).ApplyObjects(); err != nil { + logrus.Errorf("failed to clean up objectset %s: %s", setID, err) + } + + logrus.Infof("applied %s", setID) + + go h.sharedHandler.OnChange(setID, nil) +} diff --git a/pkg/helm-locker/objectset/parser/parse.go b/pkg/helm-locker/objectset/parser/parse.go new file mode 100644 index 0000000..291ea8d --- /dev/null +++ b/pkg/helm-locker/objectset/parser/parse.go @@ -0,0 +1,33 @@ +package parser + +import ( + "bytes" + + "github.com/rancher/wrangler/pkg/objectset" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/yaml" +) + +// Parse parses the runtime.Objects tracked in a Kubernetes manifest (represented as a string) into an ObjectSet +func Parse(manifest string) (*objectset.ObjectSet, error) { + var multierr error + + var u unstructured.Unstructured + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000) + os := objectset.NewObjectSet() + for { + uCopy := u.DeepCopy() + err := decoder.Decode(uCopy) + if err != nil { + break + } + if uCopy.GetAPIVersion() == "" || uCopy.GetKind() == "" { + // Encountered empty YAML document but successfully decoded, skip + continue + } + os = os.Add(uCopy) + logrus.Debugf("obj: %s, Kind=%s (%s/%s)", uCopy.GetAPIVersion(), uCopy.GetKind(), uCopy.GetName(), uCopy.GetNamespace()) + } + return os, multierr +} diff --git a/pkg/helm-locker/objectset/state.go b/pkg/helm-locker/objectset/state.go new file mode 100644 index 0000000..50b3871 --- /dev/null +++ b/pkg/helm-locker/objectset/state.go @@ -0,0 +1,117 @@ +package objectset + +import ( + "time" + + "github.com/google/uuid" + "github.com/rancher/wrangler/pkg/objectset" + "github.com/rancher/wrangler/pkg/schemes" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" +) + +var ( + internalGroupVersion = schema.GroupVersion{Group: "internal.cattle.io", Version: "v1alpha1"} +) + +// init adds the internal type to the default scheme for wrangler.apply to be able to use to add owner key details +func init() { + schemes.Register(addInternalTypes) +} + +// Adds the list of known types to Scheme. +func addInternalTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(internalGroupVersion, &objectSetState{}, &objectSetStateList{}) + metav1.AddToGroupVersion(scheme, internalGroupVersion) + return nil +} + +// newObjectSetState returns a new objectSetState for internal consumption +func newObjectSetState(namespace, name string, obj objectSetState) *objectSetState { + obj.APIVersion, obj.Kind = internalGroupVersion.WithKind("objectSetState").ToAPIVersionAndKind() + obj.Name = name + obj.Namespace = namespace + // set values normally set by the server for an internal object + obj.Generation = 0 + obj.UID = types.UID(uuid.New().String()) + obj.CreationTimestamp = metav1.NewTime(time.Now()) + obj.ResourceVersion = "0" + return &obj +} + +// objectSetState represents the state of an object set. This resource is only intended for +// internal use by this controller +type objectSetState struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // ObjectSet is a pointer to the underlying ObjectSet whose state is being tracked + ObjectSet *objectset.ObjectSet `json:"objectSet,omitempty"` + + // Locked represents whether the ObjectSet should be locked in the cluster or not + Locked bool `json:"locked"` +} + +// DeepCopyInto is a deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *objectSetState) DeepCopyInto(out *objectSetState) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.ObjectSet = in.ObjectSet + out.Locked = in.Locked +} + +// DeepCopy is a deepcopy function, copying the receiver, creating a new objectSetState. +func (in *objectSetState) DeepCopy() *objectSetState { + if in == nil { + return nil + } + out := new(objectSetState) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is a deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *objectSetState) DeepCopyObject() runtime.Object { + return in.DeepCopy() +} + +// objectSetStateList represents a list of objectSetStates +type objectSetStateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + // Items are the objectSetStates tracked by this list + Items []objectSetState `json:"items"` +} + +// DeepCopyInto is a deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *objectSetStateList) DeepCopyInto(out *objectSetStateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]objectSetState, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is a deepcopy function, copying the receiver, creating a new objectSetStateList. +func (in *objectSetStateList) DeepCopy() *objectSetStateList { + if in == nil { + return nil + } + out := new(objectSetStateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is a deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *objectSetStateList) DeepCopyObject() runtime.Object { + return in.DeepCopy() +} diff --git a/pkg/helm-locker/objectset/utils.go b/pkg/helm-locker/objectset/utils.go new file mode 100644 index 0000000..da50f50 --- /dev/null +++ b/pkg/helm-locker/objectset/utils.go @@ -0,0 +1,13 @@ +package objectset + +import ( + "github.com/rancher/wrangler/pkg/relatedresource" +) + +// keyFunc is a utility function that returns a relatedresource.Key from a namespace and a name +func keyFunc(namespace, name string) relatedresource.Key { + return relatedresource.Key{ + Namespace: namespace, + Name: name, + } +} diff --git a/pkg/helm-locker/objectset/wrapper.go b/pkg/helm-locker/objectset/wrapper.go new file mode 100644 index 0000000..bd0a34c --- /dev/null +++ b/pkg/helm-locker/objectset/wrapper.go @@ -0,0 +1,23 @@ +package objectset + +import ( + "context" + + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/pkg/start" +) + +// controllerToStarterWrapper wraps the generic controller.Controller interface with a dummy call for Sync +type controllerToStarterWrapper struct { + controller.Controller +} + +// Sync does a noop +func (w controllerToStarterWrapper) Sync(_ context.Context) error { + return nil +} + +// wrapStarter returns a start.Starter around a controller.Controller +func wrapStarter(c controller.Controller) start.Starter { + return controllerToStarterWrapper{c} +} diff --git a/pkg/helm-locker/releases/releases.go b/pkg/helm-locker/releases/releases.go new file mode 100644 index 0000000..a117c7d --- /dev/null +++ b/pkg/helm-locker/releases/releases.go @@ -0,0 +1,47 @@ +package releases + +import ( + "sync" + + "github.com/sirupsen/logrus" + rspb "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage" + "helm.sh/helm/v3/pkg/storage/driver" + "k8s.io/client-go/kubernetes" +) + +type HelmReleaseGetter interface { + Last(namespace, name string) (*rspb.Release, error) +} + +func NewHelmReleaseGetter(k8s kubernetes.Interface) HelmReleaseGetter { + return &latestReleaseGetter{ + K8s: k8s, + namespacedStorage: make(map[string]*storage.Storage), + } +} + +type latestReleaseGetter struct { + K8s kubernetes.Interface + + namespacedStorage map[string]*storage.Storage + storageLock sync.Mutex +} + +func (g *latestReleaseGetter) getStore(namespace string) *storage.Storage { + g.storageLock.Lock() + defer g.storageLock.Unlock() + store, ok := g.namespacedStorage[namespace] + if ok && store != nil { + return store + } + store = storage.Init(driver.NewSecrets(g.K8s.CoreV1().Secrets(namespace))) + store.Log = logrus.Debugf + g.namespacedStorage[namespace] = store + return store +} + +func (g *latestReleaseGetter) Last(namespace, name string) (*rspb.Release, error) { + store := g.getStore(namespace) + return store.Last(name) +} diff --git a/pkg/helm-locker/remove/handler.go b/pkg/helm-locker/remove/handler.go new file mode 100644 index 0000000..891cfb4 --- /dev/null +++ b/pkg/helm-locker/remove/handler.go @@ -0,0 +1,39 @@ +package remove + +import ( + "context" + + "github.com/rancher/wrangler/pkg/generic" + "k8s.io/apimachinery/pkg/runtime" +) + +// Controller is an interface that allows the ScopedOnRemoveHandler to register a generic RemoveHandler +type Controller interface { + AddGenericHandler(ctx context.Context, name string, handler generic.Handler) + Updater() generic.Updater +} + +// ScopeFunc is a function that determines whether the ScopedOnRemoveHandler should manage the lifecycle of the given object +type ScopeFunc func(key string, obj runtime.Object) (bool, error) + +// RegisterScopedOnRemoveHandler registers a handler that does the same thing as an OnRemove handler but only applies finalizers or sync logic +// to objects that pass the provided scopeFunc; this ensures that finalizers are not added to all resources across an entire cluster but are +// instead only scoped to resources that this controller is meant to watch. +// +// TODO: move this to rancher/wrangler as a generic construct to be used across multiple controllers as part of the auto-generated code +func RegisterScopedOnRemoveHandler(ctx context.Context, controller Controller, name string, scopeFunc ScopeFunc, handler generic.Handler) { + onRemove := generic.NewRemoveHandler(name, controller.Updater(), handler) + controller.AddGenericHandler(ctx, name, func(key string, obj runtime.Object) (runtime.Object, error) { + if obj == nil { + return nil, nil + } + isScoped, err := scopeFunc(key, obj) + if err != nil { + return obj, err + } + if !isScoped { + return obj, nil + } + return onRemove(key, obj) + }) +} diff --git a/pkg/helm-locker/version/version.go b/pkg/helm-locker/version/version.go new file mode 100644 index 0000000..943ce3d --- /dev/null +++ b/pkg/helm-locker/version/version.go @@ -0,0 +1,13 @@ +package version + +import "fmt" + +var ( + Version = "v0.0.0-dev" + GitCommit = "HEAD" +) + +// FriendlyVersion returns the version to be displayed on running --version +func FriendlyVersion() string { + return fmt.Sprintf("%s (%s)", Version, GitCommit) +} From 5396999577b7647badd03aa9127bbcea133bd8c8 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 11:49:23 -0400 Subject: [PATCH 13/48] Adjust build target logic on build scripts --- scripts/build | 14 ++++++++++---- scripts/build-chart | 8 +++++++- scripts/default | 2 -- scripts/package | 3 +-- scripts/version | 4 +++- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/scripts/build b/scripts/build index 9b00767..89f0af0 100755 --- a/scripts/build +++ b/scripts/build @@ -1,22 +1,28 @@ #!/bin/bash set -e -BUILD_TARGET_CMD=${BUILD_TARGET_CMD:-"./${BUILD_TARGET}.go"} source $(dirname $0)/version +BUILD_CMD_TARGET=${BUILD_CMD_TARGET:-"./cmd/${BUILD_TARGET}/main.go"} cd $(dirname $0)/.. ./scripts/build-chart +echo "Building \`${BUILD_TARGET}\` from \`${BUILD_CMD_TARGET}\`"; + mkdir -p bin if [ "$(uname)" = "Linux" ]; then OTHER_LINKFLAGS="-extldflags -static -s" fi LINKFLAGS="-X github.com/rancher/helm-project-operator/pkg/version.Version=$VERSION" LINKFLAGS="-X github.com/rancher/helm-project-operator/pkg/version.GitCommit=$COMMIT $LINKFLAGS" -CGO_ENABLED=0 go build -ldflags "$LINKFLAGS $OTHER_LINKFLAGS" -o bin/helm-project-operator "${BUILD_TARGET_CMD}" +BIN_DEST="bin/${BUILD_TARGET}" +CGO_ENABLED=0 go build -ldflags "$LINKFLAGS $OTHER_LINKFLAGS" -o "${BIN_DEST}" "${BUILD_CMD_TARGET}" if [ "$CROSS" = "true" ] && [ "$ARCH" = "amd64" ]; then - GOOS=darwin go build -ldflags "$LINKFLAGS" -o bin/helm-project-operator-darwin "${BUILD_TARGET_CMD}" - GOOS=windows go build -ldflags "$LINKFLAGS" -o bin/helm-project-operator-windows "${BUILD_TARGET_CMD}" + BIN_DEST="${BIN_DEST}, bin/${BUILD_TARGET}-darwin, bin/${BUILD_TARGET}-windows" + GOOS=darwin go build -ldflags "$LINKFLAGS" -o "bin/${BUILD_TARGET}-darwin" "${BUILD_CMD_TARGET}" + GOOS=windows go build -ldflags "$LINKFLAGS" -o "bin/${BUILD_TARGET}-windows" "${BUILD_CMD_TARGET}" fi + +echo "Build complete, binary at: \`"${BIN_DEST}"\`" \ No newline at end of file diff --git a/scripts/build-chart b/scripts/build-chart index 3d1bc01..0a65c20 100755 --- a/scripts/build-chart +++ b/scripts/build-chart @@ -1,6 +1,12 @@ #!/bin/bash set -e +BUILD_TARGET=${BUILD_TARGET:-"helm-project-operator"} + +if [[ "${BUILD_TARGET}" != "helm-project-operator" ]]; then + exit +fi + source $(dirname $0)/version cd $(dirname $0)/.. @@ -9,5 +15,5 @@ CHART=${CHART:-"project-operator-example"} VERSION=0.0.0 helm package charts/${CHART} --destination bin/${CHART} -base64 -i bin/${CHART}/${CHART}-${VERSION}.tgz > bin/${CHART}/${CHART}.tgz.base64 +base64 -i bin/${CHART}/${CHART}-${VERSION}.tgz > cmd/${BUILD_TARGET}/fs/${CHART}.tgz.base64 rm bin/${CHART}/${CHART}-${VERSION}.tgz \ No newline at end of file diff --git a/scripts/default b/scripts/default index 87ac486..be04b1e 100755 --- a/scripts/default +++ b/scripts/default @@ -1,8 +1,6 @@ #!/bin/bash set -e -BUILD_TARGET=${BUILD_TARGET:-"helm-project-operator"} - cd $(dirname $0) export BUILD_TARGET diff --git a/scripts/package b/scripts/package index da9f299..c89b309 100755 --- a/scripts/package +++ b/scripts/package @@ -1,9 +1,8 @@ #!/bin/bash set -e -DOCKER_TARGET=${DOCKER_TARGET:-"-${BUILD_TARGET}"} - source $(dirname $0)/version +DOCKER_TARGET=${DOCKER_TARGET:-"-${BUILD_TARGET}"} cd $(dirname $0)/.. echo Building ${IMAGE} ... diff --git a/scripts/version b/scripts/version index 30abcef..0ae2e21 100755 --- a/scripts/version +++ b/scripts/version @@ -1,5 +1,7 @@ #!/bin/bash +BUILD_TARGET=${BUILD_TARGET:-"helm-project-operator"} + if [ -n "$(git status --porcelain --untracked-files=no)" ]; then DIRTY="-dirty" fi @@ -19,4 +21,4 @@ REPO=${REPO:-rancher} if echo $TAG | grep -q dirty; then TAG=dev fi -IMAGE=${IMAGE:-${REPO}/helm-project-operator:${TAG}} \ No newline at end of file +IMAGE=${IMAGE:-${REPO}/${BUILD_TARGET}:${TAG}} \ No newline at end of file From d11e5705f2c0162582a90dc7a923128a170a415c Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 11:50:51 -0400 Subject: [PATCH 14/48] Move helm-project-operator to new cmd --- .gitignore | 3 ++- helm-project-operator.go => cmd/helm-project-operator/main.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename helm-project-operator.go => cmd/helm-project-operator/main.go (96%) diff --git a/.gitignore b/.gitignore index d1ebb81..d80e9d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ /.dapper /.cache /bin +/cmd/helm-project-operator/fs/project-operator-example.tgz.base64 /dist *.swp .idea /helm-project-operator -/.vscode \ No newline at end of file +/.vscode diff --git a/helm-project-operator.go b/cmd/helm-project-operator/main.go similarity index 96% rename from helm-project-operator.go rename to cmd/helm-project-operator/main.go index 0582854..5fe5ca7 100644 --- a/helm-project-operator.go +++ b/cmd/helm-project-operator/main.go @@ -28,7 +28,7 @@ var ( // DummySystemNamespaces is the system namespaces scoped for the dummy project-operator-example chart. DummySystemNamespaces = []string{"kube-system"} - //go:embed bin/project-operator-example/project-operator-example.tgz.base64 + //go:embed fs/project-operator-example.tgz.base64 base64TgzChart string debugConfig command.DebugConfig From 03f32b4131b316e8d37e34b5994c258b4fa111bb Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 11:51:07 -0400 Subject: [PATCH 15/48] Add helm-locker local CLI --- cmd/helm-locker/main.go | 67 +++++++++++++++++++++++ package/Dockerfile-helm-locker | 16 ++++++ pkg/helm-locker/controllers/controller.go | 1 + 3 files changed, 84 insertions(+) create mode 100644 cmd/helm-locker/main.go create mode 100644 package/Dockerfile-helm-locker diff --git a/cmd/helm-locker/main.go b/cmd/helm-locker/main.go new file mode 100644 index 0000000..5ea8d6d --- /dev/null +++ b/cmd/helm-locker/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "log" + "net/http" + _ "net/http/pprof" + + "github.com/rancher/helm-project-operator/pkg/helm-locker/controllers" + "github.com/rancher/helm-project-operator/pkg/helm-locker/crd" + "github.com/rancher/helm-project-operator/pkg/helm-locker/version" + command "github.com/rancher/wrangler-cli" + _ "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io" + _ "github.com/rancher/wrangler/pkg/generated/controllers/networking.k8s.io" + "github.com/rancher/wrangler/pkg/kubeconfig" + "github.com/rancher/wrangler/pkg/ratelimit" + "github.com/spf13/cobra" +) + +var ( + debugConfig command.DebugConfig +) + +type HelmLocker struct { + Kubeconfig string `usage:"Kubeconfig file" env:"KUBECONFIG"` + Namespace string `usage:"Namespace to watch for HelmReleases" default:"cattle-helm-system" env:"NAMESPACE"` + ControllerName string `usage:"Unique name to identify this controller that is added to all HelmReleases tracked by this controller" default:"helm-locker" env:"CONTROLLER_NAME"` + NodeName string `usage:"Name of the node this controller is running on" env:"NODE_NAME"` +} + +func (a *HelmLocker) Run(cmd *cobra.Command, _ []string) error { + if len(a.Namespace) == 0 { + return fmt.Errorf("helm-locker can only be started in a single namespace") + } + + go func() { + log.Println(http.ListenAndServe("localhost:6060", nil)) + }() + debugConfig.MustSetupDebug() + + cfg := kubeconfig.GetNonInteractiveClientConfig(a.Kubeconfig) + clientConfig, err := cfg.ClientConfig() + if err != nil { + return err + } + clientConfig.RateLimiter = ratelimit.None + + ctx := cmd.Context() + if err := crd.Create(ctx, clientConfig); err != nil { + return err + } + + if err := controllers.Register(ctx, a.Namespace, a.ControllerName, a.NodeName, cfg); err != nil { + return err + } + + <-cmd.Context().Done() + return nil +} + +func main() { + cmd := command.Command(&HelmLocker{}, cobra.Command{ + Version: version.FriendlyVersion(), + }) + cmd = command.AddDebug(cmd, &debugConfig) + command.Main(cmd) +} diff --git a/package/Dockerfile-helm-locker b/package/Dockerfile-helm-locker new file mode 100644 index 0000000..3dd3d26 --- /dev/null +++ b/package/Dockerfile-helm-locker @@ -0,0 +1,16 @@ +FROM --platform=$BUILDPLATFORM registry.suse.com/bci/golang:1.22 AS builder +WORKDIR /usr/src/app +RUN zypper -n install git vim less file curl wget +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN BUILD_TARGET=helm-locker make build + +FROM registry.suse.com/bci/bci-micro:15.5 +RUN echo 'helmlocker:x:1000:1000::/home/helmlocker:/bin/bash' >> /etc/passwd && \ + echo 'helmlocker:x:1000:' >> /etc/group && \ + mkdir /home/helmlocker && \ + chown -R helmlocker:helmlocker /home/helmlocker +COPY --from=builder /usr/src/app/bin/helm-locker /usr/bin/ +USER helmlocker +CMD ["helm-locker"] diff --git a/pkg/helm-locker/controllers/controller.go b/pkg/helm-locker/controllers/controller.go index d541a45..ea1b410 100644 --- a/pkg/helm-locker/controllers/controller.go +++ b/pkg/helm-locker/controllers/controller.go @@ -6,6 +6,7 @@ import ( "time" "github.com/rancher/helm-project-operator/pkg/helm-locker/controllers/release" + "github.com/rancher/helm-project-operator/pkg/helm-locker/generated/controllers/helm.cattle.io" helmcontroller "github.com/rancher/helm-project-operator/pkg/helm-locker/generated/controllers/helm.cattle.io/v1alpha1" "github.com/rancher/helm-project-operator/pkg/helm-locker/objectset" "github.com/rancher/lasso/pkg/cache" From 43bad6697947b14515529716c5b57efd9d60c74c Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 12:19:51 -0400 Subject: [PATCH 16/48] Reorg GHA ci files --- .../package/{Dockerfile => Dockerfile-helm-project-operator} | 0 .github/workflows/{ci.yaml => hpo-ci.yaml} | 0 .github/workflows/{e2e-ci.yaml => hpo-e2e-ci.yaml} | 0 .../workflows/{publish-image.yaml => hpo-publish-image.yaml} | 0 docs/helm-project-operator/experimental/e2e.md | 4 ++-- 5 files changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/e2e/package/{Dockerfile => Dockerfile-helm-project-operator} (100%) rename .github/workflows/{ci.yaml => hpo-ci.yaml} (100%) rename .github/workflows/{e2e-ci.yaml => hpo-e2e-ci.yaml} (100%) rename .github/workflows/{publish-image.yaml => hpo-publish-image.yaml} (100%) diff --git a/.github/workflows/e2e/package/Dockerfile b/.github/workflows/e2e/package/Dockerfile-helm-project-operator similarity index 100% rename from .github/workflows/e2e/package/Dockerfile rename to .github/workflows/e2e/package/Dockerfile-helm-project-operator diff --git a/.github/workflows/ci.yaml b/.github/workflows/hpo-ci.yaml similarity index 100% rename from .github/workflows/ci.yaml rename to .github/workflows/hpo-ci.yaml diff --git a/.github/workflows/e2e-ci.yaml b/.github/workflows/hpo-e2e-ci.yaml similarity index 100% rename from .github/workflows/e2e-ci.yaml rename to .github/workflows/hpo-e2e-ci.yaml diff --git a/.github/workflows/publish-image.yaml b/.github/workflows/hpo-publish-image.yaml similarity index 100% rename from .github/workflows/publish-image.yaml rename to .github/workflows/hpo-publish-image.yaml diff --git a/docs/helm-project-operator/experimental/e2e.md b/docs/helm-project-operator/experimental/e2e.md index c9b3fd0..2e34f7a 100644 --- a/docs/helm-project-operator/experimental/e2e.md +++ b/docs/helm-project-operator/experimental/e2e.md @@ -2,7 +2,7 @@ ## What does E2E CI do? -The E2E CI described in [.github/scripts/](../../../.github/workflows/e2e-ci.yaml) checks out the current Git repository, builds a Docker image using the repository's build scripts, sets up a [k3d](https://k3d.io) cluster, imports the built `helm-project-operator` image into the cluster (which automatically uses the latest `project-operator-example` chart since it is embedded into the binary as part of the build process), and then uses Helm to install `helm-project-operator` (using the Helm chart contained in the repository). +The E2E CI described in [.github/scripts/](../../../.github/workflows/hpo-e2e-ci.yaml) checks out the current Git repository, builds a Docker image using the repository's build scripts, sets up a [k3d](https://k3d.io) cluster, imports the built `helm-project-operator` image into the cluster (which automatically uses the latest `project-operator-example` chart since it is embedded into the binary as part of the build process), and then uses Helm to install `helm-project-operator` (using the Helm chart contained in the repository). Once it is installed, it will run checks to ensure that all workloads are up and running in the Helm install and then mimic creating a Project (by creating a namespace with a particular label on it). @@ -14,7 +14,7 @@ Finally, it deletes the ProjectHelmChart, asserts the helm uninstall Job on the To run the end-to-end GitHub Workflow CI locally to test whether your changes work, it's recommended to install [`nektos/act`](https://github.com/nektos/act). -An slim image has been defined in [`.github/workflows/e2e/package/Dockerfile`](../../../.github/workflows/e2e/package/Dockerfile) that has the necessary dependencies to be used as a Runner for act for this GitHub Workflow. To build the image, run the following commmand (make sure you re-run it if you make any changes to add dependencies): +An slim image has been defined in [`.github/workflows/e2e/package/Dockerfile-helm-project-operator`](../../../.github/workflows/e2e/package/Dockerfile-helm-project-operator) that has the necessary dependencies to be used as a Runner for act for this GitHub Workflow. To build the image, run the following commmand (make sure you re-run it if you make any changes to add dependencies): ```bash docker build -f ./.github/workflows/e2e/package/Dockerfile-helm-project-operator -t rancher/helm-project-operator-e2e:latest . From e8e9f460516c65a45dc4d813a2e58e00ba119091 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 12:23:13 -0400 Subject: [PATCH 17/48] Add helm-locker GHA into local repo --- .github/workflows/e2e/scripts/install-k3d.sh | 17 ++++++ .github/workflows/hl-ci.yaml | 23 ++++++++ .github/workflows/hl-e2e.yaml | 43 +++++++++++++++ .github/workflows/hl-publish.yaml | 58 ++++++++++++++++++++ .github/workflows/lint.yaml | 31 +++++++++++ 5 files changed, 172 insertions(+) create mode 100755 .github/workflows/e2e/scripts/install-k3d.sh create mode 100644 .github/workflows/hl-ci.yaml create mode 100644 .github/workflows/hl-e2e.yaml create mode 100644 .github/workflows/hl-publish.yaml create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/e2e/scripts/install-k3d.sh b/.github/workflows/e2e/scripts/install-k3d.sh new file mode 100755 index 0000000..51ed39f --- /dev/null +++ b/.github/workflows/e2e/scripts/install-k3d.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e +set -x + +K3D_URL=https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh +DEFAULT_K3D_VERSION=v5.4.6 + +install_k3d(){ + local k3dVersion=${K3D_VERSION:-${DEFAULT_K3D_VERSION}} + echo -e "Downloading k3d@${k3dVersion} see: ${K3D_URL}" + curl --silent --fail ${K3D_URL} | TAG=${k3dVersion} bash +} + +install_k3d + +k3d version \ No newline at end of file diff --git a/.github/workflows/hl-ci.yaml b/.github/workflows/hl-ci.yaml new file mode 100644 index 0000000..10e9ae1 --- /dev/null +++ b/.github/workflows/hl-ci.yaml @@ -0,0 +1,23 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name : Set up Go + uses : actions/setup-go@v5 + with: + go-version: 1.22 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Run CI + run: make ci diff --git a/.github/workflows/hl-e2e.yaml b/.github/workflows/hl-e2e.yaml new file mode 100644 index 0000000..0018df0 --- /dev/null +++ b/.github/workflows/hl-e2e.yaml @@ -0,0 +1,43 @@ +name: CI-e2e + +on: + pull_request: + push: + branches: + - main + +env: + CLUSTER_NAME : test-cluster + K3S_VERSION : v1.27.9-k3s1 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name : Set up Go + uses : actions/setup-go@v5 + with: + go-version: 1.22 + - name : Setup up kubectl + run : | + curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" + sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl + - name: Set up k3d + run : ./.github/workflows/scripts/install-k3d.sh + - name: build + run: make build + - name : Setup cluster + run : CLUSTER_NAME=${{ env.CLUSTER_NAME }} K3S_VERSION=${{ env.K3S_VERSION }} ./scripts/setup-cluster.sh + # temporary hack to run the helm-locker controller in the k3d cluster + - name : run helm-locker + run : | + kubectl create ns cattle-helm-system + ./bin/helm-locker & + - name : run e2e tests + run: | + k3d kubeconfig get ${{ env.CLUSTER_NAME }} > kubeconfig.yaml + export KUBECONFIG=$(pwd)/kubeconfig.yaml + cd tests && KUBECONFIG=$KUBECONFIG go test -v -race -timeout 30m ./... diff --git a/.github/workflows/hl-publish.yaml b/.github/workflows/hl-publish.yaml new file mode 100644 index 0000000..7da8e7b --- /dev/null +++ b/.github/workflows/hl-publish.yaml @@ -0,0 +1,58 @@ +name : Publish Images + +on: + push: + tags: + - "*" + +env: + REGISTRY: docker.io + +jobs: + push: + name : Build and push helm-locker images + runs-on : ubuntu-latest + permissions: + contents : read + id-token: write + steps: + - name : "Read Secrets" + uses : rancher-eio/read-vault-secrets@main + with: + secrets: | + secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ; + secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD + - name : Checkout repository + uses: actions/checkout@v4 + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.DOCKER_USERNAME }} + password: ${{ env.DOCKER_PASSWORD }} + - name : Setup go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.DOCKER_USERNAME }} + password: ${{ env.DOCKER_PASSWORD }} + - name : Build, test & validate + run : make ci + - name : Export version info + run : | + source ./scripts/version + echo IMAGE=$IMAGE >> $GITHUB_ENV + - name: Build and push helm-locker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./package/Dockerfile + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE }} + platforms : linux/amd64,linux/arm64 \ No newline at end of file diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..15d3cfe --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,31 @@ +name: golangci-lint +on: + pull_request: + paths-ignore: + - 'docs/**' + - '*.md' + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.22' + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + # Require: The version of golangci-lint to use. + # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. + # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. + version: v1.56 \ No newline at end of file From f310e1c8250b77e415494fce5dad55cb2cc5bc09 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 14:22:44 -0400 Subject: [PATCH 18/48] Add go generate step to linter --- .github/workflows/lint.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 15d3cfe..711a15e 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -22,6 +22,7 @@ jobs: - uses: actions/setup-go@v5 with: go-version: '1.22' + - run: go generate - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: From e89bd9336a4a09893a8a5c583faa01f2385537a6 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 15:07:31 -0400 Subject: [PATCH 19/48] Add make build chart to lint job --- .github/workflows/lint.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 711a15e..2c53293 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -23,6 +23,7 @@ jobs: with: go-version: '1.22' - run: go generate + - run: make build-chart - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: From a8cc1251fdd944d6027d0821271feb6a0ff47bb2 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 15:31:37 -0400 Subject: [PATCH 20/48] switch from ioutil to io --- pkg/helm-locker/controllers/release/decode.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/helm-locker/controllers/release/decode.go b/pkg/helm-locker/controllers/release/decode.go index 7fdf999..340933b 100644 --- a/pkg/helm-locker/controllers/release/decode.go +++ b/pkg/helm-locker/controllers/release/decode.go @@ -5,9 +5,8 @@ import ( "compress/gzip" "encoding/base64" "encoding/json" - "io/ioutil" - rspb "helm.sh/helm/v3/pkg/release" + "io" ) // decodeRelease decodes the bytes of data into a release @@ -29,7 +28,7 @@ func decodeRelease(data string) (*rspb.Release, error) { return nil, err } defer r.Close() - b2, err := ioutil.ReadAll(r) + b2, err := io.ReadAll(r) if err != nil { return nil, err } From b99ed1f261739d46b9a3a8c662f6bce12a19b1f5 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 15:44:57 -0400 Subject: [PATCH 21/48] Fix linting issues --- cmd/helm-project-operator/main.go | 2 +- go.mod | 2 +- pkg/controllers/hardened/controller.go | 4 ++-- pkg/controllers/hardened/resolvers.go | 4 ++-- pkg/controllers/namespace/controller.go | 4 ++-- pkg/controllers/namespace/resolvers.go | 2 +- pkg/controllers/project/controller.go | 6 +++--- pkg/controllers/project/resolvers.go | 10 +++++----- pkg/controllers/project/status.go | 12 ++++++------ pkg/crd/crds.go | 6 +++--- pkg/helm-locker/controllers/release/decode.go | 3 ++- 11 files changed, 28 insertions(+), 27 deletions(-) diff --git a/cmd/helm-project-operator/main.go b/cmd/helm-project-operator/main.go index 5fe5ca7..67fcc59 100644 --- a/cmd/helm-project-operator/main.go +++ b/cmd/helm-project-operator/main.go @@ -41,7 +41,7 @@ type DummyOperator struct { Kubeconfig string `usage:"Kubeconfig file"` } -func (o *DummyOperator) Run(cmd *cobra.Command, args []string) error { +func (o *DummyOperator) Run(cmd *cobra.Command, _ []string) error { go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() diff --git a/go.mod b/go.mod index ab032df..c22b9bd 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 gopkg.in/yaml.v2 v2.4.0 + helm.sh/helm/v3 v3.8.0 k8s.io/api v0.23.3 k8s.io/apimachinery v0.23.3 k8s.io/client-go v0.23.3 @@ -79,7 +80,6 @@ require ( gopkg.in/gorp.v1 v1.7.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - helm.sh/helm/v3 v3.8.0 // indirect k8s.io/apiextensions-apiserver v0.23.1 // indirect k8s.io/code-generator v0.23.3 // indirect k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect diff --git a/pkg/controllers/hardened/controller.go b/pkg/controllers/hardened/controller.go index 8a8f7ac..56b94ec 100644 --- a/pkg/controllers/hardened/controller.go +++ b/pkg/controllers/hardened/controller.go @@ -24,7 +24,7 @@ type handler struct { func Register( ctx context.Context, apply apply.Apply, - opts common.HardeningOptions, + _ common.HardeningOptions, namespaces corecontroller.NamespaceController, namespaceCache corecontroller.NamespaceCache, serviceaccounts corecontroller.ServiceAccountController, @@ -48,7 +48,7 @@ func Register( namespaces.OnChange(ctx, "harden-hpo-operated-namespace", h.OnChange) } -func (h *handler) OnChange(name string, namespace *corev1.Namespace) (*corev1.Namespace, error) { +func (h *handler) OnChange(_ string, namespace *corev1.Namespace) (*corev1.Namespace, error) { if namespace == nil { return namespace, nil } diff --git a/pkg/controllers/hardened/resolvers.go b/pkg/controllers/hardened/resolvers.go index 2daa98a..8a960ec 100644 --- a/pkg/controllers/hardened/resolvers.go +++ b/pkg/controllers/hardened/resolvers.go @@ -46,7 +46,7 @@ func (h *handler) resolveHardenedProjectRegistrationNamespaceData(namespace, nam return nil, nil } -func (h *handler) resolveServiceAccount(namespace, name string, serviceAccount *corev1.ServiceAccount) ([]relatedresource.Key, error) { +func (h *handler) resolveServiceAccount(namespace, name string, _ *corev1.ServiceAccount) ([]relatedresource.Key, error) { // check if name matches if name == defaultServiceAccountName { return []relatedresource.Key{{ @@ -56,7 +56,7 @@ func (h *handler) resolveServiceAccount(namespace, name string, serviceAccount * return nil, nil } -func (h *handler) resolveNetworkPolicy(namespace, name string, networkPolicy *networkingv1.NetworkPolicy) ([]relatedresource.Key, error) { +func (h *handler) resolveNetworkPolicy(namespace, name string, _ *networkingv1.NetworkPolicy) ([]relatedresource.Key, error) { // check if name matches if name == defaultNetworkPolicyName { return []relatedresource.Key{{ diff --git a/pkg/controllers/namespace/controller.go b/pkg/controllers/namespace/controller.go index 6e18003..d402f69 100644 --- a/pkg/controllers/namespace/controller.go +++ b/pkg/controllers/namespace/controller.go @@ -106,7 +106,7 @@ func Register( // Single Namespace Handler -func (h *handler) OnSingleNamespaceChange(name string, namespace *corev1.Namespace) (*corev1.Namespace, error) { +func (h *handler) OnSingleNamespaceChange(_ string, namespace *corev1.Namespace) (*corev1.Namespace, error) { if namespace.Name != h.systemNamespace { // enqueue system namespace to ensure that rolebindings are updated @@ -129,7 +129,7 @@ func (h *handler) OnSingleNamespaceChange(name string, namespace *corev1.Namespa // Multiple Namespaces Handler -func (h *handler) OnMultiNamespaceChange(name string, namespace *corev1.Namespace) (*corev1.Namespace, error) { +func (h *handler) OnMultiNamespaceChange(_ string, namespace *corev1.Namespace) (*corev1.Namespace, error) { if namespace == nil { logrus.Debugf("OnMultiNamespaceChange() called with no namespace.") return namespace, nil diff --git a/pkg/controllers/namespace/resolvers.go b/pkg/controllers/namespace/resolvers.go index 7b09e9b..9ab2e9f 100644 --- a/pkg/controllers/namespace/resolvers.go +++ b/pkg/controllers/namespace/resolvers.go @@ -34,7 +34,7 @@ func (h *handler) resolveProjectRegistrationNamespaceData(namespace, name string return nil, nil } -func (h *handler) resolveConfigMap(namespace, name string, configmap *corev1.ConfigMap) ([]relatedresource.Key, error) { +func (h *handler) resolveConfigMap(namespace, name string, _ *corev1.ConfigMap) ([]relatedresource.Key, error) { // check if name matches if name == h.getConfigMapName() { return []relatedresource.Key{{ diff --git a/pkg/controllers/project/controller.go b/pkg/controllers/project/controller.go index 34bc401..a591af7 100644 --- a/pkg/controllers/project/controller.go +++ b/pkg/controllers/project/controller.go @@ -128,7 +128,7 @@ func Register( }) remove.RegisterScopedOnRemoveHandler(ctx, projectHelmCharts, "on-project-helm-chart-remove", - func(key string, obj runtime.Object) (bool, error) { + func(_ string, obj runtime.Object) (bool, error) { if obj == nil { return false, nil } @@ -315,7 +315,7 @@ func (h *handler) OnChange(projectHelmChart *v1alpha1.ProjectHelmChart, projectH return objs, projectHelmChartStatus, nil } -func (h *handler) OnRemove(key string, projectHelmChart *v1alpha1.ProjectHelmChart) (*v1alpha1.ProjectHelmChart, error) { +func (h *handler) OnRemove(_ string, projectHelmChart *v1alpha1.ProjectHelmChart) (*v1alpha1.ProjectHelmChart, error) { if projectHelmChart == nil { return nil, nil } @@ -326,7 +326,7 @@ func (h *handler) OnRemove(key string, projectHelmChart *v1alpha1.ProjectHelmCha return projectHelmChart, err } - // Get orphaned release namsepace and apply it; if another ProjectHelmChart exists in this namespace, it will automatically remove + // Get orphaned release namespace and apply it; if another ProjectHelmChart exists in this namespace, it will automatically remove // the orphaned label on enqueuing the namespace since that will enqueue all ProjectHelmCharts associated with it projectReleaseNamespace := h.getProjectReleaseNamespace(projectID, true, projectHelmChart) if projectReleaseNamespace == nil { diff --git a/pkg/controllers/project/resolvers.go b/pkg/controllers/project/resolvers.go index a5cfe70..a26e62b 100644 --- a/pkg/controllers/project/resolvers.go +++ b/pkg/controllers/project/resolvers.go @@ -46,7 +46,7 @@ func (h *handler) initResolvers(ctx context.Context) { // Project Release Namespace -func (h *handler) resolveProjectReleaseNamespace(namespace, name string, obj runtime.Object) ([]relatedresource.Key, error) { +func (h *handler) resolveProjectReleaseNamespace(_, _ string, obj runtime.Object) ([]relatedresource.Key, error) { if obj == nil { return nil, nil } @@ -62,7 +62,7 @@ func (h *handler) resolveProjectReleaseNamespace(namespace, name string, obj run // System Namespace Data -func (h *handler) resolveSystemNamespaceData(namespace, name string, obj runtime.Object) ([]relatedresource.Key, error) { +func (h *handler) resolveSystemNamespaceData(namespace, _ string, obj runtime.Object) ([]relatedresource.Key, error) { if namespace != h.systemNamespace { return nil, nil } @@ -98,7 +98,7 @@ func (h *handler) resolveProjectRegistrationNamespaceData(namespace, name string return nil, nil } -func (h *handler) resolveProjectRegistrationNamespaceRoleBinding(namespace, name string, rb *rbacv1.RoleBinding) ([]relatedresource.Key, error) { +func (h *handler) resolveProjectRegistrationNamespaceRoleBinding(namespace, _ string, rb *rbacv1.RoleBinding) ([]relatedresource.Key, error) { namespaceObj, err := h.namespaceCache.Get(namespace) if err != nil { logrus.Debugf("Namespace not found %s: ", namespace) @@ -134,7 +134,7 @@ func (h *handler) resolveProjectRegistrationNamespaceRoleBinding(namespace, name return keys, nil } -func (h *handler) resolveClusterRoleBinding(namespace, name string, crb *rbacv1.ClusterRoleBinding) ([]relatedresource.Key, error) { +func (h *handler) resolveClusterRoleBinding(_, _ string, crb *rbacv1.ClusterRoleBinding) ([]relatedresource.Key, error) { // we want to re-enqueue the ProjectHelmChart if the rolebinding's ref points to one of the operator default roles _, isDefaultRoleRef := common.IsDefaultClusterRoleRef(h.opts, crb.RoleRef.Name) if !isDefaultRoleRef { @@ -174,7 +174,7 @@ func (h *handler) resolveClusterRoleBinding(namespace, name string, crb *rbacv1. // Project Release Namespace Data -func (h *handler) resolveProjectReleaseNamespaceData(namespace, name string, obj runtime.Object) ([]relatedresource.Key, error) { +func (h *handler) resolveProjectReleaseNamespaceData(_, _ string, obj runtime.Object) ([]relatedresource.Key, error) { if obj == nil { return nil, nil } diff --git a/pkg/controllers/project/status.go b/pkg/controllers/project/status.go index 40f50a6..afe5fa1 100644 --- a/pkg/controllers/project/status.go +++ b/pkg/controllers/project/status.go @@ -8,7 +8,7 @@ import ( ) // getCleanupStatus returns the status on seeing the cleanup label on a ProjectHelmChart -func (h *handler) getCleanupStatus(projectHelmChart *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus) v1alpha1.ProjectHelmChartStatus { +func (h *handler) getCleanupStatus(projectHelmChart *v1alpha1.ProjectHelmChart, _ v1alpha1.ProjectHelmChartStatus) v1alpha1.ProjectHelmChartStatus { return v1alpha1.ProjectHelmChartStatus{ Status: "AwaitingOperatorRedeployment", StatusMessage: fmt.Sprintf( @@ -21,7 +21,7 @@ func (h *handler) getCleanupStatus(projectHelmChart *v1alpha1.ProjectHelmChart, } // getUnableToCreateHelmReleaseStatus returns the status on seeing a conflicting ProjectHelmChart already tracking the desired Helm release -func (h *handler) getUnableToCreateHelmReleaseStatus(projectHelmChart *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus, err error) v1alpha1.ProjectHelmChartStatus { +func (h *handler) getUnableToCreateHelmReleaseStatus(projectHelmChart *v1alpha1.ProjectHelmChart, _ v1alpha1.ProjectHelmChartStatus, err error) v1alpha1.ProjectHelmChartStatus { releaseNamespace, releaseName := h.getReleaseNamespaceAndName(projectHelmChart) return v1alpha1.ProjectHelmChartStatus{ Status: "UnableToCreateHelmRelease", @@ -34,7 +34,7 @@ func (h *handler) getUnableToCreateHelmReleaseStatus(projectHelmChart *v1alpha1. // getNoTargetNamespacesStatus returns the status on seeing that a ProjectHelmChart's projectNamespaceSelector (or // the Project Registration Namespace's namespaceSelector) targets no namespaces -func (h *handler) getNoTargetNamespacesStatus(projectHelmChart *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus) v1alpha1.ProjectHelmChartStatus { +func (h *handler) getNoTargetNamespacesStatus(_ *v1alpha1.ProjectHelmChart, _ v1alpha1.ProjectHelmChartStatus) v1alpha1.ProjectHelmChartStatus { return v1alpha1.ProjectHelmChartStatus{ Status: "NoTargetProjectNamespaces", StatusMessage: "There are no project namespaces to deploy a ProjectHelmChart.", @@ -42,7 +42,7 @@ func (h *handler) getNoTargetNamespacesStatus(projectHelmChart *v1alpha1.Project } // getValuesParseErrorStatus returns the status on encountering an error with parsing the provided contents of spec.values on the ProjectHelmChart -func (h *handler) getValuesParseErrorStatus(projectHelmChart *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus, err error) v1alpha1.ProjectHelmChartStatus { +func (h *handler) getValuesParseErrorStatus(_ *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus, err error) v1alpha1.ProjectHelmChartStatus { // retain existing status if possible projectHelmChartStatus.Status = "UnableToParseValues" projectHelmChartStatus.StatusMessage = fmt.Sprintf("Unable to convert provided spec.values into valid configuration of ProjectHelmChart: %s", err) @@ -52,7 +52,7 @@ func (h *handler) getValuesParseErrorStatus(projectHelmChart *v1alpha1.ProjectHe // getWaitingForDashboardValuesStatus returns the transitionary status that occurs after deploying a Helm chart but before a dashboard configmap is created // If a ProjectHelmChart is stuck in this status, it is likely either an error on the Operator for not creating this ConfigMap or there might be an issue // with the underlying Job ran by the child HelmChart resource created on this ProjectHelmChart's behalf -func (h *handler) getWaitingForDashboardValuesStatus(projectHelmChart *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus) v1alpha1.ProjectHelmChartStatus { +func (h *handler) getWaitingForDashboardValuesStatus(_ *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus) v1alpha1.ProjectHelmChartStatus { // retain existing status projectHelmChartStatus.Status = "WaitingForDashboardValues" projectHelmChartStatus.StatusMessage = "Waiting for status.dashboardValues content to be provided by the deployed Helm release, but HelmChart and HelmRelease should be deployed." @@ -61,7 +61,7 @@ func (h *handler) getWaitingForDashboardValuesStatus(projectHelmChart *v1alpha1. } // getDeployedStatus returns the status that indicates the ProjectHelmChart is successfully deployed -func (h *handler) getDeployedStatus(projectHelmChart *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus) v1alpha1.ProjectHelmChartStatus { +func (h *handler) getDeployedStatus(_ *v1alpha1.ProjectHelmChart, projectHelmChartStatus v1alpha1.ProjectHelmChartStatus) v1alpha1.ProjectHelmChartStatus { // retain existing status projectHelmChartStatus.Status = "Deployed" projectHelmChartStatus.StatusMessage = "ProjectHelmChart has been successfully deployed!" diff --git a/pkg/crd/crds.go b/pkg/crd/crds.go index c1bc967..a520054 100644 --- a/pkg/crd/crds.go +++ b/pkg/crd/crds.go @@ -87,15 +87,15 @@ func Print(out io.Writer, depOut io.Writer) { if err != nil { logrus.Fatalf("%s", err) } - if err := print(out, objs); err != nil { + if err := printCrd(out, objs); err != nil { logrus.Fatalf("%s", err) } - if err := print(depOut, depObjs); err != nil { + if err := printCrd(depOut, depObjs); err != nil { logrus.Fatalf("%s", err) } } -func print(out io.Writer, objs []runtime.Object) error { +func printCrd(out io.Writer, objs []runtime.Object) error { data, err := yaml.Export(objs...) if err != nil { return err diff --git a/pkg/helm-locker/controllers/release/decode.go b/pkg/helm-locker/controllers/release/decode.go index 340933b..c3f6124 100644 --- a/pkg/helm-locker/controllers/release/decode.go +++ b/pkg/helm-locker/controllers/release/decode.go @@ -5,8 +5,9 @@ import ( "compress/gzip" "encoding/base64" "encoding/json" - rspb "helm.sh/helm/v3/pkg/release" "io" + + rspb "helm.sh/helm/v3/pkg/release" ) // decodeRelease decodes the bytes of data into a release From f660e6dd5ef25d18894da9a9e414e54121ef9f0a Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 15:49:43 -0400 Subject: [PATCH 22/48] Adjust ignore/keep for git --- .gitignore | 3 ++- cmd/helm-project-operator/fs/.gitkeep | 0 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 cmd/helm-project-operator/fs/.gitkeep diff --git a/.gitignore b/.gitignore index d80e9d2..57570d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ /.dapper /.cache /bin -/cmd/helm-project-operator/fs/project-operator-example.tgz.base64 +!/cmd/helm-project-operator/fs/.gitkeep +/cmd/helm-project-operator/fs/* /dist *.swp .idea diff --git a/cmd/helm-project-operator/fs/.gitkeep b/cmd/helm-project-operator/fs/.gitkeep new file mode 100644 index 0000000..e69de29 From dace9d844447a84cf23a2b27aae6f3b37a2af02f Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 15:51:44 -0400 Subject: [PATCH 23/48] Adjust GHA job names for package delineation --- .github/workflows/hl-ci.yaml | 2 +- .github/workflows/hl-e2e.yaml | 2 +- .github/workflows/hl-publish.yaml | 2 +- .github/workflows/hpo-ci.yaml | 2 +- .github/workflows/hpo-e2e-ci.yaml | 2 +- .github/workflows/hpo-publish-image.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hl-ci.yaml b/.github/workflows/hl-ci.yaml index 10e9ae1..00309b6 100644 --- a/.github/workflows/hl-ci.yaml +++ b/.github/workflows/hl-ci.yaml @@ -1,4 +1,4 @@ -name: CI +name: [helm-locker] CI on: pull_request: diff --git a/.github/workflows/hl-e2e.yaml b/.github/workflows/hl-e2e.yaml index 0018df0..06bd661 100644 --- a/.github/workflows/hl-e2e.yaml +++ b/.github/workflows/hl-e2e.yaml @@ -1,4 +1,4 @@ -name: CI-e2e +name: [helm-locker] CI-e2e on: pull_request: diff --git a/.github/workflows/hl-publish.yaml b/.github/workflows/hl-publish.yaml index 7da8e7b..9d03e4c 100644 --- a/.github/workflows/hl-publish.yaml +++ b/.github/workflows/hl-publish.yaml @@ -1,4 +1,4 @@ -name : Publish Images +name: [helm-locker] Publish Images on: push: diff --git a/.github/workflows/hpo-ci.yaml b/.github/workflows/hpo-ci.yaml index 469a9e3..fa1858c 100644 --- a/.github/workflows/hpo-ci.yaml +++ b/.github/workflows/hpo-ci.yaml @@ -1,4 +1,4 @@ -name: ci +name: [helm-project-operator] ci env: CGO_ENABLED: 0 diff --git a/.github/workflows/hpo-e2e-ci.yaml b/.github/workflows/hpo-e2e-ci.yaml index d633170..6a2b322 100644 --- a/.github/workflows/hpo-e2e-ci.yaml +++ b/.github/workflows/hpo-e2e-ci.yaml @@ -1,4 +1,4 @@ -name: E2E Helm Project Operator +name: [helm-project-operator] E2E Helm Project Operator on: workflow_dispatch: diff --git a/.github/workflows/hpo-publish-image.yaml b/.github/workflows/hpo-publish-image.yaml index c80d95e..46a986d 100644 --- a/.github/workflows/hpo-publish-image.yaml +++ b/.github/workflows/hpo-publish-image.yaml @@ -1,4 +1,4 @@ -name : Publish images +name: [helm-project-operator] Publish images on: push: From 5b7dcf0cb55b0fb6f12326dca8c2a743baeb7191 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 16:00:44 -0400 Subject: [PATCH 24/48] update go deps files --- go.mod | 4 ++-- go.sum | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index c22b9bd..7ebfb2c 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,8 @@ replace ( ) require ( + github.com/google/uuid v1.2.0 + github.com/hashicorp/go-multierror v1.1.0 github.com/k3s-io/helm-controller v0.13.1 github.com/rancher/lasso v0.0.0-20220303220127-8cf5555ec03c github.com/rancher/wrangler v0.8.11-0.20220217210408-3ecd23dfea3b @@ -39,10 +41,8 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.6 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.2.0 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmoiron/sqlx v1.3.4 // indirect diff --git a/go.sum b/go.sum index 952eaec..f1a3f30 100644 --- a/go.sum +++ b/go.sum @@ -834,9 +834,8 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= @@ -846,9 +845,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -943,8 +941,6 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/rancher/client-go v1.22.3-rancher.1 h1:aNVLaIY5YGah1i9wRVXLOGRbLyekohjQAKHXeQm6Cxo= github.com/rancher/client-go v1.22.3-rancher.1/go.mod h1:ElDjYf8gvZsKDYexmsmnMQ0DYO8W9RwBjfQ1PI53yow= -github.com/rancher/helm-locker v0.0.0-20220511204622-3b216418e2f4 h1:UhVIXnKxCkqmsdabLIsRxIqYQAEp9Q5Gj5ojHhJdOkE= -github.com/rancher/helm-locker v0.0.0-20220511204622-3b216418e2f4/go.mod h1:PRThM9wL4o7MXJwUDeAk/+9s1vpmbRbacnGm+HoGqbY= github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08/go.mod h1:9qZd/S8DqWzfKtjKGgSoHqGEByYmUE3qRaBaaAHwfEM= github.com/rancher/lasso v0.0.0-20220303220127-8cf5555ec03c h1:TyDYClPPCN2rWM97gd1jkvzlEy6ByYEN9IMK6nUY3dY= github.com/rancher/lasso v0.0.0-20220303220127-8cf5555ec03c/go.mod h1:T6WoUopOHBWTGjnphruTJAgoZ+dpm6llvn6GDYaa7Kw= From 0c929ccf69b19dc8306a63b519cc394ee8563bea Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 16:04:44 -0400 Subject: [PATCH 25/48] correct helm-ci target --- .github/workflows/hl-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hl-ci.yaml b/.github/workflows/hl-ci.yaml index 00309b6..d8bae5d 100644 --- a/.github/workflows/hl-ci.yaml +++ b/.github/workflows/hl-ci.yaml @@ -20,4 +20,4 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Run CI - run: make ci + run: BUILD_TARGET=helm-locker make ci From 9086dbd8b18d877dd010765e24b5f11fa2c75e3d Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 16:04:58 -0400 Subject: [PATCH 26/48] correct build scripts --- scripts/test | 2 ++ scripts/validate-chart | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/scripts/test b/scripts/test index 0866243..827fd8e 100755 --- a/scripts/test +++ b/scripts/test @@ -3,5 +3,7 @@ set -e cd $(dirname $0)/.. +# TODO: Should we add logic to dest helm-locker stuff alone? + echo Running tests go test -cover -tags=test ./... diff --git a/scripts/validate-chart b/scripts/validate-chart index 120e808..5d22eb5 100755 --- a/scripts/validate-chart +++ b/scripts/validate-chart @@ -1,6 +1,10 @@ #!/bin/bash set -e +if [[ "${BUILD_TARGET}" != "helm-project-operator" ]]; then + exit +fi + cd $(dirname $0)/.. app_version=$(yq e '.appVersion' charts/helm-project-operator/Chart.yaml) From e56e833b2fce518ae911d20f31630f3418fba611 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 16:10:05 -0400 Subject: [PATCH 27/48] correct syntax error in GHA workflow --- .github/workflows/hl-ci.yaml | 2 +- .github/workflows/hl-e2e.yaml | 2 +- .github/workflows/hl-publish.yaml | 2 +- .github/workflows/hpo-ci.yaml | 2 +- .github/workflows/hpo-e2e-ci.yaml | 2 +- .github/workflows/hpo-publish-image.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hl-ci.yaml b/.github/workflows/hl-ci.yaml index d8bae5d..1d6e1d4 100644 --- a/.github/workflows/hl-ci.yaml +++ b/.github/workflows/hl-ci.yaml @@ -1,4 +1,4 @@ -name: [helm-locker] CI +name: "[helm-locker] CI" on: pull_request: diff --git a/.github/workflows/hl-e2e.yaml b/.github/workflows/hl-e2e.yaml index 06bd661..0e80304 100644 --- a/.github/workflows/hl-e2e.yaml +++ b/.github/workflows/hl-e2e.yaml @@ -1,4 +1,4 @@ -name: [helm-locker] CI-e2e +name: "[helm-locker] CI-e2e" on: pull_request: diff --git a/.github/workflows/hl-publish.yaml b/.github/workflows/hl-publish.yaml index 9d03e4c..1c1c737 100644 --- a/.github/workflows/hl-publish.yaml +++ b/.github/workflows/hl-publish.yaml @@ -1,4 +1,4 @@ -name: [helm-locker] Publish Images +name: "[helm-locker] Publish Images" on: push: diff --git a/.github/workflows/hpo-ci.yaml b/.github/workflows/hpo-ci.yaml index fa1858c..756ea77 100644 --- a/.github/workflows/hpo-ci.yaml +++ b/.github/workflows/hpo-ci.yaml @@ -1,4 +1,4 @@ -name: [helm-project-operator] ci +name: "[helm-project-operator] ci" env: CGO_ENABLED: 0 diff --git a/.github/workflows/hpo-e2e-ci.yaml b/.github/workflows/hpo-e2e-ci.yaml index 6a2b322..7afdd69 100644 --- a/.github/workflows/hpo-e2e-ci.yaml +++ b/.github/workflows/hpo-e2e-ci.yaml @@ -1,4 +1,4 @@ -name: [helm-project-operator] E2E Helm Project Operator +name: "[helm-project-operator] E2E Helm Project Operator" on: workflow_dispatch: diff --git a/.github/workflows/hpo-publish-image.yaml b/.github/workflows/hpo-publish-image.yaml index 46a986d..5efecae 100644 --- a/.github/workflows/hpo-publish-image.yaml +++ b/.github/workflows/hpo-publish-image.yaml @@ -1,4 +1,4 @@ -name: [helm-project-operator] Publish images +name: "[helm-project-operator] Publish images" on: push: From b4531f0e3b9ded71016305b5cb0562f0a844a61a Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 16:29:32 -0400 Subject: [PATCH 28/48] rename step name to be more accurate --- .github/workflows/hpo-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hpo-ci.yaml b/.github/workflows/hpo-ci.yaml index 756ea77..b278651 100644 --- a/.github/workflows/hpo-ci.yaml +++ b/.github/workflows/hpo-ci.yaml @@ -29,7 +29,7 @@ jobs: helm version - name: Perform CI run : make ci - push-images: + build-images: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 39dfd50ab99e4b85847b003d6ed5a1ee86dcb144 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 16:29:49 -0400 Subject: [PATCH 29/48] Adjust how tests are done to only test target command --- cmd/helm-locker/main.go | 2 ++ cmd/helm-project-operator/main.go | 2 ++ scripts/test | 7 +++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cmd/helm-locker/main.go b/cmd/helm-locker/main.go index 5ea8d6d..a6e692e 100644 --- a/cmd/helm-locker/main.go +++ b/cmd/helm-locker/main.go @@ -1,3 +1,5 @@ +//go:build helm_locker + package main import ( diff --git a/cmd/helm-project-operator/main.go b/cmd/helm-project-operator/main.go index 67fcc59..d92cacf 100644 --- a/cmd/helm-project-operator/main.go +++ b/cmd/helm-project-operator/main.go @@ -1,3 +1,5 @@ +//go:build helm_project_operator + package main import ( diff --git a/scripts/test b/scripts/test index 827fd8e..c96515a 100755 --- a/scripts/test +++ b/scripts/test @@ -3,7 +3,10 @@ set -e cd $(dirname $0)/.. -# TODO: Should we add logic to dest helm-locker stuff alone? +TARGET_TAG=helm_project_operator +if [[ "${BUILD_TARGET}" == "helm-locker" ]]; then + TARGET_TAG=helm_locker +fi echo Running tests -go test -cover -tags=test ./... +go test -cover -tags="test,${TARGET_TAG}" ./... From 3a885f332397fc3b85ca7d3103728100a20b0521 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 16:40:51 -0400 Subject: [PATCH 30/48] update target and script path --- .github/workflows/hl-e2e.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hl-e2e.yaml b/.github/workflows/hl-e2e.yaml index 0e80304..1d3dfbb 100644 --- a/.github/workflows/hl-e2e.yaml +++ b/.github/workflows/hl-e2e.yaml @@ -26,9 +26,9 @@ jobs: curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl - name: Set up k3d - run : ./.github/workflows/scripts/install-k3d.sh + run : ./.github/workflows/e2e/scripts/install-k3d.sh - name: build - run: make build + run: BUILD_TARGET=helm-locker make build - name : Setup cluster run : CLUSTER_NAME=${{ env.CLUSTER_NAME }} K3S_VERSION=${{ env.K3S_VERSION }} ./scripts/setup-cluster.sh # temporary hack to run the helm-locker controller in the k3d cluster From b9b2527a9db25e9955d9f99be79dba41789aa695 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 16:46:59 -0400 Subject: [PATCH 31/48] Add missing cluster script --- scripts/setup-cluster.sh | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100755 scripts/setup-cluster.sh diff --git a/scripts/setup-cluster.sh b/scripts/setup-cluster.sh new file mode 100755 index 0000000..8738346 --- /dev/null +++ b/scripts/setup-cluster.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -e + +if [ -z "$CLUSTER_NAME" ]; then + echo "CLUSTER_NAME must be specified when setting up a cluster" + exit 1 +fi + +if [ -z "$K3S_VERSION" ]; then + echo "K3S_VERSION must be specified when setting up a cluster, use $(k3d version list k3s) to find valid versions" + exit 1 +fi + +# waits until all nodes are ready +wait_for_nodes(){ + timeout=120 + start_time=$(date +%s) + echo "wait until all agents are ready" + while : + do + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) + if [ $elapsed_time -ge $timeout ]; then + echo "Timeout reached, exiting..." + exit 1 + fi + + readyNodes=1 + statusList=$(kubectl get nodes --no-headers | awk '{ print $2}') + # shellcheck disable=SC2162 + while read status + do + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) + if [ $elapsed_time -ge $timeout ]; then + echo "Timeout reached, exiting..." + exit 1 + fi + if [ "$status" == "NotReady" ] || [ "$status" == "" ] + then + readyNodes=0 + break + fi + done <<< "$(echo -e "$statusList")" + # all nodes are ready; exit + if [[ $readyNodes == 1 ]] + then + break + fi + sleep 1 + done +} + +k3d cluster delete $CLUSTER_NAME || true +k3d cluster create $CLUSTER_NAME --image "docker.io/rancher/k3s:${K3S_VERSION}" + +wait_for_nodes + +echo "$CLUSTER_NAME ready" + +kubectl cluster-info --context k3d-${CLUSTER_NAME} +kubectl config use-context k3d-${CLUSTER_NAME} +kubectl get nodes -o wide From a03241f88f4c4b9e4a20621bf8d362ef8a504203 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 17:20:58 -0400 Subject: [PATCH 32/48] Bring over tests folder from helm-locker --- tests/e2e/e2e_suite_test.go | 80 ++ tests/e2e/e2e_test.go | 397 ++++++++++ tests/examples/foo-chart/.helmignore | 23 + tests/examples/foo-chart/Chart.yaml | 6 + .../foo-chart/templates/configmap.yaml | 7 + tests/examples/foo-chart/values.yaml | 1 + tests/go.mod | 64 ++ tests/go.sum | 717 ++++++++++++++++++ 8 files changed, 1295 insertions(+) create mode 100644 tests/e2e/e2e_suite_test.go create mode 100644 tests/e2e/e2e_test.go create mode 100644 tests/examples/foo-chart/.helmignore create mode 100644 tests/examples/foo-chart/Chart.yaml create mode 100644 tests/examples/foo-chart/templates/configmap.yaml create mode 100644 tests/examples/foo-chart/values.yaml create mode 100644 tests/go.mod create mode 100644 tests/go.sum diff --git a/tests/e2e/e2e_suite_test.go b/tests/e2e/e2e_suite_test.go new file mode 100644 index 0000000..76ceaeb --- /dev/null +++ b/tests/e2e/e2e_suite_test.go @@ -0,0 +1,80 @@ +package e2e_test + +import ( + "context" + "errors" + "os" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + lockerv1alpha1 "github.com/rancher/helm-locker/pkg/apis/helm.cattle.io/v1alpha1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + + "k8s.io/client-go/rest" + + env "github.com/caarlos0/env/v11" + "github.com/kralicky/kmatch" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +func TestE2e(t *testing.T) { + SetDefaultEventuallyTimeout(10 * time.Second) + SetDefaultEventuallyPollingInterval(50 * time.Millisecond) + SetDefaultConsistentlyDuration(2 * time.Second) + SetDefaultConsistentlyPollingInterval(50 * time.Millisecond) + RegisterFailHandler(Fail) + RunSpecs(t, "E2e Suite") +} + +var ( + k8sClient client.Client + cfg *rest.Config + testCtx context.Context + clientSet *kubernetes.Clientset +) + +type TestSpec struct { + Kubeconfig string `env:"KUBECONFIG,required"` +} + +func (t *TestSpec) Validate() error { + var errs []error + if _, err := os.Stat(t.Kubeconfig); err != nil { + errs = append(errs, err) + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + +var _ = BeforeSuite(func() { + ts := TestSpec{} + Expect(env.Parse(&ts)).To(Succeed(), "Could not parse test spec from environment variables") + Expect(ts.Validate()).To(Succeed(), "Invalid input e2e test spec") + + ctxCa, ca := context.WithCancel(context.Background()) + DeferCleanup(func() { + ca() + }) + + testCtx = ctxCa + newCfg, err := config.GetConfig() + Expect(err).NotTo(HaveOccurred(), "Could not initialize kubernetes client config") + cfg = newCfg + newClientset, err := kubernetes.NewForConfig(cfg) + Expect(err).To(Succeed(), "Could not initialize kubernetes clientset") + clientSet = newClientset + + newK8sClient, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred(), "Could not initialize kubernetes client") + k8sClient = newK8sClient + lockerv1alpha1.AddToScheme(k8sClient.Scheme()) + apiextensionsv1.AddToScheme(k8sClient.Scheme()) + kmatch.SetDefaultObjectClient(k8sClient) +}) diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go new file mode 100644 index 0000000..755b13a --- /dev/null +++ b/tests/e2e/e2e_test.go @@ -0,0 +1,397 @@ +package e2e_test + +import ( + "errors" + "fmt" + "os/exec" + + . "github.com/kralicky/kmatch" + "github.com/rancher/helm-locker/pkg/apis/helm.cattle.io/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func createIfNotExist(obj client.Object) { + err := k8sClient.Create(testCtx, obj) + if err != nil && !apierrors.IsAlreadyExists(err) { + Fail(fmt.Sprintf("Failed to create object %s", err)) + } +} + +const ( + objectSetHash = "objectset.rio.cattle.io/hash" + + objectSetApplied = "objectset.rio.cattle.io/applied" + objsetSetId = "objectset.rio.cattle.io/id" + objectSetOnwerGVK = "objectset.rio.cattle.io/owner-gvk" + ownerName = "objectset.rio.cattle.io/owner-name" + ownerNamespace = "objectset.rio.cattle.io/owner-namespace" +) + +const ( + exampleReleaseName = "foochart" + exampleReleaseNs = "foo" +) + +var _ = Describe("E2E helm locker operator tests", Ordered, Label("integration"), func() { + When("we use the helm locker operator", func() { + Specify("Expect to find prerequisited CRDs in test cluster", func() { + // loosely checks that the embedded helm controller is installed + gvk := schema.GroupVersionKind{ + Group: "helm.cattle.io", + Version: "v1", + Kind: "HelmChart", + } + + Eventually(GVK(gvk)).Should(Exist()) + }) + + It("should run the helm project operator", func() { + // TODO : setup helm controller here, once we fix the dependency mess + ns := "cattle-helm-system" + err := k8sClient.Create(testCtx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + }) + if err != nil && !apierrors.IsAlreadyExists(err) { + Fail(fmt.Sprintf("Failed to create namespace %s", err)) + } + }) + + It("Should have applied the helmrelease CRD", func() { + helmRelease := schema.GroupVersionKind{ + Group: "helm.cattle.io", + Version: "v1alpha1", + Kind: "HelmRelease", + } + + Eventually(GVK(helmRelease)).Should(Exist()) + }) + + It("should install an example helm chart", func() { + cmd := exec.CommandContext( + testCtx, + "helm", + "upgrade", + "--install", + "-n", + exampleReleaseNs, + "--create-namespace", + exampleReleaseName, + "../examples/foo-chart", + "--set", + "contents=\"abc\"", + ) + err := cmd.Start() + Expect(err).NotTo(HaveOccurred(), "Failed to run helm command") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + err = cmd.Wait() + Expect(err).NotTo(HaveOccurred(), "helm upgrade command had a non-zero exit code") + + By("verifying the resource managed by the example chart exists") + cfg := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-configmap", + Namespace: exampleReleaseNs, + }, + } + Eventually(Object(cfg)).Should(ExistAnd( + HaveLabels( + "app.kubernetes.io/managed-by", + "Helm", + ), + HaveAnnotations( + "meta.helm.sh/release-name", + exampleReleaseName, + "meta.helm.sh/release-namespace", + exampleReleaseNs, + ), + HaveData( + "contents", "abc", + ), + )) + }) + + When("we create a helm release", func() { + It("should create a helm release", func() { + release := &v1alpha1.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-release", + Namespace: "cattle-helm-system", + }, + Spec: v1alpha1.HelmReleaseSpec{ + Release: v1alpha1.ReleaseKey{ + Name: exampleReleaseName, + Namespace: exampleReleaseNs, + }, + }, + } + createIfNotExist(release) + + By("Verifing it has the appropriate annotations and finalizers") + Eventually(Object(release)).Should( + ExistAnd( + HaveAnnotations( + "helmreleases.cattle.io/managed-by", "helm-locker", + ), + HaveFinalizers("wrangler.cattle.io/on-helm-release-remove"), + ), + ) + + By("Verifying the helm-locker is consistently in the deployed state", func() { + extractState := func() string { + retRelease, err := Object(release)() + if err != nil { + return v1alpha1.UnknownState + } + return retRelease.Status.State + } + Consistently(extractState).Should(Equal(v1alpha1.DeployedState)) + }) + }) + + Specify("We should not be able to edit or delete resources managed by the helm-chart", func() { + By("verifying the config map has the correct objectset annotations and labels") + origHash := "" + origApplied := "" + cfg := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-configmap", + Namespace: "foo", + }, + Data: map[string]string{ + "contents": "Hello, World! Updated", + }, + } + Eventually(func() error { + errs := []error{} + + retCfg, err := Object(cfg)() + if err != nil { + return err + } + + if val, ok := retCfg.Labels[objectSetHash]; !ok { + errs = append(errs, errors.New("objectset hash label not found")) + } else { + origHash = val + } + + if val, ok := retCfg.Annotations[objectSetApplied]; !ok { + errs = append(errs, errors.New("objectset hash not found or incorrect")) + } else { + origApplied = val + } + + if val, ok := retCfg.Annotations[objsetSetId]; !ok || val != "object-set-applier" { + errs = append(errs, fmt.Errorf("objectset id not found or incorrect: '%s'", val)) + } + if val, ok := retCfg.Annotations[objectSetOnwerGVK]; !ok || val != "internal.cattle.io/v1alpha1, Kind=objectSetState" { + errs = append(errs, fmt.Errorf("objectset owner gvk not found or incorrect '%s'", val)) + } + return errors.Join(errs...) + }).Should(Succeed()) + + Expect(origHash).NotTo(BeEmpty(), "helm locker should manage the objectset hash") + Expect(origApplied).NotTo(BeEmpty(), "helm locker should manage the objectset applied annotation") + + By("trying to update the helm locked resource") + Expect(k8sClient.Update(testCtx, &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-configmap", + Namespace: "foo", + }, + Data: map[string]string{ + "contents": "Hello, World! Updated", + }, + })).To(Succeed()) + + By("verifying the update was not applied") + Eventually(Object(cfg)).Should(ExistAnd( + HaveData( + "contents", "abc", + ), + HaveAnnotations( + ownerName, exampleReleaseName, + ownerNamespace, exampleReleaseNs, + ), + )) + Eventually(func() error { + retCfg, err := Object(cfg)() + if err != nil { + return err + } + if val, ok := retCfg.Labels[objectSetHash]; !ok || val != origHash { + return fmt.Errorf("objectset hash label does not match the original one : '%s' vs '%s'", origHash, val) + } + if val, ok := retCfg.Annotations[objectSetApplied]; !ok || val != origApplied { + return fmt.Errorf("objectset applied annotation does not match the original one : '%s' vs '%s'", origApplied, val) + } + return nil + }).Should(Succeed()) + + Consistently(Object(cfg)).Should(ExistAnd( + HaveData( + "contents", "abc", + ), + HaveAnnotations( + ownerName, exampleReleaseName, + ownerNamespace, exampleReleaseNs, + ), + )) + }) + + Specify("We should only be able to update resources managed by the helm chart through helm", func() { + cfg := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-configmap", + Namespace: exampleReleaseNs, + }, + } + By("extracting the objectset hash before the helm update") + origHash := "" + origApplied := "" + Eventually(func() error { + retCfg, err := Object(cfg)() + if err != nil { + return err + } + if val, ok := retCfg.Labels[objectSetHash]; ok { + origHash = val + } + if val, ok := retCfg.Annotations[objectSetApplied]; ok { + origApplied = val + } + return nil + }).Should(Succeed()) + Expect(origHash).NotTo(BeEmpty(), "helm locker should be managing the object set hash") + Expect(origApplied).NotTo(BeEmpty(), "helm locker should be managing the object set hash") + Eventually(func() error { + return nil + }).Should(Succeed()) + + By("upgrading the helm resource using helm") + cmd := exec.CommandContext( + testCtx, + "helm", + "upgrade", + "--install", + "-n", + exampleReleaseNs, + "--create-namespace", + exampleReleaseName, + "../examples/foo-chart", + "--set", + "contents=\"Updated!\"", + ) + err := cmd.Start() + Expect(err).NotTo(HaveOccurred(), "Failed to run helm command") + + err = cmd.Wait() + Expect(err).NotTo(HaveOccurred(), "helm install command had a non-zero exit code") + + By("verifying the resource managed by the example chart exists") + Eventually(Object(cfg)).Should(ExistAnd( + HaveLabels( + "app.kubernetes.io/managed-by", + "Helm", + ), + HaveAnnotations( + "meta.helm.sh/release-name", + exampleReleaseName, + "meta.helm.sh/release-namespace", + exampleReleaseNs, + ), + HaveData( + "contents", "Updated!", + ), + )) + + Consistently(Object(cfg)).Should(ExistAnd( + HaveData( + "contents", "Updated!", + ), + )) + + By("extracting the objectset hash after the helm update") + newHash := "" + newApplied := "" + Eventually(func() error { + retCfg, err := Object(cfg)() + if err != nil { + return err + } + if val, ok := retCfg.Labels[objectSetHash]; ok { + newHash = val + } + if val, ok := retCfg.Annotations[objectSetApplied]; ok { + newApplied = val + } + return nil + }).Should(Succeed()) + Expect(newHash).NotTo(BeEmpty(), "helm locker should be managing the object set hash") + Expect(newApplied).NotTo(BeEmpty(), "helm locker should be managing the object set hash") + Expect(newHash).To(Equal(origHash), "objectset hash should not have changed after helm update, since no new resource keys are tracked") + Expect(newApplied).NotTo( + Equal(origApplied), + "objectset applied annotation should have changed after helm update", + ) + }) + }) + + When("we delete the helm release", func() { + It("should remove the helm release", func() { + release := &v1alpha1.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-release", + Namespace: "cattle-helm-system", + }, + } + err := k8sClient.Delete(testCtx, release) + Expect(err).ToNot(HaveOccurred()) + + By("Verifing it has the appropriate annotations and finalizers") + Eventually(Object(release)).Should(Not(Exist())) + }) + + Specify("we should be able to edit and delete resources managed by the helm-chart", func() { + Expect(k8sClient.Update(testCtx, &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-configmap", + Namespace: exampleReleaseNs, + }, + Data: map[string]string{ + "contents": "Hello, World! Updated", + }, + })).To(Succeed()) + + By("verifying the update was applied") + cfg := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-configmap", + Namespace: exampleReleaseNs, + }, + } + Eventually(Object(cfg)).Should(ExistAnd( + HaveData( + "contents", "Hello, World! Updated", + ), + )) + + Consistently(Object(cfg)).Should(ExistAnd( + HaveData( + "contents", "Hello, World! Updated", + ), + )) + }) + }) + }) +}) diff --git a/tests/examples/foo-chart/.helmignore b/tests/examples/foo-chart/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/tests/examples/foo-chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/tests/examples/foo-chart/Chart.yaml b/tests/examples/foo-chart/Chart.yaml new file mode 100644 index 0000000..6854e91 --- /dev/null +++ b/tests/examples/foo-chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: foo-chart +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/tests/examples/foo-chart/templates/configmap.yaml b/tests/examples/foo-chart/templates/configmap.yaml new file mode 100644 index 0000000..b6a11d5 --- /dev/null +++ b/tests/examples/foo-chart/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo-configmap + namespace: {{ .Release.Namespace }} +data: + "contents" : {{ .Values.contents}} diff --git a/tests/examples/foo-chart/values.yaml b/tests/examples/foo-chart/values.yaml new file mode 100644 index 0000000..c96ca22 --- /dev/null +++ b/tests/examples/foo-chart/values.yaml @@ -0,0 +1 @@ +contents : "Hello, World!" \ No newline at end of file diff --git a/tests/go.mod b/tests/go.mod new file mode 100644 index 0000000..e907fb5 --- /dev/null +++ b/tests/go.mod @@ -0,0 +1,64 @@ +module example.com + +go 1.22.3 + +require ( + github.com/caarlos0/env/v11 v11.0.0 + github.com/kralicky/kmatch v0.0.0-20240530002100-abef8971a37b + github.com/onsi/ginkgo/v2 v2.17.3 + github.com/onsi/gomega v1.33.1 + github.com/rancher/helm-locker v0.0.1 + k8s.io/api v0.30.1 + k8s.io/apiextensions-apiserver v0.30.0 + k8s.io/apimachinery v0.30.1 + k8s.io/client-go v0.30.0 + sigs.k8s.io/controller-runtime v0.18.2 +) + +require ( + emperror.dev/errors v0.8.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rancher/wrangler v0.8.11-0.20220217210408-3ecd23dfea3b // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.21.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/tests/go.sum b/tests/go.sum new file mode 100644 index 0000000..e6aa32e --- /dev/null +++ b/tests/go.sum @@ -0,0 +1,717 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= +emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/caarlos0/env/v11 v11.0.0 h1:ZIlkOjuL3xoZS0kmUJlF74j2Qj8GMOq3CDLX/Viak8Q= +github.com/caarlos0/env/v11 v11.0.0/go.mod h1:2RC3HQu8BQqtEK3V4iHPxj0jOdWdbPpWJ6pOueeU1xM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= +github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kralicky/kmatch v0.0.0-20240530002100-abef8971a37b h1:OZRT5i0+pRzxyMu0db5nZF0z7e9M6BmSezGeCueuryg= +github.com/kralicky/kmatch v0.0.0-20240530002100-abef8971a37b/go.mod h1:JRnTh8vZ0vEr8ljMxpSWPDD6b9LcTtdJ+ofrI6yCLiA= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= +github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= +github.com/rancher/helm-locker v0.0.1 h1:v/m7Uu5wGivn+FQn5/xMuUG2L+CzocSzD7sjM7+/74E= +github.com/rancher/helm-locker v0.0.1/go.mod h1:PRThM9wL4o7MXJwUDeAk/+9s1vpmbRbacnGm+HoGqbY= +github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08/go.mod h1:9qZd/S8DqWzfKtjKGgSoHqGEByYmUE3qRaBaaAHwfEM= +github.com/rancher/wrangler v0.8.11-0.20220217210408-3ecd23dfea3b h1:nFwp2dz+JHH96joqSVRYelOgmTQS9K8DSC3A/d1Gc9I= +github.com/rancher/wrangler v0.8.11-0.20220217210408-3ecd23dfea3b/go.mod h1:Lte9WjPtGYxYacIWeiS9qawvu2R4NujFU9xuXWJvc/0= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191017205301-920acffc3e65/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48= +k8s.io/api v0.0.0-20191214185829-ca1d04f8b0d3/go.mod h1:itOjKREfmUTvcjantxOsyYU5mbFsU7qUnyUuRfF5+5M= +k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= +k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= +k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= +k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY= +k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= +k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= +k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= +k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= +k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= +k8s.io/apimachinery v0.0.0-20191214185652-442f8fb2f03a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY= +k8s.io/apimachinery v0.0.0-20191216025728-0ee8b4573e3a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY= +k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg= +k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= +k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= +k8s.io/cli-runtime v0.0.0-20191214191754-e6dc6d5c8724/go.mod h1:wzlq80lvjgHW9if6MlE4OIGC86MDKsy5jtl9nxz/IYY= +k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= +k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk= +k8s.io/client-go v0.0.0-20191214190045-a32a6f7a3052/go.mod h1:tAaoc/sYuIL0+njJefSAmE28CIcxyaFV4kbIujBlY2s= +k8s.io/client-go v0.0.0-20191219150334-0b8da7416048/go.mod h1:ZEe8ZASDUAuqVGJ+UN0ka0PfaR+b6a6E1PGsSNZRui8= +k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= +k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= +k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= +k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= +k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE= +k8s.io/code-generator v0.0.0-20191214185510-0b9b3c99f9f2/go.mod h1:BjGKcoq1MRUmcssvHiSxodCco1T6nVIt4YeCT5CMSao= +k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA= +k8s.io/component-base v0.0.0-20191214190519-d868452632e2/go.mod h1:wupxkh1T/oUDqyTtcIjiEfpbmIHGm8By/vqpSKC6z8c= +k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= +k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-aggregator v0.18.0/go.mod h1:ateewQ5QbjMZF/dihEFXwaEwoA4v/mayRvzfmvb6eqI= +k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo= +k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM= +k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= +sigs.k8s.io/cli-utils v0.16.0/go.mod h1:9Jqm9K2W6ShhCxsEuaz6HSRKKOXigPUx3ZfypGgxBLY= +sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= +sigs.k8s.io/controller-runtime v0.18.2 h1:RqVW6Kpeaji67CY5nPEfRz6ZfFMk0lWQlNrLqlNpx+Q= +sigs.k8s.io/controller-runtime v0.18.2/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= +sigs.k8s.io/kustomize/kyaml v0.4.0/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= From d7a4d99b168d28ad3ea8af533076a1aa718da482 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 17:24:53 -0400 Subject: [PATCH 33/48] Adjust more paths for repo re-org --- .github/workflows/e2e/scripts/create-projecthelmchart.sh | 2 +- .github/workflows/e2e/scripts/delete-projecthelmchart.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e/scripts/create-projecthelmchart.sh b/.github/workflows/e2e/scripts/create-projecthelmchart.sh index 0956bc1..897d244 100755 --- a/.github/workflows/e2e/scripts/create-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/create-projecthelmchart.sh @@ -5,7 +5,7 @@ source $(dirname $0)/entry cd $(dirname $0)/../../../.. -kubectl apply -f ./examples/ci-example.yaml +kubectl apply -f ./examples/helm-project-operator/ci-example.yaml sleep ${DEFAULT_SLEEP_TIMEOUT_SECONDS}; if ! kubectl get -n cattle-helm-system job/helm-install-project-project-operator-example-dummy; then diff --git a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh index d2a9195..cc30762 100755 --- a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh @@ -5,7 +5,7 @@ source $(dirname $0)/entry cd $(dirname $0)/../../../.. -kubectl delete -f ./examples/ci-example.yaml +kubectl delete -f ./examples/helm-project-operator/ci-example.yaml if kubectl get -n cattle-helm-system job/helm-delete-project-project-operator-example-dummy --ignore-not-found; then if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-delete-project-project-operator-example-dummy; then echo "ERROR: Helm Uninstall Job for Example Chart never completed after ${KUBECTL_WAIT_TIMEOUT}" From ad90f02b18c76739dd9b8f131eb28f18edc52478 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 20:28:36 -0400 Subject: [PATCH 34/48] tweak publish workflows to work on specific tags for now... ...very temporary as a way to prevent incorrect publishing. we will converge the files together as we have with the repos. however we will also want to choose if helm-locker images go to ghcr or dockerhub. worth considering ghcr instead as these are mainly test images not for end-users AFAIK. (josh to confirm that hopefully) --- .github/workflows/hl-publish.yaml | 2 +- .github/workflows/hpo-publish-image.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hl-publish.yaml b/.github/workflows/hl-publish.yaml index 1c1c737..4be4dd3 100644 --- a/.github/workflows/hl-publish.yaml +++ b/.github/workflows/hl-publish.yaml @@ -3,7 +3,7 @@ name: "[helm-locker] Publish Images" on: push: tags: - - "*" + - "helm-locker/*" env: REGISTRY: docker.io diff --git a/.github/workflows/hpo-publish-image.yaml b/.github/workflows/hpo-publish-image.yaml index 5efecae..b12ee3c 100644 --- a/.github/workflows/hpo-publish-image.yaml +++ b/.github/workflows/hpo-publish-image.yaml @@ -3,7 +3,7 @@ name: "[helm-project-operator] Publish images" on: push: tags: - - "*" + - "helm-project-operator/*" jobs: push: From 582ff226ee91366f9787437a4ab6b1beb488fbc0 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 20:28:52 -0400 Subject: [PATCH 35/48] Adjust style for easier workflow reading --- .github/workflows/hpo-e2e-ci.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/hpo-e2e-ci.yaml b/.github/workflows/hpo-e2e-ci.yaml index 7afdd69..4d17f3c 100644 --- a/.github/workflows/hpo-e2e-ci.yaml +++ b/.github/workflows/hpo-e2e-ci.yaml @@ -88,14 +88,19 @@ jobs: - name: Check if Helm Project Operator is up run: ./.github/workflows/e2e/scripts/validate-helm-project-operator.sh; + - name: Check if Project Registration Namespace is auto-created on namespace detection run: ./.github/workflows/e2e/scripts/create-project-namespace.sh; + - name: Deploy Example Chart via ProjectHelmChart CR run: ./.github/workflows/e2e/scripts/create-projecthelmchart.sh; + - name: Delete Example Chart run: ./.github/workflows/e2e/scripts/delete-projecthelmchart.sh; + - name: Uninstall Helm Project Operator run: ./.github/workflows/e2e/scripts/uninstall-helm-project-operator.sh; + - name: Delete k3d cluster if: always() run: k3d cluster delete e2e-ci-helm-project-operator From d0616ace60cd3cbd11ebe4bae06b7fea4a736877 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 20:29:03 -0400 Subject: [PATCH 36/48] Correct e2e job name --- .github/workflows/e2e/scripts/create-projecthelmchart.sh | 6 +++--- .github/workflows/e2e/scripts/delete-projecthelmchart.sh | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e/scripts/create-projecthelmchart.sh b/.github/workflows/e2e/scripts/create-projecthelmchart.sh index 897d244..a43ea4b 100755 --- a/.github/workflows/e2e/scripts/create-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/create-projecthelmchart.sh @@ -8,7 +8,7 @@ cd $(dirname $0)/../../../.. kubectl apply -f ./examples/helm-project-operator/ci-example.yaml sleep ${DEFAULT_SLEEP_TIMEOUT_SECONDS}; -if ! kubectl get -n cattle-helm-system job/helm-install-project-project-operator-example-dummy; then +if ! kubectl get -n cattle-helm-system job/helm-install-project-operator-example-chart-dummy; then echo "ERROR: Helm Install Job for Example Chart was never created after ${KUBECTL_WAIT_TIMEOUT} seconds" echo "PROJECT HELM CHARTS:" kubectl get projecthelmchart -n cattle-project-p-example -o yaml @@ -23,9 +23,9 @@ fi if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-install-project-project-operator-example-dummy; then echo "ERROR: Helm Install Job for Example Chart never completed after ${KUBECTL_WAIT_TIMEOUT} seconds" - kubectl logs job/helm-install-project-project-operator-example-dummy -n cattle-helm-system + kubectl logs job/helm-install-project-operator-example-chart-dummy -n cattle-helm-system exit 1 fi -kubectl logs job/helm-install-project-project-operator-example-dummy -n cattle-helm-system +kubectl logs job/helm-install-project-operator-example-chart-dummy -n cattle-helm-system echo "PASS: Adding ProjectHelmChart successfully installed Example Chart" diff --git a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh index cc30762..87afe33 100755 --- a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh @@ -6,10 +6,10 @@ source $(dirname $0)/entry cd $(dirname $0)/../../../.. kubectl delete -f ./examples/helm-project-operator/ci-example.yaml -if kubectl get -n cattle-helm-system job/helm-delete-project-project-operator-example-dummy --ignore-not-found; then +if kubectl get -n cattle-helm-system job/helm-install-project-operator-example-chart-dummy --ignore-not-found; then if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-delete-project-project-operator-example-dummy; then echo "ERROR: Helm Uninstall Job for Example Chart never completed after ${KUBECTL_WAIT_TIMEOUT}" - kubectl logs job/helm-delete-project-project-operator-example-dummy -n cattle-helm-system + kubectl logs job/helm-install-project-operator-example-chart-dummy -n cattle-helm-system exit 1 fi fi From 56cf70d8cb726e671968160ef7f266d87013543f Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 20:45:31 -0400 Subject: [PATCH 37/48] correct last reference to old name --- .github/workflows/e2e/scripts/create-projecthelmchart.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e/scripts/create-projecthelmchart.sh b/.github/workflows/e2e/scripts/create-projecthelmchart.sh index a43ea4b..da65e57 100755 --- a/.github/workflows/e2e/scripts/create-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/create-projecthelmchart.sh @@ -21,7 +21,7 @@ if ! kubectl get -n cattle-helm-system job/helm-install-project-operator-example exit 1 fi -if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-install-project-project-operator-example-dummy; then +if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-install-project-operator-example-chart-dummy; then echo "ERROR: Helm Install Job for Example Chart never completed after ${KUBECTL_WAIT_TIMEOUT} seconds" kubectl logs job/helm-install-project-operator-example-chart-dummy -n cattle-helm-system exit 1 From 92721ba7cddec6c583ec23420a32eccb7ec89c66 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 20:54:04 -0400 Subject: [PATCH 38/48] fix scirpt job name --- .../workflows/e2e/scripts/delete-projecthelmchart.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh index 87afe33..e9b46d8 100755 --- a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh @@ -5,13 +5,13 @@ source $(dirname $0)/entry cd $(dirname $0)/../../../.. -kubectl delete -f ./examples/helm-project-operator/ci-example.yaml -if kubectl get -n cattle-helm-system job/helm-install-project-operator-example-chart-dummy --ignore-not-found; then - if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-delete-project-project-operator-example-dummy; then +kubectl delete -f ./examples/ci-example.yaml +if kubectl get -n cattle-helm-system job/helm-delete-project-operator-example-chart-dummy --ignore-not-found; then + if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-delete-project-operator-example-chart-dummy; then echo "ERROR: Helm Uninstall Job for Example Chart never completed after ${KUBECTL_WAIT_TIMEOUT}" - kubectl logs job/helm-install-project-operator-example-chart-dummy -n cattle-helm-system + kubectl logs job/helm-delete-project-operator-example-chart-dummy -n cattle-helm-system exit 1 fi fi -echo "PASS: Removing ProjectHelmChart successfully uninstalled Example Chart" +echo "PASS: Removing ProjectHelmChart successfully uninstalled Example Chart" \ No newline at end of file From 3abe679849634e2ab42fd7aff47f9a879ea78357 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Tue, 10 Sep 2024 20:58:43 -0400 Subject: [PATCH 39/48] correct path for delete script --- .github/workflows/e2e/scripts/delete-projecthelmchart.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh index e9b46d8..9b42a1f 100755 --- a/.github/workflows/e2e/scripts/delete-projecthelmchart.sh +++ b/.github/workflows/e2e/scripts/delete-projecthelmchart.sh @@ -5,7 +5,7 @@ source $(dirname $0)/entry cd $(dirname $0)/../../../.. -kubectl delete -f ./examples/ci-example.yaml +kubectl delete -f ./examples/helm-project-operator/ci-example.yaml if kubectl get -n cattle-helm-system job/helm-delete-project-operator-example-chart-dummy --ignore-not-found; then if ! kubectl wait --for=condition=complete --timeout="${KUBECTL_WAIT_TIMEOUT}" -n cattle-helm-system job/helm-delete-project-operator-example-chart-dummy; then echo "ERROR: Helm Uninstall Job for Example Chart never completed after ${KUBECTL_WAIT_TIMEOUT}" From 1140ae4aace0e653a51fc301ff8cdd9143700e35 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Wed, 11 Sep 2024 10:18:50 -0400 Subject: [PATCH 40/48] remove duplicate crds file --- crds/crds.yaml | 85 -------------------------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 crds/crds.yaml diff --git a/crds/crds.yaml b/crds/crds.yaml deleted file mode 100644 index 7958825..0000000 --- a/crds/crds.yaml +++ /dev/null @@ -1,85 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: helmreleases.helm.cattle.io -spec: - group: helm.cattle.io - names: - kind: HelmRelease - plural: helmreleases - singular: helmrelease - preserveUnknownFields: false - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.release.name - name: Release Name - type: string - - jsonPath: .spec.release.namespace - name: Release Namespace - type: string - - jsonPath: .status.version - name: Version - type: string - - jsonPath: .status.state - name: State - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - spec: - properties: - release: - properties: - name: - nullable: true - type: string - namespace: - nullable: true - type: string - type: object - type: object - status: - properties: - conditions: - items: - properties: - lastTransitionTime: - nullable: true - type: string - lastUpdateTime: - nullable: true - type: string - message: - nullable: true - type: string - reason: - nullable: true - type: string - status: - nullable: true - type: string - type: - nullable: true - type: string - type: object - nullable: true - type: array - description: - nullable: true - type: string - notes: - nullable: true - type: string - state: - nullable: true - type: string - version: - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} From 464f1fd3e221d4728828ef28e16ce6d918eac987 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 23 Sep 2024 11:34:51 -0400 Subject: [PATCH 41/48] Merge 2 publish workflows into 1 --- .github/workflows/hl-publish.yaml | 58 --------------------- .github/workflows/hpo-publish-image.yaml | 43 --------------- .github/workflows/publish.yaml | 66 ++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 101 deletions(-) delete mode 100644 .github/workflows/hl-publish.yaml delete mode 100644 .github/workflows/hpo-publish-image.yaml create mode 100644 .github/workflows/publish.yaml diff --git a/.github/workflows/hl-publish.yaml b/.github/workflows/hl-publish.yaml deleted file mode 100644 index 4be4dd3..0000000 --- a/.github/workflows/hl-publish.yaml +++ /dev/null @@ -1,58 +0,0 @@ -name: "[helm-locker] Publish Images" - -on: - push: - tags: - - "helm-locker/*" - -env: - REGISTRY: docker.io - -jobs: - push: - name : Build and push helm-locker images - runs-on : ubuntu-latest - permissions: - contents : read - id-token: write - steps: - - name : "Read Secrets" - uses : rancher-eio/read-vault-secrets@main - with: - secrets: | - secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ; - secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD - - name : Checkout repository - uses: actions/checkout@v4 - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.DOCKER_USERNAME }} - password: ${{ env.DOCKER_PASSWORD }} - - name : Setup go - uses: actions/setup-go@v5 - with: - go-version: 1.22 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.DOCKER_USERNAME }} - password: ${{ env.DOCKER_PASSWORD }} - - name : Build, test & validate - run : make ci - - name : Export version info - run : | - source ./scripts/version - echo IMAGE=$IMAGE >> $GITHUB_ENV - - name: Build and push helm-locker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./package/Dockerfile - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE }} - platforms : linux/amd64,linux/arm64 \ No newline at end of file diff --git a/.github/workflows/hpo-publish-image.yaml b/.github/workflows/hpo-publish-image.yaml deleted file mode 100644 index b12ee3c..0000000 --- a/.github/workflows/hpo-publish-image.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: "[helm-project-operator] Publish images" - -on: - push: - tags: - - "helm-project-operator/*" - -jobs: - push: - runs-on: ubuntu-latest - permissions: - contents : read - id-token: write - steps: - - name : "Read Secrets" - uses : rancher-eio/read-vault-secrets@main - with: - secrets: | - secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ; - secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.DOCKER_USERNAME }} - password: ${{ env.DOCKER_PASSWORD }} - - name : Export image version - run : | - source ./scripts/version - echo IMAGE=$IMAGE >> $GITHUB_ENV - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Build Helm-Project-Operator image - uses: docker/build-push-action@v5 - with: - context: . - file: ./package/Dockerfile-helm-project-operator - push: true - tags: ${{ env.IMAGE }} - platforms: linux/amd64,linux/arm64 \ No newline at end of file diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..569f47f --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,66 @@ +name: "Publish Images" + +on: + push: + tags: + - "v*" + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + push: + name : Build and push helm-locker images + runs-on : ubuntu-latest + permissions: + contents: read + packages: write + attestations: write + id-token: write + steps: + - name : Checkout repository + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name : Setup go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name : Build, test & validate + run : BUILD_TARGET=helm-locker make ci + - name: Extract metadata (tags, labels) for helm-locker image + id: meta-locker + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/helm-locker + - name: Build and push helm-locker image + id: push + uses: docker/build-push-action@v5 + with: + context: . + file: ./package/Dockerfile-helm-locker + push: true + tags: ${{ steps.meta-locker.outputs.tags }} + labels: ${{ steps.meta-locker.outputs.labels }} + platforms : linux/amd64,linux/arm64 + - name: Extract metadata (tags, labels) for Helm-Project-Operator image + id: meta-hpo + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/helm-project-operator + - name: Build Helm-Project-Operator image + uses: docker/build-push-action@v5 + with: + context: . + file: ./package/Dockerfile-helm-project-operator + push: true + tags: ${{ steps.meta-hpo.outputs.tags }} + labels: ${{ steps.meta-hpo.outputs.labels }} + platforms: linux/amd64,linux/arm64 \ No newline at end of file From 6d3ae417e375b7fde8f94ec2acb6a3015df0c51b Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 23 Sep 2024 11:37:54 -0400 Subject: [PATCH 42/48] Correct job name --- .github/workflows/publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 569f47f..d349336 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -11,7 +11,7 @@ env: jobs: push: - name : Build and push helm-locker images + name : Build and push helm-locker & Helm-Project-Operator images runs-on : ubuntu-latest permissions: contents: read From a24395eb2f5f0f6d34dbdf9a2ae5fc91a517b065 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 23 Sep 2024 11:57:12 -0400 Subject: [PATCH 43/48] Update readme docs --- README-helm-locker.md | 81 +++++++++++++++++++++++++++++++++++++++++++ README.md | 11 ++++-- 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 README-helm-locker.md diff --git a/README-helm-locker.md b/README-helm-locker.md new file mode 100644 index 0000000..072958d --- /dev/null +++ b/README-helm-locker.md @@ -0,0 +1,81 @@ +helm-locker +======== + +Helm Locker is a Kubernetes operator that prevents resource drift on (i.e. "locks") Kubernetes objects that are tracked by Helm 3 releases. + +Once installed, a user can create a `HelmRelease` CR in the `Helm Release Registration Namespace` (default: `cattle-helm-system`) by providing: +1. The name of a Helm 3 release +2. The namespace that contains the Helm Release Secret (supplied as `--namespace` on the `helm install` command that created the release) + +Once created, the Helm Locker controllers will watch all resources tracked by the Helm Release Secret and automatically revert any changes to the persisted resources that were not made through Helm (e.g. changes that were directly applied via `kubectl` or other controllers). + +## Getting Started + +For more information, see the [Getting Started guide](docs/gettingstarted.md). + +## Who needs Helm Locker? + +Anyone who would like to declaratively manage resources deployed by existing Helm chart releases. + +## How is this different from projects like `fluxcd/helm-controller`? + +Projects like [`fluxcd/helm-controller`](https://github.com/fluxcd/helm-controller) allow users to declaratively manage **Helm charts from deployment to release**, whereas this project only allows you lock an **existing** Helm chart release; as a result, the scope of this project is much more narrow than what is offered by `fluxcd/helm-controller` and should be integrable with any solution that produces Helm releases. + +If you are looking for a larger, more opinionated solution that also has features around **how** Helm charts should be deployed onto a cluster (e.g. from a `GitRepository` or `Bucket` or `HelmRepository`), this is not the project for you. + +However, if you are looking for something light-weight that simply guarentees that **Helm is the only way to modify resources tracked by Helm releases**, this is a good solution to use. + +## How does Helm Locker know whether a release was changed by Helm or by another source? + +In order to prevent multiple Helm instances from performing the same upgrade at the same time, Helm 3 will always first update the `info.status` field on a Helm Release Secret from `deployed` to another state (e.g. `pending-upgrade`, `pending-install`, `uninstalling`, etc.) before performing the operation; once the operation is complete, the Helm Release Secret is expected to be reverted back to `deployed`. + +Therefore, if Helm Locker observes a Helm Release Secret tied to a `HelmRelease` has been updated, it will check to see what the current status of the release is; if the release is anything but `deployed`, Helm Locker will not perform any operations on the resources tracked by this release, which will allow upgrades to occur as expected. + +However, once a release is `deployed`, if what is tracked in the Helm secret is different than what is currently installed onto the cluster, Helm Locker will revert all resources back to what was tracked by the Helm release (in case a change was made to the resource tracked by the Helm Release while the release was being modified). + +## Developing + +### Which branch do I make changes on? + +Helm Locker is built and released off the contents of the `main` branch. To make a contribution, open up a PR to the `main` branch. + +For more information, see the [Developing guide](docs/developing.md). + +## Debugging + +### How do I manually inspect the content of the Helm Release Secret to debug a possible Helm Locker issue? + +Identify the release namespace (`RELEASE_NAMESPACE`), release name (`RELEASE_NAME`), and release version (`RELEASE_VERSION`) that identifies the Secret used by Helm to store the release data. Then, with access to your Kubernetes cluster via `kubectl`, run the following command (e.g. run base64 decode, base64 decode, gzip decompress the .data.release of the Secret): + +```bash +RELEASE_NAMESPACE=default +RELEASE_NAME=test +RELEASE_VERSION=v1 + +# Magic one-liner! jq call is optional... +kubectl get secrets -n ${RELEASE_NAMESPACE} sh.helm.release.v1.${RELEASE_NAME}.${RELEASE_VERSION} -o=jsonpath='{ .data.release }' | base64 -d | base64 -d | gunzip -c | jq -r '.' +``` + +## Building + +`make` + + +## Running + +`./bin/helm-locker` + +## License +Copyright (c) 2020 [Rancher Labs, Inc.](http://rancher.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index 60f2cfe..80d8087 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@ helm-project-operator ======== -The Helm Project Operator is a generic design for a Kubernetes Operator that acts on `ProjectHelmChart` CRs. +This repo contains a set of two interlinked projects: -**Note: this project is not intended for standalone use.** +- The **Helm Project Operator** is a generic design for a Kubernetes Operator that acts on `ProjectHelmChart` CRs. +- **Helm Locker** is a Kubernetes operator that prevents resource drift on (i.e. "locks") Kubernetes objects that are tracked by Helm 3 releases. -It is intended to be implemented by a Project Operator (e.g. [`rancher/prometheus-federator`](https://github.com/rancher/prometheus-federator)) but provides a common definition for all Project Operators to use in order to support deploy specific, pre-bundled Helm charts (tied to a unique registered `spec.helmApiVersion` associated with the operator) across all project namespaces detected by this operator. +**Note: These project are not intended for standalone use.** + +For more info on _Helm Locker_, see the [dedicated README file](README-helm-locker.md). + +Helm Project Operator is intended to be implemented by a Project Operator (e.g. [`rancher/prometheus-federator`](https://github.com/rancher/prometheus-federator)) but provides a common definition for all Project Operators to use in order to support deploy specific, pre-bundled Helm charts (tied to a unique registered `spec.helmApiVersion` associated with the operator) across all project namespaces detected by this operator. ## Getting Started From 43c4e68aae0705607da32bde5824e16b0d7d6bcd Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 23 Sep 2024 12:03:07 -0400 Subject: [PATCH 44/48] Only trigger hl-e2e on push to paths related to helm-locker --- .github/workflows/hl-e2e.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/hl-e2e.yaml b/.github/workflows/hl-e2e.yaml index 1d3dfbb..6de58f6 100644 --- a/.github/workflows/hl-e2e.yaml +++ b/.github/workflows/hl-e2e.yaml @@ -5,6 +5,13 @@ on: push: branches: - main + paths: + - 'go.mod' + - 'charts/helm-locker*/**' + - 'crds/helm-locker/**' + - 'package/Dockerfile-helm-locker' + - 'cmd/helm-locker/**' + - 'pkg/helm-locker/**' env: CLUSTER_NAME : test-cluster From 6cc146b5d877f532a28390b4391571bdeb851415 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 23 Sep 2024 12:08:40 -0400 Subject: [PATCH 45/48] Test better image tags --- .github/workflows/publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index d349336..83e3d93 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -54,7 +54,7 @@ jobs: id: meta-hpo uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/helm-project-operator + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build Helm-Project-Operator image uses: docker/build-push-action@v5 with: From e69ff8e9e7078fbdefc050a9e7d6a71f80156b92 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 23 Sep 2024 12:14:35 -0400 Subject: [PATCH 46/48] Adjust rancher version annotations --- charts/helm-locker-example/Chart.yaml | 2 +- charts/helm-locker/Chart.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/helm-locker-example/Chart.yaml b/charts/helm-locker-example/Chart.yaml index 442ad34..9c26b6b 100644 --- a/charts/helm-locker-example/Chart.yaml +++ b/charts/helm-locker-example/Chart.yaml @@ -9,7 +9,7 @@ annotations: catalog.cattle.io/kube-version: '>=1.16.0-0' catalog.cattle.io/namespace: cattle-helm-system catalog.cattle.io/permits-os: linux,windows - catalog.cattle.io/rancher-version: '>= 2.6.0-0 <=2.6.99-0' + catalog.cattle.io/rancher-version: '>= 2.6.0-0' catalog.cattle.io/release-name: helm-locker-example catalog.cattle.io/os: linux,windows maintainers: diff --git a/charts/helm-locker/Chart.yaml b/charts/helm-locker/Chart.yaml index 512980a..8027025 100644 --- a/charts/helm-locker/Chart.yaml +++ b/charts/helm-locker/Chart.yaml @@ -10,7 +10,7 @@ annotations: catalog.cattle.io/namespace: cattle-helm-system catalog.cattle.io/permits-os: linux,windows catalog.cattle.io/provides-gvr: helm.cattle.io.helmrelease/v1alpha1 - catalog.cattle.io/rancher-version: '>= 2.6.0-0 <=2.6.99-0' + catalog.cattle.io/rancher-version: '>= 2.6.0-0' catalog.cattle.io/release-name: helm-locker catalog.cattle.io/os: linux,windows maintainers: From 8f2a52bae032e9425c7925d6431666e2a3bfe919 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 23 Sep 2024 12:14:44 -0400 Subject: [PATCH 47/48] update chart maintainer --- charts/helm-locker-example/Chart.yaml | 5 ++--- charts/helm-locker/Chart.yaml | 4 ++-- charts/helm-project-operator/Chart.yaml | 3 +++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/charts/helm-locker-example/Chart.yaml b/charts/helm-locker-example/Chart.yaml index 9c26b6b..8349411 100644 --- a/charts/helm-locker-example/Chart.yaml +++ b/charts/helm-locker-example/Chart.yaml @@ -13,6 +13,5 @@ annotations: catalog.cattle.io/release-name: helm-locker-example catalog.cattle.io/os: linux,windows maintainers: -- email: arvind.iyengar@suse.com - name: aiyengar2 - + - email: dan.pock@suse.com + name: mallardduck diff --git a/charts/helm-locker/Chart.yaml b/charts/helm-locker/Chart.yaml index 8027025..1c78d7a 100644 --- a/charts/helm-locker/Chart.yaml +++ b/charts/helm-locker/Chart.yaml @@ -14,5 +14,5 @@ annotations: catalog.cattle.io/release-name: helm-locker catalog.cattle.io/os: linux,windows maintainers: -- email: arvind.iyengar@suse.com - name: aiyengar2 \ No newline at end of file + - email: dan.pock@suse.com + name: mallardduck diff --git a/charts/helm-project-operator/Chart.yaml b/charts/helm-project-operator/Chart.yaml index d0845d5..94a3b84 100644 --- a/charts/helm-project-operator/Chart.yaml +++ b/charts/helm-project-operator/Chart.yaml @@ -13,3 +13,6 @@ annotations: catalog.cattle.io/rancher-version: '>= 2.6.0-0' catalog.cattle.io/release-name: helm-project-operator catalog.cattle.io/os: linux,windows +maintainers: + - email: dan.pock@suse.com + name: mallardduck From ec905597ddc5378949175409870ca508a6725ad6 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 23 Sep 2024 12:27:42 -0400 Subject: [PATCH 48/48] Adjust both charts to use GHCR for images --- charts/helm-locker/values.yaml | 4 ++-- charts/helm-project-operator/values.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/helm-locker/values.yaml b/charts/helm-locker/values.yaml index e4a037e..0742dcc 100644 --- a/charts/helm-locker/values.yaml +++ b/charts/helm-locker/values.yaml @@ -48,8 +48,8 @@ namespaceOverride: "" replicas: 1 image: - repository: rancher/helm-locker - tag: v0.0.2 + repository: ghcr.io/rancher/helm-project-operator/helm-locker + tag: v0.3.0 pullPolicy: IfNotPresent # Additional arguments to be passed into the Helm Locker image diff --git a/charts/helm-project-operator/values.yaml b/charts/helm-project-operator/values.yaml index 63fae45..796bf5f 100644 --- a/charts/helm-project-operator/values.yaml +++ b/charts/helm-project-operator/values.yaml @@ -129,8 +129,8 @@ namespaceOverride: "" replicas: 1 image: - repository: rancher/helm-project-operator - tag: v0.2.1 + repository: ghcr.io/rancher/helm-project-operator + tag: v0.3.0 pullPolicy: IfNotPresent helmController: