diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3fa5655cbc..343c643b21 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -33,3 +33,12 @@ jobs: build: uses: ./.github/workflows/build.yml + + helm_unit_tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Set up Helm + uses: azure/setup-helm@v4 + - name: Run Helm unit tests + run: ./helm/run-tests.sh diff --git a/helm/generic-helm-chart/Chart.yaml b/helm/generic-helm-chart/Chart.yaml new file mode 100644 index 0000000000..9deb8046bd --- /dev/null +++ b/helm/generic-helm-chart/Chart.yaml @@ -0,0 +1,22 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v2 +name: generic-operator-chart +description: A generic reusable Helm chart for Java operators built with Java Operator SDK (JOSDK) +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/helm/generic-helm-chart/templates/_helpers.tpl b/helm/generic-helm-chart/templates/_helpers.tpl new file mode 100644 index 0000000000..337cf741b8 --- /dev/null +++ b/helm/generic-helm-chart/templates/_helpers.tpl @@ -0,0 +1,100 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "generic-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "generic-operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "generic-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "generic-operator.labels" -}} +helm.sh/chart: {{ include "generic-operator.chart" . }} +{{ include "generic-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "generic-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "generic-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "generic-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "generic-operator.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Image tag - defaults to chart appVersion +*/}} +{{- define "generic-operator.imageTag" -}} +{{- .Values.image.tag | default .Chart.AppVersion }} +{{- end }} + +{{/* +Default verbs for primary resources +*/}} +{{- define "generic-operator.primaryVerbs" -}} +- get +- list +- watch +- patch +- update +{{- end }} + +{{/* +Default verbs for primary resource status +*/}} +{{- define "generic-operator.primaryStatusVerbs" -}} +- get +- patch +- update +{{- end }} + +{{/* +Default verbs for secondary resources +*/}} +{{- define "generic-operator.secondaryVerbs" -}} +- get +- list +- watch +- create +- update +- patch +- delete +{{- end }} diff --git a/helm/generic-helm-chart/templates/clusterrole.yaml b/helm/generic-helm-chart/templates/clusterrole.yaml new file mode 100644 index 0000000000..04070da489 --- /dev/null +++ b/helm/generic-helm-chart/templates/clusterrole.yaml @@ -0,0 +1,97 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "generic-operator.fullname" . }} + labels: + {{- include "generic-operator.labels" . | nindent 4 }} +rules: +{{- /* Primary resources - the custom resources the operator reconciles */}} +{{- range .Values.primaryResources }} +- apiGroups: + - {{ .apiGroup | quote }} + resources: + {{- range .resources }} + - {{ . }} + {{- end }} + verbs: + {{- if .verbs }} + {{- range .verbs }} + - {{ . }} + {{- end }} + {{- else }} + {{- include "generic-operator.primaryVerbs" $ | nindent 2 }} + {{- end }} +- apiGroups: + - {{ .apiGroup | quote }} + resources: + {{- range .resources }} + - {{ . }}/status + {{- end }} + verbs: + {{- if .statusVerbs }} + {{- range .statusVerbs }} + - {{ . }} + {{- end }} + {{- else }} + {{- include "generic-operator.primaryStatusVerbs" $ | nindent 2 }} + {{- end }} +{{- end }} +{{- /* Secondary resources - resources managed by the operator */}} +{{- range .Values.secondaryResources }} +- apiGroups: + - {{ .apiGroup | quote }} + resources: + {{- range .resources }} + - {{ . }} + {{- end }} + verbs: + {{- if .verbs }} + {{- range .verbs }} + - {{ . }} + {{- end }} + {{- else }} + {{- include "generic-operator.secondaryVerbs" $ | nindent 2 }} + {{- end }} +{{- end }} +# Event permissions - for recording events +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +{{- /* Leader election - Lease permissions */}} +{{- if .Values.leaderElection.enabled }} +# Lease permissions - for leader election +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +{{- end }} +{{- end }} diff --git a/helm/generic-helm-chart/templates/clusterrolebinding.yaml b/helm/generic-helm-chart/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000..d96ddaaab1 --- /dev/null +++ b/helm/generic-helm-chart/templates/clusterrolebinding.yaml @@ -0,0 +1,32 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "generic-operator.fullname" . }} + labels: + {{- include "generic-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "generic-operator.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "generic-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/helm/generic-helm-chart/templates/configmap.yaml b/helm/generic-helm-chart/templates/configmap.yaml new file mode 100644 index 0000000000..3fb9b5b6af --- /dev/null +++ b/helm/generic-helm-chart/templates/configmap.yaml @@ -0,0 +1,27 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "generic-operator.fullname" . }}-config + labels: + {{- include "generic-operator.labels" . | nindent 4 }} +data: + config.yaml: | + {{- index .Values.operatorConfig "config" | nindent 4 }} + log4j2.xml: | + {{- .Values.operatorConfig.log4j2 | nindent 4 }} diff --git a/helm/generic-helm-chart/templates/deployment.yaml b/helm/generic-helm-chart/templates/deployment.yaml new file mode 100644 index 0000000000..dd06916155 --- /dev/null +++ b/helm/generic-helm-chart/templates/deployment.yaml @@ -0,0 +1,87 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "generic-operator.fullname" . }} + labels: + {{- include "generic-operator.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "generic-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ .Values.operatorConfig | toJson | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "generic-operator.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "generic-operator.serviceAccountName" . }} + {{- with .Values.extraInitContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: operator + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ required "A valid .Values.image.repository is required" .Values.image.repository }}:{{ include "generic-operator.imageTag" . }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.operator.watchNamespace }} + - name: WATCH_NAMESPACE + value: {{ .Values.operator.watchNamespace | quote }} + {{- end }} + {{- with .Values.operator.env }} + {{- toYaml . | nindent 8 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /config + readOnly: true + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.extraContainers }} + {{- toYaml . | nindent 6 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ include "generic-operator.fullname" . }}-config + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 6 }} + {{- end }} diff --git a/helm/generic-helm-chart/templates/serviceaccount.yaml b/helm/generic-helm-chart/templates/serviceaccount.yaml new file mode 100644 index 0000000000..7ddc5e39c1 --- /dev/null +++ b/helm/generic-helm-chart/templates/serviceaccount.yaml @@ -0,0 +1,28 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "generic-operator.serviceAccountName" . }} + labels: + {{- include "generic-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/generic-helm-chart/tests/clusterrole_test.yaml b/helm/generic-helm-chart/tests/clusterrole_test.yaml new file mode 100644 index 0000000000..7cfdf4dc99 --- /dev/null +++ b/helm/generic-helm-chart/tests/clusterrole_test.yaml @@ -0,0 +1,271 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: ClusterRole tests +templates: + - clusterrole.yaml +set: + image.repository: example.com/my-operator +tests: + - it: should create ClusterRole when rbac.create is true + asserts: + - isKind: + of: ClusterRole + - hasDocuments: + count: 1 + + - it: should not create ClusterRole when rbac.create is false + set: + rbac.create: false + asserts: + - hasDocuments: + count: 0 + + - it: should always include event permissions + asserts: + - contains: + path: rules + content: + apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + + - it: should add primary resource rules with default verbs + set: + primaryResources: + - apiGroup: "example.com" + resources: + - myresources + asserts: + - contains: + path: rules + content: + apiGroups: + - "example.com" + resources: + - myresources + verbs: + - get + - list + - watch + - patch + - update + - contains: + path: rules + content: + apiGroups: + - "example.com" + resources: + - myresources/status + verbs: + - get + - patch + - update + + - it: should use custom verbs for primary resources + set: + primaryResources: + - apiGroup: "example.com" + resources: + - myresources + verbs: + - get + - list + asserts: + - contains: + path: rules + content: + apiGroups: + - "example.com" + resources: + - myresources + verbs: + - get + - list + + - it: should use custom status verbs for primary resources + set: + primaryResources: + - apiGroup: "example.com" + resources: + - myresources + statusVerbs: + - get + asserts: + - contains: + path: rules + content: + apiGroups: + - "example.com" + resources: + - myresources/status + verbs: + - get + + - it: should add secondary resource rules with default verbs + set: + secondaryResources: + - apiGroup: "" + resources: + - configmaps + - secrets + asserts: + - contains: + path: rules + content: + apiGroups: + - "" + resources: + - configmaps + - secrets + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + + - it: should use custom verbs for secondary resources + set: + secondaryResources: + - apiGroup: "" + resources: + - configmaps + verbs: + - get + - list + - watch + asserts: + - contains: + path: rules + content: + apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + + - it: should not include lease rules when leader election is disabled + asserts: + - notContains: + path: rules + content: + apiGroups: + - coordination.k8s.io + any: true + + - it: should include lease rules when leader election is enabled + set: + leaderElection.enabled: true + asserts: + - contains: + path: rules + content: + apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + + - it: should handle multiple primary and secondary resources + set: + primaryResources: + - apiGroup: "example.com" + resources: + - foos + - apiGroup: "other.io" + resources: + - bars + secondaryResources: + - apiGroup: "" + resources: + - configmaps + - apiGroup: "apps" + resources: + - deployments + asserts: + - contains: + path: rules + content: + apiGroups: + - "example.com" + resources: + - foos + verbs: + - get + - list + - watch + - patch + - update + - contains: + path: rules + content: + apiGroups: + - "other.io" + resources: + - bars + verbs: + - get + - list + - watch + - patch + - update + - contains: + path: rules + content: + apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - contains: + path: rules + content: + apiGroups: + - "apps" + resources: + - deployments + verbs: + - get + - list + - watch + - create + - update + - patch + - delete diff --git a/helm/generic-helm-chart/tests/clusterrolebinding_test.yaml b/helm/generic-helm-chart/tests/clusterrolebinding_test.yaml new file mode 100644 index 0000000000..fc358beb21 --- /dev/null +++ b/helm/generic-helm-chart/tests/clusterrolebinding_test.yaml @@ -0,0 +1,64 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: ClusterRoleBinding tests +templates: + - clusterrolebinding.yaml +set: + image.repository: example.com/my-operator +tests: + - it: should create ClusterRoleBinding when rbac.create is true + asserts: + - isKind: + of: ClusterRoleBinding + - hasDocuments: + count: 1 + + - it: should not create ClusterRoleBinding when rbac.create is false + set: + rbac.create: false + asserts: + - hasDocuments: + count: 0 + + - it: should reference the correct ClusterRole + asserts: + - equal: + path: roleRef.kind + value: ClusterRole + - equal: + path: roleRef.name + value: RELEASE-NAME-generic-operator-chart + - equal: + path: roleRef.apiGroup + value: rbac.authorization.k8s.io + + - it: should reference the correct ServiceAccount + asserts: + - equal: + path: subjects[0].kind + value: ServiceAccount + - equal: + path: subjects[0].name + value: RELEASE-NAME-generic-operator-chart + + - it: should use custom service account name + set: + serviceAccount.name: "my-sa" + asserts: + - equal: + path: subjects[0].name + value: my-sa diff --git a/helm/generic-helm-chart/tests/configmap_test.yaml b/helm/generic-helm-chart/tests/configmap_test.yaml new file mode 100644 index 0000000000..03d1114632 --- /dev/null +++ b/helm/generic-helm-chart/tests/configmap_test.yaml @@ -0,0 +1,57 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: ConfigMap tests +templates: + - configmap.yaml +set: + image.repository: example.com/my-operator +tests: + - it: should create a ConfigMap + asserts: + - isKind: + of: ConfigMap + - equal: + path: metadata.name + value: RELEASE-NAME-generic-operator-chart-config + + - it: should contain config.yaml key + asserts: + - exists: + path: data["config.yaml"] + + - it: should contain log4j2.xml key + asserts: + - exists: + path: data["log4j2.xml"] + + - it: should render custom operator config + set: + operatorConfig: + config: | + josdk.reconciliation.concurrent-threads: 100 + asserts: + - matchRegex: + path: data["config.yaml"] + pattern: "concurrent-threads: 100" + + - it: should have standard labels + asserts: + - exists: + path: metadata.labels["helm.sh/chart"] + - equal: + path: metadata.labels["app.kubernetes.io/managed-by"] + value: Helm diff --git a/helm/generic-helm-chart/tests/deployment_test.yaml b/helm/generic-helm-chart/tests/deployment_test.yaml new file mode 100644 index 0000000000..bdc8845ae9 --- /dev/null +++ b/helm/generic-helm-chart/tests/deployment_test.yaml @@ -0,0 +1,290 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Deployment tests +templates: + - deployment.yaml +set: + image.repository: example.com/my-operator +tests: + - it: should render a Deployment with default values + asserts: + - isKind: + of: Deployment + - equal: + path: spec.replicas + value: 1 + - equal: + path: spec.template.spec.containers[0].name + value: operator + - equal: + path: spec.template.spec.containers[0].image + value: "example.com/my-operator:0.1.0" + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: IfNotPresent + + - it: should set custom replica count + documentSelector: + path: kind + value: Deployment + set: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + + - it: should set custom image tag + documentSelector: + path: kind + value: Deployment + set: + image.tag: "1.2.3" + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: "example.com/my-operator:1.2.3" + + - it: should inject OPERATOR_NAMESPACE env var + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + + - it: should set WATCH_NAMESPACE when configured + documentSelector: + path: kind + value: Deployment + set: + operator.watchNamespace: "my-namespace" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WATCH_NAMESPACE + value: "my-namespace" + + - it: should not set WATCH_NAMESPACE when empty + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: WATCH_NAMESPACE + any: true + + - it: should add custom env vars + documentSelector: + path: kind + value: Deployment + set: + operator.env: + - name: MY_VAR + value: "my-value" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: MY_VAR + value: "my-value" + + - it: should add env var from secret reference + documentSelector: + path: kind + value: Deployment + set: + operator.env: + - name: SECRET_VAR + valueFrom: + secretKeyRef: + name: my-secret + key: password + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SECRET_VAR + valueFrom: + secretKeyRef: + name: my-secret + key: password + + - it: should add multiple custom env vars + documentSelector: + path: kind + value: Deployment + set: + operator.env: + - name: VAR_ONE + value: "one" + - name: VAR_TWO + value: "two" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: VAR_ONE + value: "one" + - contains: + path: spec.template.spec.containers[0].env + content: + name: VAR_TWO + value: "two" + + - it: should mount config volume + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: config + mountPath: /config + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: config + configMap: + name: RELEASE-NAME-generic-operator-chart-config + + - it: should add extra volumes and volume mounts + documentSelector: + path: kind + value: Deployment + set: + extraVolumes: + - name: my-secret + secret: + secretName: my-secret + extraVolumeMounts: + - name: my-secret + mountPath: /secrets + readOnly: true + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: my-secret + secret: + secretName: my-secret + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: my-secret + mountPath: /secrets + readOnly: true + + - it: should add extra sidecar containers + documentSelector: + path: kind + value: Deployment + set: + extraContainers: + - name: sidecar + image: sidecar:latest + asserts: + - contains: + path: spec.template.spec.containers + content: + name: sidecar + image: sidecar:latest + + - it: should add extra init containers + documentSelector: + path: kind + value: Deployment + set: + extraInitContainers: + - name: init + image: init:latest + asserts: + - contains: + path: spec.template.spec.initContainers + content: + name: init + image: init:latest + + - it: should set pod security context + asserts: + - equal: + path: spec.template.spec.securityContext.runAsUser + value: 9999 + - equal: + path: spec.template.spec.securityContext.fsGroup + value: 9999 + + - it: should set container security context + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + - equal: + path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem + value: true + + - it: should set pod annotations + documentSelector: + path: kind + value: Deployment + set: + podAnnotations: + prometheus.io/scrape: "true" + asserts: + - equal: + path: spec.template.metadata.annotations["prometheus.io/scrape"] + value: "true" + + - it: should set resource limits and requests + asserts: + - equal: + path: spec.template.spec.containers[0].resources.limits.cpu + value: 500m + - equal: + path: spec.template.spec.containers[0].resources.limits.memory + value: 512Mi + - equal: + path: spec.template.spec.containers[0].resources.requests.cpu + value: 100m + - equal: + path: spec.template.spec.containers[0].resources.requests.memory + value: 128Mi + + - it: should set image pull secrets + documentSelector: + path: kind + value: Deployment + set: + imagePullSecrets: + - name: my-registry + asserts: + - contains: + path: spec.template.spec.imagePullSecrets + content: + name: my-registry + + - it: should use fullnameOverride for service account reference + documentSelector: + path: kind + value: Deployment + set: + fullnameOverride: "my-operator" + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: my-operator diff --git a/helm/generic-helm-chart/tests/serviceaccount_test.yaml b/helm/generic-helm-chart/tests/serviceaccount_test.yaml new file mode 100644 index 0000000000..60486cac21 --- /dev/null +++ b/helm/generic-helm-chart/tests/serviceaccount_test.yaml @@ -0,0 +1,77 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: ServiceAccount tests +templates: + - serviceaccount.yaml +set: + image.repository: example.com/my-operator +tests: + - it: should create ServiceAccount when serviceAccount.create is true + asserts: + - isKind: + of: ServiceAccount + - hasDocuments: + count: 1 + + - it: should not create ServiceAccount when serviceAccount.create is false + set: + serviceAccount.create: false + asserts: + - hasDocuments: + count: 0 + + - it: should use fullname as default name + asserts: + - equal: + path: metadata.name + value: RELEASE-NAME-generic-operator-chart + + - it: should use custom name when provided + set: + serviceAccount.name: "my-sa" + asserts: + - equal: + path: metadata.name + value: my-sa + + - it: should add annotations when provided + set: + serviceAccount.annotations: + eks.amazonaws.com/role-arn: "arn:aws:iam::role/my-role" + asserts: + - equal: + path: metadata.annotations["eks.amazonaws.com/role-arn"] + value: "arn:aws:iam::role/my-role" + + - it: should not have annotations by default + asserts: + - notExists: + path: metadata.annotations + + - it: should have standard labels + asserts: + - exists: + path: metadata.labels["helm.sh/chart"] + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: generic-operator-chart + - equal: + path: metadata.labels["app.kubernetes.io/instance"] + value: RELEASE-NAME + - equal: + path: metadata.labels["app.kubernetes.io/managed-by"] + value: Helm diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml new file mode 100644 index 0000000000..8ab452059c --- /dev/null +++ b/helm/generic-helm-chart/values.yaml @@ -0,0 +1,130 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Default values for generic JOSDK operator chart + +replicaCount: 1 + +image: + repository: "" # REQUIRED: set to your operator image + pullPolicy: IfNotPresent + tag: "" # Overrides the image tag whose default is the chart appVersion + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +podSecurityContext: + fsGroup: 9999 + runAsUser: 9999 + runAsGroup: 9999 + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +# Leader election configuration +leaderElection: + enabled: false + +# Operator configuration +operator: + # Namespace to watch (empty for all namespaces) + watchNamespace: "" + # Additional environment variables for the operator container + env: [] + # - name: MY_VAR + # value: "my-value" + +# Operator config files mounted at /config/ +operatorConfig: + # Operator configuration YAML (config.yaml) + config: | + # see options here: https://javaoperatorsdk.io/docs/documentation/configuration/#loading-configuration-from-external-sources + # Log4j2 configuration XML (log4j2.xml) + log4j2: | + + + + + + + + + + + + + + +# Primary resources - custom resources that the operator reconciles +# These get full CRUD + status permissions in the ClusterRole +primaryResources: [] +# - apiGroup: "example.com" +# resources: +# - myresources +# # optional: custom verbs (defaults to get, list, watch, patch, update) +# verbs: [] +# # optional: custom status verbs (defaults to get, patch, update) +# statusVerbs: [] + +# Secondary resources - resources that the operator creates/manages +# These get full CRUD permissions in the ClusterRole +secondaryResources: [] +# - apiGroup: "" +# resources: +# - configmaps +# - secrets +# # optional: custom verbs (defaults to get, list, watch, create, update, patch, delete) +# verbs: [] + +# Additional containers to add to the operator pod (sidecars) +extraContainers: [] + +# Additional init containers to add to the operator pod +extraInitContainers: [] + +# Additional volumes to add to the operator pod +extraVolumes: [] + +# Additional volume mounts to add to the operator container +extraVolumeMounts: [] + +# RBAC configuration +rbac: + create: true diff --git a/helm/run-tests.sh b/helm/run-tests.sh new file mode 100755 index 0000000000..a3366c28b9 --- /dev/null +++ b/helm/run-tests.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PLUGIN_NAME="unittest" +PLUGIN_URL="https://github.com/helm-unittest/helm-unittest" + +# Install helm-unittest plugin if not already installed +if ! helm plugin list | grep -q "${PLUGIN_NAME}"; then + echo "Installing helm-unittest plugin..." + helm plugin install --verify=false "${PLUGIN_URL}" +fi + +echo "Running helm unit tests..." +helm unittest "${SCRIPT_DIR}/generic-helm-chart" diff --git a/sample-operators/metrics-processing/k8s/operator.yaml b/sample-operators/metrics-processing/k8s/operator.yaml deleted file mode 100644 index 336d587a5b..0000000000 --- a/sample-operators/metrics-processing/k8s/operator.yaml +++ /dev/null @@ -1,84 +0,0 @@ -# -# Copyright Java Operator SDK Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: metrics-processing-operator - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: metrics-processing-operator -spec: - selector: - matchLabels: - app: metrics-processing-operator - replicas: 1 - template: - metadata: - labels: - app: metrics-processing-operator - spec: - serviceAccountName: metrics-processing-operator - containers: - - name: operator - image: metrics-processing-operator - imagePullPolicy: Never - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: metrics-processing-operator-admin -subjects: -- kind: ServiceAccount - name: metrics-processing-operator - namespace: default -roleRef: - kind: ClusterRole - name: metrics-processing-operator - apiGroup: rbac.authorization.k8s.io - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: metrics-processing-operator -rules: -- apiGroups: - - "" - resources: - - pods - verbs: - - '*' -- apiGroups: - - "apiextensions.k8s.io" - resources: - - customresourcedefinitions - verbs: - - '*' -- apiGroups: - - "sample.javaoperatorsdk" - resources: - - metricshandlingcustomresource1s - - metricshandlingcustomresource1s/status - - metricshandlingcustomresource2s - - metricshandlingcustomresource2s/status - verbs: - - '*' - diff --git a/sample-operators/metrics-processing/pom.xml b/sample-operators/metrics-processing/pom.xml index 752bec3e6a..00adf4fec7 100644 --- a/sample-operators/metrics-processing/pom.xml +++ b/sample-operators/metrics-processing/pom.xml @@ -101,6 +101,11 @@ metrics-processing-operator + + + -Dlog4j.configurationFile=/config/log4j2.xml + + diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java index ed6d65a4a9..3234deedaf 100644 --- a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java +++ b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java @@ -26,10 +26,14 @@ import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; import io.javaoperatorsdk.operator.sample.metrics.customresource.MetricsHandlingCustomResource1; -@ControllerConfiguration +import static io.javaoperatorsdk.operator.sample.metrics.MetricsHandlingReconciler1.NAME; + +@ControllerConfiguration(name = NAME) public class MetricsHandlingReconciler1 extends AbstractMetricsHandlingReconciler { + public static final String NAME = "MetricsHandlingReconciler1"; + private static final long TIMER_DELAY = 5000; private final TimerEventSource timerEventSource; diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java index 2c77c7f5fb..0484d2848e 100644 --- a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java +++ b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java @@ -22,6 +22,8 @@ public class MetricsHandlingReconciler2 extends AbstractMetricsHandlingReconciler { + public static final String NAME = "MetricsHandlingReconciler2"; + public MetricsHandlingReconciler2() { super(150); } diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java index 9f563bec9c..2c6b9c3e90 100644 --- a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java +++ b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java @@ -17,7 +17,9 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.time.Duration; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -29,6 +31,11 @@ import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.api.monitoring.Metrics; +import io.javaoperatorsdk.operator.config.loader.ConfigLoader; +import io.javaoperatorsdk.operator.config.loader.ConfigProvider; +import io.javaoperatorsdk.operator.config.loader.provider.AggregatePriorityListConfigProvider; +import io.javaoperatorsdk.operator.config.loader.provider.EnvVarConfigProvider; +import io.javaoperatorsdk.operator.config.loader.provider.YamlConfigProvider; import io.javaoperatorsdk.operator.monitoring.micrometer.MicrometerMetricsV2; import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.MeterRegistry; @@ -48,13 +55,6 @@ public class MetricsHandlingSampleOperator { private static final Logger log = LoggerFactory.getLogger(MetricsHandlingSampleOperator.class); - public static boolean isLocal() { - String deployment = System.getProperty("test.deployment"); - boolean remote = (deployment != null && deployment.equals("remote")); - log.info("Running the operator {} ", remote ? "remotely" : "locally"); - return !remote; - } - /** * Based on env variables a different flavor of Reconciler is used, showcasing how the same logic * can be implemented using the low level and higher level APIs. @@ -62,11 +62,20 @@ public static boolean isLocal() { public static void main(String[] args) { log.info("Metrics Handling Sample Operator starting!"); + var configProviders = new ArrayList(); + configProviders.add(new EnvVarConfigProvider()); + configProviders.add(new YamlConfigProvider(Path.of("/config/config.yaml"))); + var configLoader = new ConfigLoader(new AggregatePriorityListConfigProvider(configProviders)); + Metrics metrics = initOTLPMetrics(isLocal()); Operator operator = - new Operator(o -> o.withStopOnInformerErrorDuringStartup(false).withMetrics(metrics)); - operator.register(new MetricsHandlingReconciler1()); - operator.register(new MetricsHandlingReconciler2()); + new Operator(o -> configLoader.applyConfigs().andThen(k -> k.withMetrics(metrics))); + operator.register( + new MetricsHandlingReconciler1(), + configLoader.applyControllerConfigs(MetricsHandlingReconciler1.NAME)); + operator.register( + new MetricsHandlingReconciler2(), + configLoader.applyControllerConfigs(MetricsHandlingReconciler2.NAME)); operator.start(); } @@ -148,4 +157,12 @@ private static Map loadConfigFromYaml() { } return configMap; } + + // only for testing purposes + public static boolean isLocal() { + String deployment = System.getProperty("test.deployment"); + boolean remote = (deployment != null && deployment.equals("remote")); + log.info("Running the operator {} ", remote ? "remotely" : "locally"); + return !remote; + } } diff --git a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java index 40924c572d..34e96a5870 100644 --- a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java +++ b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java @@ -59,13 +59,14 @@ class MetricsHandlingE2E { static final int OTEL_COLLECTOR_PORT = 4318; public static final Duration TEST_DURATION = Duration.ofSeconds(60); public static final String NAME_LABEL_KEY = "app.kubernetes.io/name"; + static final String HELM_RELEASE_NAME = "metrics-processing"; private LocalPortForward prometheusPortForward; private LocalPortForward otelCollectorPortForward; static final KubernetesClient client = new KubernetesClientBuilder().build(); - MetricsHandlingE2E() throws FileNotFoundException {} + MetricsHandlingE2E() {} @RegisterExtension AbstractOperatorExtension operator = @@ -76,18 +77,15 @@ class MetricsHandlingE2E { .withConfigurationService( c -> c.withMetrics(MetricsHandlingSampleOperator.initOTLPMetrics(true))) .build() - : ClusterDeployedOperatorExtension.builder() - .withOperatorDeployment( - new KubernetesClientBuilder() - .build() - .load(new FileInputStream("k8s/operator.yaml")) - .items()) - .build(); + : ClusterDeployedOperatorExtension.builder().build(); @BeforeAll - void setupObservability() { + void setup() { log.info("Setting up observability stack..."); installObservabilityServices(); + if (!isLocal()) { + helmInstall(); + } prometheusPortForward = portForward(NAME_LABEL_KEY, "prometheus", PROMETHEUS_PORT); if (isLocal()) { otelCollectorPortForward = @@ -97,10 +95,67 @@ void setupObservability() { @AfterAll void cleanup() throws IOException { + if (!isLocal()) { + helmUninstall(); + } closePortForward(prometheusPortForward); closePortForward(otelCollectorPortForward); } + private void helmInstall() { + try { + var chartPath = + findProjectRoot("helm").toPath().resolve("helm/generic-helm-chart").toString(); + var valuesUrl = MetricsHandlingE2E.class.getClassLoader().getResource("helm-values.yaml"); + if (valuesUrl == null) { + throw new IllegalStateException("helm-values.yaml not found on classpath"); + } + var valuesPath = new File(valuesUrl.toURI()).getAbsolutePath(); + var namespace = getNamespace(); + + log.info("Installing helm release '{}' into namespace '{}'", HELM_RELEASE_NAME, namespace); + runCommand( + "helm", + "install", + HELM_RELEASE_NAME, + chartPath, + "-f", + valuesPath, + "--namespace", + namespace, + "--wait", + "--timeout", + "2m"); + log.info("Helm release '{}' installed successfully", HELM_RELEASE_NAME); + } catch (Exception e) { + throw new RuntimeException("Failed to install helm chart", e); + } + } + + private String getNamespace() { + var ns = operator.getNamespace(); + return ns == null ? "default" : ns; + } + + private void helmUninstall() { + try { + var namespace = getNamespace(); + log.info("Uninstalling helm release '{}' from namespace '{}'", HELM_RELEASE_NAME, namespace); + runCommand( + "helm", + "uninstall", + HELM_RELEASE_NAME, + "--namespace", + namespace, + "--wait", + "--timeout", + "2m"); + log.info("Helm release '{}' uninstalled successfully", HELM_RELEASE_NAME); + } catch (Exception e) { + log.warn("Failed to uninstall helm release", e); + } + } + private LocalPortForward portForward(String labelKey, String labelValue, int port) { log.info("Waiting for pod with label {}={} to be ready...", labelKey, labelValue); AtomicReference portForwardPod = new AtomicReference<>(); @@ -307,43 +362,43 @@ private String queryPrometheus(String prometheusUrl, String query) throws IOExce private void installObservabilityServices() { try { - // Find the observability script relative to project root - File projectRoot = new File(".").getCanonicalFile(); - while (projectRoot != null && !new File(projectRoot, "observability").exists()) { - projectRoot = projectRoot.getParentFile(); - } - - if (projectRoot == null) { - throw new IllegalStateException("Could not find observability directory"); - } - - File scriptFile = new File(projectRoot, "observability/install-observability.sh"); - if (!scriptFile.exists()) { - throw new IllegalStateException( - "Observability script not found at: " + scriptFile.getAbsolutePath()); - } + File scriptFile = + findProjectRoot("observability") + .toPath() + .resolve("observability/install-observability.sh") + .toFile(); log.info("Running observability setup script: {}", scriptFile.getAbsolutePath()); + runCommand("/bin/sh", scriptFile.getAbsolutePath()); + log.info("Observability stack is ready"); + } catch (Exception e) { + log.error("Failed to setup observability stack", e); + throw new RuntimeException(e); + } + } - // Run the install-observability.sh script - ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", scriptFile.getAbsolutePath()); - processBuilder.redirectErrorStream(true); + private static File findProjectRoot(String marker) throws IOException { + File dir = new File(".").getCanonicalFile(); + while (dir != null && !new File(dir, marker).exists()) { + dir = dir.getParentFile(); + } + if (dir == null) { + throw new IllegalStateException("Could not find '" + marker + "' directory in project root"); + } + return dir; + } - processBuilder.environment().putAll(System.getenv()); - Process process = processBuilder.start(); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + private static void runCommand(String... command) throws IOException, InterruptedException { + var process = new ProcessBuilder(command).redirectErrorStream(true).start(); + try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { - log.info("Observability setup: {}", line); + log.info("{}: {}", command[0], line); } - - int exitCode = process.waitFor(); - if (exitCode != 0) { - log.warn("Observability setup script returned exit code: {}", exitCode); - } - log.info("Observability stack is ready"); - } catch (Exception e) { - log.error("Failed to setup observability stack", e); - throw new RuntimeException(e); + } + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IllegalStateException( + String.join(" ", command) + " failed with exit code: " + exitCode); } } } diff --git a/sample-operators/metrics-processing/src/test/resources/helm-values.yaml b/sample-operators/metrics-processing/src/test/resources/helm-values.yaml new file mode 100644 index 0000000000..bb8e251139 --- /dev/null +++ b/sample-operators/metrics-processing/src/test/resources/helm-values.yaml @@ -0,0 +1,35 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Helm values for metrics-processing operator E2E test deployment +# Used with the generic-operator-chart from helm/generic-helm-chart/ + +image: + repository: metrics-processing-operator + pullPolicy: Never + tag: "latest" + +nameOverride: "metrics-processing-operator" + +resources: {} + +# Primary resources - custom resources that the operator reconciles +primaryResources: + - apiGroup: "sample.javaoperatorsdk" + resources: + - metricshandlingcustomresource1s + - metricshandlingcustomresource2s +