Merge pull request #1076 from stakater/csi-support-without-SHA512

Feature - Csi support without sha512
This commit is contained in:
Faizan Ahmad
2026-01-08 18:14:04 +01:00
committed by GitHub
26 changed files with 1918 additions and 184 deletions

View File

@@ -13,7 +13,7 @@
## 🔁 What is Reloader? ## 🔁 What is Reloader?
Reloader is a Kubernetes controller that automatically triggers rollouts of workloads (like Deployments, StatefulSets, and more) whenever referenced `Secrets` or `ConfigMaps` are updated. Reloader is a Kubernetes controller that automatically triggers rollouts of workloads (like Deployments, StatefulSets, and more) whenever referenced `Secrets`, `ConfigMaps` or **optionally CSI-mounted secrets** are updated.
In a traditional Kubernetes setup, updating a `Secret` or `ConfigMap` does not automatically restart or redeploy your workloads. This can lead to stale configurations running in production, especially when dealing with dynamic values like credentials, feature flags, or environment configs. In a traditional Kubernetes setup, updating a `Secret` or `ConfigMap` does not automatically restart or redeploy your workloads. This can lead to stale configurations running in production, especially when dealing with dynamic values like credentials, feature flags, or environment configs.
@@ -169,9 +169,11 @@ metadata:
This instructs Reloader to skip all reload logic for that resource across all workloads. This instructs Reloader to skip all reload logic for that resource across all workloads.
### 4. ⚙️ Workload-Specific Rollout Strategy ### 4. ⚙️ Workload-Specific Rollout Strategy (Argo Rollouts Only)
By default, Reloader uses the **rollout** strategy — it updates the pod template to trigger a new rollout. This works well in most cases, but it can cause problems if you're using GitOps tools like ArgoCD, which detect this as configuration drift. Note: This is only applicable when using [Argo Rollouts](https://argoproj.github.io/argo-rollouts/). It is ignored for standard Kubernetes `Deployments`, `StatefulSets`, or `DaemonSets`. To use this feature, Argo Rollouts support must be enabled in Reloader (for example via --is-argo-rollouts=true).
By default, Reloader triggers the Argo Rollout controller to perform a standard rollout by updating the pod template. This works well in most cases, however, because this modifies the workload spec, GitOps tools like ArgoCD will detect this as "Configuration Drift" and mark your application as OutOfSync.
To avoid that, you can switch to the **restart** strategy, which simply restarts the pod without changing the pod template. To avoid that, you can switch to the **restart** strategy, which simply restarts the pod without changing the pod template.
@@ -192,6 +194,8 @@ metadata:
1. You want a quick restart without changing the workload spec 1. You want a quick restart without changing the workload spec
1. Your platform restricts metadata changes 1. Your platform restricts metadata changes
This setting affects Argo Rollouts behavior, not Argo CD sync settings.
### 5. ❗ Annotation Behavior Rules & Compatibility ### 5. ❗ Annotation Behavior Rules & Compatibility
- `reloader.stakater.com/auto` and `reloader.stakater.com/search` **cannot be used together** — the `auto` annotation takes precedence. - `reloader.stakater.com/auto` and `reloader.stakater.com/search` **cannot be used together** — the `auto` annotation takes precedence.
@@ -239,6 +243,61 @@ This feature allows you to pause rollouts for a deployment for a specified durat
1. ✅ Your deployment references multiple ConfigMaps or Secrets that may be updated at the same time. 1. ✅ Your deployment references multiple ConfigMaps or Secrets that may be updated at the same time.
1. ✅ You want to minimize unnecessary rollouts and reduce downtime caused by back-to-back configuration changes. 1. ✅ You want to minimize unnecessary rollouts and reduce downtime caused by back-to-back configuration changes.
### 8. 🔐 CSI Secret Provider Support
Reloader supports the [Secrets Store CSI Driver](https://secrets-store-csi-driver.sigs.k8s.io/), which allows mounting secrets from external secret stores (like AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) directly into pods.
Unlike Kubernetes Secret objects, CSI-mounted secrets do not always trigger native Kubernetes update events. Reloader solves this by watching CSI status resources and restarting affected workloads when mounted secret versions change.
#### How it works
When secret rotation is enabled, the Secrets Store CSI Driver updates a Kubernetes resource called: `SecretProviderClassPodStatus`
This resource reflects the currently mounted secret versions for a pod.
Reloader watches these updates and triggers a rollout when a change is detected.
#### Prerequisites
- Secrets Store CSI Driver must be installed in your cluster
- Secret rotation enabled in the CSI driver.
- Enable CSI integration in Reloader: `--enable-csi-integration=true`
#### Annotations for CSI-mounted Secrets
| Annotation | Description |
|------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| `reloader.stakater.com/auto: "true"` | Global Discovery: Automatically discovers and reloads the workload when any mounted ConfigMap or Secret is updated. |
| `secretproviderclass.reloader.stakater.com/auto: 'true'` | CSI Discovery: Specifically watches for updates to all SecretProviderClasses used by the workload (CSI driver integration). |
| `secretproviderclass.reloader.stakater.com/reload: "my-secretproviderclass"` | Targeted Reload: Only reloads the workload when the specifically named SecretProviderClass(es) are updated. |
Reloader monitors changes at the **per-secret level** by watching the `SecretProviderClassPodStatus`. Make sure each secret you want to monitor is properly defined with a `secretKey` in your `SecretProviderClass`:
```yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-reloader-demo
namespace: test
spec:
provider: vault
parameters:
vaultAddress: "http://vault.vault.svc:8200"
vaultSkipTLSVerify: "true"
roleName: "demo-role"
objects: |
- objectName: "password"
secretPath: "secret/data/reloader-demo"
secretKey: "password"
```
***Important***: Reloader tracks changes to individual secrets (identified by `secretKey`). If your SecretProviderClass doesn't specify `secretKey` for each object, Reloader may not detect updates correctly.
#### Notes & Limitations
Reloader reacts to CSI status changes, not direct updates to external secret stores
Secret rotation must be enabled in the CSI driver for updates to be detected
CSI limitations (such as `subPath` mounts) still apply and may require pod restarts
If secrets are synced to Kubernetes Secret objects, standard Reloader behavior applies and CSI support may not be required
## 🚀 Installation ## 🚀 Installation
### 1. 📦 Helm ### 1. 📦 Helm
@@ -430,7 +489,7 @@ PRs are welcome. In general, we follow the "fork-and-pull" Git workflow:
## Release Processes ## Release Processes
_Repository GitHub releases_: As requested by the community in [issue 685](https://github.com/stakater/Reloader/issues/685), Reloader is now based on a manual release process. Releases are no longer done on every merged PR to the main branch, but manually on request. *Repository GitHub releases*: As requested by the community in [issue 685](https://github.com/stakater/Reloader/issues/685), Reloader is now based on a manual release process. Releases are no longer done on every merged PR to the main branch, but manually on request.
To make a GitHub release: To make a GitHub release:
@@ -443,7 +502,7 @@ To make a GitHub release:
1. Code owners create another branch from `master` and bump the helm chart version as well as Reloader image version. 1. Code owners create another branch from `master` and bump the helm chart version as well as Reloader image version.
- Code owners create a PR with `release/helm-chart` label, example: [PR-846](https://github.com/stakater/Reloader/pull/846) - Code owners create a PR with `release/helm-chart` label, example: [PR-846](https://github.com/stakater/Reloader/pull/846)
_Repository git tagging_: Push to the main branch will create a merge-image and merge-tag named `merge-${{ github.event.number }}`, for example `merge-800` when pull request number 800 is merged. *Repository git tagging*: Push to the main branch will create a merge-image and merge-tag named `merge-${{ github.event.number }}`, for example `merge-800` when pull request number 800 is merged.
## Changelog ## Changelog

View File

@@ -105,6 +105,17 @@ rules:
- create - create
- get - get
- update - update
{{- end}}
{{- if .Values.reloader.enableCSIIntegration }}
- apiGroups:
- "secrets-store.csi.x-k8s.io"
resources:
- secretproviderclasspodstatuses
- secretproviderclasses
verbs:
- list
- get
- watch
{{- end}} {{- end}}
- apiGroups: - apiGroups:
- "" - ""

View File

@@ -210,7 +210,7 @@ spec:
{{- . | toYaml | nindent 10 }} {{- . | toYaml | nindent 10 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if or (.Values.reloader.logFormat) (.Values.reloader.logLevel) (.Values.reloader.ignoreSecrets) (.Values.reloader.ignoreNamespaces) (include "reloader-namespaceSelector" .) (.Values.reloader.resourceLabelSelector) (.Values.reloader.ignoreConfigMaps) (.Values.reloader.custom_annotations) (eq .Values.reloader.isArgoRollouts true) (eq .Values.reloader.reloadOnCreate true) (eq .Values.reloader.reloadOnDelete true) (ne .Values.reloader.reloadStrategy "default") (.Values.reloader.enableHA) (.Values.reloader.autoReloadAll) (.Values.reloader.ignoreJobs) (.Values.reloader.ignoreCronJobs)}} {{- if or (.Values.reloader.logFormat) (.Values.reloader.logLevel) (.Values.reloader.ignoreSecrets) (.Values.reloader.ignoreNamespaces) (include "reloader-namespaceSelector" .) (.Values.reloader.resourceLabelSelector) (.Values.reloader.ignoreConfigMaps) (.Values.reloader.custom_annotations) (eq .Values.reloader.isArgoRollouts true) (eq .Values.reloader.reloadOnCreate true) (eq .Values.reloader.reloadOnDelete true) (ne .Values.reloader.reloadStrategy "default") (.Values.reloader.enableHA) (.Values.reloader.autoReloadAll) (.Values.reloader.ignoreJobs) (.Values.reloader.ignoreCronJobs) (.Values.reloader.enableCSIIntegration)}}
args: args:
{{- if .Values.reloader.logFormat }} {{- if .Values.reloader.logFormat }}
- "--log-format={{ .Values.reloader.logFormat }}" - "--log-format={{ .Values.reloader.logFormat }}"
@@ -246,6 +246,9 @@ spec:
- "--pprof-addr={{ .Values.reloader.pprofAddr }}" - "--pprof-addr={{ .Values.reloader.pprofAddr }}"
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if .Values.reloader.enableCSIIntegration }}
- "--enable-csi-integration=true"
{{- end }}
{{- if .Values.reloader.custom_annotations }} {{- if .Values.reloader.custom_annotations }}
{{- if .Values.reloader.custom_annotations.configmap }} {{- if .Values.reloader.custom_annotations.configmap }}
- "--configmap-annotation" - "--configmap-annotation"

View File

@@ -92,6 +92,17 @@ rules:
- create - create
- get - get
- update - update
{{- end}}
{{- if .Values.reloader.enableCSIIntegration }}
- apiGroups:
- "secrets-store.csi.x-k8s.io"
resources:
- secretproviderclasspodstatuses
- secretproviderclasses
verbs:
- list
- get
- watch
{{- end}} {{- end}}
- apiGroups: - apiGroups:
- "" - ""

View File

@@ -49,6 +49,7 @@ reloader:
enableHA: false enableHA: false
# Set to true to enable pprof for profiling # Set to true to enable pprof for profiling
enablePProf: false enablePProf: false
enableCSIIntegration: false
# Address to start pprof server on. Default is ":6060" # Address to start pprof server on. Default is ":6060"
pprofAddr: ":6060" pprofAddr: ":6060"
# Set to true if you have a pod security policy that enforces readOnlyRootFilesystem # Set to true if you have a pod security policy that enforces readOnlyRootFilesystem

View File

@@ -3,9 +3,9 @@
Reloader is inspired from [`configmapcontroller`](https://github.com/fabric8io/configmapcontroller) but there are many ways in which it differs from `configmapcontroller`. Below is the small comparison between these two controllers. Reloader is inspired from [`configmapcontroller`](https://github.com/fabric8io/configmapcontroller) but there are many ways in which it differs from `configmapcontroller`. Below is the small comparison between these two controllers.
| Reloader | ConfigMap | | Reloader | ConfigMap |
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Reloader can watch both `Secrets` and `ConfigMaps`. | `configmapcontroller` can only watch changes in `ConfigMaps`. It cannot detect changes in other resources like `Secrets`. | | Reloader can watch both `Secrets` and `ConfigMaps`. | `configmapcontroller` can only watch changes in `ConfigMaps`. It cannot detect changes in other resources like `Secrets`. |
| Reloader can perform rolling upgrades on `deployments` as well as on `statefulsets` and `daemonsets` | `configmapcontroller` can only perform rolling upgrades on `deployments`. It currently does not support rolling upgrades on `statefulsets` and `daemonsets` | | Reloader can perform rolling upgrades on `deployments` as well as on `statefulsets` and `daemonsets` | `configmapcontroller` can only perform rolling upgrades on `deployments`. It currently does not support rolling upgrades on `statefulsets` and `daemonsets` |
| Reloader provides both unit test cases and end to end integration test cases for future updates. So one can make sure that new changes do not break any old functionality. | Currently there are not any unit test cases or end to end integration test cases in `configmap-controller`. It add difficulties for any additional updates in `configmap-controller` and one can not know for sure whether new changes breaks any old functionality or not. | | Reloader provides both unit test cases and end to end integration test cases for future updates. So one can make sure that new changes do not break any old functionality. | Currently there are not any unit test cases or end to end integration test cases in `configmap-controller`. It adds difficulties for any additional updates in `configmap-controller` and one can not know for sure whether new changes breaks any old functionality or not. |
| Reloader uses SHA1 to encode the change in `ConfigMap` or `Secret`. It then saves the SHA1 value in `STAKATER_FOO_CONFIGMAP` or `STAKATER_FOO_SECRET` environment variable depending upon where the change has happened. The use of SHA1 provides a concise 40 characters encoded value that is very less prone to collision. | `configmap-controller` uses `FABRICB_FOO_REVISION` environment variable to store any change in `ConfigMap` controller. It does not encode it or convert it in suitable hash value to avoid data pollution in deployment. | | Reloader uses SHA1 to encode the change in `ConfigMap` or `Secret`. It then saves the SHA1 value in `STAKATER_FOO_CONFIGMAP` or `STAKATER_FOO_SECRET` environment variable depending upon where the change has happened. The use of SHA1 provides a concise 40 characters encoded value that is very less prone to collision. | `configmap-controller` uses `FABRICB_FOO_REVISION` environment variable to store any change in `ConfigMap` controller. It does not encode it or convert it in suitable hash value to avoid data pollution in deployment. |
| Reloader allows you to customize your own annotation (for both `Secrets` and `ConfigMaps`) using command line flags | `configmap-controller` restricts you to only their provided annotation | | Reloader allows you to customize your own annotation (for both `Secrets` and `ConfigMaps`) using command line flags | `configmap-controller` restricts you to only their provided annotation |

View File

@@ -6,7 +6,7 @@ Reloader and k8s-trigger-controller are both built for same purpose. So there ar
- Both controllers support change detection in `ConfigMaps` and `Secrets` - Both controllers support change detection in `ConfigMaps` and `Secrets`
- Both controllers support deployment `rollout` - Both controllers support deployment `rollout`
- Both controllers use SHA1 for hashing - Reloader controller use SHA1 for hashing
- Both controllers have end to end as well as unit test cases. - Both controllers have end to end as well as unit test cases.
## Differences ## Differences

67
go.mod
View File

@@ -4,18 +4,19 @@ go 1.25.5
require ( require (
github.com/argoproj/argo-rollouts v1.8.3 github.com/argoproj/argo-rollouts v1.8.3
github.com/openshift/api v0.0.0-20250411135543-10a8fa583797 github.com/openshift/api v0.0.0-20260102143802-d2ec16864f86
github.com/openshift/client-go v0.0.0-20250402181141-b3bad3b645f2 github.com/openshift/client-go v0.0.0-20251223102348-558b0eef16bc
github.com/parnurzeal/gorequest v0.3.0 github.com/parnurzeal/gorequest v0.3.0
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.23.2
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
k8s.io/api v0.32.3 k8s.io/api v0.35.0
k8s.io/apimachinery v0.32.3 k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.32.3 k8s.io/client-go v0.35.0
k8s.io/kubectl v0.32.3 k8s.io/kubectl v0.35.0
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2
sigs.k8s.io/secrets-store-csi-driver v1.5.5
) )
require ( require (
@@ -24,16 +25,14 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 // indirect github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/swag v0.23.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
@@ -41,41 +40,43 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/moul/http2curl v1.0.0 // indirect github.com/moul/http2curl v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/spf13/pflag v1.0.9 // indirect github.com/spf13/pflag v1.0.9 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/oauth2 v0.29.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/net v0.47.0 // indirect
golang.org/x/term v0.31.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/sys v0.39.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.11.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect
) )
// Replacements for argo-rollouts // Replacements for argo-rollouts
replace ( replace (
github.com/go-check/check => github.com/go-check/check v0.0.0-20201130134442-10cb98267c6c github.com/go-check/check => github.com/go-check/check v0.0.0-20201130134442-10cb98267c6c
k8s.io/api v0.0.0 => k8s.io/api v0.32.3 k8s.io/api v0.0.0 => k8s.io/api v0.35.0
k8s.io/apimachinery v0.0.0 => k8s.io/apimachinery v0.32.3 k8s.io/apimachinery v0.0.0 => k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.0.0 => k8s.io/client-go v0.32.3 k8s.io/client-go v0.0.0 => k8s.io/client-go v0.35.0
k8s.io/cloud-provider v0.0.0 => k8s.io/cloud-provider v0.24.2 k8s.io/cloud-provider v0.0.0 => k8s.io/cloud-provider v0.24.2
k8s.io/controller-manager v0.0.0 => k8s.io/controller-manager v0.24.2 k8s.io/controller-manager v0.0.0 => k8s.io/controller-manager v0.24.2
k8s.io/cri-api v0.0.0 => k8s.io/cri-api v0.20.5-rc.0 k8s.io/cri-api v0.0.0 => k8s.io/cri-api v0.20.5-rc.0
@@ -84,7 +85,7 @@ replace (
k8s.io/kube-controller-manager v0.0.0 => k8s.io/kube-controller-manager v0.24.2 k8s.io/kube-controller-manager v0.0.0 => k8s.io/kube-controller-manager v0.24.2
k8s.io/kube-proxy v0.0.0 => k8s.io/kube-proxy v0.24.2 k8s.io/kube-proxy v0.0.0 => k8s.io/kube-proxy v0.24.2
k8s.io/kube-scheduler v0.0.0 => k8s.io/kube-scheduler v0.24.2 k8s.io/kube-scheduler v0.0.0 => k8s.io/kube-scheduler v0.24.2
k8s.io/kubectl v0.0.0 => k8s.io/kubectl v0.32.3 k8s.io/kubectl v0.0.0 => k8s.io/kubectl v0.35.0
k8s.io/kubelet v0.0.0 => k8s.io/kubelet v0.24.2 k8s.io/kubelet v0.0.0 => k8s.io/kubelet v0.24.2
k8s.io/legacy-cloud-providers v0.0.0 => k8s.io/legacy-cloud-providers v0.24.2 k8s.io/legacy-cloud-providers v0.0.0 => k8s.io/legacy-cloud-providers v0.24.2
k8s.io/mount-utils v0.0.0 => k8s.io/mount-utils v0.20.5-rc.0 k8s.io/mount-utils v0.0.0 => k8s.io/mount-utils v0.20.5-rc.0

148
go.sum
View File

@@ -1,3 +1,5 @@
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/argoproj/argo-rollouts v1.8.3 h1:blbtQva4IK9r6gFh+dWkCrLnFdPOWiv9ubQYu36qeaA= github.com/argoproj/argo-rollouts v1.8.3 h1:blbtQva4IK9r6gFh+dWkCrLnFdPOWiv9ubQYu36qeaA=
github.com/argoproj/argo-rollouts v1.8.3/go.mod h1:kCAUvIfMGfOyVf3lvQbBt0nqQn4Pd+zB5/YwKv+UBa8= github.com/argoproj/argo-rollouts v1.8.3/go.mod h1:kCAUvIfMGfOyVf3lvQbBt0nqQn4Pd+zB5/YwKv+UBa8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -13,10 +15,10 @@ github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 h1:1NyRx2f4W4WBRyg
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380/go.mod h1:thX175TtLTzLj3p7N/Q9IiKZ7NF+p72cvL91emV0hzo= github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380/go.mod h1:thX175TtLTzLj3p7N/Q9IiKZ7NF+p72cvL91emV0hzo=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
@@ -27,18 +29,13 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -66,20 +63,22 @@ github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUt
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/openshift/api v0.0.0-20250411135543-10a8fa583797 h1:8x3G8QOZqo2bRAL8JFlPz/odqQECI/XmlZeRwnFxJ8I= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/openshift/api v0.0.0-20250411135543-10a8fa583797/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw= github.com/openshift/api v0.0.0-20260102143802-d2ec16864f86 h1:Vsqg+WqSA91LjrwK5lzkSCjztK/B+T8MPKI3MIALx3w=
github.com/openshift/client-go v0.0.0-20250402181141-b3bad3b645f2 h1:bPXR0R8zp1o12nSUphN26hSM+OKYq5pMorbDCpApzDQ= github.com/openshift/api v0.0.0-20260102143802-d2ec16864f86/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/client-go v0.0.0-20250402181141-b3bad3b645f2/go.mod h1:dT1cJyVTperQ53GvVRa+GZ27r02fDZy2k5j+9QoQsCo= github.com/openshift/client-go v0.0.0-20251223102348-558b0eef16bc h1:nIlRaJfr/yGjPV15MNF5eVHLAGyXFjcUzO+hXeWDDk8=
github.com/openshift/client-go v0.0.0-20251223102348-558b0eef16bc/go.mod h1:cs9BwTu96sm2vQvy7r9rOiltgu90M6ju2qIHFG9WU+o=
github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI= github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI=
github.com/parnurzeal/gorequest v0.3.0/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/parnurzeal/gorequest v0.3.0/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -87,16 +86,16 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -104,50 +103,60 @@ github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.3.0/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -155,44 +164,45 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/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-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.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/kubectl v0.32.3 h1:VMi584rbboso+yjfv0d8uBHwwxbC438LKq+dXd5tOAI= k8s.io/kubectl v0.35.0 h1:cL/wJKHDe8E8+rP3G7avnymcMg6bH6JEcR5w5uo06wc=
k8s.io/kubectl v0.32.3/go.mod h1:6Euv2aso5GKzo/UVMacV6C7miuyevpfI91SvBvV9Zdg= k8s.io/kubectl v0.35.0/go.mod h1:VR5/TSkYyxZwrRwY5I5dDq6l5KXmiCb+9w8IKplk3Qo=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 h1:OfgiEo21hGiwx1oJUU5MpEaeOEg6coWndBkZF/lkFuE=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/secrets-store-csi-driver v1.5.5 h1:LJDpDL5TILhlP68nGvtGSlJFxSDgAD2m148NT0Ts7os=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/secrets-store-csi-driver v1.5.5/go.mod h1:i2WqLicYH00hrTG3JAzICPMF4HL4KMEORlDt9UQoZLk=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -49,7 +49,7 @@ func newTestFixtures() testFixtures {
func setupTestClients() kube.Clients { func setupTestClients() kube.Clients {
return kube.Clients{ return kube.Clients{
KubernetesClient: fake.NewSimpleClientset(), KubernetesClient: fake.NewClientset(),
ArgoRolloutClient: fakeargoclientset.NewSimpleClientset(), ArgoRolloutClient: fakeargoclientset.NewSimpleClientset(),
} }
} }

View File

@@ -160,6 +160,10 @@ func startReloader(cmd *cobra.Command, args []string) {
var controllers []*controller.Controller var controllers []*controller.Controller
for k := range kube.ResourceMap { for k := range kube.ResourceMap {
if k == constants.SecretProviderClassController && !shouldRunCSIController() {
continue
}
if ignoredResourcesList.Contains(k) || (len(namespaceLabelSelector) == 0 && k == "namespaces") { if ignoredResourcesList.Contains(k) || (len(namespaceLabelSelector) == 0 && k == "namespaces") {
continue continue
} }
@@ -207,3 +211,15 @@ func startPProfServer() {
logrus.Errorf("Failed to start pprof server: %v", err) logrus.Errorf("Failed to start pprof server: %v", err)
} }
} }
func shouldRunCSIController() bool {
if !options.EnableCSIIntegration {
logrus.Info("Skipping secretproviderclasspodstatuses controller: EnableCSIIntegration is disabled")
return false
}
if !kube.IsCSIInstalled {
logrus.Info("Skipping secretproviderclasspodstatuses controller: CSI CRDs not installed")
return false
}
return true
}

View File

@@ -8,6 +8,8 @@ const (
ConfigmapEnvVarPostfix = "CONFIGMAP" ConfigmapEnvVarPostfix = "CONFIGMAP"
// SecretEnvVarPostfix is a postfix for secret envVar // SecretEnvVarPostfix is a postfix for secret envVar
SecretEnvVarPostfix = "SECRET" SecretEnvVarPostfix = "SECRET"
// SecretProviderClassEnvVarPostfix is a postfix for secretproviderclasspodstatus envVar
SecretProviderClassEnvVarPostfix = "SECRETPROVIDERCLASS"
// EnvVarPrefix is a Prefix for environment variable // EnvVarPrefix is a Prefix for environment variable
EnvVarPrefix = "STAKATER_" EnvVarPrefix = "STAKATER_"
@@ -22,6 +24,8 @@ const (
EnvVarsReloadStrategy = "env-vars" EnvVarsReloadStrategy = "env-vars"
// AnnotationsReloadStrategy instructs Reloader to add pod template annotations to facilitate a restart // AnnotationsReloadStrategy instructs Reloader to add pod template annotations to facilitate a restart
AnnotationsReloadStrategy = "annotations" AnnotationsReloadStrategy = "annotations"
// SecretProviderClassController enables support for SecretProviderClassPodStatus resources
SecretProviderClassController = "secretproviderclasspodstatuses"
) )
// Leadership election related consts // Leadership election related consts

View File

@@ -2,9 +2,11 @@ package controller
import ( import (
"fmt" "fmt"
"slices"
"time" "time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/stakater/Reloader/internal/pkg/constants"
"github.com/stakater/Reloader/internal/pkg/handler" "github.com/stakater/Reloader/internal/pkg/handler"
"github.com/stakater/Reloader/internal/pkg/metrics" "github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options" "github.com/stakater/Reloader/internal/pkg/options"
@@ -21,7 +23,7 @@ import (
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/utils/strings/slices" csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
) )
// Controller for checking events // Controller for checking events
@@ -79,7 +81,12 @@ func NewController(
} }
} }
listWatcher := cache.NewFilteredListWatchFromClient(client.CoreV1().RESTClient(), resource, namespace, optionsModifier) getterRESTClient, err := getClientForResource(resource, client)
if err != nil {
return nil, fmt.Errorf("failed to initialize REST client for %s: %w", resource, err)
}
listWatcher := cache.NewFilteredListWatchFromClient(getterRESTClient, resource, namespace, optionsModifier)
_, informer := cache.NewInformerWithOptions(cache.InformerOptions{ _, informer := cache.NewInformerWithOptions(cache.InformerOptions{
ListerWatcher: listWatcher, ListerWatcher: listWatcher,
@@ -108,6 +115,8 @@ func (c *Controller) Add(obj interface{}) {
case *v1.Namespace: case *v1.Namespace:
c.addSelectedNamespaceToCache(*object) c.addSelectedNamespaceToCache(*object)
return return
case *csiv1.SecretProviderClassPodStatus:
return
} }
if options.ReloadOnCreate == "true" { if options.ReloadOnCreate == "true" {
@@ -122,11 +131,13 @@ func (c *Controller) Add(obj interface{}) {
} }
func (c *Controller) resourceInIgnoredNamespace(raw interface{}) bool { func (c *Controller) resourceInIgnoredNamespace(raw interface{}) bool {
switch object := raw.(type) { switch obj := raw.(type) {
case *v1.ConfigMap: case *v1.ConfigMap:
return c.ignoredNamespaces.Contains(object.Namespace) return c.ignoredNamespaces.Contains(obj.Namespace)
case *v1.Secret: case *v1.Secret:
return c.ignoredNamespaces.Contains(object.Namespace) return c.ignoredNamespaces.Contains(obj.Namespace)
case *csiv1.SecretProviderClassPodStatus:
return c.ignoredNamespaces.Contains(obj.Namespace)
} }
return false return false
} }
@@ -145,6 +156,10 @@ func (c *Controller) resourceInSelectedNamespaces(raw interface{}) bool {
if slices.Contains(selectedNamespacesCache, object.GetNamespace()) { if slices.Contains(selectedNamespacesCache, object.GetNamespace()) {
return true return true
} }
case *csiv1.SecretProviderClassPodStatus:
if slices.Contains(selectedNamespacesCache, object.GetNamespace()) {
return true
}
} }
return false return false
} }
@@ -183,6 +198,9 @@ func (c *Controller) Update(old interface{}, new interface{}) {
// Delete function to add an object to the queue in case of deleting a resource // Delete function to add an object to the queue in case of deleting a resource
func (c *Controller) Delete(old interface{}) { func (c *Controller) Delete(old interface{}) {
if _, ok := old.(*csiv1.SecretProviderClassPodStatus); ok {
return
}
if options.ReloadOnDelete == "true" { if options.ReloadOnDelete == "true" {
if !c.resourceInIgnoredNamespace(old) && c.resourceInSelectedNamespaces(old) && secretControllerInitialized && configmapControllerInitialized { if !c.resourceInIgnoredNamespace(old) && c.resourceInSelectedNamespaces(old) && secretControllerInitialized && configmapControllerInitialized {
@@ -280,3 +298,14 @@ func (c *Controller) handleErr(err error, key interface{}) {
logrus.Errorf("Dropping key out of the queue: %v", err) logrus.Errorf("Dropping key out of the queue: %v", err)
logrus.Debugf("Dropping the key %q out of the queue: %v", key, err) logrus.Debugf("Dropping the key %q out of the queue: %v", key, err)
} }
func getClientForResource(resource string, coreClient kubernetes.Interface) (cache.Getter, error) {
if resource == constants.SecretProviderClassController {
csiClient, err := kube.GetCSIClient()
if err != nil {
return nil, fmt.Errorf("failed to get CSI client: %w", err)
}
return csiClient.SecretsstoreV1().RESTClient(), nil
}
return coreClient.CoreV1().RESTClient(), nil
}

View File

@@ -30,6 +30,7 @@ var (
namespace = "test-reloader-" + testutil.RandSeq(5) namespace = "test-reloader-" + testutil.RandSeq(5)
configmapNamePrefix = "testconfigmap-reloader" configmapNamePrefix = "testconfigmap-reloader"
secretNamePrefix = "testsecret-reloader" secretNamePrefix = "testsecret-reloader"
secretProviderClassPodStatusPrefix = "testsecretproviderclasspodstatus-reloader"
data = "dGVzdFNlY3JldEVuY29kaW5nRm9yUmVsb2FkZXI=" data = "dGVzdFNlY3JldEVuY29kaW5nRm9yUmVsb2FkZXI="
newData = "dGVzdE5ld1NlY3JldEVuY29kaW5nRm9yUmVsb2FkZXI=" newData = "dGVzdE5ld1NlY3JldEVuY29kaW5nRm9yUmVsb2FkZXI="
updatedData = "dGVzdFVwZGF0ZWRTZWNyZXRFbmNvZGluZ0ZvclJlbG9hZGVy" updatedData = "dGVzdFVwZGF0ZWRTZWNyZXRFbmNvZGluZ0ZvclJlbG9hZGVy"
@@ -46,6 +47,10 @@ func TestMain(m *testing.M) {
logrus.Infof("Creating controller") logrus.Infof("Creating controller")
for k := range kube.ResourceMap { for k := range kube.ResourceMap {
// Don't create controller if CSI provider is not installed
if k == "secretproviderclasspodstatuses" && !kube.IsCSIInstalled {
continue
}
if k == "namespaces" { if k == "namespaces" {
continue continue
} }
@@ -579,6 +584,217 @@ func TestControllerUpdatingSecretLabelsShouldNotCreateOrUpdatePodAnnotationInDep
time.Sleep(sleepDuration) time.Sleep(sleepDuration)
} }
// Perform rolling upgrade on deployment and create pod annotation var upon updating the secretclassproviderpodstatus
func TestControllerUpdatingSecretProviderClassPodStatusShouldCreatePodAnnotationInDeployment(t *testing.T) {
options.ReloadStrategy = constants.AnnotationsReloadStrategy
if !kube.IsCSIInstalled {
return
}
// Creating secretproviderclass
secretproviderclasspodstatusName := secretProviderClassPodStatusPrefix + "-update-" + testutil.RandSeq(5)
_, err := testutil.CreateSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretproviderclass %v", err)
}
// Creating secretproviderclasspodstatus
spcpsClient, err := testutil.CreateSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretclasssproviderpodstatus %v", err)
}
// Creating deployment
_, err = testutil.CreateDeployment(clients.KubernetesClient, secretproviderclasspodstatusName, namespace, true)
if err != nil {
t.Errorf("Error in deployment creation: %v", err)
}
// Updating secretproviderclasspodstatus for first time
updateErr := testutil.UpdateSecretProviderClassPodStatus(spcpsClient, namespace, secretproviderclasspodstatusName, "", newData)
if updateErr != nil {
t.Errorf("Secretproviderclasspodstatus was not updated")
}
// Verifying deployment update
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretProviderClassPodStatusResourceType, namespace, secretproviderclasspodstatusName, newData)
config := common.Config{
Namespace: namespace,
ResourceName: secretproviderclasspodstatusName,
SHAValue: shaData,
Annotation: options.SecretProviderClassUpdateOnChangeAnnotation,
}
deploymentFuncs := handler.GetDeploymentRollingUpgradeFuncs()
updated := testutil.VerifyResourceAnnotationUpdate(clients, config, deploymentFuncs)
if !updated {
t.Errorf("Deployment was not updated")
}
time.Sleep(sleepDuration)
// Deleting deployment
err = testutil.DeleteDeployment(clients.KubernetesClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the deployment %v", err)
}
// Deleting secretproviderclass
err = testutil.DeleteSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclass %v", err)
}
// Deleting secretproviderclasspodstatus
err = testutil.DeleteSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclasspodstatus %v", err)
}
time.Sleep(sleepDuration)
}
// Perform rolling upgrade on deployment and update pod annotation var upon updating the secretproviderclasspodstatus
func TestControllerUpdatingSecretProviderClassPodStatusShouldUpdatePodAnnotationInDeployment(t *testing.T) {
options.ReloadStrategy = constants.AnnotationsReloadStrategy
if !kube.IsCSIInstalled {
return
}
// Creating secretproviderclass
secretproviderclasspodstatusName := secretProviderClassPodStatusPrefix + "-update-" + testutil.RandSeq(5)
_, err := testutil.CreateSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretproviderclass %v", err)
}
// Creating secretproviderclasspodstatus
spcpsClient, err := testutil.CreateSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretclasssproviderpodstatus %v", err)
}
// Creating deployment
_, err = testutil.CreateDeployment(clients.KubernetesClient, secretproviderclasspodstatusName, namespace, true)
if err != nil {
t.Errorf("Error in deployment creation: %v", err)
}
// Updating Secret
err = testutil.UpdateSecretProviderClassPodStatus(spcpsClient, namespace, secretproviderclasspodstatusName, "", newData)
if err != nil {
t.Errorf("Error while updating secretproviderclasspodstatus %v", err)
}
// Updating Secret
err = testutil.UpdateSecretProviderClassPodStatus(spcpsClient, namespace, secretproviderclasspodstatusName, "", updatedData)
if err != nil {
t.Errorf("Error while updating secretproviderclasspodstatus %v", err)
}
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.SecretProviderClassPodStatusResourceType, namespace, secretproviderclasspodstatusName, updatedData)
config := common.Config{
Namespace: namespace,
ResourceName: secretproviderclasspodstatusName,
SHAValue: shaData,
Annotation: options.SecretProviderClassUpdateOnChangeAnnotation,
}
deploymentFuncs := handler.GetDeploymentRollingUpgradeFuncs()
updated := testutil.VerifyResourceAnnotationUpdate(clients, config, deploymentFuncs)
if !updated {
t.Errorf("Deployment was not updated")
}
// Deleting Deployment
err = testutil.DeleteDeployment(clients.KubernetesClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the deployment %v", err)
}
// Deleting secretproviderclass
err = testutil.DeleteSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclass %v", err)
}
// Deleting secretproviderclasspodstatus
err = testutil.DeleteSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclasspodstatus %v", err)
}
time.Sleep(sleepDuration)
}
// Do not Perform rolling upgrade on pod and create or update a pod annotation upon updating the label in secretproviderclasspodstatus
func TestControllerUpdatingSecretProviderClassPodStatusWithSameDataShouldNotCreateOrUpdatePodAnnotationInDeployment(t *testing.T) {
options.ReloadStrategy = constants.AnnotationsReloadStrategy
if !kube.IsCSIInstalled {
return
}
// Creating secretproviderclass
secretproviderclasspodstatusName := secretProviderClassPodStatusPrefix + "-update-" + testutil.RandSeq(5)
_, err := testutil.CreateSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretproviderclass %v", err)
}
// Creating secretproviderclasspodstatus
spcpsClient, err := testutil.CreateSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretclasssproviderpodstatus %v", err)
}
// Creating deployment
_, err = testutil.CreateDeployment(clients.KubernetesClient, secretproviderclasspodstatusName, namespace, true)
if err != nil {
t.Errorf("Error in deployment creation: %v", err)
}
err = testutil.UpdateSecretProviderClassPodStatus(spcpsClient, namespace, secretproviderclasspodstatusName, "", data)
if err != nil {
t.Errorf("Error while updating secretproviderclasspodstatus %v", err)
}
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretProviderClassPodStatusResourceType, namespace, secretproviderclasspodstatusName, data)
config := common.Config{
Namespace: namespace,
ResourceName: secretproviderclasspodstatusName,
SHAValue: shaData,
Annotation: options.SecretProviderClassUpdateOnChangeAnnotation,
}
deploymentFuncs := handler.GetDeploymentRollingUpgradeFuncs()
updated := testutil.VerifyResourceAnnotationUpdate(clients, config, deploymentFuncs)
if updated {
t.Errorf("Deployment should not be updated by changing in secretproviderclasspodstatus")
}
// Deleting Deployment
err = testutil.DeleteDeployment(clients.KubernetesClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the deployment %v", err)
}
// Deleting secretproviderclass
err = testutil.DeleteSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclass %v", err)
}
// Deleting secretproviderclasspodstatus
err = testutil.DeleteSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclasspodstatus %v", err)
}
time.Sleep(sleepDuration)
}
// Perform rolling upgrade on DaemonSet and create pod annotation var upon updating the configmap // Perform rolling upgrade on DaemonSet and create pod annotation var upon updating the configmap
func TestControllerUpdatingConfigmapShouldCreatePodAnnotationInDaemonSet(t *testing.T) { func TestControllerUpdatingConfigmapShouldCreatePodAnnotationInDaemonSet(t *testing.T) {
options.ReloadStrategy = constants.AnnotationsReloadStrategy options.ReloadStrategy = constants.AnnotationsReloadStrategy
@@ -1531,6 +1747,215 @@ func TestControllerUpdatingSecretLabelsShouldNotCreateOrUpdateEnvInDeployment(t
time.Sleep(sleepDuration) time.Sleep(sleepDuration)
} }
// Perform rolling upgrade on pod and create a env var upon updating the secretproviderclasspodstatus
func TestControllerUpdatingSecretProviderClassPodStatusShouldCreateEnvInDeployment(t *testing.T) {
options.ReloadStrategy = constants.EnvVarsReloadStrategy
if !kube.IsCSIInstalled {
return
}
// Creating secretproviderclass
secretproviderclasspodstatusName := secretProviderClassPodStatusPrefix + "-update-" + testutil.RandSeq(5)
_, err := testutil.CreateSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretproviderclass %v", err)
}
// Creating secretproviderclasspodstatus
spcpsClient, err := testutil.CreateSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretclasssproviderpodstatus %v", err)
}
// Creating deployment
_, err = testutil.CreateDeployment(clients.KubernetesClient, secretproviderclasspodstatusName, namespace, true)
if err != nil {
t.Errorf("Error in deployment creation: %v", err)
}
// Updating Secret
err = testutil.UpdateSecretProviderClassPodStatus(spcpsClient, namespace, secretproviderclasspodstatusName, "", newData)
if err != nil {
t.Errorf("Error while updating secretproviderclasspodstatus %v", err)
}
// Verifying Upgrade
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretProviderClassPodStatusResourceType, namespace, secretproviderclasspodstatusName, newData)
config := common.Config{
Namespace: namespace,
ResourceName: secretproviderclasspodstatusName,
SHAValue: shaData,
Annotation: options.SecretProviderClassUpdateOnChangeAnnotation,
}
deploymentFuncs := handler.GetDeploymentRollingUpgradeFuncs()
updated := testutil.VerifyResourceEnvVarUpdate(clients, config, constants.SecretProviderClassEnvVarPostfix, deploymentFuncs)
if !updated {
t.Errorf("Deployment was not updated")
}
// Deleting Deployment
err = testutil.DeleteDeployment(clients.KubernetesClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the deployment %v", err)
}
// Deleting secretproviderclass
err = testutil.DeleteSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclass %v", err)
}
// Deleting secretproviderclasspodstatus
err = testutil.DeleteSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclasspodstatus %v", err)
}
time.Sleep(sleepDuration)
}
// Perform rolling upgrade on deployment and update env var upon updating the secretproviderclasspodstatus
func TestControllerUpdatingSecretProviderClassPodStatusShouldUpdateEnvInDeployment(t *testing.T) {
options.ReloadStrategy = constants.EnvVarsReloadStrategy
if !kube.IsCSIInstalled {
return
}
// Creating secretproviderclass
secretproviderclasspodstatusName := secretProviderClassPodStatusPrefix + "-update-" + testutil.RandSeq(5)
_, err := testutil.CreateSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretproviderclass %v", err)
}
// Creating secretproviderclasspodstatus
spcpsClient, err := testutil.CreateSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretclasssproviderpodstatus %v", err)
}
// Creating deployment
_, err = testutil.CreateDeployment(clients.KubernetesClient, secretproviderclasspodstatusName, namespace, true)
if err != nil {
t.Errorf("Error in deployment creation: %v", err)
}
// Updating secretproviderclasspodstatus
err = testutil.UpdateSecretProviderClassPodStatus(spcpsClient, namespace, secretproviderclasspodstatusName, "", newData)
if err != nil {
t.Errorf("Error while updating secretproviderclasspodstatus %v", err)
}
// Updating secretproviderclasspodstatus
err = testutil.UpdateSecretProviderClassPodStatus(spcpsClient, namespace, secretproviderclasspodstatusName, "", updatedData)
if err != nil {
t.Errorf("Error while updating secretproviderclasspodstatus %v", err)
}
// Verifying Upgrade
logrus.Infof("Verifying env var has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.SecretProviderClassPodStatusResourceType, namespace, secretproviderclasspodstatusName, updatedData)
config := common.Config{
Namespace: namespace,
ResourceName: secretproviderclasspodstatusName,
SHAValue: shaData,
Annotation: options.SecretProviderClassUpdateOnChangeAnnotation,
}
deploymentFuncs := handler.GetDeploymentRollingUpgradeFuncs()
updated := testutil.VerifyResourceEnvVarUpdate(clients, config, constants.SecretProviderClassEnvVarPostfix, deploymentFuncs)
if !updated {
t.Errorf("Deployment was not updated")
}
// Deleting Deployment
err = testutil.DeleteDeployment(clients.KubernetesClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the deployment %v", err)
}
// Deleting secretproviderclass
err = testutil.DeleteSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclass %v", err)
}
// Deleting secretproviderclasspodstatus
err = testutil.DeleteSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclasspodstatus %v", err)
}
time.Sleep(sleepDuration)
}
// Do not Perform rolling upgrade on pod and create or update a env var upon updating the label in secretclasssproviderpodstatus
func TestControllerUpdatingSecretProviderClassPodStatusLabelsShouldNotCreateOrUpdateEnvInDeployment(t *testing.T) {
options.ReloadStrategy = constants.EnvVarsReloadStrategy
if !kube.IsCSIInstalled {
return
}
// Creating secretproviderclass
secretproviderclasspodstatusName := secretProviderClassPodStatusPrefix + "-update-" + testutil.RandSeq(5)
_, err := testutil.CreateSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretproviderclass %v", err)
}
// Creating secretproviderclasspodstatus
spcpsClient, err := testutil.CreateSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName, data)
if err != nil {
t.Errorf("Error while creating the secretclasssproviderpodstatus %v", err)
}
// Creating deployment
_, err = testutil.CreateDeployment(clients.KubernetesClient, secretproviderclasspodstatusName, namespace, true)
if err != nil {
t.Errorf("Error in deployment creation: %v", err)
}
err = testutil.UpdateSecretProviderClassPodStatus(spcpsClient, namespace, secretproviderclasspodstatusName, "test", data)
if err != nil {
t.Errorf("Error while updating secretproviderclasspodstatus %v", err)
}
// Verifying Upgrade
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretProviderClassPodStatusResourceType, namespace, secretproviderclasspodstatusName, data)
config := common.Config{
Namespace: namespace,
ResourceName: secretproviderclasspodstatusName,
SHAValue: shaData,
Annotation: options.SecretProviderClassUpdateOnChangeAnnotation,
}
deploymentFuncs := handler.GetDeploymentRollingUpgradeFuncs()
updated := testutil.VerifyResourceEnvVarUpdate(clients, config, constants.SecretProviderClassEnvVarPostfix, deploymentFuncs)
if updated {
t.Errorf("Deployment should not be updated by changing label in secretproviderclasspodstatus")
}
// Deleting Deployment
err = testutil.DeleteDeployment(clients.KubernetesClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the deployment %v", err)
}
// Deleting secretproviderclass
err = testutil.DeleteSecretProviderClass(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclass %v", err)
}
// Deleting secretproviderclasspodstatus
err = testutil.DeleteSecretProviderClassPodStatus(clients.CSIClient, namespace, secretproviderclasspodstatusName)
if err != nil {
logrus.Errorf("Error while deleting the secretproviderclasspodstatus %v", err)
}
time.Sleep(sleepDuration)
}
// Perform rolling upgrade on DaemonSet and create env var upon updating the configmap // Perform rolling upgrade on DaemonSet and create env var upon updating the configmap
func TestControllerUpdatingConfigmapShouldCreateEnvInDaemonSet(t *testing.T) { func TestControllerUpdatingConfigmapShouldCreateEnvInDaemonSet(t *testing.T) {
options.ReloadStrategy = constants.EnvVarsReloadStrategy options.ReloadStrategy = constants.EnvVarsReloadStrategy
@@ -2332,7 +2757,7 @@ func TestController_resourceInNamespaceSelector(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
fakeClient := fake.NewSimpleClientset() fakeClient := fake.NewClientset()
namespace, _ := fakeClient.CoreV1().Namespaces().Create(context.Background(), &tt.fields.namespace, metav1.CreateOptions{}) namespace, _ := fakeClient.CoreV1().Namespaces().Create(context.Background(), &tt.fields.namespace, metav1.CreateOptions{})
logrus.Infof("created fakeClient namespace for testing = %s", namespace.Name) logrus.Infof("created fakeClient namespace for testing = %s", namespace.Name)

View File

@@ -13,3 +13,16 @@ func TestGenerateSHA(t *testing.T) {
t.Errorf("Failed to generate SHA") t.Errorf("Failed to generate SHA")
} }
} }
// TestGenerateSHAEmptyString verifies that empty string generates a valid hash
// This ensures consistent behavior and avoids issues with string matching operations
func TestGenerateSHAEmptyString(t *testing.T) {
result := GenerateSHA("")
expected := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
if result != expected {
t.Errorf("Failed to generate SHA for empty string. Expected: %s, Got: %s", expected, result)
}
if len(result) != 40 {
t.Errorf("SHA hash should be 40 characters long, got %d", len(result))
}
}

