diff --git a/.gitignore b/.gitignore index 06e28cf1..d1685801 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ __pycache__/ .cursorrules .cursor/ .vscode/ +.venv/ diff --git a/README.md b/README.md index 7d3f984c..52498699 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,9 @@ These components are commented out in `values-hub.yaml` by default. Uncomment th * [Red Hat Quay](https://docs.redhat.com/en/documentation/red_hat_quay) _(externalized chart)_ * Enables a private OCI image registry within the environment * [Multicloud Object Gateway (NooBaa MCG)](https://docs.redhat.com/en/documentation/red_hat_openshift_data_foundation/latest/html/managing_hybrid_and_multicloud_resources/about-the-multicloud-object-gateway) - * Provides S3-compatible object storage for Quay and RHTPA + * Provides S3-compatible object storage for Quay, RHTPA, and Network Observability +* [Network Observability](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/network_observability) + * eBPF flow collection and console Network Traffic views; requires storage/NooBaa * [Red Hat Trusted Artifact Signer (RHTAS)](https://docs.redhat.com/en/documentation/red_hat_trusted_artifact_signer/1.3) * Provides cryptographic signing and verification of software artifacts and container images * [Red Hat Trusted Profile Analyzer (RHTPA)](https://docs.redhat.com/en/documentation/red_hat_trusted_profile_analyzer) diff --git a/charts/netobserv/Chart.yaml b/charts/netobserv/Chart.yaml new file mode 100644 index 00000000..b3e9ca7d --- /dev/null +++ b/charts/netobserv/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: netobserv +description: OpenShift Network Observability (NooBaa-backed LokiStack + FlowCollector) for ZTVP +type: application +version: 0.1.0 +appVersion: "1.12" +keywords: + - network-observability + - zero-trust +maintainers: + - name: Validated Patterns Team +home: https://validatedpatterns.io/patterns/layered-zero-trust/ +sources: + - https://github.com/validatedpatterns/layered-zero-trust diff --git a/charts/netobserv/templates/_helpers.tpl b/charts/netobserv/templates/_helpers.tpl new file mode 100644 index 00000000..6bbefadf --- /dev/null +++ b/charts/netobserv/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{- define "netobserv.name" -}} +netobserv +{{- end }} + +{{- define "netobserv.labels" -}} +app.kubernetes.io/name: {{ include "netobserv.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Resolve LokiStack PVC storageClassName: +explicit value, else the cluster default StorageClass, else fail. +*/}} +{{- define "netobserv.lokiStorageClassName" -}} +{{- $sc := .Values.loki.storageClassName | default "" -}} +{{- if $sc -}} +{{- $sc -}} +{{- else -}} +{{- $default := "" -}} +{{- range (lookup "storage.k8s.io/v1" "StorageClass" "" "").items | default list -}} +{{- $annotations := .metadata.annotations | default dict -}} +{{- if eq (index $annotations "storageclass.kubernetes.io/is-default-class" | default "") "true" -}} +{{- $default = .metadata.name -}} +{{- end -}} +{{- end -}} +{{- if not $default -}} +{{- fail "loki.storageClassName is empty and no default StorageClass was found; set loki.storageClassName in charts/netobserv values" -}} +{{- end -}} +{{- $default -}} +{{- end -}} +{{- end }} diff --git a/charts/netobserv/templates/flowcollector.yaml b/charts/netobserv/templates/flowcollector.yaml new file mode 100644 index 00000000..0114be75 --- /dev/null +++ b/charts/netobserv/templates/flowcollector.yaml @@ -0,0 +1,27 @@ +{{- if .Values.enabled }} +apiVersion: flows.netobserv.io/v1beta2 +kind: FlowCollector +metadata: + name: {{ .Values.flowcollector.name }} + labels: + {{- include "netobserv.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "42" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true +spec: + namespace: {{ .Values.flowcollector.namespace }} + deploymentModel: {{ .Values.flowcollector.deploymentModel }} + agent: + type: {{ .Values.flowcollector.agent.type }} + ebpf: + sampling: {{ .Values.flowcollector.agent.sampling }} + privileged: {{ .Values.flowcollector.agent.privileged }} + loki: + enable: {{ .Values.flowcollector.loki.enable }} + mode: {{ .Values.flowcollector.loki.mode }} + lokiStack: + name: {{ .Values.loki.name }} + namespace: {{ .Values.loki.namespace }} + consolePlugin: + enable: {{ .Values.flowcollector.consolePlugin.enable }} +{{- end }} diff --git a/charts/netobserv/templates/loki-s3-credentials-job.yaml b/charts/netobserv/templates/loki-s3-credentials-job.yaml new file mode 100644 index 00000000..cb7437d9 --- /dev/null +++ b/charts/netobserv/templates/loki-s3-credentials-job.yaml @@ -0,0 +1,164 @@ +{{- if and .Values.enabled .Values.loki.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: netobserv-loki-s3-setup + namespace: {{ .Values.loki.namespace }} + labels: + {{- include "netobserv.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "37" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: netobserv-loki-s3-setup + namespace: {{ .Values.loki.namespace }} + labels: + {{- include "netobserv.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "37" +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "create", "update", "patch"] + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list"] + - apiGroups: ["objectbucket.io"] + resources: ["objectbucketclaims"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: netobserv-loki-s3-setup + namespace: {{ .Values.loki.namespace }} + labels: + {{- include "netobserv.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "37" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: netobserv-loki-s3-setup +subjects: + - kind: ServiceAccount + name: netobserv-loki-s3-setup + namespace: {{ .Values.loki.namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: netobserv-loki-s3-credentials + namespace: {{ .Values.loki.namespace }} + labels: + {{- include "netobserv.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "39" + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + ttlSecondsAfterFinished: 600 + backoffLimit: {{ .Values.credentialsJob.backoffLimit }} + template: + metadata: + labels: + {{- include "netobserv.labels" . | nindent 8 }} + spec: + serviceAccountName: netobserv-loki-s3-setup + automountServiceAccountToken: true + restartPolicy: OnFailure + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - name: remap-obc-secret + image: {{ .Values.credentialsJob.image }} + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + resources: + {{- toYaml .Values.credentialsJob.resources | nindent 12 }} + volumeMounts: + - name: tmp + mountPath: /tmp + env: + - name: NS + value: {{ .Values.loki.namespace | quote }} + - name: OBC_NAME + value: {{ .Values.loki.objectBucketClaim.name | quote }} + - name: LOKI_SECRET + value: {{ .Values.loki.storageSecretName | quote }} + - name: REGION + value: {{ .Values.loki.region | quote }} + command: + - /bin/bash + - -ec + - | + # NooBaa OBC creates a Secret (keys) + ConfigMap (bucket/host/port) + # with the same name as the claim — not all keys live in the Secret. + echo "Checking ObjectBucketClaim ${OBC_NAME} in ${NS}..." + oc get objectbucketclaim "${OBC_NAME}" -n "${NS}" + + echo "Waiting for ObjectBucketClaim ${OBC_NAME} to be Bound (timeout: 10 minutes)..." + oc wait --for=jsonpath='{.status.phase}'=Bound \ + "objectbucketclaim/${OBC_NAME}" -n "${NS}" --timeout=600s || { + echo "ERROR: ObjectBucketClaim failed to reach Bound state within timeout" >&2 + oc describe objectbucketclaim "${OBC_NAME}" -n "${NS}" >&2 || true + exit 1 + } + + echo "Waiting for OBC Secret and ConfigMap ${OBC_NAME}..." + found=0 + for i in $(seq 1 60); do + if oc get secret "${OBC_NAME}" -n "${NS}" >/dev/null 2>&1 \ + && oc get configmap "${OBC_NAME}" -n "${NS}" >/dev/null 2>&1; then + found=1 + break + fi + sleep 5 + done + if [[ "${found}" -ne 1 ]]; then + echo "ERROR: OBC Secret and/or ConfigMap ${OBC_NAME} not available after 5 minutes" >&2 + oc describe objectbucketclaim "${OBC_NAME}" -n "${NS}" >&2 || true + exit 1 + fi + + ACCESS=$(oc get secret "${OBC_NAME}" -n "${NS}" -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d) + SECRET=$(oc get secret "${OBC_NAME}" -n "${NS}" -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d) + BUCKET=$(oc get configmap "${OBC_NAME}" -n "${NS}" -o jsonpath='{.data.BUCKET_NAME}') + HOST=$(oc get configmap "${OBC_NAME}" -n "${NS}" -o jsonpath='{.data.BUCKET_HOST}') + PORT=$(oc get configmap "${OBC_NAME}" -n "${NS}" -o jsonpath='{.data.BUCKET_PORT}') + + if [[ -z "${ACCESS}" || -z "${SECRET}" || -z "${BUCKET}" || -z "${HOST}" ]]; then + echo "OBC Secret/ConfigMap is missing required keys" >&2 + exit 1 + fi + + if [[ -n "${PORT}" && "${PORT}" != "443" ]]; then + ENDPOINT="https://${HOST}:${PORT}" + else + ENDPOINT="https://${HOST}" + fi + + oc create secret generic "${LOKI_SECRET}" -n "${NS}" \ + --from-literal=access_key_id="${ACCESS}" \ + --from-literal=access_key_secret="${SECRET}" \ + --from-literal=bucketnames="${BUCKET}" \ + --from-literal=endpoint="${ENDPOINT}" \ + --from-literal=region="${REGION}" \ + --dry-run=client -o yaml | oc apply -f - + echo "Created/updated ${LOKI_SECRET} for LokiStack (endpoint=${ENDPOINT})" + volumes: + - name: tmp + emptyDir: {} +{{- end }} diff --git a/charts/netobserv/templates/lokistack.yaml b/charts/netobserv/templates/lokistack.yaml new file mode 100644 index 00000000..0a12eda1 --- /dev/null +++ b/charts/netobserv/templates/lokistack.yaml @@ -0,0 +1,24 @@ +{{- if and .Values.enabled .Values.loki.enabled }} +apiVersion: loki.grafana.com/v1 +kind: LokiStack +metadata: + name: {{ .Values.loki.name }} + namespace: {{ .Values.loki.namespace }} + labels: + {{- include "netobserv.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "41" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true +spec: + size: {{ .Values.loki.size }} + storage: + schemas: + - version: v13 + effectiveDate: "2022-06-01" + secret: + name: {{ .Values.loki.storageSecretName }} + type: s3 + storageClassName: {{ include "netobserv.lokiStorageClassName" . }} + tenants: + mode: openshift-network +{{- end }} diff --git a/charts/netobserv/templates/object-bucket-claim.yaml b/charts/netobserv/templates/object-bucket-claim.yaml new file mode 100644 index 00000000..b401bc34 --- /dev/null +++ b/charts/netobserv/templates/object-bucket-claim.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.enabled .Values.loki.enabled }} +apiVersion: objectbucket.io/v1alpha1 +kind: ObjectBucketClaim +metadata: + name: {{ .Values.loki.objectBucketClaim.name }} + namespace: {{ .Values.loki.namespace }} + labels: + {{- include "netobserv.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "36" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true +spec: + generateBucketName: {{ .Values.loki.objectBucketClaim.bucketName }} + storageClassName: {{ .Values.loki.objectBucketClaim.storageClass }} +{{- end }} diff --git a/charts/netobserv/values.yaml b/charts/netobserv/values.yaml new file mode 100644 index 00000000..a224d18a --- /dev/null +++ b/charts/netobserv/values.yaml @@ -0,0 +1,41 @@ +enabled: true + +loki: + enabled: true + namespace: netobserv-loki + name: loki + size: 1x.extra-small + # Empty: use the cluster default StorageClass (or set explicitly) + storageClassName: "" + storageSecretName: loki-s3 + objectBucketClaim: + name: netobserv-loki-obc + bucketName: netobserv-loki + storageClass: openshift-storage.noobaa.io + # NooBaa MCG default region used in remapped secret + region: us-east-1 + +flowcollector: + name: cluster + namespace: netobserv + deploymentModel: Direct + agent: + type: eBPF + sampling: 50 + privileged: false + loki: + enable: true + mode: LokiStack + consolePlugin: + enable: true + +credentialsJob: + image: registry.redhat.io/openshift4/ose-cli-rhel9:latest + backoffLimit: 5 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi diff --git a/docs/SYNC-WAVE-INVENTORY.md b/docs/SYNC-WAVE-INVENTORY.md index 5181a7a8..9b866b95 100644 --- a/docs/SYNC-WAVE-INVENTORY.md +++ b/docs/SYNC-WAVE-INVENTORY.md @@ -24,10 +24,16 @@ Every sync-wave in the repository, in order. **App** = hub-level Argo CD Applica | 26 | └ openshift-storage | ns | Namespace + OperatorGroup | | 26 | └ rhtpa-operator | ns | Namespace + OperatorGroup | | 26 | └ external-secrets-operator | ns | Namespace + OperatorGroup | +| 26 | └ openshift-operators-redhat | ns | Namespace + OperatorGroup (NetObserv / Loki) | +| 26 | └ openshift-netobserv-operator | ns | Namespace + OperatorGroup | +| 26 | └ netobserv-loki | ns | Namespace | +| 26 | └ netobserv | ns | Namespace | | 27 | └ odf | sub | ODF operator install | | 27 | └ rhtpa-operator | sub | RHTPA operator install | | 27 | └ external-secrets | sub | External Secrets Operator install | +| 27 | └ loki-operator | sub | Loki Operator install (NetObserv) | | 28 | └ quay-operator | sub | Quay operator install | +| 28 | └ netobserv-operator | sub | Network Observability Operator | | 29 | └ rhtas-operator | sub | RHTAS operator install | | 30 | zero-trust-workload-identity-manager | **App** | | | 31 | └ rhtpa-operator | chart | ingress-ca-job (SA, Role, RoleBinding, ConfigMap, Job) | @@ -59,6 +65,12 @@ Every sync-wave in the repository, in order. **App** = hub-level Argo CD Applica | 41 | acs-central | **App** | | | 41 | quay-registry | **App** | | | 41 | trusted-profile-analyzer | **App** | | +| 42 | netobserv | **App** | Network Observability (after NooBaa) | +| 36 | └ netobserv | chart | object-bucket-claim | +| 37 | └ netobserv | chart | loki-s3 credentials SA/Role/RoleBinding | +| 39 | └ netobserv | chart | loki-s3-credentials Job (hook) | +| 41 | └ netobserv | chart | LokiStack | +| 42 | └ netobserv | chart | FlowCollector | | 41 | └ rhtpa-operator | chart | postgresql-statefulset, postgresql-service | | 41 | └ keycloak | chart | keycloak-realm-import | | 41 | └ quay-registry | chart | quay-registry (QuayRegistry CR) | @@ -111,6 +123,7 @@ Every sync-wave in the repository, in order. **App** = hub-level Argo CD Applica | rh-keycloak | — | 35 | After ZTWIM for SPIFFE IdP (newly added) | | noobaa-mcg | 5 | 36 | Deploy after core services | | qtodo | — | 38 | After Keycloak, Vault (newly added) | +| netobserv | — | 42 | After NooBaa | | acs-central | 10 | 41 | — | | quay-registry | 10 | 41 | Deploy after NooBaa | | trusted-profile-analyzer | 10 | 41 | Chart resources (OBC, DB, etc.) | @@ -171,6 +184,16 @@ Charts marked **(external)** have been externalized to standalone repositories m | noobaa-system.yaml | 2 | 33 | | bucket-class.yaml | 3 | 34 | +### netobserv (`charts/netobserv/templates/`) — App wave: 42 + +| Resource | Old | Current | +| --- | ---: | ---: | +| object-bucket-claim.yaml | — | 36 | +| loki-s3-credentials-job.yaml (SA, Role, RoleBinding) | — | 37 | +| loki-s3-credentials-job.yaml (Job hook) | — | 39 | +| lokistack.yaml | — | 41 | +| flowcollector.yaml | — | 42 | + ### keycloak — **(external)** `rhbk-chart` v0.0.4 — App wave: 35 | Resource | Old | Current | diff --git a/scripts/features/features.yaml b/scripts/features/features.yaml index dd048fe6..5a324cde 100644 --- a/scripts/features/features.yaml +++ b/scripts/features/features.yaml @@ -6,6 +6,10 @@ features: description: "ODF object storage + NooBaa MCG (S3 backend)" depends_on: [] + netobserv: + description: "OpenShift Network Observability (LokiStack + FlowCollector via NooBaa)" + depends_on: [storage] + quay: description: "Red Hat Quay container registry" depends_on: [storage] diff --git a/scripts/features/netobserv.yaml b/scripts/features/netobserv.yaml new file mode 100644 index 00000000..70bf0cc1 --- /dev/null +++ b/scripts/features/netobserv.yaml @@ -0,0 +1,49 @@ +# Network Observability +# Depends on: storage (NooBaa MCG for Loki object storage) +clusterGroup: + namespaces: + openshift-operators-redhat: + operatorGroup: true + targetNamespaces: [] + annotations: + argocd.argoproj.io/sync-wave: "26" + openshift-netobserv-operator: + operatorGroup: true + targetNamespaces: [] + labels: + openshift.io/cluster-monitoring: "true" + annotations: + argocd.argoproj.io/sync-wave: "26" + netobserv-loki: + annotations: + argocd.argoproj.io/sync-wave: "26" + netobserv: + labels: + openshift.io/cluster-monitoring: "true" + annotations: + argocd.argoproj.io/sync-wave: "26" + + subscriptions: + loki-operator: + name: loki-operator + namespace: openshift-operators-redhat + channel: stable-6.6 + source: redhat-operators + annotations: + argocd.argoproj.io/sync-wave: "27" + netobserv-operator: + name: netobserv-operator + namespace: openshift-netobserv-operator + channel: stable + source: redhat-operators + annotations: + argocd.argoproj.io/sync-wave: "28" + + applications: + netobserv: + name: netobserv + namespace: netobserv + project: hub + path: charts/netobserv + annotations: + argocd.argoproj.io/sync-wave: "42" diff --git a/scripts/gen-feature-variants.md b/scripts/gen-feature-variants.md index bf94cead..05797b3f 100644 --- a/scripts/gen-feature-variants.md +++ b/scripts/gen-feature-variants.md @@ -45,6 +45,9 @@ python3 scripts/gen-feature-variants.py --list-features # Enable a single feature (dependencies are resolved automatically) python3 scripts/gen-feature-variants.py --features rhtpa +# Network Observability (auto-resolves: storage -> netobserv) +python3 scripts/gen-feature-variants.py --features netobserv + # Enable multiple features python3 scripts/gen-feature-variants.py --features rhtpa,rhtas diff --git a/values-hub.yaml b/values-hub.yaml index 940edcde..4d195764 100644 --- a/values-hub.yaml +++ b/values-hub.yaml @@ -68,7 +68,7 @@ clusterGroup: operatorGroup: true targetNamespace: cert-manager-operator # Layer 1: Storage and Registry - # Required for RHTPA and QUAY (provides NooBaa object storage backend) + # Required for RHTPA, QUAY, and Network Observability (provides NooBaa object storage backend) # openshift-storage: # operatorGroup: true # targetNamespace: openshift-storage @@ -97,6 +97,29 @@ clusterGroup: # argocd.argoproj.io/sync-wave: "32" # Create before RHTPA components # labels: # openshift.io/cluster-monitoring: "true" + # Network Observability (LokiStack + FlowCollector) + # Depends on: storage / NooBaa (uncomment openshift-storage + odf + noobaa-mcg above/below) + # Prefer: python3 scripts/gen-feature-variants.py --features netobserv + # openshift-operators-redhat: + # operatorGroup: true + # targetNamespaces: [] + # annotations: + # argocd.argoproj.io/sync-wave: "26" + # openshift-netobserv-operator: + # operatorGroup: true + # targetNamespaces: [] + # labels: + # openshift.io/cluster-monitoring: "true" + # annotations: + # argocd.argoproj.io/sync-wave: "26" + # netobserv-loki: + # annotations: + # argocd.argoproj.io/sync-wave: "26" + # netobserv: + # labels: + # openshift.io/cluster-monitoring: "true" + # annotations: + # argocd.argoproj.io/sync-wave: "26" zero-trust-workload-identity-manager: operatorGroup: true targetNamespace: zero-trust-workload-identity-manager @@ -161,7 +184,7 @@ clusterGroup: source: redhat-operators # # Storage and Registry operator subscriptions - # Required for RHTPA and QUAY (provides NooBaa object storage backend) + # Required for RHTPA, QUAY, and Network Observability (provides NooBaa object storage backend) # ODF provides object storage backend (NooBaa) for RHTPA and optionally Quay # odf: # name: odf-operator @@ -194,6 +217,21 @@ clusterGroup: # catalogSource: redhat-operators # annotations: # argocd.argoproj.io/sync-wave: "27" # Install after OperatorGroup (26), before applications + # Network Observability operator subscriptions (requires storage / NooBaa) + # loki-operator: + # name: loki-operator + # namespace: openshift-operators-redhat + # channel: stable-6.6 + # source: redhat-operators + # annotations: + # argocd.argoproj.io/sync-wave: "27" + # netobserv-operator: + # name: netobserv-operator + # namespace: openshift-netobserv-operator + # channel: stable + # source: redhat-operators + # annotations: + # argocd.argoproj.io/sync-wave: "28" argoProjects: - hub # Explicitly mention the cluster-state based overrides we plan to use for this pattern. @@ -338,7 +376,7 @@ clusterGroup: annotations: argocd.argoproj.io/sync-wave: "25" # Shared Object Storage Backend - # Required for RHTPA and QUAY (provides S3-compatible storage via NooBaa MCG) + # Required for RHTPA, QUAY, and Network Observability (provides S3-compatible storage via NooBaa MCG) # NooBaa MCG provides S3-compatible object storage for multiple applications # noobaa-mcg: # name: noobaa-mcg @@ -347,6 +385,16 @@ clusterGroup: # path: charts/noobaa-mcg # annotations: # argocd.argoproj.io/sync-wave: "36" # Deploy after core services + # Network Observability (LokiStack + FlowCollector via NooBaa) + # Depends on: storage / NooBaa MCG above + # Prefer: python3 scripts/gen-feature-variants.py --features netobserv + # netobserv: + # name: netobserv + # namespace: netobserv + # project: hub + # path: charts/netobserv + # annotations: + # argocd.argoproj.io/sync-wave: "42" # Deploy after NooBaa storage backend # Quay Container Registry (uses NooBaa for storage) # quay-registry: # name: quay-registry