From 86cd68b195c402edd24fab9e6976f01626afd525 Mon Sep 17 00:00:00 2001 From: Tongtong Zhou Date: Mon, 19 Jan 2026 21:43:44 +0800 Subject: [PATCH 1/4] [HYPERFLEET-479]: Create Helm chart for adapter-pull-secret deployment --- .gitignore | 4 +- charts/pull-secret/Chart.yaml | 8 +- charts/pull-secret/README.md | 82 ++++++--- charts/pull-secret/templates/NOTES.txt | 63 +++++++ charts/pull-secret/templates/_helpers.tpl | 46 ++++- charts/pull-secret/templates/job.yaml | 88 ++++++++-- charts/pull-secret/templates/rbac.yaml | 36 ++++ .../pull-secret/templates/serviceaccount.yaml | 7 +- charts/pull-secret/values.yaml | 166 +++++++++++++----- 9 files changed, 409 insertions(+), 91 deletions(-) create mode 100644 charts/pull-secret/templates/NOTES.txt create mode 100644 charts/pull-secret/templates/rbac.yaml diff --git a/.gitignore b/.gitignore index 093e58e..dd6f54b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Binaries for programs and plugins -pull-secret -pull-secret-mvp +/pull-secret +/pull-secret-mvp *.exe *.exe~ *.dll diff --git a/charts/pull-secret/Chart.yaml b/charts/pull-secret/Chart.yaml index ab540dc..90016eb 100644 --- a/charts/pull-secret/Chart.yaml +++ b/charts/pull-secret/Chart.yaml @@ -4,10 +4,14 @@ description: HyperFleet Pull Secret Adapter - Manages pull secrets in GCP Secret type: application version: 0.1.0 appVersion: "1.0" +maintainers: + - name: HyperFleet Team + email: hyperfleet-team@redhat.com keywords: - hyperfleet + - adapter - pull-secret - gcp - secret-manager -maintainers: - - name: HyperFleet Team + - kubernetes +home: https://github.com/openshift-hyperfleet/adapter-pull-secret diff --git a/charts/pull-secret/README.md b/charts/pull-secret/README.md index f89a0af..dc93bd4 100644 --- a/charts/pull-secret/README.md +++ b/charts/pull-secret/README.md @@ -40,9 +40,9 @@ Deploy with custom configuration: helm install pullsecret-job ./charts/pull-secret \ --namespace hyperfleet-system \ --create-namespace \ - --set env.gcpProjectId=my-project \ - --set env.clusterId=my-cluster-123 \ - --set env.pullSecretData='{"auths":{...}}' \ + --set gcp.projectId=my-project \ + --set cluster.id=my-cluster-123 \ + --set pullSecret.data='{"auths":{...}}' \ --set image.tag=latest ``` @@ -51,14 +51,19 @@ helm install pullsecret-job ./charts/pull-secret \ Create a custom values file (`my-values.yaml`): ```yaml -env: - gcpProjectId: "my-gcp-project" - clusterId: "my-cluster-123" - secretName: "hyperfleet-my-cluster-123-pull-secret" - pullSecretData: '{"auths":{"registry.example.com":{"auth":"...","email":"user@example.com"}}}' +gcp: + projectId: "my-gcp-project" + +cluster: + id: "my-cluster-123" + +pullSecret: + name: "hyperfleet-my-cluster-123-pull-secret" + data: '{"auths":{"registry.example.com":{"auth":"...","email":"user@example.com"}}}' serviceAccount: - gcpServiceAccount: "my-service-account@my-project.iam.gserviceaccount.com" + annotations: + iam.gke.io/gcp-service-account: "my-service-account@my-project.iam.gserviceaccount.com" image: tag: "v1.0.0" @@ -73,25 +78,61 @@ helm install pullsecret-job ./charts/pull-secret \ -f my-values.yaml ``` +## Umbrella Chart Integration + +This chart supports integration with the `hyperfleet-chart` umbrella chart. + +### Adding to hyperfleet-chart + +In your umbrella chart's `Chart.yaml`: + +```yaml +dependencies: + - name: pull-secret + version: "0.1.0" + repository: "git+https://github.com/openshift-hyperfleet/adapter-pull-secret@charts/pull-secret?ref=main" + condition: pull-secret.enabled +``` + +### Global Image Override + +When deployed via umbrella chart, you can set a global image registry: + +```yaml +global: + image: + registry: "quay.io/my-org" # Overrides all subchart image registries + +pull-secret: + enabled: true + gcp: + projectId: "my-project" + cluster: + id: "my-cluster" +``` + ## Configuration The following table lists the configurable parameters: | Parameter | Description | Default | |-----------|-------------|---------| -| `namespace` | Kubernetes namespace | `hyperfleet-system` | -| `job.name` | Job name | `pullsecret-job` | -| `job.backoffLimit` | Number of retries on failure | `3` | -| `job.ttlSecondsAfterFinished` | Cleanup delay after completion | `3600` (1 hour) | -| `image.repository` | Container image repository | `quay.io/hyperfleet/pull-secret` | +| `global.image.registry` | Global image registry override (umbrella chart) | `""` | +| `image.registry` | Container image registry | `quay.io/openshift-hyperfleet` | +| `image.repository` | Container image repository | `pull-secret` | | `image.tag` | Container image tag | `latest` | | `image.pullPolicy` | Image pull policy | `Always` | -| `serviceAccount.name` | Kubernetes ServiceAccount name | `pullsecret-adapter` | -| `serviceAccount.gcpServiceAccount` | GCP service account for Workload Identity | `your-service-account@your-project.iam.gserviceaccount.com` | -| `env.gcpProjectId` | GCP project ID | `your-gcp-project` | -| `env.clusterId` | Cluster identifier | `your-cluster-id` | -| `env.secretName` | Secret name in GCP Secret Manager | `hyperfleet-your-cluster-id-pull-secret` | -| `env.pullSecretData` | Pull secret JSON data (required) | `{"auths":{...}}` | +| `serviceAccount.create` | Create ServiceAccount | `true` | +| `serviceAccount.name` | Kubernetes ServiceAccount name | `""` (auto-generated) | +| `serviceAccount.annotations` | ServiceAccount annotations (for Workload Identity) | `{}` | +| `rbac.create` | Create RBAC resources | `true` | +| `job.name` | Job name | `""` (auto-generated) | +| `job.backoffLimit` | Number of retries on failure | `3` | +| `job.ttlSecondsAfterFinished` | Cleanup delay after completion | `3600` (1 hour) | +| `gcp.projectId` | GCP project ID | `""` | +| `cluster.id` | Cluster identifier | `""` | +| `pullSecret.name` | Secret name in GCP Secret Manager | Auto-generated | +| `pullSecret.data` | Pull secret JSON data (required) | `""` | | `resources.requests.cpu` | CPU request | `100m` | | `resources.requests.memory` | Memory request | `128Mi` | | `resources.limits.cpu` | CPU limit | `500m` | @@ -191,3 +232,4 @@ helm package ./charts/pull-secret - [Helm Documentation](https://helm.sh/docs/) - [GKE Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) - [Kubernetes Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/job/) +- [HyperFleet Chart](https://github.com/openshift-hyperfleet/hyperfleet-chart) diff --git a/charts/pull-secret/templates/NOTES.txt b/charts/pull-secret/templates/NOTES.txt new file mode 100644 index 0000000..b81485b --- /dev/null +++ b/charts/pull-secret/templates/NOTES.txt @@ -0,0 +1,63 @@ + +=============================================================================== + HyperFleet Pull Secret Adapter +=============================================================================== + +The pull-secret adapter has been deployed as a Kubernetes Job. + +Release Name: {{ .Release.Name }} +Namespace: {{ .Release.Namespace }} +Job Name: {{ include "pull-secret.jobName" . }} + +------------------------------------------------------------------------------- +CONFIGURATION +------------------------------------------------------------------------------- +GCP Project: {{ .Values.gcp.projectId | default "NOT SET - REQUIRED!" }} +Cluster ID: {{ .Values.cluster.id | default "NOT SET - REQUIRED!" }} +Secret Name: {{ include "pull-secret.secretName" . }} +Image: {{ include "pull-secret.image" . }} + +------------------------------------------------------------------------------- +MONITORING +------------------------------------------------------------------------------- +Check job status: + kubectl get job {{ include "pull-secret.jobName" . }} -n {{ .Release.Namespace }} + +View job logs: + kubectl logs -f job/{{ include "pull-secret.jobName" . }} -n {{ .Release.Namespace }} + +Get job details: + kubectl describe job {{ include "pull-secret.jobName" . }} -n {{ .Release.Namespace }} + +------------------------------------------------------------------------------- +CLEANUP +------------------------------------------------------------------------------- +The job will be automatically cleaned up {{ .Values.job.ttlSecondsAfterFinished }} seconds after completion. + +To manually uninstall: + helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} + +------------------------------------------------------------------------------- +TROUBLESHOOTING +------------------------------------------------------------------------------- +{{- if not .Values.gcp.projectId }} +WARNING: gcp.projectId is not set! The job will fail. + Set it using: --set gcp.projectId=YOUR_PROJECT_ID +{{- end }} +{{- if not .Values.cluster.id }} +WARNING: cluster.id is not set! The job will fail. + Set it using: --set cluster.id=YOUR_CLUSTER_ID +{{- end }} +{{- if not .Values.pullSecret.data }} +WARNING: pullSecret.data is not set! The job will fail. + Set it using: --set pullSecret.data='{"auths":{...}}' +{{- end }} + +For Workload Identity issues, ensure: +1. GCP service account has Secret Manager permissions +2. Kubernetes SA is annotated with GCP SA: + kubectl annotate sa {{ include "pull-secret.serviceAccountName" . }} \ + iam.gke.io/gcp-service-account=GSA_NAME@PROJECT.iam.gserviceaccount.com \ + -n {{ .Release.Namespace }} + +=============================================================================== diff --git a/charts/pull-secret/templates/_helpers.tpl b/charts/pull-secret/templates/_helpers.tpl index f64b21c..c7ce8b6 100644 --- a/charts/pull-secret/templates/_helpers.tpl +++ b/charts/pull-secret/templates/_helpers.tpl @@ -7,6 +7,8 @@ Expand the name of the chart. {{/* Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. */}} {{- define "pull-secret.fullname" -}} {{- if .Values.fullnameOverride }} @@ -38,6 +40,8 @@ helm.sh/chart: {{ include "pull-secret.chart" . }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/component: adapter +hyperfleet.io/adapter-type: pull-secret {{- end }} {{/* @@ -46,12 +50,50 @@ Selector labels {{- define "pull-secret.selectorLabels" -}} app.kubernetes.io/name: {{ include "pull-secret.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} -app: {{ .Values.labels.app }} {{- end }} {{/* Create the name of the service account to use */}} {{- define "pull-secret.serviceAccountName" -}} -{{- default "pullsecret-adapter" .Values.serviceAccount.name }} +{{- if .Values.serviceAccount.create }} +{{- default (include "pull-secret.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the name of the job +*/}} +{{- define "pull-secret.jobName" -}} +{{- default (include "pull-secret.fullname" .) .Values.job.name }} +{{- end }} + +{{/* +Create the image reference with global override support. +Global image registry takes precedence over local registry. +*/}} +{{- define "pull-secret.image" -}} +{{- $registry := .Values.image.registry }} +{{- if .Values.global }} +{{- if .Values.global.image }} +{{- if .Values.global.image.registry }} +{{- $registry = .Values.global.image.registry }} +{{- end }} +{{- end }} +{{- end }} +{{- printf "%s/%s:%s" $registry .Values.image.repository (.Values.image.tag | default .Chart.AppVersion) }} +{{- end }} + +{{/* +Create the secret name in GCP Secret Manager +Auto-generates as: hyperfleet-{cluster.id}-pull-secret if not provided +*/}} +{{- define "pull-secret.secretName" -}} +{{- if .Values.pullSecret.name }} +{{- .Values.pullSecret.name }} +{{- else }} +{{- printf "hyperfleet-%s-pull-secret" .Values.cluster.id }} +{{- end }} {{- end }} diff --git a/charts/pull-secret/templates/job.yaml b/charts/pull-secret/templates/job.yaml index 4c19d49..20b1243 100644 --- a/charts/pull-secret/templates/job.yaml +++ b/charts/pull-secret/templates/job.yaml @@ -1,11 +1,10 @@ apiVersion: batch/v1 kind: Job metadata: - name: {{ .Values.job.name }} - namespace: {{ .Values.namespace }} + name: {{ include "pull-secret.jobName" . }} labels: {{- include "pull-secret.labels" . | nindent 4 }} - job-type: {{ .Values.labels.jobType }} + job-type: pull-secret spec: backoffLimit: {{ .Values.job.backoffLimit }} ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} @@ -13,23 +12,74 @@ spec: metadata: labels: {{- include "pull-secret.selectorLabels" . | nindent 8 }} + job-type: pull-secret spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} serviceAccountName: {{ include "pull-secret.serviceAccountName" . }} restartPolicy: {{ .Values.job.restartPolicy }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - - name: pull-secret - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - env: - - name: GCP_PROJECT_ID - value: {{ .Values.env.gcpProjectId | quote }} - - name: CLUSTER_ID - value: {{ .Values.env.clusterId | quote }} - - name: SECRET_NAME - value: {{ .Values.env.secretName | quote }} - - name: PULL_SECRET_DATA - value: {{ .Values.env.pullSecretData | quote }} - resources: - {{- toYaml .Values.resources | nindent 10 }} - securityContext: - {{- toYaml .Values.securityContext | nindent 10 }} + - name: pull-secret + image: {{ include "pull-secret.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /usr/local/bin/pull-secret + args: + - run-job + - pull-secret + env: + - name: GCP_PROJECT_ID + value: {{ .Values.gcp.projectId | quote }} + - name: CLUSTER_ID + value: {{ .Values.cluster.id | quote }} + - name: SECRET_NAME + value: {{ include "pull-secret.secretName" . | quote }} + {{- if .Values.pullSecret.data }} + - name: PULL_SECRET_DATA + value: {{ .Values.pullSecret.data | quote }} + {{- end }} + {{- if .Values.hyperfleetApi.baseUrl }} + - name: HYPERFLEET_API_BASE_URL + value: {{ .Values.hyperfleetApi.baseUrl | quote }} + {{- end }} + - name: HYPERFLEET_API_VERSION + value: {{ .Values.hyperfleetApi.version | default "v1" | quote }} + {{- with .Values.env }} + {{- range . }} + - name: {{ .name }} + {{- if .value }} + value: {{ .value | quote }} + {{- else if .valueFrom }} + valueFrom: + {{- toYaml .valueFrom | nindent 16 }} + {{- end }} + {{- end }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/pull-secret/templates/rbac.yaml b/charts/pull-secret/templates/rbac.yaml new file mode 100644 index 0000000..e63de0b --- /dev/null +++ b/charts/pull-secret/templates/rbac.yaml @@ -0,0 +1,36 @@ +{{- if .Values.rbac.create }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "pull-secret.fullname" . }} + labels: + {{- include "pull-secret.labels" . | nindent 4 }} +rules: + # Secret management permissions - required for pull secret operations + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "patch"] + # Namespace access - read only for listing available namespaces + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list", "watch"] + {{- with .Values.rbac.rules }} + {{- toYaml . | nindent 2 }} + {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "pull-secret.fullname" . }} + labels: + {{- include "pull-secret.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "pull-secret.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "pull-secret.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/pull-secret/templates/serviceaccount.yaml b/charts/pull-secret/templates/serviceaccount.yaml index 70a85aa..fdfa79d 100644 --- a/charts/pull-secret/templates/serviceaccount.yaml +++ b/charts/pull-secret/templates/serviceaccount.yaml @@ -1,9 +1,12 @@ +{{- if .Values.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "pull-secret.serviceAccountName" . }} - namespace: {{ .Values.namespace }} labels: {{- include "pull-secret.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} annotations: - iam.gke.io/gcp-service-account: {{ .Values.serviceAccount.gcpServiceAccount }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/pull-secret/values.yaml b/charts/pull-secret/values.yaml index 326db90..14a4a97 100644 --- a/charts/pull-secret/values.yaml +++ b/charts/pull-secret/values.yaml @@ -1,57 +1,109 @@ -# Default values for pull-secret adapter +# Pull Secret Adapter - Helm Values +# +# This adapter manages pull secrets in GCP Secret Manager for HyperFleet clusters. +# It runs as a Kubernetes Job that stores/updates pull secrets. +# +# For umbrella chart integration, set global.image.registry to override image registry. -# Namespace where the job will be deployed -namespace: hyperfleet-system +# ============================================================================= +# Global Overrides (for umbrella chart integration) +# ============================================================================= +# When deployed as part of hyperfleet-chart umbrella, these can be set globally +global: + image: + registry: "" # Override registry for all images (e.g., "quay.io/croche") -# Job configuration -job: - # Name of the Kubernetes Job - name: pullsecret-job +# ============================================================================= +# Chart Identity +# ============================================================================= +nameOverride: "" +fullnameOverride: "" + +# ============================================================================= +# Image Configuration +# ============================================================================= +image: + registry: quay.io/openshift-hyperfleet + repository: pull-secret + tag: "latest" + pullPolicy: Always + +imagePullSecrets: [] +# ============================================================================= +# ServiceAccount Configuration +# ============================================================================= +serviceAccount: + # Create a ServiceAccount for the job + create: true + # Name of the ServiceAccount (auto-generated if empty) + name: "" + # Annotations for Workload Identity + # For GKE: iam.gke.io/gcp-service-account: GSA_NAME@PROJECT_ID.iam.gserviceaccount.com + annotations: {} + +# ============================================================================= +# RBAC Configuration +# ============================================================================= +rbac: + # Create RBAC resources (ClusterRole and ClusterRoleBinding) + create: true + # Additional rules to add to the ClusterRole + rules: [] + +# ============================================================================= +# Job Configuration +# ============================================================================= +job: + # Name override for the job (auto-generated if empty) + name: "" # Number of times to retry the job on failure backoffLimit: 3 - # Time in seconds after job completion before cleanup (1 hour) ttlSecondsAfterFinished: 3600 - # Job restart policy restartPolicy: Never -# Image configuration -image: - repository: quay.io/hyperfleet/pull-secret - tag: latest - pullPolicy: Always - -# ServiceAccount configuration -serviceAccount: - # Name of the Kubernetes ServiceAccount - name: pullsecret-adapter - - # GCP service account email for Workload Identity - # IMPORTANT: Replace with your actual GCP service account - gcpServiceAccount: your-service-account@your-project.iam.gserviceaccount.com - -# Environment variables -env: +# ============================================================================= +# GCP Configuration +# ============================================================================= +gcp: # GCP project ID where secrets will be stored - # IMPORTANT: Replace with your actual GCP project ID - gcpProjectId: "your-gcp-project" + # REQUIRED: Must be set before deployment + projectId: "" - # Cluster identifier - # IMPORTANT: Replace with your actual cluster ID - clusterId: "your-cluster-id" +# ============================================================================= +# Cluster Configuration +# ============================================================================= +cluster: + # Cluster identifier - used to name the secret in GCP + # REQUIRED: Must be set before deployment + id: "" - # Secret name in GCP Secret Manager (auto-derived if not provided) - # This will be auto-generated as: hyperfleet-{clusterId}-pull-secret - secretName: "hyperfleet-your-cluster-id-pull-secret" +# ============================================================================= +# Pull Secret Configuration +# ============================================================================= +pullSecret: + # Secret name in GCP Secret Manager + # Auto-generated as: hyperfleet-{cluster.id}-pull-secret if empty + name: "" + # Pull secret JSON data + # REQUIRED: Must be set before deployment + # Example: '{"auths":{"registry.example.com":{"auth":"...","email":"user@example.com"}}}' + data: "" - # Pull secret JSON data (must be provided) - # IMPORTANT: Replace with your actual pull secret data - # This is a dummy example - use your real OpenShift pull secret - pullSecretData: '{"auths":{"registry.example.com":{"auth":"ZXhhbXBsZTpleGFtcGxl","email":"user@example.com"}}}' +# ============================================================================= +# HyperFleet API Configuration (optional) +# ============================================================================= +hyperfleetApi: + # Base URL for HyperFleet API (for status reporting) + baseUrl: "" + # API version + version: "v1" -# Resource limits +# ============================================================================= +# Resource Limits +# ============================================================================= resources: requests: cpu: 100m @@ -60,17 +112,43 @@ resources: cpu: 500m memory: 512Mi -# Security context +# ============================================================================= +# Security Context +# ============================================================================= securityContext: runAsNonRoot: true runAsUser: 1000 allowPrivilegeEscalation: false readOnlyRootFilesystem: true + seccompProfile: + type: RuntimeDefault capabilities: drop: - ALL -# Labels -labels: - app: pullsecret-adapter - jobType: pull-secret +# ============================================================================= +# Pod Security Context +# ============================================================================= +podSecurityContext: + fsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + +# ============================================================================= +# Platform-specific Scheduling +# ============================================================================= +nodeSelector: {} +tolerations: [] +affinity: {} + +# ============================================================================= +# Additional Environment Variables +# ============================================================================= +env: [] + # - name: MY_VAR + # value: "my-value" + # - name: MY_SECRET + # valueFrom: + # secretKeyRef: + # name: my-secret + # key: key From d78ca014c107262a44481ff638def9ac925b22e2 Mon Sep 17 00:00:00 2001 From: Tongtong Zhou Date: Tue, 20 Jan 2026 15:25:29 +0800 Subject: [PATCH 2/4] Address CodeRabbit review feedback --- charts/pull-secret/README.md | 4 ++-- charts/pull-secret/templates/_helpers.tpl | 2 ++ charts/pull-secret/templates/job.yaml | 8 +++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/charts/pull-secret/README.md b/charts/pull-secret/README.md index dc93bd4..78011a7 100644 --- a/charts/pull-secret/README.md +++ b/charts/pull-secret/README.md @@ -131,8 +131,8 @@ The following table lists the configurable parameters: | `job.ttlSecondsAfterFinished` | Cleanup delay after completion | `3600` (1 hour) | | `gcp.projectId` | GCP project ID | `""` | | `cluster.id` | Cluster identifier | `""` | -| `pullSecret.name` | Secret name in GCP Secret Manager | Auto-generated | -| `pullSecret.data` | Pull secret JSON data (required) | `""` | +| `pullSecret.name` | Secret name in GCP Secret Manager | `hyperfleet-{cluster.id}-pull-secret` | +| `pullSecret.data` | Pull secret JSON data (**required**) | `""` | | `resources.requests.cpu` | CPU request | `100m` | | `resources.requests.memory` | Memory request | `128Mi` | | `resources.limits.cpu` | CPU limit | `500m` | diff --git a/charts/pull-secret/templates/_helpers.tpl b/charts/pull-secret/templates/_helpers.tpl index c7ce8b6..b510b0d 100644 --- a/charts/pull-secret/templates/_helpers.tpl +++ b/charts/pull-secret/templates/_helpers.tpl @@ -58,6 +58,8 @@ Create the name of the service account to use {{- define "pull-secret.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "pull-secret.fullname" .) .Values.serviceAccount.name }} +{{- else if .Values.rbac.create }} +{{- required "serviceAccount.name must be set when serviceAccount.create=false and rbac.create=true" .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} diff --git a/charts/pull-secret/templates/job.yaml b/charts/pull-secret/templates/job.yaml index 20b1243..9dfd073 100644 --- a/charts/pull-secret/templates/job.yaml +++ b/charts/pull-secret/templates/job.yaml @@ -35,15 +35,13 @@ spec: - pull-secret env: - name: GCP_PROJECT_ID - value: {{ .Values.gcp.projectId | quote }} + value: {{ required "gcp.projectId is required" .Values.gcp.projectId | quote }} - name: CLUSTER_ID - value: {{ .Values.cluster.id | quote }} + value: {{ required "cluster.id is required" .Values.cluster.id | quote }} - name: SECRET_NAME value: {{ include "pull-secret.secretName" . | quote }} - {{- if .Values.pullSecret.data }} - name: PULL_SECRET_DATA - value: {{ .Values.pullSecret.data | quote }} - {{- end }} + value: {{ required "pullSecret.data is required" .Values.pullSecret.data | quote }} {{- if .Values.hyperfleetApi.baseUrl }} - name: HYPERFLEET_API_BASE_URL value: {{ .Values.hyperfleetApi.baseUrl | quote }} From c64d8ebb04b4db0e1cf60f1ec158208ab62ba5ab Mon Sep 17 00:00:00 2001 From: Tongtong Zhou Date: Fri, 23 Jan 2026 14:50:56 +0800 Subject: [PATCH 3/4] Address CodeRabbit review: improve README documentation --- charts/pull-secret/README.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/charts/pull-secret/README.md b/charts/pull-secret/README.md index 78011a7..40d90dd 100644 --- a/charts/pull-secret/README.md +++ b/charts/pull-secret/README.md @@ -37,15 +37,18 @@ helm install pullsecret-job ./charts/pull-secret \ Deploy with custom configuration: ```bash +# Create a pull-secret.json file with your credentials first helm install pullsecret-job ./charts/pull-secret \ --namespace hyperfleet-system \ --create-namespace \ --set gcp.projectId=my-project \ --set cluster.id=my-cluster-123 \ - --set pullSecret.data='{"auths":{...}}' \ + --set-file pullSecret.data=./pull-secret.json \ --set image.tag=latest ``` +> **Note**: Use `--set-file` for `pullSecret.data` to avoid exposing sensitive data in shell history. + ### Using a Values File Create a custom values file (`my-values.yaml`): @@ -118,25 +121,38 @@ The following table lists the configurable parameters: | Parameter | Description | Default | |-----------|-------------|---------| | `global.image.registry` | Global image registry override (umbrella chart) | `""` | +| `nameOverride` | Override chart name | `""` | +| `fullnameOverride` | Override full release name | `""` | | `image.registry` | Container image registry | `quay.io/openshift-hyperfleet` | | `image.repository` | Container image repository | `pull-secret` | | `image.tag` | Container image tag | `latest` | | `image.pullPolicy` | Image pull policy | `Always` | +| `imagePullSecrets` | Image pull secrets | `[]` | | `serviceAccount.create` | Create ServiceAccount | `true` | | `serviceAccount.name` | Kubernetes ServiceAccount name | `""` (auto-generated) | | `serviceAccount.annotations` | ServiceAccount annotations (for Workload Identity) | `{}` | | `rbac.create` | Create RBAC resources | `true` | +| `rbac.rules` | Additional RBAC rules | `[]` | | `job.name` | Job name | `""` (auto-generated) | | `job.backoffLimit` | Number of retries on failure | `3` | | `job.ttlSecondsAfterFinished` | Cleanup delay after completion | `3600` (1 hour) | -| `gcp.projectId` | GCP project ID | `""` | -| `cluster.id` | Cluster identifier | `""` | +| `job.restartPolicy` | Job restart policy | `Never` | +| `gcp.projectId` | GCP project ID (**required**) | `""` | +| `cluster.id` | Cluster identifier (**required**) | `""` | | `pullSecret.name` | Secret name in GCP Secret Manager | `hyperfleet-{cluster.id}-pull-secret` | | `pullSecret.data` | Pull secret JSON data (**required**) | `""` | +| `hyperfleetApi.baseUrl` | HyperFleet API base URL | `""` | +| `hyperfleetApi.version` | HyperFleet API version | `v1` | | `resources.requests.cpu` | CPU request | `100m` | | `resources.requests.memory` | Memory request | `128Mi` | | `resources.limits.cpu` | CPU limit | `500m` | | `resources.limits.memory` | Memory limit | `512Mi` | +| `securityContext` | Container security context | See values.yaml | +| `podSecurityContext` | Pod security context | See values.yaml | +| `nodeSelector` | Node selector for pod scheduling | `{}` | +| `tolerations` | Tolerations for pod scheduling | `[]` | +| `affinity` | Affinity rules for pod scheduling | `{}` | +| `env` | Additional environment variables | `[]` | ## Usage From bd7e77a2ab602a475d5923f2010c449b4d84a668 Mon Sep 17 00:00:00 2001 From: Tongtong Zhou Date: Fri, 23 Jan 2026 21:51:58 +0800 Subject: [PATCH 4/4] HYPERFLEET-479 - Refactor Helm chart to use Adapter Framework pattern --- charts/pull-secret/README.md | 211 +++++++++++------- .../configs/pull-secret-adapter.yaml | 105 +++++++++ .../configs/pull-secret-job-adapter-task.yaml | 132 +++++++++++ charts/pull-secret/templates/NOTES.txt | 128 ++++++----- charts/pull-secret/templates/_helpers.tpl | 36 ++- .../pull-secret/templates/configmap-app.yaml | 11 + .../templates/configmap-broker.yaml | 30 +++ charts/pull-secret/templates/deployment.yaml | 141 ++++++++++++ charts/pull-secret/templates/job.yaml | 83 ------- charts/pull-secret/templates/rbac.yaml | 38 +++- charts/pull-secret/values.yaml | 127 +++++------ 11 files changed, 729 insertions(+), 313 deletions(-) create mode 100644 charts/pull-secret/configs/pull-secret-adapter.yaml create mode 100644 charts/pull-secret/configs/pull-secret-job-adapter-task.yaml create mode 100644 charts/pull-secret/templates/configmap-app.yaml create mode 100644 charts/pull-secret/templates/configmap-broker.yaml create mode 100644 charts/pull-secret/templates/deployment.yaml delete mode 100644 charts/pull-secret/templates/job.yaml diff --git a/charts/pull-secret/README.md b/charts/pull-secret/README.md index 40d90dd..6ead2e5 100644 --- a/charts/pull-secret/README.md +++ b/charts/pull-secret/README.md @@ -1,6 +1,27 @@ # Pull Secret Adapter Helm Chart -This Helm chart deploys the HyperFleet Pull Secret Adapter as a Kubernetes Job on GKE. +This Helm chart deploys the HyperFleet Pull Secret Adapter using the Adapter Framework pattern. The adapter listens to PubSub messages and dynamically creates jobs to store pull secrets in GCP Secret Manager. + +## Architecture + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Adapter Framework (Deployment) │ +│ - Listens to PubSub messages │ +│ - Creates Jobs based on AdapterConfig │ +└────────────────────────┬─────────────────────────────────────────┘ + │ + ▼ (creates dynamically) +┌──────────────────────────────────────────────────────────────────┐ +│ Pull Secret Job (per cluster) │ +│ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ pull-secret │ │ status-reporter │ │ +│ │ (main container) │ │ (sidecar) │ │ +│ └─────────────────────┘ └─────────────────────┘ │ +│ │ │ │ +│ └───── shared volume ─────┘ │ +└──────────────────────────────────────────────────────────────────┘ +``` ## Prerequisites @@ -16,20 +37,28 @@ This Helm chart deploys the HyperFleet Pull Secret Adapter as a Kubernetes Job o --project=YOUR_PROJECT_ID ``` -3. **Workload Identity configured** - - Service Account: `your-service-account@your-project.iam.gserviceaccount.com` - - Workload Pool: `your-project.svc.id.goog` +3. **GCP Pub/Sub configured** + - Topic for adapter messages + - Subscription for the adapter + +4. **Workload Identity configured** + - Using `principalSet://` for Workload Identity Federation + - No ServiceAccount annotation required (modern approach) ## Installation ### Quick Start -Deploy with default values: +Deploy with required Pub/Sub configuration: ```bash -helm install pullsecret-job ./charts/pull-secret \ +helm install pull-secret-adapter ./charts/pull-secret \ --namespace hyperfleet-system \ - --create-namespace + --create-namespace \ + --set broker.googlepubsub.projectId=my-project \ + --set broker.googlepubsub.topic=hyperfleet-events \ + --set broker.googlepubsub.subscription=pull-secret-adapter-sub \ + --set hyperfleetApi.baseUrl=https://api.hyperfleet.example.com ``` ### Custom Values @@ -37,45 +66,41 @@ helm install pullsecret-job ./charts/pull-secret \ Deploy with custom configuration: ```bash -# Create a pull-secret.json file with your credentials first -helm install pullsecret-job ./charts/pull-secret \ +helm install pull-secret-adapter ./charts/pull-secret \ --namespace hyperfleet-system \ --create-namespace \ - --set gcp.projectId=my-project \ - --set cluster.id=my-cluster-123 \ - --set-file pullSecret.data=./pull-secret.json \ - --set image.tag=latest + --set broker.googlepubsub.projectId=my-project \ + --set broker.googlepubsub.topic=hyperfleet-events \ + --set broker.googlepubsub.subscription=pull-secret-adapter-sub \ + --set hyperfleetApi.baseUrl=https://api.hyperfleet.example.com \ + --set pullSecretAdapter.image.tag=v1.0.0 ``` -> **Note**: Use `--set-file` for `pullSecret.data` to avoid exposing sensitive data in shell history. - ### Using a Values File Create a custom values file (`my-values.yaml`): ```yaml -gcp: - projectId: "my-gcp-project" - -cluster: - id: "my-cluster-123" - -pullSecret: - name: "hyperfleet-my-cluster-123-pull-secret" - data: '{"auths":{"registry.example.com":{"auth":"...","email":"user@example.com"}}}' - -serviceAccount: - annotations: - iam.gke.io/gcp-service-account: "my-service-account@my-project.iam.gserviceaccount.com" - -image: - tag: "v1.0.0" +broker: + type: "googlepubsub" + googlepubsub: + projectId: "my-gcp-project" + topic: "hyperfleet-events" + subscription: "pull-secret-adapter-sub" + +hyperfleetApi: + baseUrl: "https://api.hyperfleet.example.com" + version: "v1" + +pullSecretAdapter: + image: + tag: "v1.0.0" ``` Then install: ```bash -helm install pullsecret-job ./charts/pull-secret \ +helm install pull-secret-adapter ./charts/pull-secret \ --namespace hyperfleet-system \ --create-namespace \ -f my-values.yaml @@ -108,10 +133,11 @@ global: pull-secret: enabled: true - gcp: - projectId: "my-project" - cluster: - id: "my-cluster" + broker: + googlepubsub: + projectId: "my-project" + topic: "hyperfleet-events" + subscription: "pull-secret-adapter-sub" ``` ## Configuration @@ -120,76 +146,100 @@ The following table lists the configurable parameters: | Parameter | Description | Default | |-----------|-------------|---------| -| `global.image.registry` | Global image registry override (umbrella chart) | `""` | +| **Global** | | | +| `global.image.registry` | Global image registry override | `""` | | `nameOverride` | Override chart name | `""` | | `fullnameOverride` | Override full release name | `""` | -| `image.registry` | Container image registry | `quay.io/openshift-hyperfleet` | -| `image.repository` | Container image repository | `pull-secret` | -| `image.tag` | Container image tag | `latest` | +| `replicaCount` | Number of adapter framework replicas | `1` | +| **Adapter Framework Image** | | | +| `image.registry` | Adapter framework image registry | `registry.ci.openshift.org` | +| `image.repository` | Adapter framework image repository | `ci/hyperfleet-adapter` | +| `image.tag` | Adapter framework image tag | `latest` | | `image.pullPolicy` | Image pull policy | `Always` | | `imagePullSecrets` | Image pull secrets | `[]` | +| **ServiceAccount** | | | | `serviceAccount.create` | Create ServiceAccount | `true` | -| `serviceAccount.name` | Kubernetes ServiceAccount name | `""` (auto-generated) | -| `serviceAccount.annotations` | ServiceAccount annotations (for Workload Identity) | `{}` | +| `serviceAccount.name` | ServiceAccount name | `""` (auto-generated) | +| `serviceAccount.annotations` | ServiceAccount annotations | `{}` | +| **RBAC** | | | | `rbac.create` | Create RBAC resources | `true` | -| `rbac.rules` | Additional RBAC rules | `[]` | -| `job.name` | Job name | `""` (auto-generated) | -| `job.backoffLimit` | Number of retries on failure | `3` | -| `job.ttlSecondsAfterFinished` | Cleanup delay after completion | `3600` (1 hour) | -| `job.restartPolicy` | Job restart policy | `Never` | -| `gcp.projectId` | GCP project ID (**required**) | `""` | -| `cluster.id` | Cluster identifier (**required**) | `""` | -| `pullSecret.name` | Secret name in GCP Secret Manager | `hyperfleet-{cluster.id}-pull-secret` | -| `pullSecret.data` | Pull secret JSON data (**required**) | `""` | +| **Logging** | | | +| `logging.level` | Log level (debug, info, warn, error) | `info` | +| `logging.format` | Log format (text, json) | `text` | +| `logging.output` | Log output (stdout, stderr) | `stderr` | +| **Broker** | | | +| `broker.type` | Broker type (googlepubsub, rabbitmq) | `googlepubsub` | +| `broker.googlepubsub.projectId` | GCP project ID for Pub/Sub | `""` | +| `broker.googlepubsub.topic` | Pub/Sub topic name | `""` | +| `broker.googlepubsub.subscription` | Pub/Sub subscription name | `""` | +| `broker.googlepubsub.deadLetterTopic` | Dead letter topic (optional) | `""` | +| `broker.subscriber.parallelism` | Message processing parallelism | `1` | +| **HyperFleet API** | | | | `hyperfleetApi.baseUrl` | HyperFleet API base URL | `""` | | `hyperfleetApi.version` | HyperFleet API version | `v1` | -| `resources.requests.cpu` | CPU request | `100m` | -| `resources.requests.memory` | Memory request | `128Mi` | -| `resources.limits.cpu` | CPU limit | `500m` | -| `resources.limits.memory` | Memory limit | `512Mi` | -| `securityContext` | Container security context | See values.yaml | -| `podSecurityContext` | Pod security context | See values.yaml | -| `nodeSelector` | Node selector for pod scheduling | `{}` | -| `tolerations` | Tolerations for pod scheduling | `[]` | -| `affinity` | Affinity rules for pod scheduling | `{}` | +| **Pull Secret Adapter** | | | +| `pullSecretAdapter.image.registry` | Job container image registry | `quay.io/openshift-hyperfleet` | +| `pullSecretAdapter.image.repository` | Job container image repository | `pull-secret` | +| `pullSecretAdapter.image.tag` | Job container image tag | `latest` | +| `pullSecretAdapter.statusReporterImage` | Status reporter sidecar image | `registry.ci.openshift.org/ci/status-reporter:latest` | +| `pullSecretAdapter.resultsPath` | Shared result path | `/results/adapter-result.json` | +| `pullSecretAdapter.maxWaitTimeSeconds` | Max job wait time | `300` | +| `pullSecretAdapter.logLevel` | Job container log level | `info` | +| **Scheduling** | | | +| `nodeSelector` | Node selector | `{}` | +| `tolerations` | Tolerations | `[]` | +| `affinity` | Affinity rules | `{}` | | `env` | Additional environment variables | `[]` | +## How It Works + +1. **PubSub Message**: HyperFleet sends a message with cluster info (`clusterId`, `projectId`, `pullSecretData`) +2. **Adapter Framework**: Receives the message and creates a Job based on the AdapterConfig +3. **Pull Secret Job**: Stores the pull secret in GCP Secret Manager +4. **Status Reporter**: Reports job status back to HyperFleet API + ## Usage ### Monitoring -Check job status: +Check deployment status: +```bash +helm status pull-secret-adapter -n hyperfleet-system +kubectl get deployment -n hyperfleet-system +kubectl get pods -n hyperfleet-system +``` + +View adapter logs: ```bash -helm status pullsecret-job -n hyperfleet-system -kubectl get job pullsecret-job -n hyperfleet-system +kubectl logs -f deployment/pull-secret-adapter -n hyperfleet-system ``` -View logs: +View job logs (for a specific cluster): ```bash -kubectl logs -f job/pullsecret-job -n hyperfleet-system +kubectl logs -f job/pull-secret-- -n ``` ### Upgrading -Upgrade the deployment with new values: +Upgrade the deployment: ```bash -helm upgrade pullsecret-job ./charts/pull-secret \ +helm upgrade pull-secret-adapter ./charts/pull-secret \ --namespace hyperfleet-system \ - --set image.tag=v1.1.0 + --set pullSecretAdapter.image.tag=v1.1.0 ``` ### Uninstalling -Remove the job: +Remove the adapter: ```bash -helm uninstall pullsecret-job -n hyperfleet-system +helm uninstall pull-secret-adapter -n hyperfleet-system ``` ## Dry Run Mode -Test without creating secrets: +Test without deploying: ```bash -helm install pullsecret-job ./charts/pull-secret \ +helm install pull-secret-adapter ./charts/pull-secret \ --namespace hyperfleet-system \ --dry-run --debug ``` @@ -198,26 +248,26 @@ helm install pullsecret-job ./charts/pull-secret \ ### View rendered templates ```bash -helm template pullsecret-job ./charts/pull-secret +helm template pull-secret-adapter ./charts/pull-secret ``` ### Check deployment issues ```bash -kubectl describe job pullsecret-job -n hyperfleet-system +kubectl describe deployment pull-secret-adapter -n hyperfleet-system kubectl get events -n hyperfleet-system --sort-by='.lastTimestamp' ``` ### Authentication errors -Verify Workload Identity binding: +Verify Workload Identity: ```bash # Check ServiceAccount -kubectl get sa pullsecret-adapter -n hyperfleet-system -o yaml +kubectl get sa -n hyperfleet-system -# Check GCP IAM binding -gcloud iam service-accounts get-iam-policy \ - your-service-account@your-project.iam.gserviceaccount.com \ - --project=your-project +# Verify IAM binding (using principalSet) +gcloud projects get-iam-policy YOUR_PROJECT_ID \ + --flatten="bindings[].members" \ + --filter="bindings.members:principalSet://" ``` ## Development @@ -249,3 +299,4 @@ helm package ./charts/pull-secret - [GKE Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) - [Kubernetes Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/job/) - [HyperFleet Chart](https://github.com/openshift-hyperfleet/hyperfleet-chart) +- [Adapter Framework Pattern](https://github.com/openshift-hyperfleet/adapter-validation-gcp) diff --git a/charts/pull-secret/configs/pull-secret-adapter.yaml b/charts/pull-secret/configs/pull-secret-adapter.yaml new file mode 100644 index 0000000..4ff6f79 --- /dev/null +++ b/charts/pull-secret/configs/pull-secret-adapter.yaml @@ -0,0 +1,105 @@ +# HyperFleet Pull Secret Adapter Configuration +# +# This adapter creates a job to store pull secrets in GCP Secret Manager +# for HyperFleet clusters. +apiVersion: hyperfleet.redhat.com/v1alpha1 +kind: AdapterConfig +metadata: + name: pull-secret-adapter + namespace: hyperfleet-system + labels: + hyperfleet.io/adapter-type: pull-secret + hyperfleet.io/component: adapter + hyperfleet.io/provider: gcp +spec: + adapter: + version: "0.1.0" + # ============================================================================ + # HyperFleet API Configuration + # ============================================================================ + hyperfleetApi: + timeout: 2s + retryAttempts: 3 + # ============================================================================ + # Kubernetes Configuration + # ============================================================================ + kubernetes: + apiVersion: "batch/v1" + # ============================================================================ + # Parameters + # ============================================================================ + params: + - name: "hyperfleetApiBaseUrl" + source: "env.HYPERFLEET_API_BASE_URL" + type: "string" + description: "Base URL for the HyperFleet API" + required: true + - name: "hyperfleetApiVersion" + source: "env.HYPERFLEET_API_VERSION" + type: "string" + default: "v1" + description: "API version to use" + - name: "clusterId" + source: "event.id" + type: "string" + description: "Unique identifier for the target cluster" + required: true + - name: "projectId" + source: "event.projectId" + type: "string" + description: "GCP project ID where secrets will be stored" + required: true + - name: "statusReporterImage" + source: "env.STATUS_REPORTER_IMAGE" + type: "string" + default: "registry.ci.openshift.org/ci/status-reporter:latest" + description: "Container image for the status reporter" + # Pull Secret Adapter configuration + - name: "pullSecretImage" + source: "env.PULL_SECRET_IMAGE" + type: "string" + default: "quay.io/openshift-hyperfleet/pull-secret:latest" + description: "Pull secret adapter container image" + - name: "pullSecretData" + source: "event.pullSecretData" + type: "string" + description: "Pull secret JSON data to store" + required: true + - name: "secretName" + source: "event.secretName" + type: "string" + description: "Secret name in GCP Secret Manager (optional, auto-generated if empty)" + - name: "resultPath" + source: "env.RESULTS_PATH" + type: "string" + default: "/results/adapter-result.json" + description: "Adapter shared result path with status reporter" + - name: "maxWaitTimeSeconds" + source: "env.MAX_WAIT_TIME_SECONDS" + type: "string" + default: "300" + description: "Maximum time to wait for job completion" + - name: "logLevel" + source: "env.LOG_LEVEL" + type: "string" + default: "info" + description: "Log level for containers (debug, info, warn, error)" + - name: "adapterTaskServiceAccount" + source: "env.ADAPTER_TASK_SERVICE_ACCOUNT" + type: "string" + default: "pull-secret-adapter-task-sa" + description: "Kubernetes ServiceAccount name for the adapter task" + - name: "managedByResourceName" + source: "env.MANAGED_BY_RESOURCE_NAME" + type: "string" + default: "pull-secret-adapter" + description: "The value for hyperfleet.io/managed-by" + # ============================================================================ + # Preconditions + # ============================================================================ + preconditions: [] + # ============================================================================ + # Resource Template + # ============================================================================ + resourceTemplate: + path: "/etc/adapter/job-template.yaml" diff --git a/charts/pull-secret/configs/pull-secret-job-adapter-task.yaml b/charts/pull-secret/configs/pull-secret-job-adapter-task.yaml new file mode 100644 index 0000000..83cdc5a --- /dev/null +++ b/charts/pull-secret/configs/pull-secret-job-adapter-task.yaml @@ -0,0 +1,132 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: "pull-secret-{{ .clusterId | lower }}-{{ .generationId }}" + namespace: "{{ .clusterId | lower }}" + labels: + hyperfleet.io/cluster-id: "{{ .clusterId }}" + hyperfleet.io/managed-by: "{{ .managedByResourceName }}" + hyperfleet.io/resource-type: "pull-secret-job" + app: pull-secret + annotations: + hyperfleet.io/generation: "{{ .generationId }}" +spec: + backoffLimit: 0 + # Maximum time to wait for k8s job completion (maxWaitTimeSeconds + 10 second buffer) + activeDeadlineSeconds: 310 + template: + metadata: + labels: + app: pull-secret + hyperfleet.io/cluster-id: "{{ .clusterId }}" + spec: + # Created before the job is created, as specified in the adapter configuration. + serviceAccountName: "{{ .adapterTaskServiceAccount }}" + restartPolicy: Never + securityContext: + fsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + volumes: + - name: results + emptyDir: {} + # Required for readOnlyRootFilesystem: true - provides writable /tmp + - name: tmp + emptyDir: {} + containers: + # Pull Secret Adapter Container + - name: pull-secret + image: "{{ .pullSecretImage }}" + imagePullPolicy: Always + command: + - /usr/local/bin/pull-secret + args: + - run-job + - pull-secret + env: + # Required + - name: GCP_PROJECT_ID + value: "{{ .projectId }}" + - name: CLUSTER_ID + value: "{{ .clusterId }}" + - name: SECRET_NAME + value: "{{ if .secretName }}{{ .secretName }}{{ else }}hyperfleet-{{ .clusterId }}-pull-secret{{ end }}" + - name: PULL_SECRET_DATA + value: "{{ .pullSecretData }}" + - name: RESULTS_PATH + value: "{{ .resultPath }}" + # Logging + - name: LOG_LEVEL + value: "{{ .logLevel }}" + volumeMounts: + - name: results + mountPath: /results + - name: tmp + mountPath: /tmp + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "500m" + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + + # Status reporter sidecar + - name: status-reporter + image: "{{ .statusReporterImage }}" + imagePullPolicy: Always + env: + # Required environment variables + - name: JOB_NAME + value: "pull-secret-{{ .clusterId | lower }}-{{ .generationId }}" + - name: JOB_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + # Optional configuration + - name: RESULTS_PATH + value: "{{ .resultPath }}" + - name: POLL_INTERVAL_SECONDS + value: "2" + - name: MAX_WAIT_TIME_SECONDS + value: "{{ .maxWaitTimeSeconds }}" + - name: CONDITION_TYPE + value: "Available" + - name: LOG_LEVEL + value: "{{ .logLevel }}" + - name: ADAPTER_CONTAINER_NAME + value: "pull-secret" + volumeMounts: + - name: results + mountPath: /results + resources: + requests: + memory: "64Mi" + cpu: "100m" + limits: + memory: "128Mi" + cpu: "200m" + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL diff --git a/charts/pull-secret/templates/NOTES.txt b/charts/pull-secret/templates/NOTES.txt index b81485b..9181c14 100644 --- a/charts/pull-secret/templates/NOTES.txt +++ b/charts/pull-secret/templates/NOTES.txt @@ -1,63 +1,91 @@ - -=============================================================================== +============================================================ HyperFleet Pull Secret Adapter -=============================================================================== +============================================================ + +The Pull Secret Adapter has been deployed successfully! + +Architecture: + - Adapter Framework (Deployment) listens to PubSub messages + - Creates Jobs dynamically to store pull secrets in GCP Secret Manager + - Status Reporter sidecar reports status back to HyperFleet API + +Release: {{ .Release.Name }} +Namespace: {{ .Release.Namespace }} + +============================================================ + COMPONENTS +============================================================ + +Adapter Framework Deployment: + Name: {{ include "pull-secret.fullname" . }} + Replicas: {{ .Values.replicaCount }} + +ServiceAccount: + Name: {{ include "pull-secret.serviceAccountName" . }} + +============================================================ + BROKER CONFIGURATION +============================================================ +{{- if .Values.broker.type }} +Broker Type: {{ .Values.broker.type }} +{{- if eq .Values.broker.type "googlepubsub" }} + Project ID: {{ .Values.broker.googlepubsub.projectId }} + Topic: {{ .Values.broker.googlepubsub.topic }} + Subscription: {{ .Values.broker.googlepubsub.subscription }} +{{- end }} +{{- else }} +WARNING: No broker configured! The adapter will not receive any messages. +{{- end }} + +============================================================ + IMAGES +============================================================ + +Adapter Framework: + {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }} -The pull-secret adapter has been deployed as a Kubernetes Job. +Pull Secret Job: + {{ include "pull-secret.adapterTaskImage" . }} -Release Name: {{ .Release.Name }} -Namespace: {{ .Release.Namespace }} -Job Name: {{ include "pull-secret.jobName" . }} +Status Reporter: + {{ .Values.pullSecretAdapter.statusReporterImage }} -------------------------------------------------------------------------------- -CONFIGURATION -------------------------------------------------------------------------------- -GCP Project: {{ .Values.gcp.projectId | default "NOT SET - REQUIRED!" }} -Cluster ID: {{ .Values.cluster.id | default "NOT SET - REQUIRED!" }} -Secret Name: {{ include "pull-secret.secretName" . }} -Image: {{ include "pull-secret.image" . }} +============================================================ + MONITORING +============================================================ -------------------------------------------------------------------------------- -MONITORING -------------------------------------------------------------------------------- -Check job status: - kubectl get job {{ include "pull-secret.jobName" . }} -n {{ .Release.Namespace }} +Check deployment status: + kubectl get deployment {{ include "pull-secret.fullname" . }} -n {{ .Release.Namespace }} + kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ include "pull-secret.name" . }} -View job logs: - kubectl logs -f job/{{ include "pull-secret.jobName" . }} -n {{ .Release.Namespace }} +View adapter logs: + kubectl logs -f deployment/{{ include "pull-secret.fullname" . }} -n {{ .Release.Namespace }} -Get job details: - kubectl describe job {{ include "pull-secret.jobName" . }} -n {{ .Release.Namespace }} +View dynamically created jobs: + kubectl get jobs -n -l hyperfleet.io/managed-by={{ include "pull-secret.fullname" . }} -------------------------------------------------------------------------------- -CLEANUP -------------------------------------------------------------------------------- -The job will be automatically cleaned up {{ .Values.job.ttlSecondsAfterFinished }} seconds after completion. +============================================================ + UPGRADE / UNINSTALL +============================================================ -To manually uninstall: +Upgrade: + helm upgrade {{ .Release.Name }} ./charts/pull-secret -n {{ .Release.Namespace }} + +Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} -------------------------------------------------------------------------------- -TROUBLESHOOTING -------------------------------------------------------------------------------- -{{- if not .Values.gcp.projectId }} -WARNING: gcp.projectId is not set! The job will fail. - Set it using: --set gcp.projectId=YOUR_PROJECT_ID -{{- end }} -{{- if not .Values.cluster.id }} -WARNING: cluster.id is not set! The job will fail. - Set it using: --set cluster.id=YOUR_CLUSTER_ID -{{- end }} -{{- if not .Values.pullSecret.data }} -WARNING: pullSecret.data is not set! The job will fail. - Set it using: --set pullSecret.data='{"auths":{...}}' -{{- end }} +============================================================ + TROUBLESHOOTING +============================================================ + +1. Check adapter framework logs: + kubectl logs -f deployment/{{ include "pull-secret.fullname" . }} -n {{ .Release.Namespace }} + +2. Check PubSub subscription: + gcloud pubsub subscriptions describe {{ .Values.broker.googlepubsub.subscription }} --project={{ .Values.broker.googlepubsub.projectId }} -For Workload Identity issues, ensure: -1. GCP service account has Secret Manager permissions -2. Kubernetes SA is annotated with GCP SA: - kubectl annotate sa {{ include "pull-secret.serviceAccountName" . }} \ - iam.gke.io/gcp-service-account=GSA_NAME@PROJECT.iam.gserviceaccount.com \ - -n {{ .Release.Namespace }} +3. Verify ServiceAccount permissions: + kubectl get clusterrolebinding {{ include "pull-secret.fullname" . }} -o yaml -=============================================================================== +4. Check for events: + kubectl get events -n {{ .Release.Namespace }} --sort-by='.lastTimestamp' diff --git a/charts/pull-secret/templates/_helpers.tpl b/charts/pull-secret/templates/_helpers.tpl index b510b0d..6eb790e 100644 --- a/charts/pull-secret/templates/_helpers.tpl +++ b/charts/pull-secret/templates/_helpers.tpl @@ -58,26 +58,30 @@ Create the name of the service account to use {{- define "pull-secret.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "pull-secret.fullname" .) .Values.serviceAccount.name }} -{{- else if .Values.rbac.create }} -{{- required "serviceAccount.name must be set when serviceAccount.create=false and rbac.create=true" .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} {{/* -Create the name of the job +Create the ConfigMap name for adapter configuration */}} -{{- define "pull-secret.jobName" -}} -{{- default (include "pull-secret.fullname" .) .Values.job.name }} +{{- define "pull-secret.configMapName" -}} +{{- printf "%s-config" (include "pull-secret.fullname" .) }} {{- end }} {{/* -Create the image reference with global override support. -Global image registry takes precedence over local registry. +Create the ConfigMap name for broker configuration */}} -{{- define "pull-secret.image" -}} -{{- $registry := .Values.image.registry }} +{{- define "pull-secret.brokerConfigMapName" -}} +{{- printf "%s-broker" (include "pull-secret.fullname" .) }} +{{- end }} + +{{/* +Create the adapter task (job) image reference with global override support. +*/}} +{{- define "pull-secret.adapterTaskImage" -}} +{{- $registry := .Values.pullSecretAdapter.image.registry }} {{- if .Values.global }} {{- if .Values.global.image }} {{- if .Values.global.image.registry }} @@ -85,17 +89,5 @@ Global image registry takes precedence over local registry. {{- end }} {{- end }} {{- end }} -{{- printf "%s/%s:%s" $registry .Values.image.repository (.Values.image.tag | default .Chart.AppVersion) }} -{{- end }} - -{{/* -Create the secret name in GCP Secret Manager -Auto-generates as: hyperfleet-{cluster.id}-pull-secret if not provided -*/}} -{{- define "pull-secret.secretName" -}} -{{- if .Values.pullSecret.name }} -{{- .Values.pullSecret.name }} -{{- else }} -{{- printf "hyperfleet-%s-pull-secret" .Values.cluster.id }} -{{- end }} +{{- printf "%s/%s:%s" $registry .Values.pullSecretAdapter.image.repository (.Values.pullSecretAdapter.image.tag | default .Chart.AppVersion) }} {{- end }} diff --git a/charts/pull-secret/templates/configmap-app.yaml b/charts/pull-secret/templates/configmap-app.yaml new file mode 100644 index 0000000..c0680bf --- /dev/null +++ b/charts/pull-secret/templates/configmap-app.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "pull-secret.configMapName" . }} + labels: + {{- include "pull-secret.labels" . | nindent 4 }} +data: + adapter.yaml: | +{{ .Files.Get "configs/pull-secret-adapter.yaml" | indent 4 }} + job-template.yaml: | +{{ .Files.Get "configs/pull-secret-job-adapter-task.yaml" | indent 4 }} diff --git a/charts/pull-secret/templates/configmap-broker.yaml b/charts/pull-secret/templates/configmap-broker.yaml new file mode 100644 index 0000000..de571b1 --- /dev/null +++ b/charts/pull-secret/templates/configmap-broker.yaml @@ -0,0 +1,30 @@ +{{- if .Values.broker.type }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "pull-secret.brokerConfigMapName" . }} + labels: + {{- include "pull-secret.labels" . | nindent 4 }} +data: + {{- if .Values.broker.yaml }} + broker.yaml: | +{{ .Values.broker.yaml | indent 4 }} + {{- else }} + broker.yaml: | + type: {{ .Values.broker.type }} + {{- if eq .Values.broker.type "googlepubsub" }} + googlepubsub: + projectId: {{ .Values.broker.googlepubsub.projectId | quote }} + topic: {{ .Values.broker.googlepubsub.topic | quote }} + subscription: {{ .Values.broker.googlepubsub.subscription | quote }} + {{- if .Values.broker.googlepubsub.deadLetterTopic }} + deadLetterTopic: {{ .Values.broker.googlepubsub.deadLetterTopic | quote }} + {{- end }} + {{- else if eq .Values.broker.type "rabbitmq" }} + rabbitmq: + url: {{ .Values.broker.rabbitmq.url | quote }} + {{- end }} + subscriber: + parallelism: {{ .Values.broker.subscriber.parallelism | default 1 }} + {{- end }} +{{- end }} diff --git a/charts/pull-secret/templates/deployment.yaml b/charts/pull-secret/templates/deployment.yaml new file mode 100644 index 0000000..6920873 --- /dev/null +++ b/charts/pull-secret/templates/deployment.yaml @@ -0,0 +1,141 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "pull-secret.fullname" . }} + labels: + {{- include "pull-secret.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "pull-secret.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap-app.yaml") . | sha256sum }} + {{- if .Values.broker.type }} + checksum/broker-config: {{ include (print $.Template.BasePath "/configmap-broker.yaml") . | sha256sum }} + {{- end }} + labels: + {{- include "pull-secret.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "pull-secret.serviceAccountName" . }} + securityContext: + fsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + containers: + - name: {{ .Chart.Name }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + seccompProfile: + type: RuntimeDefault + image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /app/adapter + args: + - serve + env: + - name: LOG_LEVEL + value: {{ .Values.logging.level | default "info" | quote }} + - name: LOG_FORMAT + value: {{ .Values.logging.format | default "text" | quote }} + - name: LOG_OUTPUT + value: {{ .Values.logging.output | default "stderr" | quote }} + {{- with .Values.env }} + {{- range . }} + - name: {{ .name }} + {{- if .value }} + value: {{ .value | quote }} + {{- else if .valueFrom }} + valueFrom: + {{- toYaml .valueFrom | nindent 16 }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.hyperfleetApi.baseUrl }} + - name: HYPERFLEET_API_BASE_URL + value: {{ .Values.hyperfleetApi.baseUrl | quote }} + {{- end }} + - name: HYPERFLEET_API_VERSION + value: {{ .Values.hyperfleetApi.version | default "v1" | quote }} + - name: ADAPTER_CONFIG_PATH + value: /etc/adapter/adapter.yaml + {{- if .Values.broker.type }} + - name: BROKER_CONFIG_FILE + value: /etc/broker/broker.yaml + {{- if eq .Values.broker.type "googlepubsub" }} + - name: BROKER_SUBSCRIPTION_ID + value: {{ .Values.broker.googlepubsub.subscription | quote }} + - name: BROKER_TOPIC + value: {{ .Values.broker.googlepubsub.topic | quote }} + - name: GCP_PROJECT_ID + value: {{ .Values.broker.googlepubsub.projectId | quote }} + {{- end }} + {{- end }} + # Pull Secret Adapter specific environment variables + - name: STATUS_REPORTER_IMAGE + value: {{ .Values.pullSecretAdapter.statusReporterImage | quote }} + - name: PULL_SECRET_IMAGE + value: {{ include "pull-secret.adapterTaskImage" . | quote }} + - name: RESULTS_PATH + value: {{ .Values.pullSecretAdapter.resultsPath | quote }} + - name: MAX_WAIT_TIME_SECONDS + value: {{ .Values.pullSecretAdapter.maxWaitTimeSeconds | quote }} + - name: MANAGED_BY_RESOURCE_NAME + value: {{ include "pull-secret.fullname" . | quote }} + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + volumeMounts: + - name: tmp + mountPath: /tmp + - name: adapter-config + mountPath: /etc/adapter + readOnly: true + {{- if .Values.broker.type }} + - name: broker-config + mountPath: /etc/broker/broker.yaml + subPath: broker.yaml + readOnly: true + {{- end }} + volumes: + # Required for readOnlyRootFilesystem: true - provides writable /tmp + - name: tmp + emptyDir: {} + - name: adapter-config + configMap: + name: {{ include "pull-secret.configMapName" . }} + {{- if .Values.broker.type }} + - name: broker-config + configMap: + name: {{ include "pull-secret.brokerConfigMapName" . }} + items: + - key: broker.yaml + path: broker.yaml + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/pull-secret/templates/job.yaml b/charts/pull-secret/templates/job.yaml deleted file mode 100644 index 9dfd073..0000000 --- a/charts/pull-secret/templates/job.yaml +++ /dev/null @@ -1,83 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ include "pull-secret.jobName" . }} - labels: - {{- include "pull-secret.labels" . | nindent 4 }} - job-type: pull-secret -spec: - backoffLimit: {{ .Values.job.backoffLimit }} - ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} - template: - metadata: - labels: - {{- include "pull-secret.selectorLabels" . | nindent 8 }} - job-type: pull-secret - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "pull-secret.serviceAccountName" . }} - restartPolicy: {{ .Values.job.restartPolicy }} - {{- with .Values.podSecurityContext }} - securityContext: - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: pull-secret - image: {{ include "pull-secret.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /usr/local/bin/pull-secret - args: - - run-job - - pull-secret - env: - - name: GCP_PROJECT_ID - value: {{ required "gcp.projectId is required" .Values.gcp.projectId | quote }} - - name: CLUSTER_ID - value: {{ required "cluster.id is required" .Values.cluster.id | quote }} - - name: SECRET_NAME - value: {{ include "pull-secret.secretName" . | quote }} - - name: PULL_SECRET_DATA - value: {{ required "pullSecret.data is required" .Values.pullSecret.data | quote }} - {{- if .Values.hyperfleetApi.baseUrl }} - - name: HYPERFLEET_API_BASE_URL - value: {{ .Values.hyperfleetApi.baseUrl | quote }} - {{- end }} - - name: HYPERFLEET_API_VERSION - value: {{ .Values.hyperfleetApi.version | default "v1" | quote }} - {{- with .Values.env }} - {{- range . }} - - name: {{ .name }} - {{- if .value }} - value: {{ .value | quote }} - {{- else if .valueFrom }} - valueFrom: - {{- toYaml .valueFrom | nindent 16 }} - {{- end }} - {{- end }} - {{- end }} - resources: - {{- toYaml .Values.resources | nindent 12 }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - volumeMounts: - - name: tmp - mountPath: /tmp - volumes: - - name: tmp - emptyDir: {} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/charts/pull-secret/templates/rbac.yaml b/charts/pull-secret/templates/rbac.yaml index e63de0b..188ad9f 100644 --- a/charts/pull-secret/templates/rbac.yaml +++ b/charts/pull-secret/templates/rbac.yaml @@ -7,17 +7,41 @@ metadata: labels: {{- include "pull-secret.labels" . | nindent 4 }} rules: + # Read namespaces to verify target namespace exists + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list", "watch"] + + # Manage ServiceAccounts for adapter task jobs + - apiGroups: [""] + resources: ["serviceaccounts"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + + # Read pods to check adapter task job pod status + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] + + # Manage RBAC for adapter task jobs (Role and RoleBinding) + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + + # Manage adapter task Jobs + - apiGroups: ["batch"] + resources: ["jobs"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + + # Read and update Job status (needed by status reporter sidecar) + - apiGroups: ["batch"] + resources: ["jobs/status"] + verbs: ["get", "update", "patch"] + # Secret management permissions - required for pull secret operations - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch", "create", "update", "patch"] - # Namespace access - read only for listing available namespaces - - apiGroups: [""] - resources: ["namespaces"] - verbs: ["get", "list", "watch"] - {{- with .Values.rbac.rules }} - {{- toYaml . | nindent 2 }} - {{- end }} + --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/pull-secret/values.yaml b/charts/pull-secret/values.yaml index 14a4a97..15d6a64 100644 --- a/charts/pull-secret/values.yaml +++ b/charts/pull-secret/values.yaml @@ -1,7 +1,7 @@ # Pull Secret Adapter - Helm Values # # This adapter manages pull secrets in GCP Secret Manager for HyperFleet clusters. -# It runs as a Kubernetes Job that stores/updates pull secrets. +# It uses the Adapter Framework to listen to PubSub messages and create jobs. # # For umbrella chart integration, set global.image.registry to override image registry. @@ -19,12 +19,15 @@ global: nameOverride: "" fullnameOverride: "" +replicaCount: 1 + # ============================================================================= -# Image Configuration +# Image Configuration (Adapter Framework) # ============================================================================= image: - registry: quay.io/openshift-hyperfleet - repository: pull-secret + # Adapter Framework image + registry: registry.ci.openshift.org + repository: ci/hyperfleet-adapter tag: "latest" pullPolicy: Always @@ -34,12 +37,13 @@ imagePullSecrets: [] # ServiceAccount Configuration # ============================================================================= serviceAccount: - # Create a ServiceAccount for the job + # Create a ServiceAccount for the adapter framework deployment create: true # Name of the ServiceAccount (auto-generated if empty) name: "" - # Annotations for Workload Identity - # For GKE: iam.gke.io/gcp-service-account: GSA_NAME@PROJECT_ID.iam.gserviceaccount.com + # Annotations (optional, only needed for legacy GSA-based Workload Identity) + # Modern approach uses direct principal binding - no annotation needed + # Legacy: iam.gke.io/gcp-service-account: GSA_NAME@PROJECT_ID.iam.gserviceaccount.com annotations: {} # ============================================================================= @@ -48,52 +52,44 @@ serviceAccount: rbac: # Create RBAC resources (ClusterRole and ClusterRoleBinding) create: true - # Additional rules to add to the ClusterRole - rules: [] + # When enabled, grants minimal permissions needed for adapter: + # - Read namespaces + # - Manage ServiceAccounts, Roles, RoleBindings, Jobs + # - Read pods and update Job status # ============================================================================= -# Job Configuration +# Logging Configuration # ============================================================================= -job: - # Name override for the job (auto-generated if empty) - name: "" - # Number of times to retry the job on failure - backoffLimit: 3 - # Time in seconds after job completion before cleanup (1 hour) - ttlSecondsAfterFinished: 3600 - # Job restart policy - restartPolicy: Never +logging: + level: info # Options: debug, info, warn, error + format: text # Options: text, json + output: stderr # Options: stdout, stderr # ============================================================================= -# GCP Configuration +# Broker Configuration (required for adapter to function) # ============================================================================= -gcp: - # GCP project ID where secrets will be stored - # REQUIRED: Must be set before deployment - projectId: "" +broker: + type: "googlepubsub" # "googlepubsub" or "rabbitmq" -# ============================================================================= -# Cluster Configuration -# ============================================================================= -cluster: - # Cluster identifier - used to name the secret in GCP - # REQUIRED: Must be set before deployment - id: "" + # Google Pub/Sub (required when type: googlepubsub) + googlepubsub: + projectId: "" # GCP project ID for Pub/Sub + topic: "" # Pub/Sub topic name + subscription: "" # Pub/Sub subscription name + deadLetterTopic: "" # Optional: Dead letter topic -# ============================================================================= -# Pull Secret Configuration -# ============================================================================= -pullSecret: - # Secret name in GCP Secret Manager - # Auto-generated as: hyperfleet-{cluster.id}-pull-secret if empty - name: "" - # Pull secret JSON data - # REQUIRED: Must be set before deployment - # Example: '{"auths":{"registry.example.com":{"auth":"...","email":"user@example.com"}}}' - data: "" + # RabbitMQ (required when type: rabbitmq) + rabbitmq: + url: "" # amqp://user:pass@host:5672/ + + subscriber: + parallelism: 1 + + # Raw YAML override (bypasses structured config above) + yaml: "" # ============================================================================= -# HyperFleet API Configuration (optional) +# HyperFleet API Configuration # ============================================================================= hyperfleetApi: # Base URL for HyperFleet API (for status reporting) @@ -102,37 +98,26 @@ hyperfleetApi: version: "v1" # ============================================================================= -# Resource Limits +# Pull Secret Adapter Configuration # ============================================================================= -resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 500m - memory: 512Mi +pullSecretAdapter: + # Pull Secret adapter task image + image: + registry: quay.io/openshift-hyperfleet + repository: pull-secret + tag: "latest" -# ============================================================================= -# Security Context -# ============================================================================= -securityContext: - runAsNonRoot: true - runAsUser: 1000 - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - seccompProfile: - type: RuntimeDefault - capabilities: - drop: - - ALL + # Status reporter sidecar image + statusReporterImage: "registry.ci.openshift.org/ci/status-reporter:latest" -# ============================================================================= -# Pod Security Context -# ============================================================================= -podSecurityContext: - fsGroup: 1000 - runAsNonRoot: true - runAsUser: 1000 + # Shared result path for status reporter + resultsPath: "/results/adapter-result.json" + + # Maximum time to wait for job completion (seconds) + maxWaitTimeSeconds: "300" + + # Log level for job containers + logLevel: "info" # ============================================================================= # Platform-specific Scheduling