View File

@@ -244,7 +244,7 @@ func TestHandleMissingTimerSimple(t *testing.T) {
}() }()
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
fakeClient := testclient.NewSimpleClientset() fakeClient := testclient.NewClientset()
clients := kube.Clients{ clients := kube.Clients{
KubernetesClient: fakeClient, KubernetesClient: fakeClient,
} }
@@ -337,7 +337,7 @@ func TestPauseDeployment(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
fakeClient := testclient.NewSimpleClientset() fakeClient := testclient.NewClientset()
clients := kube.Clients{ clients := kube.Clients{
KubernetesClient: fakeClient, KubernetesClient: fakeClient,
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/stakater/Reloader/pkg/common" "github.com/stakater/Reloader/pkg/common"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
) )
// ResourceUpdatedHandler contains updated objects // ResourceUpdatedHandler contains updated objects
@@ -38,16 +39,31 @@ func (r ResourceUpdatedHandler) Handle() error {
// GetConfig gets configurations containing SHA, annotations, namespace and resource name // GetConfig gets configurations containing SHA, annotations, namespace and resource name
func (r ResourceUpdatedHandler) GetConfig() (common.Config, string) { func (r ResourceUpdatedHandler) GetConfig() (common.Config, string) {
var oldSHAData string var (
var config common.Config oldSHAData string
if _, ok := r.Resource.(*v1.ConfigMap); ok { config common.Config
oldSHAData = util.GetSHAfromConfigmap(r.OldResource.(*v1.ConfigMap)) )
config = common.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
} else if _, ok := r.Resource.(*v1.Secret); ok { switch res := r.Resource.(type) {
oldSHAData = util.GetSHAfromSecret(r.OldResource.(*v1.Secret).Data) case *v1.ConfigMap:
config = common.GetSecretConfig(r.Resource.(*v1.Secret)) if old, ok := r.OldResource.(*v1.ConfigMap); ok && old != nil {
} else { oldSHAData = util.GetSHAfromConfigmap(old)
logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found, %v", r.Resource) }
config = common.GetConfigmapConfig(res)
case *v1.Secret:
if old, ok := r.OldResource.(*v1.Secret); ok && old != nil {
oldSHAData = util.GetSHAfromSecret(old.Data)
}
config = common.GetSecretConfig(res)
case *csiv1.SecretProviderClassPodStatus:
if old, ok := r.OldResource.(*csiv1.SecretProviderClassPodStatus); ok && old != nil && old.Status.Objects != nil {
oldSHAData = util.GetSHAfromSecretProviderClassPodStatus(old.Status)
}
config = common.GetSecretProviderClassPodStatusConfig(res)
default:
logrus.Warnf("Invalid resource: Resource should be 'Secret', 'Configmap' or 'SecretProviderClassPodStatus' but found, %T", r.Resource)
} }
return config, oldSHAData return config, oldSHAData
} }

View File

@@ -2,11 +2,13 @@ package handler
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"strings"
"github.com/parnurzeal/gorequest" "github.com/parnurzeal/gorequest"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@@ -23,6 +25,7 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
patchtypes "k8s.io/apimachinery/pkg/types" patchtypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
@@ -283,6 +286,10 @@ func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs ca
return err return err
} }
} }
if config.Type == constants.SecretProviderClassEnvVarPostfix {
populateAnnotationsFromSecretProviderClass(clients, &config)
}
annotations := upgradeFuncs.AnnotationsFunc(resource) annotations := upgradeFuncs.AnnotationsFunc(resource)
podAnnotations := upgradeFuncs.PodAnnotationsFunc(resource) podAnnotations := upgradeFuncs.PodAnnotationsFunc(resource)
result := common.ShouldReload(config, upgradeFuncs.ResourceType, annotations, podAnnotations, common.GetCommandLineOptions()) result := common.ShouldReload(config, upgradeFuncs.ResourceType, annotations, podAnnotations, common.GetCommandLineOptions())
@@ -380,6 +387,10 @@ func getVolumeMountName(volumes []v1.Volume, mountType string, volumeName string
} }
} }
} }
case constants.SecretProviderClassEnvVarPostfix:
if volumes[i].CSI != nil && volumes[i].CSI.VolumeAttributes["secretProviderClass"] == volumeName {
return volumes[i].Name
}
} }
} }
@@ -516,6 +527,10 @@ func updatePodAnnotations(upgradeFuncs callbacks.RollingUpgradeFuncs, item runti
return InvokeStrategyResult{constants.NotUpdated, nil} return InvokeStrategyResult{constants.NotUpdated, nil}
} }
if config.Type == constants.SecretProviderClassEnvVarPostfix && secretProviderClassAnnotationReloaded(pa, config) {
return InvokeStrategyResult{constants.NotUpdated, nil}
}
for k, v := range annotations { for k, v := range annotations {
pa[k] = v pa[k] = v
} }
@@ -523,6 +538,11 @@ func updatePodAnnotations(upgradeFuncs callbacks.RollingUpgradeFuncs, item runti
return InvokeStrategyResult{constants.Updated, &Patch{Type: patchtypes.StrategicMergePatchType, Bytes: patch}} return InvokeStrategyResult{constants.Updated, &Patch{Type: patchtypes.StrategicMergePatchType, Bytes: patch}}
} }
func secretProviderClassAnnotationReloaded(oldAnnotations map[string]string, newConfig common.Config) bool {
annotation := oldAnnotations[getReloaderAnnotationKey()]
return strings.Contains(annotation, newConfig.ResourceName) && strings.Contains(annotation, newConfig.SHAValue)
}
func getReloaderAnnotationKey() string { func getReloaderAnnotationKey() string {
return fmt.Sprintf("%s/%s", return fmt.Sprintf("%s/%s",
constants.ReloaderAnnotationPrefix, constants.ReloaderAnnotationPrefix,
@@ -573,6 +593,10 @@ func updateContainerEnvVars(upgradeFuncs callbacks.RollingUpgradeFuncs, item run
return InvokeStrategyResult{constants.NoContainerFound, nil} return InvokeStrategyResult{constants.NoContainerFound, nil}
} }
if config.Type == constants.SecretProviderClassEnvVarPostfix && secretProviderClassEnvReloaded(upgradeFuncs.ContainersFunc(item), envVar, config.SHAValue) {
return InvokeStrategyResult{constants.NotUpdated, nil}
}
//update if env var exists //update if env var exists
updateResult := updateEnvVar(container, envVar, config.SHAValue) updateResult := updateEnvVar(container, envVar, config.SHAValue)
@@ -609,6 +633,32 @@ func updateEnvVar(container *v1.Container, envVar string, shaData string) consta
return constants.NoEnvVarFound return constants.NoEnvVarFound
} }
func secretProviderClassEnvReloaded(containers []v1.Container, envVar string, shaData string) bool {
for _, container := range containers {
for _, env := range container.Env {
if env.Name == envVar {
return env.Value == shaData
}
}
}
return false
}
func populateAnnotationsFromSecretProviderClass(clients kube.Clients, config *common.Config) {
obj, err := clients.CSIClient.SecretsstoreV1().SecretProviderClasses(config.Namespace).Get(context.Background(), config.ResourceName, metav1.GetOptions{})
annotations := make(map[string]string)
if err != nil {
if apierrors.IsNotFound(err) {
logrus.Warnf("SecretProviderClass '%s' not found in namespace '%s'", config.ResourceName, config.Namespace)
} else {
logrus.Errorf("Failed to get SecretProviderClass '%s' in namespace '%s': %v", config.ResourceName, config.Namespace, err)
}
} else if obj.Annotations != nil {
annotations = obj.Annotations
}
config.ResourceAnnotations = annotations
}
func jsonEscape(toEscape string) (string, error) { func jsonEscape(toEscape string) (string, error) {
bytes, err := json.Marshal(toEscape) bytes, err := json.Marshal(toEscape)
if err != nil { if err != nil {

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,9 @@ var (
// SecretUpdateOnChangeAnnotation is an annotation to detect changes in // SecretUpdateOnChangeAnnotation is an annotation to detect changes in
// secrets specified by name // secrets specified by name
SecretUpdateOnChangeAnnotation = "secret.reloader.stakater.com/reload" SecretUpdateOnChangeAnnotation = "secret.reloader.stakater.com/reload"
// SecretProviderClassUpdateOnChangeAnnotation is an annotation to detect changes in
// secretproviderclasses specified by name
SecretProviderClassUpdateOnChangeAnnotation = "secretproviderclass.reloader.stakater.com/reload"
// ReloaderAutoAnnotation is an annotation to detect changes in secrets/configmaps // ReloaderAutoAnnotation is an annotation to detect changes in secrets/configmaps
ReloaderAutoAnnotation = "reloader.stakater.com/auto" ReloaderAutoAnnotation = "reloader.stakater.com/auto"
// IgnoreResourceAnnotation is an annotation to ignore changes in secrets/configmaps // IgnoreResourceAnnotation is an annotation to ignore changes in secrets/configmaps
@@ -28,10 +31,14 @@ var (
ConfigmapReloaderAutoAnnotation = "configmap.reloader.stakater.com/auto" ConfigmapReloaderAutoAnnotation = "configmap.reloader.stakater.com/auto"
// SecretReloaderAutoAnnotation is an annotation to detect changes in secrets // SecretReloaderAutoAnnotation is an annotation to detect changes in secrets
SecretReloaderAutoAnnotation = "secret.reloader.stakater.com/auto" SecretReloaderAutoAnnotation = "secret.reloader.stakater.com/auto"
// SecretProviderClassReloaderAutoAnnotation is an annotation to detect changes in secretproviderclasses
SecretProviderClassReloaderAutoAnnotation = "secretproviderclass.reloader.stakater.com/auto"
// ConfigmapReloaderAutoAnnotation is a comma separated list of configmaps that excludes detecting changes on cms // ConfigmapReloaderAutoAnnotation is a comma separated list of configmaps that excludes detecting changes on cms
ConfigmapExcludeReloaderAnnotation = "configmaps.exclude.reloader.stakater.com/reload" ConfigmapExcludeReloaderAnnotation = "configmaps.exclude.reloader.stakater.com/reload"
// SecretExcludeReloaderAnnotation is a comma separated list of secrets that excludes detecting changes on secrets // SecretExcludeReloaderAnnotation is a comma separated list of secrets that excludes detecting changes on secrets
SecretExcludeReloaderAnnotation = "secrets.exclude.reloader.stakater.com/reload" SecretExcludeReloaderAnnotation = "secrets.exclude.reloader.stakater.com/reload"
// SecretProviderClassExcludeReloaderAnnotation is a comma separated list of secret provider classes that excludes detecting changes on secret provider class
SecretProviderClassExcludeReloaderAnnotation = "secretproviderclasses.exclude.reloader.stakater.com/reload"
// AutoSearchAnnotation is an annotation to detect changes in // AutoSearchAnnotation is an annotation to detect changes in
// configmaps or triggers with the SearchMatchAnnotation // configmaps or triggers with the SearchMatchAnnotation
AutoSearchAnnotation = "reloader.stakater.com/search" AutoSearchAnnotation = "reloader.stakater.com/search"
@@ -63,6 +70,8 @@ var (
EnableHA = false EnableHA = false
// Url to send a request to instead of triggering a reload // Url to send a request to instead of triggering a reload
WebhookUrl = "" WebhookUrl = ""
// EnableCSIIntegration Adds support to watch SecretProviderClassPodStatus and restart deployment based on it
EnableCSIIntegration = false
// ResourcesToIgnore is a list of resources to ignore when watching for changes // ResourcesToIgnore is a list of resources to ignore when watching for changes
ResourcesToIgnore = []string{} ResourcesToIgnore = []string{}
// WorkloadTypesToIgnore is a list of workload types to ignore when watching for changes // WorkloadTypesToIgnore is a list of workload types to ignore when watching for changes

View File

@@ -30,6 +30,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
core_v1 "k8s.io/client-go/kubernetes/typed/core/v1" core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
csiclient "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned"
csiclient_v1 "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/typed/apis/v1"
) )
var ( var (
@@ -38,6 +41,8 @@ var (
ConfigmapResourceType = "configMaps" ConfigmapResourceType = "configMaps"
// SecretResourceType is a resource type which controller watches for changes // SecretResourceType is a resource type which controller watches for changes
SecretResourceType = "secrets" SecretResourceType = "secrets"
// SecretproviderclasspodstatusResourceType is a resource type which controller watches for changes
SecretProviderClassPodStatusResourceType = "secretproviderclasspodstatuses"
) )
var ( var (
@@ -73,16 +78,16 @@ func DeleteNamespace(namespace string, client kubernetes.Interface) {
} }
} }
func getObjectMeta(namespace string, name string, autoReload bool, secretAutoReload bool, configmapAutoReload bool, extraAnnotations map[string]string) metav1.ObjectMeta { func getObjectMeta(namespace string, name string, autoReload bool, secretAutoReload bool, configmapAutoReload bool, secretproviderclass bool, extraAnnotations map[string]string) metav1.ObjectMeta {
return metav1.ObjectMeta{ return metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: namespace,
Labels: map[string]string{"firstLabel": "temp"}, Labels: map[string]string{"firstLabel": "temp"},
Annotations: getAnnotations(name, autoReload, secretAutoReload, configmapAutoReload, extraAnnotations), Annotations: getAnnotations(name, autoReload, secretAutoReload, configmapAutoReload, secretproviderclass, extraAnnotations),
} }
} }
func getAnnotations(name string, autoReload bool, secretAutoReload bool, configmapAutoReload bool, extraAnnotations map[string]string) map[string]string { func getAnnotations(name string, autoReload bool, secretAutoReload bool, configmapAutoReload bool, secretproviderclass bool, extraAnnotations map[string]string) map[string]string {
annotations := make(map[string]string) annotations := make(map[string]string)
if autoReload { if autoReload {
annotations[options.ReloaderAutoAnnotation] = "true" annotations[options.ReloaderAutoAnnotation] = "true"
@@ -93,11 +98,16 @@ func getAnnotations(name string, autoReload bool, secretAutoReload bool, configm
if configmapAutoReload { if configmapAutoReload {
annotations[options.ConfigmapReloaderAutoAnnotation] = "true" annotations[options.ConfigmapReloaderAutoAnnotation] = "true"
} }
if secretproviderclass {
annotations[options.SecretProviderClassReloaderAutoAnnotation] = "true"
}
if len(annotations) == 0 { if len(annotations) == 0 {
annotations = map[string]string{ annotations = map[string]string{
options.ConfigmapUpdateOnChangeAnnotation: name, options.ConfigmapUpdateOnChangeAnnotation: name,
options.SecretUpdateOnChangeAnnotation: name} options.SecretUpdateOnChangeAnnotation: name,
options.SecretProviderClassUpdateOnChangeAnnotation: name,
}
} }
for k, v := range extraAnnotations { for k, v := range extraAnnotations {
annotations[k] = v annotations[k] = v
@@ -176,6 +186,15 @@ func getVolumes(name string) []v1.Volume {
}, },
}, },
}, },
{
Name: "secretproviderclass",
VolumeSource: v1.VolumeSource{
CSI: &v1.CSIVolumeSource{
Driver: "secrets-store.csi.k8s.io",
VolumeAttributes: map[string]string{"secretProviderClass": name},
},
},
},
} }
} }
@@ -189,6 +208,10 @@ func getVolumeMounts() []v1.VolumeMount {
MountPath: "etc/sec", MountPath: "etc/sec",
Name: "secret", Name: "secret",
}, },
{
MountPath: "etc/spc",
Name: "secretproviderclass",
},
{ {
MountPath: "etc/projectedconfig", MountPath: "etc/projectedconfig",
Name: "projectedconfigmap", Name: "projectedconfigmap",
@@ -348,7 +371,7 @@ func getPodTemplateSpecWithInitContainerAndEnv(name string) v1.PodTemplateSpec {
func GetDeployment(namespace string, deploymentName string) *appsv1.Deployment { func GetDeployment(namespace string, deploymentName string) *appsv1.Deployment {
replicaset := int32(1) replicaset := int32(1)
return &appsv1.Deployment{ return &appsv1.Deployment{
ObjectMeta: getObjectMeta(namespace, deploymentName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, deploymentName, false, false, false, false, map[string]string{}),
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -367,7 +390,7 @@ func GetDeploymentConfig(namespace string, deploymentConfigName string) *openshi
replicaset := int32(1) replicaset := int32(1)
podTemplateSpecWithVolume := getPodTemplateSpecWithVolumes(deploymentConfigName) podTemplateSpecWithVolume := getPodTemplateSpecWithVolumes(deploymentConfigName)
return &openshiftv1.DeploymentConfig{ return &openshiftv1.DeploymentConfig{
ObjectMeta: getObjectMeta(namespace, deploymentConfigName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, deploymentConfigName, false, false, false, false, map[string]string{}),
Spec: openshiftv1.DeploymentConfigSpec{ Spec: openshiftv1.DeploymentConfigSpec{
Replicas: replicaset, Replicas: replicaset,
Strategy: openshiftv1.DeploymentStrategy{ Strategy: openshiftv1.DeploymentStrategy{
@@ -382,7 +405,7 @@ func GetDeploymentConfig(namespace string, deploymentConfigName string) *openshi
func GetDeploymentWithInitContainer(namespace string, deploymentName string) *appsv1.Deployment { func GetDeploymentWithInitContainer(namespace string, deploymentName string) *appsv1.Deployment {
replicaset := int32(1) replicaset := int32(1)
return &appsv1.Deployment{ return &appsv1.Deployment{
ObjectMeta: getObjectMeta(namespace, deploymentName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, deploymentName, false, false, false, false, map[string]string{}),
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -400,7 +423,7 @@ func GetDeploymentWithInitContainer(namespace string, deploymentName string) *ap
func GetDeploymentWithInitContainerAndEnv(namespace string, deploymentName string) *appsv1.Deployment { func GetDeploymentWithInitContainerAndEnv(namespace string, deploymentName string) *appsv1.Deployment {
replicaset := int32(1) replicaset := int32(1)
return &appsv1.Deployment{ return &appsv1.Deployment{
ObjectMeta: getObjectMeta(namespace, deploymentName, true, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, deploymentName, true, false, false, false, map[string]string{}),
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -417,7 +440,7 @@ func GetDeploymentWithInitContainerAndEnv(namespace string, deploymentName strin
func GetDeploymentWithEnvVars(namespace string, deploymentName string) *appsv1.Deployment { func GetDeploymentWithEnvVars(namespace string, deploymentName string) *appsv1.Deployment {
replicaset := int32(1) replicaset := int32(1)
return &appsv1.Deployment{ return &appsv1.Deployment{
ObjectMeta: getObjectMeta(namespace, deploymentName, true, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, deploymentName, true, false, false, false, map[string]string{}),
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -435,7 +458,7 @@ func GetDeploymentConfigWithEnvVars(namespace string, deploymentConfigName strin
replicaset := int32(1) replicaset := int32(1)
podTemplateSpecWithEnvVars := getPodTemplateSpecWithEnvVars(deploymentConfigName) podTemplateSpecWithEnvVars := getPodTemplateSpecWithEnvVars(deploymentConfigName)
return &openshiftv1.DeploymentConfig{ return &openshiftv1.DeploymentConfig{
ObjectMeta: getObjectMeta(namespace, deploymentConfigName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, deploymentConfigName, false, false, false, false, map[string]string{}),
Spec: openshiftv1.DeploymentConfigSpec{ Spec: openshiftv1.DeploymentConfigSpec{
Replicas: replicaset, Replicas: replicaset,
Strategy: openshiftv1.DeploymentStrategy{ Strategy: openshiftv1.DeploymentStrategy{
@@ -449,7 +472,7 @@ func GetDeploymentConfigWithEnvVars(namespace string, deploymentConfigName strin
func GetDeploymentWithEnvVarSources(namespace string, deploymentName string) *appsv1.Deployment { func GetDeploymentWithEnvVarSources(namespace string, deploymentName string) *appsv1.Deployment {
replicaset := int32(1) replicaset := int32(1)
return &appsv1.Deployment{ return &appsv1.Deployment{
ObjectMeta: getObjectMeta(namespace, deploymentName, true, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, deploymentName, true, false, false, false, map[string]string{}),
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -466,7 +489,7 @@ func GetDeploymentWithEnvVarSources(namespace string, deploymentName string) *ap
func GetDeploymentWithPodAnnotations(namespace string, deploymentName string, both bool) *appsv1.Deployment { func GetDeploymentWithPodAnnotations(namespace string, deploymentName string, both bool) *appsv1.Deployment {
replicaset := int32(1) replicaset := int32(1)
deployment := &appsv1.Deployment{ deployment := &appsv1.Deployment{
ObjectMeta: getObjectMeta(namespace, deploymentName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, deploymentName, false, false, false, false, map[string]string{}),
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -481,7 +504,7 @@ func GetDeploymentWithPodAnnotations(namespace string, deploymentName string, bo
if !both { if !both {
deployment.Annotations = nil deployment.Annotations = nil
} }
deployment.Spec.Template.Annotations = getAnnotations(deploymentName, true, false, false, map[string]string{}) deployment.Spec.Template.Annotations = getAnnotations(deploymentName, true, false, false, false, map[string]string{})
return deployment return deployment
} }
@@ -490,9 +513,11 @@ func GetDeploymentWithTypedAutoAnnotation(namespace string, deploymentName strin
var objectMeta metav1.ObjectMeta var objectMeta metav1.ObjectMeta
switch resourceType { switch resourceType {
case SecretResourceType: case SecretResourceType:
objectMeta = getObjectMeta(namespace, deploymentName, false, true, false, map[string]string{}) objectMeta = getObjectMeta(namespace, deploymentName, false, true, false, false, map[string]string{})
case ConfigmapResourceType: case ConfigmapResourceType:
objectMeta = getObjectMeta(namespace, deploymentName, false, false, true, map[string]string{}) objectMeta = getObjectMeta(namespace, deploymentName, false, false, true, false, map[string]string{})
case SecretProviderClassPodStatusResourceType:
objectMeta = getObjectMeta(namespace, deploymentName, false, false, false, true, map[string]string{})
} }
return &appsv1.Deployment{ return &appsv1.Deployment{
@@ -520,6 +545,8 @@ func GetDeploymentWithExcludeAnnotation(namespace string, deploymentName string,
annotation[options.SecretExcludeReloaderAnnotation] = deploymentName annotation[options.SecretExcludeReloaderAnnotation] = deploymentName
case ConfigmapResourceType: case ConfigmapResourceType:
annotation[options.ConfigmapExcludeReloaderAnnotation] = deploymentName annotation[options.ConfigmapExcludeReloaderAnnotation] = deploymentName
case SecretProviderClassPodStatusResourceType:
annotation[options.SecretProviderClassExcludeReloaderAnnotation] = deploymentName
} }
return &appsv1.Deployment{ return &appsv1.Deployment{
@@ -545,7 +572,7 @@ func GetDeploymentWithExcludeAnnotation(namespace string, deploymentName string,
// GetDaemonSet provides daemonset for testing // GetDaemonSet provides daemonset for testing
func GetDaemonSet(namespace string, daemonsetName string) *appsv1.DaemonSet { func GetDaemonSet(namespace string, daemonsetName string) *appsv1.DaemonSet {
return &appsv1.DaemonSet{ return &appsv1.DaemonSet{
ObjectMeta: getObjectMeta(namespace, daemonsetName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, daemonsetName, false, false, false, false, map[string]string{}),
Spec: appsv1.DaemonSetSpec{ Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -560,7 +587,7 @@ func GetDaemonSet(namespace string, daemonsetName string) *appsv1.DaemonSet {
func GetDaemonSetWithEnvVars(namespace string, daemonSetName string) *appsv1.DaemonSet { func GetDaemonSetWithEnvVars(namespace string, daemonSetName string) *appsv1.DaemonSet {
return &appsv1.DaemonSet{ return &appsv1.DaemonSet{
ObjectMeta: getObjectMeta(namespace, daemonSetName, true, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, daemonSetName, true, false, false, false, map[string]string{}),
Spec: appsv1.DaemonSetSpec{ Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -576,7 +603,7 @@ func GetDaemonSetWithEnvVars(namespace string, daemonSetName string) *appsv1.Dae
// GetStatefulSet provides statefulset for testing // GetStatefulSet provides statefulset for testing
func GetStatefulSet(namespace string, statefulsetName string) *appsv1.StatefulSet { func GetStatefulSet(namespace string, statefulsetName string) *appsv1.StatefulSet {
return &appsv1.StatefulSet{ return &appsv1.StatefulSet{
ObjectMeta: getObjectMeta(namespace, statefulsetName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, statefulsetName, false, false, false, false, map[string]string{}),
Spec: appsv1.StatefulSetSpec{ Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -592,7 +619,7 @@ func GetStatefulSet(namespace string, statefulsetName string) *appsv1.StatefulSe
// GetStatefulSet provides statefulset for testing // GetStatefulSet provides statefulset for testing
func GetStatefulSetWithEnvVar(namespace string, statefulsetName string) *appsv1.StatefulSet { func GetStatefulSetWithEnvVar(namespace string, statefulsetName string) *appsv1.StatefulSet {
return &appsv1.StatefulSet{ return &appsv1.StatefulSet{
ObjectMeta: getObjectMeta(namespace, statefulsetName, true, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, statefulsetName, true, false, false, false, map[string]string{}),
Spec: appsv1.StatefulSetSpec{ Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -617,6 +644,42 @@ func GetConfigmap(namespace string, configmapName string, testData string) *v1.C
} }
} }
func GetSecretProviderClass(namespace string, secretProviderClassName string, data string) *csiv1.SecretProviderClass {
return &csiv1.SecretProviderClass{
ObjectMeta: metav1.ObjectMeta{
Name: secretProviderClassName,
Namespace: namespace,
},
Spec: csiv1.SecretProviderClassSpec{
Provider: "Test",
Parameters: map[string]string{
"parameter1": data,
},
},
}
}
func GetSecretProviderClassPodStatus(namespace string, secretProviderClassPodStatusName string, data string) *csiv1.SecretProviderClassPodStatus {
return &csiv1.SecretProviderClassPodStatus{
ObjectMeta: metav1.ObjectMeta{
Name: secretProviderClassPodStatusName,
Namespace: namespace,
},
Status: csiv1.SecretProviderClassPodStatusStatus{
PodName: "test123",
SecretProviderClassName: secretProviderClassPodStatusName,
TargetPath: "/var/lib/kubelet/d8771ddf-935a-4199-a20b-f35f71c1d9e7/volumes/kubernetes.io~csi/secrets-store-inline/mount",
Mounted: true,
Objects: []csiv1.SecretProviderClassObject{
{
ID: "parameter1",
Version: data,
},
},
},
}
}
// GetConfigmapWithUpdatedLabel provides configmap for testing // GetConfigmapWithUpdatedLabel provides configmap for testing
func GetConfigmapWithUpdatedLabel(namespace string, configmapName string, testLabel string, testData string) *v1.ConfigMap { func GetConfigmapWithUpdatedLabel(namespace string, configmapName string, testLabel string, testData string) *v1.ConfigMap {
return &v1.ConfigMap{ return &v1.ConfigMap{
@@ -643,7 +706,7 @@ func GetSecret(namespace string, secretName string, data string) *v1.Secret {
func GetCronJob(namespace string, cronJobName string) *batchv1.CronJob { func GetCronJob(namespace string, cronJobName string) *batchv1.CronJob {
return &batchv1.CronJob{ return &batchv1.CronJob{
ObjectMeta: getObjectMeta(namespace, cronJobName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, cronJobName, false, false, false, false, map[string]string{}),
Spec: batchv1.CronJobSpec{ Spec: batchv1.CronJobSpec{
Schedule: "*/5 * * * *", // Run every 5 minutes Schedule: "*/5 * * * *", // Run every 5 minutes
JobTemplate: batchv1.JobTemplateSpec{ JobTemplate: batchv1.JobTemplateSpec{
@@ -660,7 +723,7 @@ func GetCronJob(namespace string, cronJobName string) *batchv1.CronJob {
func GetJob(namespace string, jobName string) *batchv1.Job { func GetJob(namespace string, jobName string) *batchv1.Job {
return &batchv1.Job{ return &batchv1.Job{
ObjectMeta: getObjectMeta(namespace, jobName, false, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, jobName, false, false, false, false, map[string]string{}),
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -672,7 +735,7 @@ func GetJob(namespace string, jobName string) *batchv1.Job {
func GetCronJobWithEnvVar(namespace string, cronJobName string) *batchv1.CronJob { func GetCronJobWithEnvVar(namespace string, cronJobName string) *batchv1.CronJob {
return &batchv1.CronJob{ return &batchv1.CronJob{
ObjectMeta: getObjectMeta(namespace, cronJobName, true, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, cronJobName, true, false, false, false, map[string]string{}),
Spec: batchv1.CronJobSpec{ Spec: batchv1.CronJobSpec{
Schedule: "*/5 * * * *", // Run every 5 minutes Schedule: "*/5 * * * *", // Run every 5 minutes
JobTemplate: batchv1.JobTemplateSpec{ JobTemplate: batchv1.JobTemplateSpec{
@@ -689,7 +752,7 @@ func GetCronJobWithEnvVar(namespace string, cronJobName string) *batchv1.CronJob
func GetJobWithEnvVar(namespace string, jobName string) *batchv1.Job { func GetJobWithEnvVar(namespace string, jobName string) *batchv1.Job {
return &batchv1.Job{ return &batchv1.Job{
ObjectMeta: getObjectMeta(namespace, jobName, true, false, false, map[string]string{}), ObjectMeta: getObjectMeta(namespace, jobName, true, false, false, false, map[string]string{}),
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},
@@ -746,7 +809,7 @@ func GetResourceSHAFromAnnotation(podAnnotations map[string]string) string {
return last.Hash return last.Hash
} }
// ConvertResourceToSHA generates SHA from secret or configmap data // ConvertResourceToSHA generates SHA from secret, configmap or secretproviderclasspodstatus data
func ConvertResourceToSHA(resourceType string, namespace string, resourceName string, data string) string { func ConvertResourceToSHA(resourceType string, namespace string, resourceName string, data string) string {
values := []string{} values := []string{}
switch resourceType { switch resourceType {
@@ -760,6 +823,12 @@ func ConvertResourceToSHA(resourceType string, namespace string, resourceName st
for k, v := range configmap.Data { for k, v := range configmap.Data {
values = append(values, k+"="+v) values = append(values, k+"="+v)
} }
case SecretProviderClassPodStatusResourceType:
secretproviderclasspodstatus := GetSecretProviderClassPodStatus(namespace, resourceName, data)
for _, v := range secretproviderclasspodstatus.Status.Objects {
values = append(values, v.ID+"="+v.Version)
}
values = append(values, "SecretProviderClassName="+secretproviderclasspodstatus.Status.SecretProviderClassName)
} }
sort.Strings(values) sort.Strings(values)
return crypto.GenerateSHA(strings.Join(values, ";")) return crypto.GenerateSHA(strings.Join(values, ";"))
@@ -774,6 +843,25 @@ func CreateConfigMap(client kubernetes.Interface, namespace string, configmapNam
return configmapClient, err return configmapClient, err
} }
// CreateSecretProviderClass creates a SecretProviderClass in given namespace and returns the SecretProviderClassInterface
func CreateSecretProviderClass(client csiclient.Interface, namespace string, secretProviderClassName string, data string) (csiclient_v1.SecretProviderClassInterface, error) {
logrus.Infof("Creating SecretProviderClass")
secretProviderClassClient := client.SecretsstoreV1().SecretProviderClasses(namespace)
_, err := secretProviderClassClient.Create(context.TODO(), GetSecretProviderClass(namespace, secretProviderClassName, data), metav1.CreateOptions{})
time.Sleep(3 * time.Second)
return secretProviderClassClient, err
}
// CreateSecretProviderClassPodStatus creates a SecretProviderClassPodStatus in given namespace and returns the SecretProviderClassPodStatusInterface
func CreateSecretProviderClassPodStatus(client csiclient.Interface, namespace string, secretProviderClassPodStatusName string, data string) (csiclient_v1.SecretProviderClassPodStatusInterface, error) {
logrus.Infof("Creating SecretProviderClassPodStatus")
secretProviderClassPodStatusClient := client.SecretsstoreV1().SecretProviderClassPodStatuses(namespace)
secretProviderClassPodStatus := GetSecretProviderClassPodStatus(namespace, secretProviderClassPodStatusName, data)
_, err := secretProviderClassPodStatusClient.Create(context.TODO(), secretProviderClassPodStatus, metav1.CreateOptions{})
time.Sleep(3 * time.Second)
return secretProviderClassPodStatusClient, err
}
// CreateSecret creates a secret in given namespace and returns the SecretInterface // CreateSecret creates a secret in given namespace and returns the SecretInterface
func CreateSecret(client kubernetes.Interface, namespace string, secretName string, data string) (core_v1.SecretInterface, error) { func CreateSecret(client kubernetes.Interface, namespace string, secretName string, data string) (core_v1.SecretInterface, error) {
logrus.Infof("Creating secret") logrus.Infof("Creating secret")
@@ -1036,6 +1124,27 @@ func UpdateSecret(secretClient core_v1.SecretInterface, namespace string, secret
return updateErr return updateErr
} }
// UpdateSecretProviderClassPodStatus updates a secretproviderclasspodstatus in given namespace and returns the error if any
func UpdateSecretProviderClassPodStatus(spcpsClient csiclient_v1.SecretProviderClassPodStatusInterface, namespace string, spcpsName string, label string, data string) error {
logrus.Infof("Updating secretproviderclasspodstatus %q.\n", spcpsName)
updatedStatus := GetSecretProviderClassPodStatus(namespace, spcpsName, data).Status
secretproviderclasspodstatus, err := spcpsClient.Get(context.TODO(), spcpsName, metav1.GetOptions{})
if err != nil {
return err
}
secretproviderclasspodstatus.Status = updatedStatus
if label != "" {
labels := secretproviderclasspodstatus.Labels
if labels == nil {
labels = make(map[string]string)
}
labels["firstLabel"] = label
}
_, updateErr := spcpsClient.Update(context.TODO(), secretproviderclasspodstatus, metav1.UpdateOptions{})
time.Sleep(3 * time.Second)
return updateErr
}
// DeleteConfigMap deletes a configmap in given namespace and returns the error if any // DeleteConfigMap deletes a configmap in given namespace and returns the error if any
func DeleteConfigMap(client kubernetes.Interface, namespace string, configmapName string) error { func DeleteConfigMap(client kubernetes.Interface, namespace string, configmapName string) error {
logrus.Infof("Deleting configmap %q.\n", configmapName) logrus.Infof("Deleting configmap %q.\n", configmapName)
@@ -1052,6 +1161,22 @@ func DeleteSecret(client kubernetes.Interface, namespace string, secretName stri
return err return err
} }
// DeleteSecretProviderClass deletes a secretproviderclass in given namespace and returns the error if any
func DeleteSecretProviderClass(client csiclient.Interface, namespace string, secretProviderClassName string) error {
logrus.Infof("Deleting secretproviderclass %q.\n", secretProviderClassName)
err := client.SecretsstoreV1().SecretProviderClasses(namespace).Delete(context.TODO(), secretProviderClassName, metav1.DeleteOptions{})
time.Sleep(3 * time.Second)
return err
}
// DeleteSecretProviderClassPodStatus deletes a secretproviderclasspodstatus in given namespace and returns the error if any
func DeleteSecretProviderClassPodStatus(client csiclient.Interface, namespace string, secretProviderClassPodStatusName string) error {
logrus.Infof("Deleting secretproviderclasspodstatus %q.\n", secretProviderClassPodStatusName)
err := client.SecretsstoreV1().SecretProviderClassPodStatuses(namespace).Delete(context.TODO(), secretProviderClassPodStatusName, metav1.DeleteOptions{})
time.Sleep(3 * time.Second)
return err
}
// RandSeq generates a random sequence // RandSeq generates a random sequence
func RandSeq(n int) string { func RandSeq(n int) string {
b := make([]rune, n) b := make([]rune, n)
@@ -1202,14 +1327,18 @@ func VerifyResourceAnnotationUpdate(clients kube.Clients, config common.Config,
} }
func GetSHAfromEmptyData() string { func GetSHAfromEmptyData() string {
return crypto.GenerateSHA("") // Use a special marker that represents "deleted" or "empty" state
// This ensures we have a distinct, deterministic hash for the delete strategy
// Note: We could use GenerateSHA("") which now returns a hash, but using a marker
// makes the intent clearer and avoids potential confusion with actual empty data
return crypto.GenerateSHA("__RELOADER_EMPTY_DELETE_MARKER__")
} }
// GetRollout provides rollout for testing // GetRollout provides rollout for testing
func GetRollout(namespace string, rolloutName string, annotations map[string]string) *argorolloutv1alpha1.Rollout { func GetRollout(namespace string, rolloutName string, annotations map[string]string) *argorolloutv1alpha1.Rollout {
replicaset := int32(1) replicaset := int32(1)
return &argorolloutv1alpha1.Rollout{ return &argorolloutv1alpha1.Rollout{
ObjectMeta: getObjectMeta(namespace, rolloutName, false, false, false, annotations), ObjectMeta: getObjectMeta(namespace, rolloutName, false, false, false, false, annotations),
Spec: argorolloutv1alpha1.RolloutSpec{ Spec: argorolloutv1alpha1.RolloutSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"}, MatchLabels: map[string]string{"secondLabel": "temp"},

View File

@@ -13,6 +13,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/crypto" "github.com/stakater/Reloader/internal/pkg/crypto"
"github.com/stakater/Reloader/internal/pkg/options" "github.com/stakater/Reloader/internal/pkg/options"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
) )
// ConvertToEnvVarName converts the given text into a usable env var // ConvertToEnvVarName converts the given text into a usable env var
@@ -57,6 +58,16 @@ func GetSHAfromSecret(data map[string][]byte) string {
return crypto.GenerateSHA(strings.Join(values, ";")) return crypto.GenerateSHA(strings.Join(values, ";"))
} }
func GetSHAfromSecretProviderClassPodStatus(data csiv1.SecretProviderClassPodStatusStatus) string {
values := []string{}
for _, v := range data.Objects {
values = append(values, v.ID+"="+v.Version)
}
values = append(values, "SecretProviderClassName="+data.SecretProviderClassName)
sort.Strings(values)
return crypto.GenerateSHA(strings.Join(values, ";"))
}
type List []string type List []string
func (l *List) Contains(s string) bool { func (l *List) Contains(s string) bool {
@@ -95,6 +106,7 @@ func ConfigureReloaderFlags(cmd *cobra.Command) {
cmd.PersistentFlags().BoolVar(&options.SyncAfterRestart, "sync-after-restart", false, "Sync add events after reloader restarts") cmd.PersistentFlags().BoolVar(&options.SyncAfterRestart, "sync-after-restart", false, "Sync add events after reloader restarts")
cmd.PersistentFlags().BoolVar(&options.EnablePProf, "enable-pprof", false, "Enable pprof for profiling") cmd.PersistentFlags().BoolVar(&options.EnablePProf, "enable-pprof", false, "Enable pprof for profiling")
cmd.PersistentFlags().StringVar(&options.PProfAddr, "pprof-addr", ":6060", "Address to start pprof server on. Default is :6060") cmd.PersistentFlags().StringVar(&options.PProfAddr, "pprof-addr", ":6060", "Address to start pprof server on. Default is :6060")
cmd.PersistentFlags().BoolVar(&options.EnableCSIIntegration, "enable-csi-integration", false, "Enables CSI integration. Default is :false")
} }
func GetIgnoredResourcesList() (List, error) { func GetIgnoredResourcesList() (List, error) {

View File

@@ -32,6 +32,8 @@ type ReloaderOptions struct {
ConfigmapUpdateOnChangeAnnotation string `json:"configmapUpdateOnChangeAnnotation"` ConfigmapUpdateOnChangeAnnotation string `json:"configmapUpdateOnChangeAnnotation"`
// SecretUpdateOnChangeAnnotation is the annotation key used to detect changes in Secrets specified by name // SecretUpdateOnChangeAnnotation is the annotation key used to detect changes in Secrets specified by name
SecretUpdateOnChangeAnnotation string `json:"secretUpdateOnChangeAnnotation"` SecretUpdateOnChangeAnnotation string `json:"secretUpdateOnChangeAnnotation"`
// SecretProviderClassUpdateOnChangeAnnotation is the annotation key used to detect changes in SecretProviderClasses specified by name
SecretProviderClassUpdateOnChangeAnnotation string `json:"secretProviderClassUpdateOnChangeAnnotation"`
// ReloaderAutoAnnotation is the annotation key used to detect changes in any referenced ConfigMaps or Secrets // ReloaderAutoAnnotation is the annotation key used to detect changes in any referenced ConfigMaps or Secrets
ReloaderAutoAnnotation string `json:"reloaderAutoAnnotation"` ReloaderAutoAnnotation string `json:"reloaderAutoAnnotation"`
// IgnoreResourceAnnotation is the annotation key used to ignore resources from being watched // IgnoreResourceAnnotation is the annotation key used to ignore resources from being watched
@@ -40,10 +42,14 @@ type ReloaderOptions struct {
ConfigmapReloaderAutoAnnotation string `json:"configmapReloaderAutoAnnotation"` ConfigmapReloaderAutoAnnotation string `json:"configmapReloaderAutoAnnotation"`
// SecretReloaderAutoAnnotation is the annotation key used to detect changes in Secrets only // SecretReloaderAutoAnnotation is the annotation key used to detect changes in Secrets only
SecretReloaderAutoAnnotation string `json:"secretReloaderAutoAnnotation"` SecretReloaderAutoAnnotation string `json:"secretReloaderAutoAnnotation"`
// SecretProviderClassReloaderAutoAnnotation is the annotation key used to detect changes in SecretProviderClasses only
SecretProviderClassReloaderAutoAnnotation string `json:"secretProviderClassReloaderAutoAnnotation"`
// ConfigmapExcludeReloaderAnnotation is the annotation key containing comma-separated list of ConfigMaps to exclude from watching // ConfigmapExcludeReloaderAnnotation is the annotation key containing comma-separated list of ConfigMaps to exclude from watching
ConfigmapExcludeReloaderAnnotation string `json:"configmapExcludeReloaderAnnotation"` ConfigmapExcludeReloaderAnnotation string `json:"configmapExcludeReloaderAnnotation"`
// SecretExcludeReloaderAnnotation is the annotation key containing comma-separated list of Secrets to exclude from watching // SecretExcludeReloaderAnnotation is the annotation key containing comma-separated list of Secrets to exclude from watching
SecretExcludeReloaderAnnotation string `json:"secretExcludeReloaderAnnotation"` SecretExcludeReloaderAnnotation string `json:"secretExcludeReloaderAnnotation"`
// SecretProviderClassExcludeReloaderAnnotation is the annotation key containing comma-separated list of SecretProviderClasses to exclude from watching
SecretProviderClassExcludeReloaderAnnotation string `json:"secretProviderClassExcludeReloaderAnnotation"`
// AutoSearchAnnotation is the annotation key used to detect changes in ConfigMaps/Secrets tagged with SearchMatchAnnotation // AutoSearchAnnotation is the annotation key used to detect changes in ConfigMaps/Secrets tagged with SearchMatchAnnotation
AutoSearchAnnotation string `json:"autoSearchAnnotation"` AutoSearchAnnotation string `json:"autoSearchAnnotation"`
// SearchMatchAnnotation is the annotation key used to tag ConfigMaps/Secrets to be found by AutoSearchAnnotation // SearchMatchAnnotation is the annotation key used to tag ConfigMaps/Secrets to be found by AutoSearchAnnotation
@@ -71,6 +77,8 @@ type ReloaderOptions struct {
SyncAfterRestart bool `json:"syncAfterRestart"` SyncAfterRestart bool `json:"syncAfterRestart"`
// EnableHA indicates whether High Availability mode is enabled with leader election // EnableHA indicates whether High Availability mode is enabled with leader election
EnableHA bool `json:"enableHA"` EnableHA bool `json:"enableHA"`
// EnableCSIIntegration indicates whether CSI integration is enabled to watch SecretProviderClassPodStatus
EnableCSIIntegration bool `json:"enableCSIIntegration"`
// WebhookUrl is the URL to send webhook notifications to instead of performing reloads // WebhookUrl is the URL to send webhook notifications to instead of performing reloads
WebhookUrl string `json:"webhookUrl"` WebhookUrl string `json:"webhookUrl"`
// ResourcesToIgnore is a list of resource types to ignore (e.g., "configmaps" or "secrets") // ResourcesToIgnore is a list of resource types to ignore (e.g., "configmaps" or "secrets")
@@ -224,6 +232,7 @@ func ShouldReload(config Config, resourceType string, annotations Map, podAnnota
typedAutoAnnotationEnabledValue, foundTypedAuto := annotations[config.TypedAutoAnnotation] typedAutoAnnotationEnabledValue, foundTypedAuto := annotations[config.TypedAutoAnnotation]
excludeConfigmapAnnotationValue, foundExcludeConfigmap := annotations[options.ConfigmapExcludeReloaderAnnotation] excludeConfigmapAnnotationValue, foundExcludeConfigmap := annotations[options.ConfigmapExcludeReloaderAnnotation]
excludeSecretAnnotationValue, foundExcludeSecret := annotations[options.SecretExcludeReloaderAnnotation] excludeSecretAnnotationValue, foundExcludeSecret := annotations[options.SecretExcludeReloaderAnnotation]
excludeSecretProviderClassProviderAnnotationValue, foundExcludeSecretProviderClass := annotations[options.SecretProviderClassExcludeReloaderAnnotation]
if !found && !foundAuto && !foundTypedAuto && !foundSearchAnn { if !found && !foundAuto && !foundTypedAuto && !foundSearchAnn {
annotations = podAnnotations annotations = podAnnotations
@@ -244,6 +253,11 @@ func ShouldReload(config Config, resourceType string, annotations Map, podAnnota
if foundExcludeSecret { if foundExcludeSecret {
isResourceExcluded = checkIfResourceIsExcluded(config.ResourceName, excludeSecretAnnotationValue) isResourceExcluded = checkIfResourceIsExcluded(config.ResourceName, excludeSecretAnnotationValue)
} }
case constants.SecretProviderClassEnvVarPostfix:
if foundExcludeSecretProviderClass {
isResourceExcluded = checkIfResourceIsExcluded(config.ResourceName, excludeSecretProviderClassProviderAnnotationValue)
}
} }
if isResourceExcluded { if isResourceExcluded {
@@ -252,15 +266,6 @@ func ShouldReload(config Config, resourceType string, annotations Map, podAnnota
} }
} }
reloaderEnabled, _ := strconv.ParseBool(reloaderEnabledValue)
typedAutoAnnotationEnabled, _ := strconv.ParseBool(typedAutoAnnotationEnabledValue)
if reloaderEnabled || typedAutoAnnotationEnabled || reloaderEnabledValue == "" && typedAutoAnnotationEnabledValue == "" && options.AutoReloadAll {
return ReloadCheckResult{
ShouldReload: true,
AutoReload: true,
}
}
values := strings.Split(annotationValue, ",") values := strings.Split(annotationValue, ",")
for _, value := range values { for _, value := range values {
value = strings.TrimSpace(value) value = strings.TrimSpace(value)
@@ -283,6 +288,15 @@ func ShouldReload(config Config, resourceType string, annotations Map, podAnnota
} }
} }
reloaderEnabled, _ := strconv.ParseBool(reloaderEnabledValue)
typedAutoAnnotationEnabled, _ := strconv.ParseBool(typedAutoAnnotationEnabledValue)
if reloaderEnabled || typedAutoAnnotationEnabled || reloaderEnabledValue == "" && typedAutoAnnotationEnabledValue == "" && options.AutoReloadAll {
return ReloadCheckResult{
ShouldReload: true,
AutoReload: true,
}
}
return ReloadCheckResult{ return ReloadCheckResult{
ShouldReload: false, ShouldReload: false,
} }
@@ -315,12 +329,15 @@ func GetCommandLineOptions() *ReloaderOptions {
CommandLineOptions.AutoReloadAll = options.AutoReloadAll CommandLineOptions.AutoReloadAll = options.AutoReloadAll
CommandLineOptions.ConfigmapUpdateOnChangeAnnotation = options.ConfigmapUpdateOnChangeAnnotation CommandLineOptions.ConfigmapUpdateOnChangeAnnotation = options.ConfigmapUpdateOnChangeAnnotation
CommandLineOptions.SecretUpdateOnChangeAnnotation = options.SecretUpdateOnChangeAnnotation CommandLineOptions.SecretUpdateOnChangeAnnotation = options.SecretUpdateOnChangeAnnotation
CommandLineOptions.SecretProviderClassUpdateOnChangeAnnotation = options.SecretProviderClassUpdateOnChangeAnnotation
CommandLineOptions.ReloaderAutoAnnotation = options.ReloaderAutoAnnotation CommandLineOptions.ReloaderAutoAnnotation = options.ReloaderAutoAnnotation
CommandLineOptions.IgnoreResourceAnnotation = options.IgnoreResourceAnnotation CommandLineOptions.IgnoreResourceAnnotation = options.IgnoreResourceAnnotation
CommandLineOptions.ConfigmapReloaderAutoAnnotation = options.ConfigmapReloaderAutoAnnotation CommandLineOptions.ConfigmapReloaderAutoAnnotation = options.ConfigmapReloaderAutoAnnotation
CommandLineOptions.SecretReloaderAutoAnnotation = options.SecretReloaderAutoAnnotation CommandLineOptions.SecretReloaderAutoAnnotation = options.SecretReloaderAutoAnnotation
CommandLineOptions.SecretProviderClassReloaderAutoAnnotation = options.SecretProviderClassReloaderAutoAnnotation
CommandLineOptions.ConfigmapExcludeReloaderAnnotation = options.ConfigmapExcludeReloaderAnnotation CommandLineOptions.ConfigmapExcludeReloaderAnnotation = options.ConfigmapExcludeReloaderAnnotation
CommandLineOptions.SecretExcludeReloaderAnnotation = options.SecretExcludeReloaderAnnotation CommandLineOptions.SecretExcludeReloaderAnnotation = options.SecretExcludeReloaderAnnotation
CommandLineOptions.SecretProviderClassExcludeReloaderAnnotation = options.SecretProviderClassExcludeReloaderAnnotation
CommandLineOptions.AutoSearchAnnotation = options.AutoSearchAnnotation CommandLineOptions.AutoSearchAnnotation = options.AutoSearchAnnotation
CommandLineOptions.SearchMatchAnnotation = options.SearchMatchAnnotation CommandLineOptions.SearchMatchAnnotation = options.SearchMatchAnnotation
CommandLineOptions.RolloutStrategyAnnotation = options.RolloutStrategyAnnotation CommandLineOptions.RolloutStrategyAnnotation = options.RolloutStrategyAnnotation
@@ -331,6 +348,7 @@ func GetCommandLineOptions() *ReloaderOptions {
CommandLineOptions.ReloadStrategy = options.ReloadStrategy CommandLineOptions.ReloadStrategy = options.ReloadStrategy
CommandLineOptions.SyncAfterRestart = options.SyncAfterRestart CommandLineOptions.SyncAfterRestart = options.SyncAfterRestart
CommandLineOptions.EnableHA = options.EnableHA CommandLineOptions.EnableHA = options.EnableHA
CommandLineOptions.EnableCSIIntegration = options.EnableCSIIntegration
CommandLineOptions.WebhookUrl = options.WebhookUrl CommandLineOptions.WebhookUrl = options.WebhookUrl
CommandLineOptions.ResourcesToIgnore = options.ResourcesToIgnore CommandLineOptions.ResourcesToIgnore = options.ResourcesToIgnore
CommandLineOptions.WorkloadTypesToIgnore = options.WorkloadTypesToIgnore CommandLineOptions.WorkloadTypesToIgnore = options.WorkloadTypesToIgnore

View File

@@ -5,6 +5,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/options" "github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/util" "github.com/stakater/Reloader/internal/pkg/util"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
) )
// Config contains rolling upgrade configuration parameters // Config contains rolling upgrade configuration parameters
@@ -46,3 +47,16 @@ func GetSecretConfig(secret *v1.Secret) Config {
Labels: secret.Labels, Labels: secret.Labels,
} }
} }
func GetSecretProviderClassPodStatusConfig(podStatus *csiv1.SecretProviderClassPodStatus) Config {
// As csi injects SecretProviderClass, we will create config for it instead of SecretProviderClassPodStatus
// ResourceAnnotations will be retrieved during PerformAction call
return Config{
Namespace: podStatus.Namespace,
ResourceName: podStatus.Status.SecretProviderClassName,
Annotation: options.SecretProviderClassUpdateOnChangeAnnotation,
TypedAutoAnnotation: options.SecretProviderClassReloaderAutoAnnotation,
SHAValue: util.GetSHAfromSecretProviderClassPodStatus(podStatus.Status),
Type: constants.SecretProviderClassEnvVarPostfix,
}
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
csiclient "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned"
) )
// Clients struct exposes interfaces for kubernetes as well as openshift if available // Clients struct exposes interfaces for kubernetes as well as openshift if available
@@ -18,11 +19,14 @@ type Clients struct {
KubernetesClient kubernetes.Interface KubernetesClient kubernetes.Interface
OpenshiftAppsClient appsclient.Interface OpenshiftAppsClient appsclient.Interface
ArgoRolloutClient argorollout.Interface ArgoRolloutClient argorollout.Interface
CSIClient csiclient.Interface
} }
var ( var (
// IsOpenshift is true if environment is Openshift, it is false if environment is Kubernetes // IsOpenshift is true if environment is Openshift, it is false if environment is Kubernetes
IsOpenshift = isOpenshift() IsOpenshift = isOpenshift()
// IsCSIEnabled is true if environment has CSI provider installed, otherwise false
IsCSIInstalled = isCSIInstalled()
) )
// GetClients returns a `Clients` object containing both openshift and kubernetes clients with an openshift identifier // GetClients returns a `Clients` object containing both openshift and kubernetes clients with an openshift identifier
@@ -48,10 +52,20 @@ func GetClients() Clients {
logrus.Warnf("Unable to create ArgoRollout client error = %v", err) logrus.Warnf("Unable to create ArgoRollout client error = %v", err)
} }
var csiClient *csiclient.Clientset
if IsCSIInstalled {
csiClient, err = GetCSIClient()
if err != nil {
logrus.Warnf("Unable to create CSI client error = %v", err)
}
}
return Clients{ return Clients{
KubernetesClient: client, KubernetesClient: client,
OpenshiftAppsClient: appsClient, OpenshiftAppsClient: appsClient,
ArgoRolloutClient: rolloutClient, ArgoRolloutClient: rolloutClient,
CSIClient: csiClient,
} }
} }
@@ -63,6 +77,28 @@ func GetArgoRolloutClient() (*argorollout.Clientset, error) {
return argorollout.NewForConfig(config) return argorollout.NewForConfig(config)
} }
func isCSIInstalled() bool {
client, err := GetKubernetesClient()
if err != nil {
logrus.Fatalf("Unable to create Kubernetes client error = %v", err)
}
_, err = client.RESTClient().Get().AbsPath("/apis/secrets-store.csi.x-k8s.io/v1").Do(context.TODO()).Raw()
if err == nil {
logrus.Info("CSI provider is installed")
return true
}
logrus.Info("CSI provider is not installed")
return false
}
func GetCSIClient() (*csiclient.Clientset, error) {
config, err := getConfig()
if err != nil {
return nil, err
}
return csiclient.NewForConfig(config)
}
func isOpenshift() bool { func isOpenshift() bool {
client, err := GetKubernetesClient() client, err := GetKubernetesClient()
if err != nil { if err != nil {

View File

@@ -3,6 +3,7 @@ package kube
import ( import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
) )
// ResourceMap are resources from where changes are going to be detected // ResourceMap are resources from where changes are going to be detected
@@ -10,4 +11,5 @@ var ResourceMap = map[string]runtime.Object{
"configmaps": &v1.ConfigMap{}, "configmaps": &v1.ConfigMap{},
"secrets": &v1.Secret{}, "secrets": &v1.Secret{},
"namespaces": &v1.Namespace{}, "namespaces": &v1.Namespace{},
"secretproviderclasspodstatuses": &csiv1.SecretProviderClassPodStatus{},
} }