diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index 1706cc149..2e43c4533 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -18,6 +18,10 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker Build
+ working-directory: ./opensearch-operator
+ run: |
+ make docker-build-multiarch
+ - name: Docker Build Sidecar image
+ working-directory: ./operator-sidecar
run: |
- cd opensearch-operator
make docker-build-multiarch
diff --git a/.github/workflows/functional-tests.yaml b/.github/workflows/functional-tests.yaml
index 1e770ed1f..e1fac42d9 100644
--- a/.github/workflows/functional-tests.yaml
+++ b/.github/workflows/functional-tests.yaml
@@ -24,7 +24,14 @@ jobs:
export CLUSTER_NAME=opensearch-operator-tests
## Check disk to avoid failed shard assignments due to watermarking
df -h
- cd opensearch-operator
+
+ ## Build sidecar docker image and import into k3d
+ cd operator-sidecar
+ IMG=operator-sidecar:test make docker-build
+ k3d image import -c $CLUSTER_NAME operator-sidecar:test
+
+ cd ../opensearch-operator
+
## Prepare kubeconfig
k3d kubeconfig get $CLUSTER_NAME > functionaltests/kubeconfig
export KUBECONFIG=$(pwd)/functionaltests/kubeconfig
@@ -36,9 +43,17 @@ jobs:
k3d image import -c $CLUSTER_NAME controller:latest
## Install helm chart
- helm install opensearch-operator ../charts/opensearch-operator --set manager.image.repository=controller --set manager.image.tag=latest --set manager.image.pullPolicy=IfNotPresent --namespace default --wait
+ helm install opensearch-operator ../charts/opensearch-operator \
+ --set manager.image.repository=controller \
+ --set manager.image.tag=latest \
+ --set manager.image.pullPolicy=IfNotPresent \
+ --set operatorSidecar.image=operator-sidecar:test \
+ --namespace default --wait
+
cd functionaltests
+ kubectl apply -f rbac.yaml
+
## Run tests
go test ./operatortests -timeout 30m
@@ -70,7 +85,13 @@ jobs:
export CLUSTER_NAME=opensearch-operator-tests
## Check disk to avoid failed shard assignments due to watermarking
df -h
- cd opensearch-operator
+
+ ## Build sidecar docker image and import into k3d
+ cd operator-sidecar
+ IMG=operator-sidecar:test make docker-build
+ k3d image import -c $CLUSTER_NAME operator-sidecar:test
+
+ cd ../opensearch-operator
## Prepare kubeconfig
k3d kubeconfig get $CLUSTER_NAME > functionaltests/kubeconfig
export KUBECONFIG=$(pwd)/functionaltests/kubeconfig
@@ -81,9 +102,22 @@ jobs:
## Import controller docker image
k3d image import -c $CLUSTER_NAME controller:latest
- ## Install helm chart
- helm install opensearch-operator ../charts/opensearch-operator --set manager.image.repository=controller --set manager.image.tag=latest --set manager.image.pullPolicy=IfNotPresent --namespace default --wait
- helm install opensearch-cluster ../charts/opensearch-cluster --set opensearchCluster.general.version=${{ matrix.version }} --set opensearchCluster.dashboards.version=${{ matrix.version }} --wait
+ ## Install helm charts
+ helm install opensearch-operator ../charts/opensearch-operator \
+ --set manager.image.repository=controller \
+ --set manager.image.tag=latest \
+ --set manager.image.pullPolicy=IfNotPresent \
+ --set operatorSidecar.image=operator-sidecar:test \
+ --namespace default --wait
+
+ kubectl apply -f functionaltests/rbac.yaml
+
+ helm install opensearch-cluster ../charts/opensearch-cluster \
+ --set opensearchCluster.general.version=${{ matrix.version }} \
+ --set opensearchCluster.dashboards.version=${{ matrix.version }} \
+ -f functionaltests/helm-cluster-values.yaml \
+ --wait
+
cd functionaltests
## Run tests
diff --git a/.github/workflows/go-linting.yaml b/.github/workflows/go-linting.yaml
index 8cd6bb608..c358cf4b1 100644
--- a/.github/workflows/go-linting.yaml
+++ b/.github/workflows/go-linting.yaml
@@ -12,10 +12,17 @@ jobs:
with:
go-version-file: 'go.work'
cache: false
- - name: lint go
+ - name: lint opensearch-operator
uses: golangci/golangci-lint-action@v4
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.57
working-directory: opensearch-operator
args: --timeout=6m --skip-dirs="(^|/)responses($|/)" -v
+ - name: lint operator-sidecar
+ uses: golangci/golangci-lint-action@v4
+ with:
+ # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
+ version: v1.57
+ working-directory: operator-sidecar
+ args: --timeout=6m -v
diff --git a/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml b/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml
index b329b1bc0..820376a8d 100644
--- a/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml
+++ b/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml
@@ -4800,6 +4800,34 @@ spec:
additionalProperties:
type: string
type: object
+ operatorSidecar:
+ properties:
+ enable:
+ type: boolean
+ image:
+ type: string
+ imagePullPolicy:
+ description: PullPolicy describes a policy for if/when to
+ pull a container image
+ type: string
+ imagePullSecrets:
+ items:
+ description: |-
+ LocalObjectReference contains enough information to let you locate the
+ referenced object inside the same namespace.
+ properties:
+ name:
+ description: |-
+ Name of the referent.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ TODO: Add other useful fields. apiVersion, kind, uid?
+ type: string
+ type: object
+ x-kubernetes-map-type: atomic
+ type: array
+ readinessPerPool:
+ type: boolean
+ type: object
pdb:
properties:
enable:
diff --git a/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml b/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml
index 4b5cb194b..9540b8728 100755
--- a/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml
+++ b/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml
@@ -72,6 +72,12 @@ spec:
value: "{{ .Values.manager.parallelRecoveryEnabled }}"
- name: PPROF_ENDPOINTS_ENABLED
value: "{{ .Values.manager.pprofEndpointsEnabled }}"
+ {{- if .Values.operatorSidecar.image }}
+ - name: OPERATOR_SIDECAR_IMAGE
+ value: {{ .Values.operatorSidecar.image }}
+ {{- end }}
+ - name: OPERATOR_SIDECAR_VERSION
+ value: "{{ default .Chart.AppVersion }}"
{{- if .Values.manager.extraEnv }}
{{- toYaml .Values.manager.extraEnv | nindent 8 }}
{{- end }}
diff --git a/charts/opensearch-operator/values.yaml b/charts/opensearch-operator/values.yaml
index 1ee839bbe..9f3b23cbe 100644
--- a/charts/opensearch-operator/values.yaml
+++ b/charts/opensearch-operator/values.yaml
@@ -65,6 +65,11 @@ manager:
# watch objects in the desired namespace. Defaults is to watch all namespaces.
watchNamespace:
+# Configuration for the operator sidecar that can be added to opensearch pods
+operatorSidecar: {}
+ # full name and tag for an alternate image to use for the sidecar (e.g. someregistry.com/operator-sidecar:1.2.3)
+ # image:
+
# Install the Custom Resource Definitions with Helm
installCRDs: true
diff --git a/docs/designs/crd.md b/docs/designs/crd.md
index 52bb82b1e..96500a4c8 100644
--- a/docs/designs/crd.md
+++ b/docs/designs/crd.md
@@ -550,6 +550,12 @@ Every NodePool is defining different Opensearch Nodes StatefulSet
Updates the probes timeouts and thresholds config |
false |
- |
+
+ operatorSidecar |
+ OperatorSidecar |
+ Special sidecar for each pod to deal with cluster-state-dependent readiness probes |
+ false |
+ - |
diff --git a/docs/userguide/main.md b/docs/userguide/main.md
index 62a579a0b..46c109861 100644
--- a/docs/userguide/main.md
+++ b/docs/userguide/main.md
@@ -285,7 +285,6 @@ To install a plugin for the bootstrap pod add it to the list under `bootstrap.pl
pluginsList: ["repository-s3"]
```
-
Please note:
* [Bundled plugins](https://opensearch.org/docs/latest/install-and-configure/install-opensearch/plugins/#bundled-plugins) do not have to be added to the list, they are installed automatically
@@ -1067,6 +1066,82 @@ The following considerations should be taken into account in order to increase t
Note: To change the `diskSize` from `G` to `Gi` or vice-versa, first make sure data is backed up and make sure the right conversion number is identified, so that the underlying volume has the same value and then re-apply the cluster yaml. This will make sure the statefulset is re-created with right value in VolueClaimTemplates, this operation is expected to have no downtime.
+### Keeping Opensearch available during Kubernetes node replacements
+
+When you have restarts of Kubernetes nodes (e.g. because you are upgrading the Kubernetes version) there is a risk of your Opensearch cluster becoming unavailable for a time. The problem is that Kubernetes cannot know when Opensearch has internally finished replicating data and has a green cluster state. So it can happen that a second node with an Opensearch pod is restarted/replaced before Opensearch had a chance to resync. This could lead to indices becoming unavailable with both primary and replica shards being on the pods that were just restarted.
+
+The canonical way around this is to set a Pod Distruption Budget (PDB). But this relies on the readiness probe of all affected pods, which for Opensearch does not reflect cluster state but only if the local pod is reachable. Changing the readiness probe to reflect Opensearch cluster state (green/yellow/red) would make all pods unreachable (because readiness controls which pods are reachable via Service/Ingress).
+
+To support such cases the operator provides a special sidecar added to all Opensearch pods. The sidecars sync with each other so that exactly one of them exposes Opensearch cluster state via the readiness probe. So if the cluster became yellow, the readiness for one pod would switch to not ready. If you also configure a PDB that allows for only one pod to be unavailable, then any Kubernetes node replacements with Opensearch pods on them will be blocked until the Opensearch cluster is healthy again.
+
+Note that there can still be situations with more than one pod down if a pod crashes or is restarted for some other reason while a node restart is in progress.
+
+> [!WARNING]
+> The sidecar feature is still experimential and could have unintended interference with normal cluster operations. Please test it before using it on a production system.
+
+To configure the sidecar you need to do the following:
+
+1. Create a Kubernetes ServiceAccount and give it the permission to perform leader election (find an example manifest below)
+2. Configure your opensearch cluster to use that ServiceAccount
+3. Enable the sidecar for nodePools with the data role
+
+The ServiceAccount manifest should look like this (you need to apply this before creating your Opensearch cluster):
+
+```yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: my-opensearch
+automountServiceAccountToken: true
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: leaderelection
+ namespace: default
+rules:
+ - apiGroups:
+ - coordination.k8s.io
+ resources:
+ - leases
+ verbs:
+ - '*'
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: opensearch-leaderelection
+ namespace: default
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: leaderelection
+subjects:
+ - kind: ServiceAccount
+ name: my-opensearch
+```
+
+The changes to the cluster manifest look like this:
+
+```yaml
+spec:
+ general:
+ serviceAccount: my-opensearch # The name of the ServiceAccount with leader election permissions
+ nodePools:
+ - component: foobar
+ pdb: # PDB is needed so that node restarts take the pods into account
+ enable: true
+ maxUnavailable: 1
+ operatorSidecar:
+ enable: true # This enables the sidecar with the cluster-state-dependent readiness probe
+ readinessPerPool: true # Setting this to false means only one pod from all nodepools has the state-dependent readiness probe instead of one per nodepool
+```
+
+Keep in mind that this setup could block cluster node restarts/replacements for a long time if Opensearch has a problem, so be sure to choose sensible timeouts / max wait times in your operations tooling (ClusterAPI or cloud provider configuration).
+
+> [!WARNING]
+> If you perform an upgrade of Opensearch itself, you should consider disabling the sidecar beforehand. Upgrades can lead to the cluster staying in yellow state during the entire upgrade which the sidecar cannot detect so the upgrade would block. To stay on the safe side it is recommended to disable the sidecar before an upgrade (requires a rolling restart of the opensearch pods) and enable it again afterwards.
+
## User and role management
An important part of any OpenSearch cluster is the user and role management to give users access to the cluster (via the opensearch-security plugin). By default the operator will use the included demo securityconfig with default users (see [internal_users.yml](https://github.com/opensearch-project/security/blob/main/config/internal_users.yml) for a list of users). For any production installation you should swap that out with your own configuration.
diff --git a/go.work b/go.work
index e8b7771b5..a2fea179d 100644
--- a/go.work
+++ b/go.work
@@ -3,4 +3,5 @@ go 1.22.1
use (
./opensearch-operator
./opensearch-operator/functionaltests
+ ./operator-sidecar
)
diff --git a/jenkins/release.jenkinsfile b/jenkins/release.jenkinsfile
index e2209accf..1282fb429 100644
--- a/jenkins/release.jenkinsfile
+++ b/jenkins/release.jenkinsfile
@@ -77,8 +77,59 @@ pipeline {
sh("git checkout ${ref_final}")
def OPERATOR_PRODUCT = "opensearch-operator"
+ def SIDECAR_PRODUCT = "operator-sidecar"
def OPERATOR_VERSION = ref_final.substring(1);
+
+ // First build and promote image for the sidecar
+ echo("${SIDECAR_PRODUCT}: ${OPERATOR_VERSION}")
+
+ // Build and push to dockerhub staging repo https://hub.docker.com/r/opensearchstaging/opensearch-operator.
+ dockerBuild: {
+ build job: 'docker-build',
+ parameters: [
+ string(name: 'DOCKER_BUILD_GIT_REPOSITORY', value: "${REPO_URL}"),
+ string(name: 'DOCKER_BUILD_GIT_REPOSITORY_REFERENCE', value: "${ref_final}"),
+ string(name: 'DOCKER_BUILD_SCRIPT_WITH_COMMANDS', value: [
+ 'ls -l',
+ 'cd operator-sidecar',
+ [
+ 'bash',
+ '../scripts/build-image-multi-arch.sh',
+ "-v ${OPERATOR_VERSION}",
+ "-a 'x64,arm,arm64'",
+ "-f Dockerfile",
+ "-p ${SIDECAR_PRODUCT}"
+ ].join(' ')
+ ].join(' && ')),
+ ]
+ }
+ // This is required to copy the operator to staging ECR https://gallery.ecr.aws/opensearchstaging/opensearch-operator repo as the docker-promotion job does not promote to staging ECR.
+ dockerCopy: {
+ build job: 'docker-copy',
+ parameters: [
+ string(name: 'SOURCE_IMAGE_REGISTRY', value: 'opensearchstaging'),
+ string(name: 'SOURCE_IMAGE', value: "${SIDECAR_PRODUCT}:${OPERATOR_VERSION}"),
+ string(name: 'DESTINATION_IMAGE_REGISTRY', value: 'public.ecr.aws/opensearchstaging'),
+ string(name: 'DESTINATION_IMAGE', value: "${SIDECAR_PRODUCT}:${OPERATOR_VERSION}")
+ ]
+ }
+
+ // Promote to DockerHub Prod https://hub.docker.com/r/opensearchproject/opensearch-operator repo.
+ // Promote to ECR Prod https://gallery.ecr.aws/opensearchproject/opensearch-operator repo.
+ dockerPromotion: {
+ build job: 'docker-promotion',
+ parameters: [
+ string(name: 'SOURCE_IMAGES', value: "${SIDECAR_PRODUCT}:${OPERATOR_VERSION}"),
+ string(name: 'RELEASE_VERSION', value: "${OPERATOR_VERSION}"),
+ booleanParam(name: 'DOCKER_HUB_PROMOTE', value: true),
+ booleanParam(name: 'ECR_PROMOTE', value: true),
+ booleanParam(name: 'TAG_LATEST', value: true),
+ booleanParam(name: 'TAG_MAJOR_VERSION', value: false)
+ ]
+ }
+
+ // Then build the actual operator image
echo("${OPERATOR_PRODUCT}: ${OPERATOR_VERSION}")
// Build and push to dockerhub staging repo https://hub.docker.com/r/opensearchstaging/opensearch-operator.
diff --git a/opensearch-operator/Dockerfile b/opensearch-operator/Dockerfile
index 569fed71d..297b84bb1 100644
--- a/opensearch-operator/Dockerfile
+++ b/opensearch-operator/Dockerfile
@@ -1,5 +1,5 @@
# Build the manager binary
-FROM --platform=$BUILDPLATFORM golang:1.22.1 as builder
+FROM --platform=$BUILDPLATFORM golang:1.22.1 AS builder
WORKDIR /workspace
# Copy the Go Modules manifests
diff --git a/opensearch-operator/api/v1/opensearch_types.go b/opensearch-operator/api/v1/opensearch_types.go
index 7da1e55a1..9bd021a8b 100644
--- a/opensearch-operator/api/v1/opensearch_types.go
+++ b/opensearch-operator/api/v1/opensearch_types.go
@@ -124,6 +124,7 @@ type NodePool struct {
PriorityClassName string `json:"priorityClassName,omitempty"`
Pdb *PdbConfig `json:"pdb,omitempty"`
Probes *ProbesConfig `json:"probes,omitempty"`
+ OperatorSidecar *OperatorSidecarConfig `json:"operatorSidecar,omitempty"`
}
// PersistencConfig defines options for data persistence
@@ -175,6 +176,12 @@ type BootstrapConfig struct {
Keystore []KeystoreValue `json:"keystore,omitempty"`
}
+type OperatorSidecarConfig struct {
+ Enable bool `json:"enable,omitempty"`
+ *ImageSpec `json:",inline,omitempty"`
+ ReadinessPerPool bool `json:"readinessPerPool,omitempty"`
+}
+
type DashboardsServiceSpec struct {
// +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer
// +kubebuilder:default=ClusterIP
diff --git a/opensearch-operator/api/v1/zz_generated.deepcopy.go b/opensearch-operator/api/v1/zz_generated.deepcopy.go
index fb0068d7f..d60678c6b 100644
--- a/opensearch-operator/api/v1/zz_generated.deepcopy.go
+++ b/opensearch-operator/api/v1/zz_generated.deepcopy.go
@@ -1096,6 +1096,11 @@ func (in *NodePool) DeepCopyInto(out *NodePool) {
*out = new(ProbesConfig)
(*in).DeepCopyInto(*out)
}
+ if in.OperatorSidecar != nil {
+ in, out := &in.OperatorSidecar, &out.OperatorSidecar
+ *out = new(OperatorSidecarConfig)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodePool.
@@ -2202,6 +2207,26 @@ func (in *OpensearchUserStatus) DeepCopy() *OpensearchUserStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OperatorSidecarConfig) DeepCopyInto(out *OperatorSidecarConfig) {
+ *out = *in
+ if in.ImageSpec != nil {
+ in, out := &in.ImageSpec, &out.ImageSpec
+ *out = new(ImageSpec)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorSidecarConfig.
+func (in *OperatorSidecarConfig) DeepCopy() *OperatorSidecarConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(OperatorSidecarConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PVCSource) DeepCopyInto(out *PVCSource) {
*out = *in
diff --git a/opensearch-operator/config/crd/bases/opensearch.opster.io_opensearchclusters.yaml b/opensearch-operator/config/crd/bases/opensearch.opster.io_opensearchclusters.yaml
index b329b1bc0..820376a8d 100644
--- a/opensearch-operator/config/crd/bases/opensearch.opster.io_opensearchclusters.yaml
+++ b/opensearch-operator/config/crd/bases/opensearch.opster.io_opensearchclusters.yaml
@@ -4800,6 +4800,34 @@ spec:
additionalProperties:
type: string
type: object
+ operatorSidecar:
+ properties:
+ enable:
+ type: boolean
+ image:
+ type: string
+ imagePullPolicy:
+ description: PullPolicy describes a policy for if/when to
+ pull a container image
+ type: string
+ imagePullSecrets:
+ items:
+ description: |-
+ LocalObjectReference contains enough information to let you locate the
+ referenced object inside the same namespace.
+ properties:
+ name:
+ description: |-
+ Name of the referent.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ TODO: Add other useful fields. apiVersion, kind, uid?
+ type: string
+ type: object
+ x-kubernetes-map-type: atomic
+ type: array
+ readinessPerPool:
+ type: boolean
+ type: object
pdb:
properties:
enable:
diff --git a/opensearch-operator/functionaltests/execute_tests.sh b/opensearch-operator/functionaltests/execute_tests.sh
index 3e9d9fd07..00f379c43 100755
--- a/opensearch-operator/functionaltests/execute_tests.sh
+++ b/opensearch-operator/functionaltests/execute_tests.sh
@@ -2,20 +2,33 @@
CLUSTER_NAME=opensearch-operator-tests
## Setup k3d cluster and prepare kubeconfig
-k3d cluster create $CLUSTER_NAME --agents 2 --kubeconfig-switch-context=false --kubeconfig-update-default=false -p "30000-30005:30000-30005@agent:0" --image=rancher/k3s:v1.22.17-k3s1
+k3d cluster create $CLUSTER_NAME --agents 2 --kubeconfig-switch-context=false --kubeconfig-update-default=false -p "30000-30005:30000-30005@agent:0" --image=rancher/k3s:v1.31.4-k3s1
k3d kubeconfig get $CLUSTER_NAME > kubeconfig
export KUBECONFIG=$(pwd)/kubeconfig
-## Build controller docker image
-cd ..
-make docker-build
+## Build sidecar docker image and import into k3d
+cd ../../operator-sidecar
+IMG=operator-sidecar:dev make docker-build
+k3d image import -c $CLUSTER_NAME operator-sidecar:dev
-## Import controller docker image
+## Build controller docker image and import into k3d
+cd ../opensearch-operator
+make docker-build
k3d image import -c $CLUSTER_NAME controller:latest
-## Install helm chart
-helm install opensearch-operator ../charts/opensearch-operator --set manager.image.repository=controller --set manager.image.tag=latest --set manager.image.pullPolicy=IfNotPresent --namespace default --wait
-helm install opensearch-cluster ../charts/opensearch-cluster --set OpenSearchClusterSpec.enabled=true --wait
+## Install helm charts
+helm install opensearch-operator ../charts/opensearch-operator \
+ --set manager.image.repository=controller \
+ --set manager.image.tag=latest \
+ --set manager.image.pullPolicy=IfNotPresent \
+ --set operatorSidecar.image=operator-sidecar:dev \
+ --namespace default --wait
+
+kubectl apply -f functionaltests/rbac.yaml
+
+helm install opensearch-cluster ../charts/opensearch-cluster \
+ -f functionaltests/helm-cluster-values.yaml \
+ --wait
cd functionaltests
diff --git a/opensearch-operator/functionaltests/helm-cluster-values.yaml b/opensearch-operator/functionaltests/helm-cluster-values.yaml
new file mode 100644
index 000000000..aabf28f96
--- /dev/null
+++ b/opensearch-operator/functionaltests/helm-cluster-values.yaml
@@ -0,0 +1,19 @@
+cluster:
+ general:
+ serviceAccount: opensearch
+ nodePools:
+ - component: masters
+ diskSize: "30Gi"
+ replicas: 3
+ roles:
+ - "master"
+ - "data"
+ resources:
+ requests:
+ memory: "2Gi"
+ cpu: "500m"
+ limits:
+ memory: "2Gi"
+ cpu: "500m"
+ operatorSidecar:
+ enable: true
diff --git a/opensearch-operator/functionaltests/operatortests/deploy-and-upgrade.yaml b/opensearch-operator/functionaltests/operatortests/deploy-and-upgrade.yaml
index 7d1c64cc9..c93fa1d18 100644
--- a/opensearch-operator/functionaltests/operatortests/deploy-and-upgrade.yaml
+++ b/opensearch-operator/functionaltests/operatortests/deploy-and-upgrade.yaml
@@ -5,10 +5,11 @@ metadata:
namespace: default
spec:
general:
- version: 1.3.0
+ version: 2.10.0
httpPort: 9200
vendor: opensearch
serviceName: deploy-and-upgrade
+ serviceAccount: opensearch
additionalConfig:
cluster.routing.allocation.disk.watermark.low: 500m
cluster.routing.allocation.disk.watermark.high: 300m
@@ -16,7 +17,7 @@ spec:
confMgmt:
smartScaler: true
dashboards:
- version: 1.3.0
+ version: 2.10.0
enable: true
replicas: 1
resources:
@@ -41,3 +42,5 @@ spec:
roles:
- "master"
- "data"
+ operatorSidecar:
+ enable: true
diff --git a/opensearch-operator/functionaltests/operatortests/deploy_and_upgrade_test.go b/opensearch-operator/functionaltests/operatortests/deploy_and_upgrade_test.go
index 59eadd43a..04afd41c2 100644
--- a/opensearch-operator/functionaltests/operatortests/deploy_and_upgrade_test.go
+++ b/opensearch-operator/functionaltests/operatortests/deploy_and_upgrade_test.go
@@ -52,8 +52,8 @@ var _ = Describe("DeployAndUpgrade", Ordered, func() {
cluster.SetGroupVersionKind(schema.GroupVersionKind{Group: "opensearch.opster.io", Version: "v1", Kind: "OpenSearchCluster"})
Get(&cluster, client.ObjectKey{Name: name, Namespace: namespace}, time.Second*5)
- SetNestedKey(cluster.Object, "2.3.0", "spec", "general", "version")
- SetNestedKey(cluster.Object, "2.3.0", "spec", "dashboards", "version")
+ SetNestedKey(cluster.Object, "2.11.0", "spec", "general", "version")
+ SetNestedKey(cluster.Object, "2.11.0", "spec", "dashboards", "version")
Expect(k8sClient.Update(context.Background(), &cluster)).ToNot(HaveOccurred())
})
@@ -66,7 +66,7 @@ var _ = Describe("DeployAndUpgrade", Ordered, func() {
return sts.Spec.Template.Spec.Containers[0].Image
}
return ""
- }, time.Minute*1, time.Second*5).Should(Equal("docker.io/opensearchproject/opensearch:2.3.0"))
+ }, time.Minute*1, time.Second*5).Should(Equal("docker.io/opensearchproject/opensearch:2.11.0"))
Eventually(func() int32 {
err := k8sClient.Get(context.Background(), client.ObjectKey{Name: name + "-masters", Namespace: namespace}, &sts)
@@ -105,7 +105,7 @@ var _ = Describe("DeployAndUpgrade", Ordered, func() {
return deployment.Spec.Template.Spec.Containers[0].Image
}
return ""
- }, time.Minute*1, time.Second*5).Should(Equal("docker.io/opensearchproject/opensearch-dashboards:2.3.0"))
+ }, time.Minute*1, time.Second*5).Should(Equal("docker.io/opensearchproject/opensearch-dashboards:2.11.0"))
Eventually(func() int32 {
err := k8sClient.Get(context.Background(), client.ObjectKey{Name: name + "-dashboards", Namespace: namespace}, &deployment)
diff --git a/opensearch-operator/functionaltests/rbac.yaml b/opensearch-operator/functionaltests/rbac.yaml
new file mode 100644
index 000000000..5f7748f86
--- /dev/null
+++ b/opensearch-operator/functionaltests/rbac.yaml
@@ -0,0 +1,31 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: opensearch
+automountServiceAccountToken: true
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: leaderelection
+ namespace: default
+rules:
+ - apiGroups:
+ - coordination.k8s.io
+ resources:
+ - leases
+ verbs:
+ - '*'
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: opensearch-leaderelection
+ namespace: default
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: leaderelection
+subjects:
+ - kind: ServiceAccount
+ name: opensearch
diff --git a/opensearch-operator/pkg/builders/cluster.go b/opensearch-operator/pkg/builders/cluster.go
index 0c5c2f76d..00ff49a10 100644
--- a/opensearch-operator/pkg/builders/cluster.go
+++ b/opensearch-operator/pkg/builders/cluster.go
@@ -443,6 +443,153 @@ func NewSTSForNodePool(
initContainers = append(initContainers, keystoreInitContainer)
}
+ var containers []corev1.Container
+
+ containers = append(containers, corev1.Container{
+ Env: []corev1.EnvVar{
+ {
+ Name: "cluster.initial_master_nodes",
+ Value: BootstrapPodName(cr),
+ },
+ {
+ Name: "discovery.seed_hosts",
+ Value: DiscoveryServiceName(cr),
+ },
+ {
+ Name: "cluster.name",
+ Value: cr.Name,
+ },
+ {
+ Name: "network.bind_host",
+ Value: "0.0.0.0",
+ },
+ {
+ // Make elasticsearch announce its hostname instead of IP so that certificates using the hostname can be verified
+ Name: "network.publish_host",
+ ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}},
+ },
+ {
+ Name: "OPENSEARCH_JAVA_OPTS",
+ Value: jvm,
+ },
+ {
+ Name: "node.roles",
+ Value: strings.Join(selectedRoles, ","),
+ },
+ {
+ Name: "http.port",
+ Value: fmt.Sprint(cr.Spec.General.HttpPort),
+ },
+ },
+ Name: "opensearch",
+ Command: mainCommand,
+ Image: image.GetImage(),
+ ImagePullPolicy: image.GetImagePullPolicy(),
+ Resources: node.Resources,
+ Ports: []corev1.ContainerPort{
+ {
+ Name: "http",
+ ContainerPort: cr.Spec.General.HttpPort,
+ },
+ {
+ Name: "transport",
+ ContainerPort: 9300,
+ },
+ },
+ StartupProbe: &startupProbe,
+ LivenessProbe: &livenessProbe,
+ ReadinessProbe: &readinessProbe,
+ VolumeMounts: volumeMounts,
+ SecurityContext: securityContext,
+ })
+
+ if node.OperatorSidecar != nil && node.OperatorSidecar.Enable {
+ sidecarImage := helpers.ResolveOperatorSidecarImage(&node)
+ var lockName string
+ if node.OperatorSidecar.ReadinessPerPool {
+ lockName = cr.Name + "-" + node.Component
+ } else {
+ lockName = cr.Name
+ }
+ containers = append(containers, corev1.Container{
+ Env: []corev1.EnvVar{
+ {
+ Name: "LOCK_NAME",
+ Value: lockName,
+ },
+ {
+ Name: "CLUSTER_NAMESPACE",
+ ValueFrom: &corev1.EnvVarSource{
+ FieldRef: &corev1.ObjectFieldSelector{
+ FieldPath: "metadata.namespace",
+ },
+ },
+ },
+ {
+ Name: "POD_NAME",
+ ValueFrom: &corev1.EnvVarSource{
+ FieldRef: &corev1.ObjectFieldSelector{
+ FieldPath: "metadata.name",
+ },
+ },
+ },
+ {
+ Name: "HTTP_PORT",
+ Value: fmt.Sprint(cr.Spec.General.HttpPort),
+ },
+ },
+ Name: "operator-sidecar",
+ Image: sidecarImage.GetImage(),
+ ImagePullPolicy: sidecarImage.GetImagePullPolicy(),
+ Resources: corev1.ResourceRequirements{
+ Requests: corev1.ResourceList{
+ "cpu": resource.MustParse("100m"),
+ "memory": resource.MustParse("64Mi"),
+ },
+ Limits: corev1.ResourceList{
+ "cpu": resource.MustParse("100m"),
+ "memory": resource.MustParse("64Mi"),
+ },
+ },
+ Ports: []corev1.ContainerPort{
+ {
+ Name: "http",
+ ContainerPort: 8123,
+ },
+ },
+ LivenessProbe: &corev1.Probe{
+ ProbeHandler: corev1.ProbeHandler{
+ HTTPGet: &corev1.HTTPGetAction{
+ Path: "/health",
+ Port: intstr.FromInt(8123),
+ },
+ },
+ InitialDelaySeconds: 2,
+ TimeoutSeconds: 2,
+ PeriodSeconds: 10,
+ FailureThreshold: 2,
+ },
+ ReadinessProbe: &corev1.Probe{
+ ProbeHandler: corev1.ProbeHandler{
+ HTTPGet: &corev1.HTTPGetAction{
+ Path: "/cluster_readiness",
+ Port: intstr.FromInt(8123),
+ },
+ },
+ InitialDelaySeconds: readinessProbeInitialDelaySeconds,
+ PeriodSeconds: readinessProbePeriodSeconds,
+ FailureThreshold: readinessProbeFailureThreshold,
+ TimeoutSeconds: readinessProbeTimeoutSeconds,
+ },
+ VolumeMounts: []corev1.VolumeMount{{
+ Name: "admin-credentials",
+ MountPath: "/mnt/admin-credentials",
+ }},
+ SecurityContext: securityContext,
+ })
+ image.ImagePullSecrets = append(image.ImagePullSecrets, sidecarImage.ImagePullSecrets...)
+ }
+
sts := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: cr.Name + "-" + node.Component,
@@ -465,65 +612,7 @@ func NewSTSForNodePool(
Annotations: annotations,
},
Spec: corev1.PodSpec{
- Containers: []corev1.Container{
- {
- Env: []corev1.EnvVar{
- {
- Name: "cluster.initial_master_nodes",
- Value: BootstrapPodName(cr),
- },
- {
- Name: "discovery.seed_hosts",
- Value: DiscoveryServiceName(cr),
- },
- {
- Name: "cluster.name",
- Value: cr.Name,
- },
- {
- Name: "network.bind_host",
- Value: "0.0.0.0",
- },
- {
- // Make elasticsearch announce its hostname instead of IP so that certificates using the hostname can be verified
- Name: "network.publish_host",
- ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}},
- },
- {
- Name: "OPENSEARCH_JAVA_OPTS",
- Value: jvm,
- },
- {
- Name: "node.roles",
- Value: strings.Join(selectedRoles, ","),
- },
- {
- Name: "http.port",
- Value: fmt.Sprint(cr.Spec.General.HttpPort),
- },
- },
- Name: "opensearch",
- Command: mainCommand,
- Image: image.GetImage(),
- ImagePullPolicy: image.GetImagePullPolicy(),
- Resources: node.Resources,
- Ports: []corev1.ContainerPort{
- {
- Name: "http",
- ContainerPort: cr.Spec.General.HttpPort,
- },
- {
- Name: "transport",
- ContainerPort: 9300,
- },
- },
- StartupProbe: &startupProbe,
- LivenessProbe: &livenessProbe,
- ReadinessProbe: &readinessProbe,
- VolumeMounts: volumeMounts,
- SecurityContext: securityContext,
- },
- },
+ Containers: containers,
InitContainers: initContainers,
Volumes: volumes,
ServiceAccountName: cr.Spec.General.ServiceAccount,
diff --git a/opensearch-operator/pkg/helpers/reconcile-helpers.go b/opensearch-operator/pkg/helpers/reconcile-helpers.go
index a0bc215aa..3f654a6c1 100644
--- a/opensearch-operator/pkg/helpers/reconcile-helpers.go
+++ b/opensearch-operator/pkg/helpers/reconcile-helpers.go
@@ -2,6 +2,7 @@ package helpers
import (
"fmt"
+ "os"
"path"
"strings"
@@ -83,6 +84,34 @@ func ResolveDashboardsImage(cr *opsterv1.OpenSearchCluster) (result opsterv1.Ima
return
}
+func ResolveOperatorSidecarImage(nodePool *opsterv1.NodePool) (result opsterv1.ImageSpec) {
+ defaultRepo := "docker.io/opensearchproject"
+ defaultImage := "opensearch-operator-sidecar"
+ defaultVersion := "latest"
+
+ // if image is specified in nodePool, use that
+ if useCustomImage(nodePool.OperatorSidecar.ImageSpec, &result) {
+ return
+ }
+
+ // if image is specified via envvar (from helm chart), use that
+ image, exists := os.LookupEnv("OPERATOR_SIDECAR_IMAGE")
+ if exists && image != "" {
+ result.Image = &image
+ return
+ }
+
+ version, exists := os.LookupEnv("OPERATOR_SIDECAR_VERSION")
+ if exists && version != "" {
+ defaultVersion = version
+ }
+
+ // otherwise fall back to default
+ result.Image = pointer.String(fmt.Sprintf("%s/%s:%s", defaultRepo, defaultImage, defaultVersion))
+
+ return
+}
+
func useCustomImage(customImageSpec *opsterv1.ImageSpec, result *opsterv1.ImageSpec) bool {
if customImageSpec != nil {
if customImageSpec.ImagePullPolicy != nil {
diff --git a/opensearch-operator/pkg/reconcilers/cluster.go b/opensearch-operator/pkg/reconcilers/cluster.go
index 0a4a3249a..3239e1e7d 100644
--- a/opensearch-operator/pkg/reconcilers/cluster.go
+++ b/opensearch-operator/pkg/reconcilers/cluster.go
@@ -17,6 +17,7 @@ import (
"github.com/go-logr/logr"
"github.com/samber/lo"
appsv1 "k8s.io/api/apps/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"
@@ -155,6 +156,35 @@ func (r *ClusterReconciler) reconcileNodeStatefulSet(nodePool opsterv1.NodePool,
return &ctrl.Result{}, err
}
+ _, err := r.client.GetStatefulSet(sts.Name, sts.Namespace)
+ // If the operator sidecar is enabled we need special handling during initial start of the nodepool
+ if nodePool.OperatorSidecar != nil && nodePool.OperatorSidecar.Enable && err != nil {
+ // If this is the initial start of the nodepool, start pods in Parallel
+ if apierrors.IsNotFound(err) {
+ // Create initially with PodManagementPolicy Parallel
+ sts.Spec.PodManagementPolicy = appsv1.ParallelPodManagement
+ result, err := r.client.ReconcileResource(sts, reconciler.StatePresent)
+ if err != nil || result != nil {
+ return result, err
+ }
+ // Wait for pods to appear
+ err = helpers.WaitForSTSReplicas(r.client, sts, nodePool.Replicas)
+ if err != nil {
+ return result, err
+ }
+ // Delete sts and recreate with normal PodManagementPolicy
+ if err := helpers.WaitForSTSDelete(r.client, sts); err != nil {
+ return result, err
+ }
+ // Reset manifest, STS will be created by code below
+ sts.Spec.PodManagementPolicy = appsv1.OrderedReadyPodManagement
+ sts.ObjectMeta.ResourceVersion = ""
+ sts.ObjectMeta.UID = ""
+ } else {
+ return &ctrl.Result{}, err
+ }
+ }
+
// First ensure that the statefulset exists
result, err := r.client.ReconcileResource(sts, reconciler.StateCreated)
if err != nil || result != nil {
@@ -201,7 +231,7 @@ func (r *ClusterReconciler) reconcileNodeStatefulSet(nodePool opsterv1.NodePool,
// A failure is assumed if n PVCs exist but less than n-1 pods (one missing pod is allowed for rolling restart purposes)
// We can assume the cluster is in a failure state and cannot recover on its own
if !helpers.IsUpgradeInProgress(r.instance.Status) &&
- pvcCount >= int(nodePool.Replicas) && existing.Status.ReadyReplicas < nodePool.Replicas-1 {
+ pvcCount >= int(nodePool.Replicas) && existing.Status.Replicas < nodePool.Replicas-1 {
r.logger.Info(fmt.Sprintf("Detected recovery situation for nodepool %s: PVC count: %d, replicas: %d. Recreating STS with parallel mode", nodePool.Component, pvcCount, existing.Status.Replicas))
if existing.Spec.PodManagementPolicy != appsv1.ParallelPodManagement {
// Switch to Parallel to jumpstart the cluster
diff --git a/opensearch-operator/pkg/reconcilers/rollingRestart.go b/opensearch-operator/pkg/reconcilers/rollingRestart.go
index 483654a44..767ef6d78 100644
--- a/opensearch-operator/pkg/reconcilers/rollingRestart.go
+++ b/opensearch-operator/pkg/reconcilers/rollingRestart.go
@@ -74,23 +74,38 @@ func (r *RollingRestartReconciler) Reconcile() (ctrl.Result, error) {
if err != nil {
return ctrl.Result{}, err
}
- if sts.Status.UpdateRevision != "" &&
- sts.Status.UpdatedReplicas != pointer.Int32Deref(sts.Spec.Replicas, 1) {
- pendingUpdate = true
- break
- } else if sts.Status.UpdateRevision != "" &&
- sts.Status.CurrentRevision != sts.Status.UpdateRevision {
- // If all pods in sts are updated to spec.replicas but current version is not updated.
- err := r.client.UdateObjectStatus(&sts, func(object client.Object) {
- instance := object.(*appsv1.StatefulSet)
- instance.Status.CurrentRevision = sts.Status.UpdateRevision
- })
- if err != nil {
- lg.Error(err, "failed to update status")
- return ctrl.Result{}, err
+
+ if sts.Status.UpdateRevision == "" {
+ // STS status is not yet updated, don't do anything
+ continue
+ }
+
+ if sts.Status.CurrentRevision != sts.Status.UpdateRevision {
+ if sts.Status.UpdatedReplicas != pointer.Int32Deref(sts.Spec.Replicas, 1) {
+ pendingUpdate = true
+ } else {
+ // If all pods in sts are updated to spec.replicas but current version is not updated.
+ err := r.client.UdateObjectStatus(&sts, func(object client.Object) {
+ instance := object.(*appsv1.StatefulSet)
+ instance.Status.CurrentRevision = sts.Status.UpdateRevision
+ })
+ if err != nil {
+ lg.Error(err, "failed to update status")
+ return ctrl.Result{}, err
+ }
+ }
+ } else {
+ updatedReplicas := sts.Status.UpdatedReplicas
+ if updatedReplicas == 0 {
+ // For newly created STS updatedReplicas is not set, so treat it as set to Spec.Replicas
+ updatedReplicas = pointer.Int32Deref(sts.Spec.Replicas, 1)
}
+ if updatedReplicas != pointer.Int32Deref(sts.Spec.Replicas, 1) {
+ pendingUpdate = true
+ }
}
+
if sts.Status.ReadyReplicas != pointer.Int32Deref(sts.Spec.Replicas, 1) {
return ctrl.Result{
Requeue: true,
@@ -219,7 +234,9 @@ func (r *RollingRestartReconciler) restartStatefulSetPod(sts *appsv1.StatefulSet
return ctrl.Result{}, err
}
- return ctrl.Result{}, nil
+ err = services.ReactivateShardAllocation(r.osClient)
+
+ return ctrl.Result{}, err
}
func (r *RollingRestartReconciler) updateStatus(status string) error {
diff --git a/opensearch-operator/pkg/reconcilers/upgrade.go b/opensearch-operator/pkg/reconcilers/upgrade.go
index 73a3acfc0..b0b6b8272 100644
--- a/opensearch-operator/pkg/reconcilers/upgrade.go
+++ b/opensearch-operator/pkg/reconcilers/upgrade.go
@@ -382,7 +382,7 @@ func (r *UpgradeReconciler) doNodePoolUpgrade(pool opsterv1.NodePool) error {
return err
}
- return nil
+ return services.ReactivateShardAllocation(r.osClient)
}
func (r *UpgradeReconciler) setComponentConditions(conditions []string, component string) {
diff --git a/operator-sidecar/Dockerfile b/operator-sidecar/Dockerfile
new file mode 100644
index 000000000..b00e1ab99
--- /dev/null
+++ b/operator-sidecar/Dockerfile
@@ -0,0 +1,26 @@
+# Build the manager binary
+FROM --platform=$BUILDPLATFORM golang:1.22.1 AS builder
+
+WORKDIR /workspace
+# Copy the Go Modules manifests
+COPY go.mod go.sum /workspace/
+# cache deps before building and copying source so that we don't need to re-download as much
+# and so that source changes don't invalidate our downloaded layer
+RUN go mod download
+
+# Copy the go source
+COPY main.go main.go
+
+# Build
+ARG TARGETOS
+ARG TARGETARCH
+RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -o sidecar main.go
+
+# Use distroless as minimal base image to package the manager binary
+# Refer to https://github.com/GoogleContainerTools/distroless for more details
+FROM --platform=$BUILDPLATFORM gcr.io/distroless/static:nonroot
+WORKDIR /
+COPY --from=builder /workspace/sidecar .
+USER 65532:65532
+
+ENTRYPOINT ["/sidecar"]
diff --git a/operator-sidecar/Makefile b/operator-sidecar/Makefile
new file mode 100644
index 000000000..7de7a4a8b
--- /dev/null
+++ b/operator-sidecar/Makefile
@@ -0,0 +1,37 @@
+# Image URL to use all building/pushing image targets
+IMG ?= operator-sidecar:latest
+PROJECT_PATH=$(CURDIR)
+
+# Setting SHELL to bash allows bash commands to be executed by recipes.
+# This is a requirement for 'setup-envtest.sh' in the test target.
+# Options are set to exit when a recipe line exits non-zero or a piped command fails.
+SHELL = /usr/bin/env bash -o pipefail
+.SHELLFLAGS = -ec
+
+all: build
+
+.PHONY: fmt
+fmt: ## Run go fmt against code.
+ go fmt ./...
+
+.PHONY: lint
+lint:
+ docker run --rm --volume="${PROJECT_PATH}:/go/src/opensearch-operator" -w /go/src/opensearch-operator golangci/golangci-lint:v1.57-alpine golangci-lint run -E gofmt --timeout=3m
+
+.PHONY: vet
+vet: ## Run go vet against code.
+ go vet ./
+
+##@ Build
+
+build: fmt vet ## Build manager binary.
+ go build -o bin/sidecar main.go
+
+docker-build: fmt vet ## Build docker image for the sidecar
+ DOCKER_BUILDKIT=1 docker build -t ${IMG} .
+
+docker-build-multiarch: ## Build docker image for all supported architectures
+ DOCKER_BUILDKIT=1 docker buildx build --platform="linux/amd64,linux/arm,linux/arm64" -t ${IMG} .
+
+docker-push: ## Push docker image to registry
+ docker push ${IMG}
diff --git a/operator-sidecar/go.mod b/operator-sidecar/go.mod
new file mode 100644
index 000000000..93f69be61
--- /dev/null
+++ b/operator-sidecar/go.mod
@@ -0,0 +1,66 @@
+module github.com/opensearch-project/opensearch-k8s-operator/controller-sidecar
+
+go 1.22.1
+
+require (
+ k8s.io/client-go v0.27.2
+ sigs.k8s.io/controller-runtime v0.15.0
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/emicklei/go-restful/v3 v3.10.1 // indirect
+ github.com/evanphx/json-patch v5.6.0+incompatible // indirect
+ github.com/evanphx/json-patch/v5 v5.6.0 // indirect
+ github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-openapi/jsonpointer v0.19.6 // indirect
+ github.com/go-openapi/jsonreference v0.20.1 // indirect
+ github.com/go-openapi/swag v0.22.3 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/gnostic v0.5.7-v3refs // indirect
+ github.com/google/go-cmp v0.5.9 // indirect
+ github.com/google/gofuzz v1.2.0 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/imdario/mergo v0.3.13 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/prometheus/client_golang v1.15.1 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.9.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/stretchr/testify v1.8.3 // indirect
+ golang.org/x/net v0.10.0 // indirect
+ golang.org/x/oauth2 v0.5.0 // indirect
+ golang.org/x/sys v0.8.0 // indirect
+ golang.org/x/term v0.8.0 // indirect
+ golang.org/x/text v0.9.0 // indirect
+ golang.org/x/time v0.3.0 // indirect
+ gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/api v0.27.2 // indirect
+ k8s.io/apiextensions-apiserver v0.27.2 // indirect
+ k8s.io/apimachinery v0.27.2 // indirect
+ k8s.io/component-base v0.27.2 // indirect
+ k8s.io/klog/v2 v2.90.1 // indirect
+ k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
+ k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
+ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
+)
diff --git a/operator-sidecar/go.sum b/operator-sidecar/go.sum
new file mode 100644
index 000000000..5b1bba502
--- /dev/null
+++ b/operator-sidecar/go.sum
@@ -0,0 +1,267 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
+github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
+github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
+github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
+github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
+github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
+github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
+github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
+github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
+github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
+github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
+github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
+github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
+go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
+golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
+golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc=
+gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo=
+k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4=
+k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo=
+k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ=
+k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg=
+k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
+k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE=
+k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ=
+k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo=
+k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo=
+k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
+k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
+k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg=
+k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU=
+k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU=
+sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/operator-sidecar/main.go b/operator-sidecar/main.go
new file mode 100644
index 000000000..aa36a8ba6
--- /dev/null
+++ b/operator-sidecar/main.go
@@ -0,0 +1,182 @@
+package main
+
+import (
+ "context"
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "time"
+
+ "sync/atomic"
+
+ "k8s.io/client-go/tools/leaderelection"
+ rl "k8s.io/client-go/tools/leaderelection/resourcelock"
+ ctrl "sigs.k8s.io/controller-runtime"
+)
+
+var (
+ // Name of the lock to use (depends on if it is per-nodepool or for the entire Opensearch cluster)
+ lockName = os.Getenv("LOCK_NAME")
+ lockNamespace = os.Getenv("CLUSTER_NAMESPACE")
+
+ // Port Opensearch HTTP API is listening on, for readiness checks
+ opensearchHttpPort = os.Getenv("HTTP_PORT")
+
+ // unique identity for the leader election
+ identity = os.Getenv("POD_NAME")
+)
+
+func main() {
+ // Get the active kubernetes context, uses in-cluster by default
+ cfg, err := ctrl.GetConfig()
+ if err != nil {
+ // If we can't get a context, bail out as there is no way to recover
+ panic(err.Error())
+ }
+
+ // Create a new lock. This will be used to create a Lease resource in the cluster.
+ l, err := rl.NewFromKubeconfig(
+ rl.LeasesResourceLock,
+ lockNamespace,
+ lockName,
+ rl.ResourceLockConfig{
+ Identity: identity,
+ },
+ cfg,
+ time.Second*5,
+ )
+ if err != nil {
+ // If we can't create the Lock, bail out as there is no way to recover
+ panic(err)
+ }
+
+ // Track if we are the leader
+ leader := &atomic.Bool{}
+
+ // https://pkg.go.dev/k8s.io/client-go/tools/leaderelection#LeaderElectionConfig
+ el, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
+ Lock: l,
+ LeaseDuration: time.Second * 10,
+ RenewDeadline: time.Second * 5,
+ RetryPeriod: time.Second * 2,
+ Name: lockName,
+ Callbacks: leaderelection.LeaderCallbacks{
+ OnStartedLeading: func(ctx context.Context) {
+ // We are now leader
+ leader.Store(true)
+ },
+ OnStoppedLeading: func() {
+ // We are no longer leader
+ leader.Store(false)
+ },
+ OnNewLeader: func(identity string) {},
+ },
+ })
+ if err != nil {
+ // If we can't create the LeaderElection, bail out as there is no way to recover
+ panic(err)
+ }
+
+ // start http server in the background
+ go healthserver(leader)
+
+ // Begin the leader election process. This will block.
+ el.Run(context.Background())
+}
+
+func healthserver(leader *atomic.Bool) {
+ // self health
+ http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "OK")
+ })
+ // readiness of Opensearch cluster
+ http.HandleFunc("/cluster_readiness", func(w http.ResponseWriter, r *http.Request) {
+ isLeader := leader.Load()
+ status, err := callClusterHealthEndpoint(isLeader)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprintf(w, "Failed to check cluster health: %s: %s", status, err)
+ return
+ }
+ if isLeader {
+ if status != "green" {
+ w.WriteHeader(http.StatusServiceUnavailable)
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+ fmt.Fprintf(w, "Cluster status is %s", status)
+ } else {
+ // if we are not the leader we are only concerned with if opensearch is reachable
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, "Cluster status is %s", status)
+ }
+ })
+
+ s := &http.Server{
+ Addr: ":8123",
+ ReadTimeout: 10 * time.Second,
+ WriteTimeout: 10 * time.Second,
+ }
+ log.Fatal(s.ListenAndServe())
+}
+
+func dialTimeout(network, addr string) (net.Conn, error) {
+ // Fail fast
+ return net.DialTimeout(network, addr, time.Duration(1*time.Second))
+}
+
+func httpClient() http.Client {
+ transport := http.Transport{
+ Dial: dialTimeout,
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ // These options are needed as otherwise connections would be kept and leak memory
+ DisableKeepAlives: true,
+ MaxIdleConns: 1,
+ }
+
+ return http.Client{
+ Transport: &transport,
+ Timeout: time.Duration(2 * time.Second),
+ }
+}
+
+func callClusterHealthEndpoint(checkStatus bool) (string, error) {
+ username, err := os.ReadFile("/mnt/admin-credentials/username")
+ if err != nil {
+ return "no_username", err
+ }
+ password, err := os.ReadFile("/mnt/admin-credentials/password")
+ if err != nil {
+ return "no_password", err
+ }
+ client := httpClient()
+ resp, err := client.Get(fmt.Sprintf("https://%s:%s@localhost:%s/_cluster/health", username, password, opensearchHttpPort))
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+ // we care about the actual status only if we are the leader, otherwise reachability is enough
+ if checkStatus {
+ if resp.StatusCode == 200 {
+ var response ClusterHealthResponse
+ if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
+ return "", err
+ }
+ return response.Status, nil
+ } else {
+ return "error", nil
+ }
+ } else {
+ return "", nil
+ }
+}
+
+// minimal response struct with only the field we need
+// other fields get ignored
+type ClusterHealthResponse struct {
+ Status string `json:"status,omitempty"`
+}
diff --git a/scripts/build-image-multi-arch.sh b/scripts/build-image-multi-arch.sh
index a38338306..14ab44a03 100644
--- a/scripts/build-image-multi-arch.sh
+++ b/scripts/build-image-multi-arch.sh
@@ -89,9 +89,9 @@ else
IFS=', ' read -r -a TARBALL_ARRAY <<< "$TARBALL"
fi
-if [ "$PRODUCT" != "opensearch-operator-busybox" ] && [ "$PRODUCT" != "opensearch-operator" ]
+if [ "$PRODUCT" != "opensearch-operator-busybox" ] && [ "$PRODUCT" != "opensearch-operator" ] && [ "$PRODUCT" != "operator-sidecar" ]
then
- echo "Enter either 'opensearch-operator' or 'opensearch-operator-busybox' as product name for -p parameter"
+ echo "Enter either 'opensearch-operator' or 'opensearch-operator-busybox' or 'operator-sidecar' as product name for -p parameter"
exit 1
else
PRODUCT_ALT=`echo $PRODUCT | sed 's@-@_@g'`