From 98a9cee6fb9637cc38c7d76f42e3c977030e39bd Mon Sep 17 00:00:00 2001 From: Jakub Hadvig Date: Thu, 18 Jun 2026 11:08:01 +0200 Subject: [PATCH] install: Add TechPreviewNoUpgrade cluster-update console plugin Add manifests to deploy the cluster-update-console-plugin, a web console interface for managing ClusterVersion updates. The plugin is gated behind the TechPreviewNoUpgrade feature set and the Console capability. Manifests include: namespace, serviceaccount, networkpolicy, deployment, service, and consoleplugin resources. The deployment uses readiness and liveness probes, a read-only root filesystem with emptyDir volumes for nginx runtime directories, and a serving-cert annotation for TLS. Extend manifestRenderConfig with an Images map populated from the release payload's image-references, so CVO manifests can resolve component images by short name at deploy time using Go template syntax: {{index .Images "cluster-update-console-plugin"}}. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...er-update-console-plugin_10_namespace.yaml | 15 ++++ ...date-console-plugin_15_serviceaccount.yaml | 11 +++ ...pdate-console-plugin_20_networkpolicy.yaml | 16 ++++ ...er-update-console-plugin_30_configmap.yaml | 28 +++++++ ...r-update-console-plugin_50_deployment.yaml | 78 +++++++++++++++++++ ...ster-update-console-plugin_60_service.yaml | 20 +++++ ...pdate-console-plugin_90_consoleplugin.yaml | 21 +++++ install/image-references | 8 ++ pkg/payload/payload.go | 5 +- pkg/payload/render.go | 22 ++++++ pkg/payload/render_test.go | 7 ++ 11 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 install/0000_50_cluster-update-console-plugin_10_namespace.yaml create mode 100644 install/0000_50_cluster-update-console-plugin_15_serviceaccount.yaml create mode 100644 install/0000_50_cluster-update-console-plugin_20_networkpolicy.yaml create mode 100644 install/0000_50_cluster-update-console-plugin_30_configmap.yaml create mode 100644 install/0000_50_cluster-update-console-plugin_50_deployment.yaml create mode 100644 install/0000_50_cluster-update-console-plugin_60_service.yaml create mode 100644 install/0000_50_cluster-update-console-plugin_90_consoleplugin.yaml create mode 100644 install/image-references diff --git a/install/0000_50_cluster-update-console-plugin_10_namespace.yaml b/install/0000_50_cluster-update-console-plugin_10_namespace.yaml new file mode 100644 index 0000000000..2e8383be0c --- /dev/null +++ b/install/0000_50_cluster-update-console-plugin_10_namespace.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: openshift-cluster-update-console-plugin + annotations: + kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates. + capability.openshift.io/name: Console + release.openshift.io/feature-set: TechPreviewNoUpgrade + exclude.release.openshift.io/internal-openshift-hosted: "true" + include.release.openshift.io/self-managed-high-availability: "true" + labels: + openshift.io/cluster-monitoring: "true" + pod-security.kubernetes.io/audit: restricted + pod-security.kubernetes.io/enforce: restricted + pod-security.kubernetes.io/warn: restricted diff --git a/install/0000_50_cluster-update-console-plugin_15_serviceaccount.yaml b/install/0000_50_cluster-update-console-plugin_15_serviceaccount.yaml new file mode 100644 index 0000000000..e124a426b8 --- /dev/null +++ b/install/0000_50_cluster-update-console-plugin_15_serviceaccount.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cluster-update-console-plugin + namespace: openshift-cluster-update-console-plugin + annotations: + kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates. + capability.openshift.io/name: Console + release.openshift.io/feature-set: TechPreviewNoUpgrade + exclude.release.openshift.io/internal-openshift-hosted: "true" + include.release.openshift.io/self-managed-high-availability: "true" diff --git a/install/0000_50_cluster-update-console-plugin_20_networkpolicy.yaml b/install/0000_50_cluster-update-console-plugin_20_networkpolicy.yaml new file mode 100644 index 0000000000..78bc822350 --- /dev/null +++ b/install/0000_50_cluster-update-console-plugin_20_networkpolicy.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny + namespace: openshift-cluster-update-console-plugin + annotations: + kubernetes.io/description: This NetworkPolicy is used to deny all ingress and egress traffic by default in this namespace, matching all Pods, and serving as a baseline. + capability.openshift.io/name: Console + release.openshift.io/feature-set: TechPreviewNoUpgrade + exclude.release.openshift.io/internal-openshift-hosted: "true" + include.release.openshift.io/self-managed-high-availability: "true" +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/install/0000_50_cluster-update-console-plugin_30_configmap.yaml b/install/0000_50_cluster-update-console-plugin_30_configmap.yaml new file mode 100644 index 0000000000..89ccc8a21d --- /dev/null +++ b/install/0000_50_cluster-update-console-plugin_30_configmap.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-update-console-plugin + namespace: openshift-cluster-update-console-plugin + annotations: + kubernetes.io/description: Nginx configuration for the cluster-update console plugin. + capability.openshift.io/name: Console + release.openshift.io/feature-set: TechPreviewNoUpgrade + exclude.release.openshift.io/internal-openshift-hosted: "true" + include.release.openshift.io/self-managed-high-availability: "true" +data: + nginx.conf: | + error_log /dev/stderr; + events {} + http { + access_log /dev/stdout; + include /etc/nginx/mime.types; + default_type application/octet-stream; + keepalive_timeout 65; + server { + listen 9001 ssl; + listen [::]:9001 ssl; + ssl_certificate /var/cert/tls.crt; + ssl_certificate_key /var/cert/tls.key; + root /usr/share/nginx/html; + } + } diff --git a/install/0000_50_cluster-update-console-plugin_50_deployment.yaml b/install/0000_50_cluster-update-console-plugin_50_deployment.yaml new file mode 100644 index 0000000000..182f1db8bc --- /dev/null +++ b/install/0000_50_cluster-update-console-plugin_50_deployment.yaml @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-update-console-plugin + namespace: openshift-cluster-update-console-plugin + annotations: + kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates. + capability.openshift.io/name: Console + release.openshift.io/feature-set: TechPreviewNoUpgrade + exclude.release.openshift.io/internal-openshift-hosted: "true" + include.release.openshift.io/self-managed-high-availability: "true" +spec: + selector: + matchLabels: + app: cluster-update-console-plugin + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + annotations: + target.workload.openshift.io/management: '{"effect": "PreferredDuringScheduling"}' + openshift.io/required-scc: restricted-v3 + labels: + app: cluster-update-console-plugin + spec: + serviceAccountName: cluster-update-console-plugin + automountServiceAccountToken: false + containers: + - name: plugin + image: '{{index .Images "cluster-update-console-plugin"}}' + imagePullPolicy: IfNotPresent + ports: + - name: https + containerPort: 9001 + resources: + requests: + cpu: 20m + memory: 50Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - mountPath: /var/cert + name: cluster-update-console-plugin-cert + readOnly: true + - mountPath: /etc/nginx/nginx.conf + name: nginx-conf + readOnly: true + subPath: nginx.conf + dnsPolicy: ClusterFirst + hostUsers: false + nodeSelector: + kubernetes.io/os: linux + priorityClassName: system-cluster-critical + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/infra + operator: Exists + volumes: + - name: cluster-update-console-plugin-cert + secret: + defaultMode: 420 + secretName: cluster-update-console-plugin-cert + - name: nginx-conf + configMap: + name: cluster-update-console-plugin + defaultMode: 420 diff --git a/install/0000_50_cluster-update-console-plugin_60_service.yaml b/install/0000_50_cluster-update-console-plugin_60_service.yaml new file mode 100644 index 0000000000..cd5f7b2b4c --- /dev/null +++ b/install/0000_50_cluster-update-console-plugin_60_service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: openshift-cluster-update-console-plugin + namespace: openshift-cluster-update-console-plugin + annotations: + kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates. + service.beta.openshift.io/serving-cert-secret-name: cluster-update-console-plugin-cert + capability.openshift.io/name: Console + release.openshift.io/feature-set: TechPreviewNoUpgrade + exclude.release.openshift.io/internal-openshift-hosted: "true" + include.release.openshift.io/self-managed-high-availability: "true" +spec: + type: ClusterIP + selector: + app: cluster-update-console-plugin + ports: + - name: https + port: 9001 + targetPort: https diff --git a/install/0000_50_cluster-update-console-plugin_90_consoleplugin.yaml b/install/0000_50_cluster-update-console-plugin_90_consoleplugin.yaml new file mode 100644 index 0000000000..69bd0d9eee --- /dev/null +++ b/install/0000_50_cluster-update-console-plugin_90_consoleplugin.yaml @@ -0,0 +1,21 @@ +apiVersion: console.openshift.io/v1 +kind: ConsolePlugin +metadata: + name: openshift-cluster-update-console-plugin + annotations: + kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates. + capability.openshift.io/name: Console + release.openshift.io/feature-set: TechPreviewNoUpgrade + exclude.release.openshift.io/internal-openshift-hosted: "true" + include.release.openshift.io/self-managed-high-availability: "true" +spec: + displayName: Cluster Updates + i18n: + loadType: Preload + backend: + type: Service + service: + name: openshift-cluster-update-console-plugin + namespace: openshift-cluster-update-console-plugin + port: 9001 + basePath: / diff --git a/install/image-references b/install/image-references new file mode 100644 index 0000000000..32c17958f7 --- /dev/null +++ b/install/image-references @@ -0,0 +1,8 @@ +kind: ImageStream +apiVersion: image.openshift.io/v1 +spec: + tags: + - name: cluster-update-console-plugin + from: + kind: DockerImage + name: placeholder.url.oc.will.replace.this.example.org:cluster-update-console-plugin diff --git a/pkg/payload/payload.go b/pkg/payload/payload.go index 7c7af838b8..2430c2c0ce 100644 --- a/pkg/payload/payload.go +++ b/pkg/payload/payload.go @@ -153,7 +153,7 @@ func LoadUpdate(dir, releaseImage, excludeIdentifier string, requiredFeatureSet return nil, err } - tasks := loadPayloadTasks(releaseDir, cvoDir, releaseImage, profile) + tasks := loadPayloadTasks(releaseDir, cvoDir, releaseImage, profile, payload.ImageRef) var onlyKnownCaps *configv1.ClusterVersionCapabilitiesStatus @@ -317,13 +317,14 @@ type payloadTasks struct { skipFiles sets.Set[string] } -func loadPayloadTasks(releaseDir, cvoDir, releaseImage, clusterProfile string) []payloadTasks { +func loadPayloadTasks(releaseDir, cvoDir, releaseImage, clusterProfile string, imageRef *imagev1.ImageStream) []payloadTasks { cjf := filepath.Join(releaseDir, cincinnatiJSONFile) irf := filepath.Join(releaseDir, imageReferencesFile) mrc := manifestRenderConfig{ ReleaseImage: releaseImage, ClusterProfile: clusterProfile, + Images: imagesFromImageRef(imageRef), } return []payloadTasks{{ diff --git a/pkg/payload/render.go b/pkg/payload/render.go index fc075a6e5c..edd20aed25 100644 --- a/pkg/payload/render.go +++ b/pkg/payload/render.go @@ -20,6 +20,7 @@ import ( "github.com/openshift/api/config" configv1 "github.com/openshift/api/config/v1" + imagev1 "github.com/openshift/api/image/v1" "github.com/openshift/library-go/pkg/manifest" ) @@ -38,6 +39,12 @@ func Render(outputDir, releaseImage, clusterVersionManifestPath, featureGateMani } ) + imageRef, err := loadImageReferences(releaseManifestsDir) + if err != nil { + return fmt.Errorf("error loading image references for manifest rendering: %w", err) + } + renderConfig.Images = imagesFromImageRef(imageRef) + overrides, err := parseClusterVersionManifest(clusterVersionManifestPath) if err != nil { return fmt.Errorf("error parsing cluster version manifest: %w", err) @@ -181,6 +188,21 @@ func renderDir(renderConfig manifestRenderConfig, idir, odir string, overrides [ type manifestRenderConfig struct { ReleaseImage string ClusterProfile string + Images map[string]string +} + +// imagesFromImageRef builds a map from image short names to their resolved URIs. +func imagesFromImageRef(imageRef *imagev1.ImageStream) map[string]string { + images := make(map[string]string) + if imageRef == nil { + return images + } + for _, tag := range imageRef.Spec.Tags { + if tag.From != nil && tag.From.Kind == "DockerImage" { + images[tag.Name] = tag.From.Name + } + } + return images } // renderManifest Executes go text template from `manifestBytes` with `config`. diff --git a/pkg/payload/render_test.go b/pkg/payload/render_test.go index deb475b161..a0fd7f3caa 100644 --- a/pkg/payload/render_test.go +++ b/pkg/payload/render_test.go @@ -313,6 +313,9 @@ func Test_cvoManifests(t *testing.T) { config := manifestRenderConfig{ ReleaseImage: "quay.io/cvo/release:latest", ClusterProfile: "some-profile", + Images: map[string]string{ + "cluster-update-console-plugin": "quay.io/openshift/cluster-update-console-plugin:latest", + }, } tests := []struct { @@ -341,6 +344,10 @@ func Test_cvoManifests(t *testing.T) { return nil } + if _, fileName := filepath.Split(path); fileName == "image-references" { + return nil + } + var manifestsWithoutIncludeAnnotation []manifest.Manifest data, err := os.ReadFile(path) if err != nil {