From f4ffc50b0da288c01092a1e43c285617505fe5cd Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:29:53 +0200 Subject: [PATCH 01/13] ROX-35000: pass install-virt through the openshift-4 flavor Adds install-virt, vm-os, and virt-node-dedicated to openshift-4 and openshift-4-perf-scale, and forwards them into the create container with the Quay pull secret and an optional vm-access artifact. Worker count bump and virt install run in automation-flavors once that image is bumped. Code partially generated by AI. --- chart/infra-server/static/flavors.yaml | 64 +++++++++++++++++++ .../static/workflow-openshift-4.yaml | 23 +++++++ 2 files changed, 87 insertions(+) diff --git a/chart/infra-server/static/flavors.yaml b/chart/infra-server/static/flavors.yaml index 644f65f3b..6398991e0 100644 --- a/chart/infra-server/static/flavors.yaml +++ b/chart/infra-server/static/flavors.yaml @@ -333,6 +333,35 @@ Defines a list of capabilities to explicitly enable. These capabilities are enabled in addition to the capabilities specified in the baseline capability set. Example: `["DeploymentConfig", "ImageRegistry"]` + - name: install-virt + description: Install OpenShift Virtualization and create a VM + value: false + kind: optional + help: | + When true, one extra worker is added at IPI install time, labeled + `node-role.kubernetes.io/virt=""`, and OpenShift Virtualization is + installed with VSOCK. A single RHEL VM is created from + `quay.io/rhacs-eng/vm-images` using `vm-os`. On e2 workers, + KVM emulation is used. + + - name: vm-os + description: OS for the virtual machine (rhel9, rhel10) + value: rhel9 + kind: optional + help: | + The RHEL version for the virtual machine container disk. + Valid values: rhel9, rhel10. + The image used is quay.io/rhacs-eng/vm-images:-dnf-primed-latest. + + - name: virt-node-dedicated + description: Taint the virt worker so only VMs are scheduled on it + value: true + kind: optional + help: | + When true, the virt worker is tainted with + `node-role.kubernetes.io/virt:NoSchedule`. Set false to let + other workloads share that node. Only used when install-virt is true. + artifacts: - name: kubeconfig description: Kube config for connecting to this cluster @@ -363,6 +392,9 @@ - name: cluster-console-password description: The password to login at the openshift console + - name: vm-access + description: SSH credentials for the optional demo VM (present when install-virt=true) + ###################### # openshift-4-demo # ###################### @@ -683,6 +715,35 @@ Defines a list of capabilities to explicitly enable. These capabilities are enabled in addition to the capabilities specified in the baseline capability set. Example: `["DeploymentConfig", "ImageRegistry"]` + - name: install-virt + description: Install OpenShift Virtualization and create a VM + value: false + kind: optional + help: | + When true, one extra worker is added at IPI install time, labeled + `node-role.kubernetes.io/virt=""`, and OpenShift Virtualization is + installed with VSOCK. A single RHEL VM is created from + `quay.io/rhacs-eng/vm-images` using `vm-os`. On e2 workers, + KVM emulation is used. + + - name: vm-os + description: OS for the virtual machine (rhel9, rhel10) + value: rhel9 + kind: optional + help: | + The RHEL version for the virtual machine container disk. + Valid values: rhel9, rhel10. + The image used is quay.io/rhacs-eng/vm-images:-dnf-primed-latest. + + - name: virt-node-dedicated + description: Taint the virt worker so only VMs are scheduled on it + value: true + kind: optional + help: | + When true, the virt worker is tainted with + `node-role.kubernetes.io/virt:NoSchedule`. Set false to let + other workloads share that node. Only used when install-virt is true. + artifacts: - name: kubeconfig description: Kube config for connecting to this cluster @@ -713,6 +774,9 @@ - name: cluster-console-password description: The password to login at the openshift console + - name: vm-access + description: SSH credentials for the optional demo VM (present when install-virt=true) + ##################### # AWS EKS # ##################### diff --git a/chart/infra-server/static/workflow-openshift-4.yaml b/chart/infra-server/static/workflow-openshift-4.yaml index a0430fce5..98c3b1a20 100644 --- a/chart/infra-server/static/workflow-openshift-4.yaml +++ b/chart/infra-server/static/workflow-openshift-4.yaml @@ -34,6 +34,12 @@ spec: value: "vCurrent" - name: additional-enabled-capabilities value: "" + - name: install-virt + value: "false" + - name: vm-os + value: "rhel9" + - name: virt-node-dedicated + value: "true" volumeClaimTemplates: - metadata: name: data @@ -46,6 +52,9 @@ spec: - name: credentials secret: secretName: openshift-4-gcp-service-account + - name: registry-pull-secret + secret: + secretName: infra-image-registry-pull-secret templates: - name: start @@ -91,6 +100,11 @@ spec: path: /data archive: tar: {} + - name: vm-access + path: /data/vm-access.md + optional: true + archive: + none: {} container: image: quay.io/stackrox-io/ci:automation-flavors-openshift-4-{{ .Chart.Annotations.automationFlavorsVersion }} imagePullPolicy: Always @@ -157,9 +171,18 @@ spec: value: '{{ "{{" }}workflow.parameters.baseline-capability-set{{ "}}" }}' - name: ADDITIONAL_ENABLED_CAPABILITIES value: '{{ "{{" }}workflow.parameters.additional-enabled-capabilities{{ "}}" }}' + - name: INSTALL_VIRT + value: '{{ "{{" }}workflow.parameters.install-virt{{ "}}" }}' + - name: VM_OS + value: '{{ "{{" }}workflow.parameters.vm-os{{ "}}" }}' + - name: VIRT_NODE_DEDICATED + value: '{{ "{{" }}workflow.parameters.virt-node-dedicated{{ "}}" }}' volumeMounts: - name: data mountPath: /data + - name: registry-pull-secret + mountPath: /infra-secrets/quay + readOnly: true - name: gather script: From 41feeac8e838cffbe0e7e3a23e36461d7b3a490a Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 15 Sep 2026 10:12:24 +0200 Subject: [PATCH 02/13] ROX-35000: document n2-standard-8 workers when install-virt is on install-virt overrides worker-node-type to n2-standard-8 so the VM has /dev/kvm. Masters are unchanged. Drop the e2/KVM-emulation help text. Prompt: it must be n2-standard-8 - maybe it would be easier to migrate all nodes to that type? Code partially generated by AI. Co-authored-by: Cursor --- chart/infra-server/static/flavors.yaml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/chart/infra-server/static/flavors.yaml b/chart/infra-server/static/flavors.yaml index 6398991e0..19d63864e 100644 --- a/chart/infra-server/static/flavors.yaml +++ b/chart/infra-server/static/flavors.yaml @@ -340,9 +340,10 @@ help: | When true, one extra worker is added at IPI install time, labeled `node-role.kubernetes.io/virt=""`, and OpenShift Virtualization is - installed with VSOCK. A single RHEL VM is created from - `quay.io/rhacs-eng/vm-images` using `vm-os`. On e2 workers, - KVM emulation is used. + installed with VSOCK. All workers are `n2-standard-8` (overrides + `worker-node-type`) so the VM has /dev/kvm. Masters are unchanged. + A single RHEL VM is created from `quay.io/rhacs-eng/vm-images` + using `vm-os`. - name: vm-os description: OS for the virtual machine (rhel9, rhel10) @@ -722,9 +723,10 @@ help: | When true, one extra worker is added at IPI install time, labeled `node-role.kubernetes.io/virt=""`, and OpenShift Virtualization is - installed with VSOCK. A single RHEL VM is created from - `quay.io/rhacs-eng/vm-images` using `vm-os`. On e2 workers, - KVM emulation is used. + installed with VSOCK. All workers are `n2-standard-8` (overrides + `worker-node-type`) so the VM has /dev/kvm. Masters are unchanged. + A single RHEL VM is created from `quay.io/rhacs-eng/vm-images` + using `vm-os`. - name: vm-os description: OS for the virtual machine (rhel9, rhel10) From 9ecdf933c4a2fcd100ffadd9193444c19edb20e8 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 15 Sep 2026 10:50:05 +0200 Subject: [PATCH 03/13] ROX-35000: reject workers without nested KVM when install-virt is on VMs need /dev/kvm. Fail create if worker-node-type is a GCP series that does not expose nested virtualization. Any n2/c3/n4d size is accepted; e2 is not. Prompt: add validation when virt is true; it does not have to be precisely n2-standard-8, just types that support KVM. Code partially generated by AI. Co-authored-by: Cursor --- chart/infra-server/static/flavors.yaml | 8 +- pkg/service/cluster/cluster.go | 7 + pkg/service/cluster/helpers.go | 42 ++++++ pkg/service/cluster/helpers_test.go | 172 +++++++++++++++++++++++++ 4 files changed, 225 insertions(+), 4 deletions(-) diff --git a/chart/infra-server/static/flavors.yaml b/chart/infra-server/static/flavors.yaml index 19d63864e..3f190dd6e 100644 --- a/chart/infra-server/static/flavors.yaml +++ b/chart/infra-server/static/flavors.yaml @@ -340,8 +340,8 @@ help: | When true, one extra worker is added at IPI install time, labeled `node-role.kubernetes.io/virt=""`, and OpenShift Virtualization is - installed with VSOCK. All workers are `n2-standard-8` (overrides - `worker-node-type`) so the VM has /dev/kvm. Masters are unchanged. + installed with VSOCK. `worker-node-type` must expose nested KVM + (for example `n2-standard-8`; not `e2`). Masters are unchanged. A single RHEL VM is created from `quay.io/rhacs-eng/vm-images` using `vm-os`. @@ -723,8 +723,8 @@ help: | When true, one extra worker is added at IPI install time, labeled `node-role.kubernetes.io/virt=""`, and OpenShift Virtualization is - installed with VSOCK. All workers are `n2-standard-8` (overrides - `worker-node-type`) so the VM has /dev/kvm. Masters are unchanged. + installed with VSOCK. `worker-node-type` must expose nested KVM + (for example `n2-standard-8`; not `e2`). Masters are unchanged. A single RHEL VM is created from `quay.io/rhacs-eng/vm-images` using `vm-os`. diff --git a/pkg/service/cluster/cluster.go b/pkg/service/cluster/cluster.go index 11bd9436c..1d7cf16d6 100644 --- a/pkg/service/cluster/cluster.go +++ b/pkg/service/cluster/cluster.go @@ -977,5 +977,12 @@ func checkAndEnrichParameters(flavorParams map[string]*v1.Parameter, requestPara } } + if err := validateVirtWorkerNodeType( + workflowParameterValue(allParams, "install-virt"), + workflowParameterValue(allParams, "worker-node-type"), + ); err != nil { + return nil, err + } + return allParams, nil } diff --git a/pkg/service/cluster/helpers.go b/pkg/service/cluster/helpers.go index 1c19c0fc2..7947fd914 100644 --- a/pkg/service/cluster/helpers.go +++ b/pkg/service/cluster/helpers.go @@ -17,6 +17,18 @@ import ( "k8s.io/apimachinery/pkg/selection" ) +// virtNestedKVMWorkerPrefixes are GCP series that expose nested KVM. +// E2, ARM, AMD (except n4d), and memory-optimized series do not: +// https://cloud.google.com/compute/docs/instances/nested-virtualization/overview +var virtNestedKVMWorkerPrefixes = []string{ + "a2-", "a3-", "a4-", + "c2-", "c3-", "c4-", "c4n-", + "g2-", + "h3-", + "n1-", "n2-", "n4-", "n4d-", + "z3-", +} + func getClusterIDFromWorkflow(workflow *v1alpha1.Workflow) string { clusterID := GetClusterID(workflow) if clusterID == "" { @@ -296,6 +308,36 @@ func emailToLabelValue(email string) string { return result } +// validateVirtWorkerNodeType rejects machine types that lack nested KVM when virt is enabled. +func validateVirtWorkerNodeType(installVirt, workerType string) error { + if !strings.EqualFold(installVirt, "true") { + return nil + } + if workerTypeHasNestedKVM(workerType) { + return nil + } + return fmt.Errorf("install-virt requires a worker-node-type with nested kvm (for example n2-standard-8), got %q", workerType) +} + +func workerTypeHasNestedKVM(workerType string) bool { + t := strings.ToLower(workerType) + for _, prefix := range virtNestedKVMWorkerPrefixes { + if strings.HasPrefix(t, prefix) { + return true + } + } + return false +} + +func workflowParameterValue(params []v1alpha1.Parameter, name string) string { + for _, p := range params { + if p.Name == name { + return p.GetValue() + } + } + return "" +} + // validateClusterID validates that a cluster ID meets Kubernetes label value requirements. // Kubernetes label values must match ([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9] and be at most 63 characters. func validateClusterID(clusterID string) error { diff --git a/pkg/service/cluster/helpers_test.go b/pkg/service/cluster/helpers_test.go index f0a329f18..449d75f66 100644 --- a/pkg/service/cluster/helpers_test.go +++ b/pkg/service/cluster/helpers_test.go @@ -308,3 +308,175 @@ func TestValidateClusterID(t *testing.T) { }) } } + +func TestValidateVirtWorkerNodeType(t *testing.T) { + tests := []struct { + name string + installVirt string + workerType string + wantErr bool + }{ + { + name: "virt off default e2", + installVirt: "false", + workerType: "e2-standard-8", + }, + { + name: "virt omitted", + installVirt: "", + workerType: "e2-standard-8", + }, + { + name: "virt on n2-standard-8", + installVirt: "true", + workerType: "n2-standard-8", + }, + { + name: "virt on n2-standard-4", + installVirt: "true", + workerType: "n2-standard-4", + }, + { + name: "virt on n2 with uppercase flag", + installVirt: "TRUE", + workerType: "n2-standard-8", + }, + { + name: "virt on c3", + installVirt: "true", + workerType: "c3-standard-8", + }, + { + name: "virt on n4d (AMD exception)", + installVirt: "true", + workerType: "n4d-standard-8", + }, + { + name: "virt on default e2", + installVirt: "true", + workerType: "e2-standard-8", + wantErr: true, + }, + { + name: "virt on n2d (AMD)", + installVirt: "true", + workerType: "n2d-standard-8", + wantErr: true, + }, + { + name: "virt on t2a (ARM)", + installVirt: "true", + workerType: "t2a-standard-8", + wantErr: true, + }, + { + name: "virt on m3 (memory-optimized)", + installVirt: "true", + workerType: "m3-ultramem-32", + wantErr: true, + }, + { + name: "virt on empty worker type", + installVirt: "true", + workerType: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateVirtWorkerNodeType(tt.installVirt, tt.workerType) + if tt.wantErr && err == nil { + t.Errorf("validateVirtWorkerNodeType(%q, %q) expected error but got none", tt.installVirt, tt.workerType) + } + if !tt.wantErr && err != nil { + t.Errorf("validateVirtWorkerNodeType(%q, %q) expected no error but got: %v", tt.installVirt, tt.workerType, err) + } + }) + } +} + +func TestCheckAndEnrichParameters_VirtWorkerType(t *testing.T) { + flavorParams := map[string]*v1.Parameter{ + "name": {Name: "name"}, + "install-virt": { + Name: "install-virt", + Value: "false", + Optional: true, + }, + "worker-node-type": { + Name: "worker-node-type", + Value: "e2-standard-8", + Optional: true, + }, + } + + tests := []struct { + name string + req map[string]string + wantErr string + wantWorkerType string + }{ + { + name: "virt omitted keeps default e2", + req: map[string]string{"name": "abc"}, + wantWorkerType: "e2-standard-8", + }, + { + name: "virt true with n2-standard-8", + req: map[string]string{ + "name": "abc", + "install-virt": "true", + "worker-node-type": "n2-standard-8", + }, + wantWorkerType: "n2-standard-8", + }, + { + name: "virt true with n2-standard-4", + req: map[string]string{ + "name": "abc", + "install-virt": "true", + "worker-node-type": "n2-standard-4", + }, + wantWorkerType: "n2-standard-4", + }, + { + name: "virt true with default e2", + req: map[string]string{ + "name": "abc", + "install-virt": "true", + }, + wantErr: "install-virt requires a worker-node-type with nested kvm", + }, + { + name: "virt true with explicit e2", + req: map[string]string{ + "name": "abc", + "install-virt": "true", + "worker-node-type": "e2-standard-8", + }, + wantErr: "install-virt requires a worker-node-type with nested kvm", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := checkAndEnrichParameters(flavorParams, tt.req) + if tt.wantErr != "" { + if err == nil { + t.Fatalf("expected error containing %q, got none", tt.wantErr) + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("error %q does not contain %q", err.Error(), tt.wantErr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gotType := workflowParameterValue(got, "worker-node-type"); gotType != tt.wantWorkerType { + t.Errorf("worker-node-type = %q, want %q", gotType, tt.wantWorkerType) + } + }) + } +} From f3098ae3062c35338ed7b3060b9ee163641ffa43 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 16 Sep 2026 11:32:11 +0200 Subject: [PATCH 04/13] ROX-35000: copy quay pull secret into the workflow namespace Argo openshift-4 workflows run in default and mount infra-image-registry-pull-secret for the VM containerDisk. The chart only created that secret in namespace infra. Prompt: address CodeRabbit review (default-namespace registry secret). Partially generated by AI. Co-authored-by: Cursor --- .../templates/openshift-4/secrets.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/chart/infra-server/templates/openshift-4/secrets.yaml b/chart/infra-server/templates/openshift-4/secrets.yaml index 3fe49ca72..1f9aa6987 100644 --- a/chart/infra-server/templates/openshift-4/secrets.yaml +++ b/chart/infra-server/templates/openshift-4/secrets.yaml @@ -43,3 +43,18 @@ metadata: data: REDHAT_PULL_SECRET: |- {{ required ".Values.openshift_4__redhat_pull_secret_json is undefined" .Values.openshift_4__redhat_pull_secret_json }} + +--- + +# Workflows run in namespace default and mount this for the VM containerDisk +# pull. The infra-namespace copy is the server's imagePullSecret. +apiVersion: v1 +kind: Secret +type: kubernetes.io/dockerconfigjson + +metadata: + name: infra-image-registry-pull-secret + namespace: default + +data: + .dockerconfigjson: {{ template "pull-secret" .Values.pullSecrets.quay }} From 669ff8f424c5b38144e62619762cf1749e9bd403 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 16 Sep 2026 11:43:58 +0200 Subject: [PATCH 05/13] Fix golint style issues --- cmd/infractl/cli/upgrade/fancy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/infractl/cli/upgrade/fancy.go b/cmd/infractl/cli/upgrade/fancy.go index 7888d2089..0865d3318 100644 --- a/cmd/infractl/cli/upgrade/fancy.go +++ b/cmd/infractl/cli/upgrade/fancy.go @@ -7,11 +7,11 @@ import ( ) type prettyCliUpgrade struct { - updatedFilename string + UpdatedFilename string `json:"updatedFilename"` } func (p prettyCliUpgrade) PrettyPrint(cmd *cobra.Command) { - cmd.Printf("Updated %s to match the infra server version\n", p.updatedFilename) + cmd.Printf("Updated %s to match the infra server version\n", p.UpdatedFilename) } func (p prettyCliUpgrade) PrettyJSONPrint(cmd *cobra.Command) error { From 29e681aa85792358ae514aa6ba0df0534a2d8ba7 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 16 Sep 2026 12:09:07 +0200 Subject: [PATCH 06/13] ROX-35000: TEMP pin automation-flavors to PR 369 snapshot infra-pr-1910 needs 0.15.7-8-g1256d1b0b7-snapshot to run virt.sh. Revert before merge; master must stay on a released tag. Prompt: temporary Chart.yaml pin to test flavors PR 369. Partially generated by AI. Co-authored-by: Cursor --- chart/infra-server/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/infra-server/Chart.yaml b/chart/infra-server/Chart.yaml index 53b210135..6ab0bf772 100644 --- a/chart/infra-server/Chart.yaml +++ b/chart/infra-server/Chart.yaml @@ -8,7 +8,7 @@ sources: - https://github.com/stackrox/infra annotations: acsDemoVersion: 4.11.2 - automationFlavorsVersion: 0.15.5 + automationFlavorsVersion: 0.15.7-8-g1256d1b0b7-snapshot ocpCredentialsMode: Passthrough dependencies: - name: argo-workflows From cc5cdd2bf5a5b3be04d818b138ba56135de1e21a Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:16:18 +0200 Subject: [PATCH 07/13] Error handling and version pin --- chart/infra-server/Chart.yaml | 2 +- pkg/service/cluster/cluster.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chart/infra-server/Chart.yaml b/chart/infra-server/Chart.yaml index 6ab0bf772..75dafbbab 100644 --- a/chart/infra-server/Chart.yaml +++ b/chart/infra-server/Chart.yaml @@ -8,7 +8,7 @@ sources: - https://github.com/stackrox/infra annotations: acsDemoVersion: 4.11.2 - automationFlavorsVersion: 0.15.7-8-g1256d1b0b7-snapshot + automationFlavorsVersion: 0.16.0-4-g1256d1b0b7-snapshot ocpCredentialsMode: Passthrough dependencies: - name: argo-workflows diff --git a/pkg/service/cluster/cluster.go b/pkg/service/cluster/cluster.go index 1d7cf16d6..80fdb3f49 100644 --- a/pkg/service/cluster/cluster.go +++ b/pkg/service/cluster/cluster.go @@ -352,7 +352,7 @@ func (s *clusterImpl) create(req *v1.CreateClusterRequest, owner, eventID string // insufficient or superfluous parameters. workflowParams, err := checkAndEnrichParameters(flav.Parameters, req.Parameters) if err != nil { - return nil, err + return nil, status.Errorf(codes.InvalidArgument, "%v", err) } workflow.Spec.Arguments.Parameters = workflowParams From 5832fbc4ed780504340887e932082dd99f498980 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:39:35 +0200 Subject: [PATCH 08/13] ROX-35000: show grpc-gateway create errors in the launch form The form printed axios's generic status text and looked for response.data.error. JSONPb returns google.rpc.Status.message. Prompt: show the worker-type validation error in the UI. Partially generated by AI. Co-authored-by: Cursor --- .../LaunchClusterPage/ClusterForm.tsx | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/ui/src/containers/LaunchClusterPage/ClusterForm.tsx b/ui/src/containers/LaunchClusterPage/ClusterForm.tsx index cd305c691..9bae5823f 100644 --- a/ui/src/containers/LaunchClusterPage/ClusterForm.tsx +++ b/ui/src/containers/LaunchClusterPage/ClusterForm.tsx @@ -13,6 +13,7 @@ import * as yup from 'yup'; import { mapValues } from 'lodash'; import { Button, Form } from '@patternfly/react-core'; import Markdown from 'react-markdown'; +import { AxiosError } from 'axios'; import { ClusterServiceApi, V1Parameter } from 'generated/client'; import configuration from 'client/configuration'; @@ -26,6 +27,19 @@ import { CloudUploadAltIcon } from '@patternfly/react-icons'; const clusterService = new ClusterServiceApi(configuration); +type GatewayErrorBody = { + message?: string; + error?: string; +}; + +// grpc-gateway JSONPb returns google.rpc.Status as { code, message }. +function serverErrorMessage(err: AxiosError): string { + const data = err.response?.data; + return ( + data?.message || data?.error || err.message || 'Cluster creation request failed' + ); +} + function helpByParameterName(name?: string): string { const help: { [key: string]: string } = { name: @@ -242,14 +256,7 @@ export default function ClusterForm({ Parameters: initialParameterValues, }; - const [error, setError] = useState<{ - message?: string; - response?: { - data?: { - error?: string; - }; - }; - }>(); + const [error, setError] = useState>(); const onSubmit = async ( values: FormikValues, @@ -264,9 +271,8 @@ export default function ClusterForm({ const { id } = response.data; if (!id) throw new Error('Server returned empty cluster ID'); onClusterCreated(id); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - setError(e); + } catch (e) { + setError(e as AxiosError); } finally { actions.setSubmitting(false); } @@ -283,8 +289,7 @@ export default function ClusterForm({
{error && (
- {`[Server Error] ${error.message || 'Cluster creation request failed'}`} - {error.response?.data?.error && ` (${error.response.data.error})`} + {`[Server Error] ${serverErrorMessage(error)}`}
)} From f99232e95e30d335472c04a1deb7bfdc622d91a1 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 17 Sep 2026 10:50:31 +0200 Subject: [PATCH 09/13] ROX-35000: drive virt from a comma-separated vm-os list Drop install-virt and virt-node-dedicated. A non-empty vm-os list is the virt switch and names the guests. Nested-KVM validation keys off that list. Help notes that roughly two VMs fit on three workers. Prompt: one CSV guest-OS field; empty skips virt; suggestion not a cap. Partially generated by AI. Co-authored-by: Cursor --- chart/infra-server/static/flavors.yaml | 72 ++----- .../static/workflow-openshift-4.yaml | 10 +- pkg/service/cluster/cluster.go | 2 +- pkg/service/cluster/helpers.go | 33 +++- pkg/service/cluster/helpers_test.go | 180 ++++++++++++------ 5 files changed, 166 insertions(+), 131 deletions(-) diff --git a/chart/infra-server/static/flavors.yaml b/chart/infra-server/static/flavors.yaml index 3f190dd6e..bbdf31e26 100644 --- a/chart/infra-server/static/flavors.yaml +++ b/chart/infra-server/static/flavors.yaml @@ -333,35 +333,17 @@ Defines a list of capabilities to explicitly enable. These capabilities are enabled in addition to the capabilities specified in the baseline capability set. Example: `["DeploymentConfig", "ImageRegistry"]` - - name: install-virt - description: Install OpenShift Virtualization and create a VM - value: false - kind: optional - help: | - When true, one extra worker is added at IPI install time, labeled - `node-role.kubernetes.io/virt=""`, and OpenShift Virtualization is - installed with VSOCK. `worker-node-type` must expose nested KVM - (for example `n2-standard-8`; not `e2`). Masters are unchanged. - A single RHEL VM is created from `quay.io/rhacs-eng/vm-images` - using `vm-os`. - - name: vm-os - description: OS for the virtual machine (rhel9, rhel10) - value: rhel9 - kind: optional - help: | - The RHEL version for the virtual machine container disk. - Valid values: rhel9, rhel10. - The image used is quay.io/rhacs-eng/vm-images:-dnf-primed-latest. - - - name: virt-node-dedicated - description: Taint the virt worker so only VMs are scheduled on it - value: true + description: Guest OS list for optional VMs (empty skips virt) + value: "" kind: optional help: | - When true, the virt worker is tainted with - `node-role.kubernetes.io/virt:NoSchedule`. Set false to let - other workloads share that node. Only used when install-virt is true. + Comma-separated guest OS list (`rhel9` or `rhel10`). Each entry + creates one VM. Empty skips OpenShift Virtualization. + Example: `rhel9,rhel9,rhel10`. + Non-empty lists require a nested-KVM `worker-node-type` (for example + `n2-standard-8`; not `e2`). Worker count is unchanged. + Roughly two VMs fit on three worker nodes. artifacts: - name: kubeconfig @@ -394,7 +376,7 @@ description: The password to login at the openshift console - name: vm-access - description: SSH credentials for the optional demo VM (present when install-virt=true) + description: SSH credentials for optional VMs (present when vm-os is non-empty) ###################### # openshift-4-demo # @@ -716,35 +698,17 @@ Defines a list of capabilities to explicitly enable. These capabilities are enabled in addition to the capabilities specified in the baseline capability set. Example: `["DeploymentConfig", "ImageRegistry"]` - - name: install-virt - description: Install OpenShift Virtualization and create a VM - value: false - kind: optional - help: | - When true, one extra worker is added at IPI install time, labeled - `node-role.kubernetes.io/virt=""`, and OpenShift Virtualization is - installed with VSOCK. `worker-node-type` must expose nested KVM - (for example `n2-standard-8`; not `e2`). Masters are unchanged. - A single RHEL VM is created from `quay.io/rhacs-eng/vm-images` - using `vm-os`. - - name: vm-os - description: OS for the virtual machine (rhel9, rhel10) - value: rhel9 - kind: optional - help: | - The RHEL version for the virtual machine container disk. - Valid values: rhel9, rhel10. - The image used is quay.io/rhacs-eng/vm-images:-dnf-primed-latest. - - - name: virt-node-dedicated - description: Taint the virt worker so only VMs are scheduled on it - value: true + description: Guest OS list for optional VMs (empty skips virt) + value: "" kind: optional help: | - When true, the virt worker is tainted with - `node-role.kubernetes.io/virt:NoSchedule`. Set false to let - other workloads share that node. Only used when install-virt is true. + Comma-separated guest OS list (`rhel9` or `rhel10`). Each entry + creates one VM. Empty skips OpenShift Virtualization. + Example: `rhel9,rhel9,rhel10`. + Non-empty lists require a nested-KVM `worker-node-type` (for example + `n2-standard-8`; not `e2`). Worker count is unchanged. + Roughly two VMs fit on three worker nodes. artifacts: - name: kubeconfig @@ -777,7 +741,7 @@ description: The password to login at the openshift console - name: vm-access - description: SSH credentials for the optional demo VM (present when install-virt=true) + description: SSH credentials for optional VMs (present when vm-os is non-empty) ##################### # AWS EKS # diff --git a/chart/infra-server/static/workflow-openshift-4.yaml b/chart/infra-server/static/workflow-openshift-4.yaml index 98c3b1a20..74866f881 100644 --- a/chart/infra-server/static/workflow-openshift-4.yaml +++ b/chart/infra-server/static/workflow-openshift-4.yaml @@ -34,12 +34,8 @@ spec: value: "vCurrent" - name: additional-enabled-capabilities value: "" - - name: install-virt - value: "false" - name: vm-os - value: "rhel9" - - name: virt-node-dedicated - value: "true" + value: "" volumeClaimTemplates: - metadata: name: data @@ -171,12 +167,8 @@ spec: value: '{{ "{{" }}workflow.parameters.baseline-capability-set{{ "}}" }}' - name: ADDITIONAL_ENABLED_CAPABILITIES value: '{{ "{{" }}workflow.parameters.additional-enabled-capabilities{{ "}}" }}' - - name: INSTALL_VIRT - value: '{{ "{{" }}workflow.parameters.install-virt{{ "}}" }}' - name: VM_OS value: '{{ "{{" }}workflow.parameters.vm-os{{ "}}" }}' - - name: VIRT_NODE_DEDICATED - value: '{{ "{{" }}workflow.parameters.virt-node-dedicated{{ "}}" }}' volumeMounts: - name: data mountPath: /data diff --git a/pkg/service/cluster/cluster.go b/pkg/service/cluster/cluster.go index 80fdb3f49..dc6ad10bc 100644 --- a/pkg/service/cluster/cluster.go +++ b/pkg/service/cluster/cluster.go @@ -978,7 +978,7 @@ func checkAndEnrichParameters(flavorParams map[string]*v1.Parameter, requestPara } if err := validateVirtWorkerNodeType( - workflowParameterValue(allParams, "install-virt"), + workflowParameterValue(allParams, "vm-os"), workflowParameterValue(allParams, "worker-node-type"), ); err != nil { return nil, err diff --git a/pkg/service/cluster/helpers.go b/pkg/service/cluster/helpers.go index 7947fd914..d23aadf9f 100644 --- a/pkg/service/cluster/helpers.go +++ b/pkg/service/cluster/helpers.go @@ -308,15 +308,40 @@ func emailToLabelValue(email string) string { return result } -// validateVirtWorkerNodeType rejects machine types that lack nested KVM when virt is enabled. -func validateVirtWorkerNodeType(installVirt, workerType string) error { - if !strings.EqualFold(installVirt, "true") { +// parseVMOSList splits a comma-separated guest OS list. Empty input means no VMs. +func parseVMOSList(vmOS string) ([]string, error) { + if strings.TrimSpace(vmOS) == "" { + return nil, nil + } + var oses []string + for token := range strings.SplitSeq(vmOS, ",") { + os := strings.ToLower(strings.TrimSpace(token)) + if os == "" { + return nil, fmt.Errorf("vm-os contains an empty entry") + } + switch os { + case "rhel9", "rhel10": + oses = append(oses, os) + default: + return nil, fmt.Errorf("unsupported vm-os %q (valid values: rhel9, rhel10)", os) + } + } + return oses, nil +} + +// validateVirtWorkerNodeType rejects machine types that lack nested KVM when any VM is requested. +func validateVirtWorkerNodeType(vmOS, workerType string) error { + oses, err := parseVMOSList(vmOS) + if err != nil { + return err + } + if len(oses) == 0 { return nil } if workerTypeHasNestedKVM(workerType) { return nil } - return fmt.Errorf("install-virt requires a worker-node-type with nested kvm (for example n2-standard-8), got %q", workerType) + return fmt.Errorf("vm-os requires a worker-node-type with nested kvm (for example n2-standard-8), got %q", workerType) } func workerTypeHasNestedKVM(workerType string) bool { diff --git a/pkg/service/cluster/helpers_test.go b/pkg/service/cluster/helpers_test.go index 449d75f66..39078c584 100644 --- a/pkg/service/cluster/helpers_test.go +++ b/pkg/service/cluster/helpers_test.go @@ -2,6 +2,7 @@ package cluster import ( "regexp" + "slices" "strings" "testing" @@ -309,88 +310,132 @@ func TestValidateClusterID(t *testing.T) { } } +func TestParseVMOSList(t *testing.T) { + tests := []struct { + name string + vmOS string + want []string + wantErr string + }{ + {name: "empty", vmOS: ""}, + {name: "whitespace", vmOS: " "}, + {name: "single", vmOS: "rhel9", want: []string{"rhel9"}}, + {name: "mixed with spaces", vmOS: "rhel9, rhel9, rhel10", want: []string{"rhel9", "rhel9", "rhel10"}}, + {name: "uppercase", vmOS: "RHEL9", want: []string{"rhel9"}}, + {name: "empty entry", vmOS: "rhel9,,rhel10", wantErr: "empty entry"}, + {name: "trailing comma", vmOS: "rhel9,", wantErr: "empty entry"}, + {name: "unsupported", vmOS: "rhel8", wantErr: "unsupported vm-os"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseVMOSList(tt.vmOS) + if tt.wantErr != "" { + if err == nil { + t.Fatalf("expected error containing %q, got none", tt.wantErr) + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("error %q does not contain %q", err.Error(), tt.wantErr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !slices.Equal(got, tt.want) { + t.Errorf("parseVMOSList(%q) = %q, want %q", tt.vmOS, got, tt.want) + } + }) + } +} + func TestValidateVirtWorkerNodeType(t *testing.T) { tests := []struct { - name string - installVirt string - workerType string - wantErr bool + name string + vmOS string + workerType string + wantErr bool }{ { - name: "virt off default e2", - installVirt: "false", - workerType: "e2-standard-8", + name: "empty vm-os default e2", + vmOS: "", + workerType: "e2-standard-8", }, { - name: "virt omitted", - installVirt: "", - workerType: "e2-standard-8", + name: "whitespace vm-os", + vmOS: " ", + workerType: "e2-standard-8", }, { - name: "virt on n2-standard-8", - installVirt: "true", - workerType: "n2-standard-8", + name: "rhel9 on n2-standard-8", + vmOS: "rhel9", + workerType: "n2-standard-8", }, { - name: "virt on n2-standard-4", - installVirt: "true", - workerType: "n2-standard-4", + name: "list on n2-standard-4", + vmOS: "rhel9,rhel10", + workerType: "n2-standard-4", }, { - name: "virt on n2 with uppercase flag", - installVirt: "TRUE", - workerType: "n2-standard-8", + name: "uppercase os on n2", + vmOS: "RHEL9", + workerType: "n2-standard-8", }, { - name: "virt on c3", - installVirt: "true", - workerType: "c3-standard-8", + name: "rhel9 on c3", + vmOS: "rhel9", + workerType: "c3-standard-8", }, { - name: "virt on n4d (AMD exception)", - installVirt: "true", - workerType: "n4d-standard-8", + name: "rhel9 on n4d (AMD exception)", + vmOS: "rhel9", + workerType: "n4d-standard-8", }, { - name: "virt on default e2", - installVirt: "true", - workerType: "e2-standard-8", - wantErr: true, + name: "rhel9 on default e2", + vmOS: "rhel9", + workerType: "e2-standard-8", + wantErr: true, }, { - name: "virt on n2d (AMD)", - installVirt: "true", - workerType: "n2d-standard-8", - wantErr: true, + name: "rhel9 on n2d (AMD)", + vmOS: "rhel9", + workerType: "n2d-standard-8", + wantErr: true, }, { - name: "virt on t2a (ARM)", - installVirt: "true", - workerType: "t2a-standard-8", - wantErr: true, + name: "rhel9 on t2a (ARM)", + vmOS: "rhel9", + workerType: "t2a-standard-8", + wantErr: true, }, { - name: "virt on m3 (memory-optimized)", - installVirt: "true", - workerType: "m3-ultramem-32", - wantErr: true, + name: "rhel9 on m3 (memory-optimized)", + vmOS: "rhel9", + workerType: "m3-ultramem-32", + wantErr: true, }, { - name: "virt on empty worker type", - installVirt: "true", - workerType: "", - wantErr: true, + name: "rhel9 on empty worker type", + vmOS: "rhel9", + workerType: "", + wantErr: true, + }, + { + name: "unsupported os", + vmOS: "debian", + workerType: "n2-standard-8", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := validateVirtWorkerNodeType(tt.installVirt, tt.workerType) + err := validateVirtWorkerNodeType(tt.vmOS, tt.workerType) if tt.wantErr && err == nil { - t.Errorf("validateVirtWorkerNodeType(%q, %q) expected error but got none", tt.installVirt, tt.workerType) + t.Errorf("validateVirtWorkerNodeType(%q, %q) expected error but got none", tt.vmOS, tt.workerType) } if !tt.wantErr && err != nil { - t.Errorf("validateVirtWorkerNodeType(%q, %q) expected no error but got: %v", tt.installVirt, tt.workerType, err) + t.Errorf("validateVirtWorkerNodeType(%q, %q) expected no error but got: %v", tt.vmOS, tt.workerType, err) } }) } @@ -399,9 +444,9 @@ func TestValidateVirtWorkerNodeType(t *testing.T) { func TestCheckAndEnrichParameters_VirtWorkerType(t *testing.T) { flavorParams := map[string]*v1.Parameter{ "name": {Name: "name"}, - "install-virt": { - Name: "install-virt", - Value: "false", + "vm-os": { + Name: "vm-os", + Value: "", Optional: true, }, "worker-node-type": { @@ -418,44 +463,53 @@ func TestCheckAndEnrichParameters_VirtWorkerType(t *testing.T) { wantWorkerType string }{ { - name: "virt omitted keeps default e2", + name: "vm-os omitted keeps default e2", req: map[string]string{"name": "abc"}, wantWorkerType: "e2-standard-8", }, { - name: "virt true with n2-standard-8", + name: "rhel9 with n2-standard-8", req: map[string]string{ "name": "abc", - "install-virt": "true", + "vm-os": "rhel9", "worker-node-type": "n2-standard-8", }, wantWorkerType: "n2-standard-8", }, { - name: "virt true with n2-standard-4", + name: "list with n2-standard-4", req: map[string]string{ "name": "abc", - "install-virt": "true", + "vm-os": "rhel9,rhel10", "worker-node-type": "n2-standard-4", }, wantWorkerType: "n2-standard-4", }, { - name: "virt true with default e2", + name: "rhel9 with default e2", req: map[string]string{ - "name": "abc", - "install-virt": "true", + "name": "abc", + "vm-os": "rhel9", }, - wantErr: "install-virt requires a worker-node-type with nested kvm", + wantErr: "vm-os requires a worker-node-type with nested kvm", }, { - name: "virt true with explicit e2", + name: "rhel9 with explicit e2", req: map[string]string{ "name": "abc", - "install-virt": "true", + "vm-os": "rhel9", "worker-node-type": "e2-standard-8", }, - wantErr: "install-virt requires a worker-node-type with nested kvm", + wantErr: "vm-os requires a worker-node-type with nested kvm", + }, + { + name: "unsupported os", + req: map[string]string{ + "name": "abc", + "vm-os": "debian", + "worker-node-type": "n2-standard-8", + }, + wantErr: "unsupported vm-os", }, } From 887ba86d6b7891186ac8f5e646845cec268bfd3e Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:20:31 +0200 Subject: [PATCH 10/13] ROX-35000: format ClusterForm error fallback for prettier CI image build failed eslint prettier/prettier on the grpc-gateway message helper. Put the return on one line. Prompt: fix the prettier failure in ClusterForm.tsx. Partially generated by AI. Co-authored-by: Cursor --- ui/src/containers/LaunchClusterPage/ClusterForm.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/src/containers/LaunchClusterPage/ClusterForm.tsx b/ui/src/containers/LaunchClusterPage/ClusterForm.tsx index 9bae5823f..357fe2738 100644 --- a/ui/src/containers/LaunchClusterPage/ClusterForm.tsx +++ b/ui/src/containers/LaunchClusterPage/ClusterForm.tsx @@ -35,9 +35,7 @@ type GatewayErrorBody = { // grpc-gateway JSONPb returns google.rpc.Status as { code, message }. function serverErrorMessage(err: AxiosError): string { const data = err.response?.data; - return ( - data?.message || data?.error || err.message || 'Cluster creation request failed' - ); + return data?.message || data?.error || err.message || 'Cluster creation request failed'; } function helpByParameterName(name?: string): string { From f4c309e54d7186a42bd5d396c6c7b4a8a7430d39 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:45:30 +0200 Subject: [PATCH 11/13] ROX-35000: TEMP pin automation-flavors to 0.16.0-5-g65755bbf48-snapshot Retarget the PR 369 pin to the openshift-4 image from 65755bb (vm-os list, no extra worker). Revert before merge. Prompt: pin Chart.yaml to the newest snapshot from flavors PR 369. Partially generated by AI. Co-authored-by: Cursor --- chart/infra-server/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/infra-server/Chart.yaml b/chart/infra-server/Chart.yaml index 75dafbbab..5237f7bc0 100644 --- a/chart/infra-server/Chart.yaml +++ b/chart/infra-server/Chart.yaml @@ -8,7 +8,7 @@ sources: - https://github.com/stackrox/infra annotations: acsDemoVersion: 4.11.2 - automationFlavorsVersion: 0.16.0-4-g1256d1b0b7-snapshot + automationFlavorsVersion: 0.16.0-5-g65755bbf48-snapshot ocpCredentialsMode: Passthrough dependencies: - name: argo-workflows From 77978348c8df07725e37453344ceb796ccbab65f Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:39:44 +0200 Subject: [PATCH 12/13] Add support for rhel8 --- chart/infra-server/static/flavors.yaml | 8 ++++---- pkg/service/cluster/helpers.go | 4 ++-- pkg/service/cluster/helpers_test.go | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/chart/infra-server/static/flavors.yaml b/chart/infra-server/static/flavors.yaml index bbdf31e26..3201838f7 100644 --- a/chart/infra-server/static/flavors.yaml +++ b/chart/infra-server/static/flavors.yaml @@ -338,8 +338,8 @@ value: "" kind: optional help: | - Comma-separated guest OS list (`rhel9` or `rhel10`). Each entry - creates one VM. Empty skips OpenShift Virtualization. + Comma-separated guest OS list (`rhel8`, `rhel9`, or `rhel10`). Each + entry creates one VM. Empty skips OpenShift Virtualization. Example: `rhel9,rhel9,rhel10`. Non-empty lists require a nested-KVM `worker-node-type` (for example `n2-standard-8`; not `e2`). Worker count is unchanged. @@ -703,8 +703,8 @@ value: "" kind: optional help: | - Comma-separated guest OS list (`rhel9` or `rhel10`). Each entry - creates one VM. Empty skips OpenShift Virtualization. + Comma-separated guest OS list (`rhel8`, `rhel9`, or `rhel10`). Each + entry creates one VM. Empty skips OpenShift Virtualization. Example: `rhel9,rhel9,rhel10`. Non-empty lists require a nested-KVM `worker-node-type` (for example `n2-standard-8`; not `e2`). Worker count is unchanged. diff --git a/pkg/service/cluster/helpers.go b/pkg/service/cluster/helpers.go index d23aadf9f..ddd37909a 100644 --- a/pkg/service/cluster/helpers.go +++ b/pkg/service/cluster/helpers.go @@ -320,10 +320,10 @@ func parseVMOSList(vmOS string) ([]string, error) { return nil, fmt.Errorf("vm-os contains an empty entry") } switch os { - case "rhel9", "rhel10": + case "rhel8", "rhel9", "rhel10": oses = append(oses, os) default: - return nil, fmt.Errorf("unsupported vm-os %q (valid values: rhel9, rhel10)", os) + return nil, fmt.Errorf("unsupported vm-os %q (valid values: rhel8, rhel9, rhel10)", os) } } return oses, nil diff --git a/pkg/service/cluster/helpers_test.go b/pkg/service/cluster/helpers_test.go index 39078c584..69bb96309 100644 --- a/pkg/service/cluster/helpers_test.go +++ b/pkg/service/cluster/helpers_test.go @@ -320,11 +320,12 @@ func TestParseVMOSList(t *testing.T) { {name: "empty", vmOS: ""}, {name: "whitespace", vmOS: " "}, {name: "single", vmOS: "rhel9", want: []string{"rhel9"}}, - {name: "mixed with spaces", vmOS: "rhel9, rhel9, rhel10", want: []string{"rhel9", "rhel9", "rhel10"}}, + {name: "rhel8", vmOS: "rhel8", want: []string{"rhel8"}}, + {name: "mixed with spaces", vmOS: "rhel8, rhel9, rhel10", want: []string{"rhel8", "rhel9", "rhel10"}}, {name: "uppercase", vmOS: "RHEL9", want: []string{"rhel9"}}, {name: "empty entry", vmOS: "rhel9,,rhel10", wantErr: "empty entry"}, {name: "trailing comma", vmOS: "rhel9,", wantErr: "empty entry"}, - {name: "unsupported", vmOS: "rhel8", wantErr: "unsupported vm-os"}, + {name: "unsupported", vmOS: "rhel7", wantErr: "unsupported vm-os"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 4d25216641ea2c32fcb9c2378a38275523859d69 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:41:00 +0200 Subject: [PATCH 13/13] ROX-35000: restore automationFlavorsVersion to 0.15.5 Drop the snapshot pin used to test flavors PR 369. Master stays on the released tag; the flavors bot bumps this after that PR merges. Prompt: let's undo the temporary commit from infra now Partially generated by AI. Co-authored-by: Cursor --- chart/infra-server/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/infra-server/Chart.yaml b/chart/infra-server/Chart.yaml index 5237f7bc0..53b210135 100644 --- a/chart/infra-server/Chart.yaml +++ b/chart/infra-server/Chart.yaml @@ -8,7 +8,7 @@ sources: - https://github.com/stackrox/infra annotations: acsDemoVersion: 4.11.2 - automationFlavorsVersion: 0.16.0-5-g65755bbf48-snapshot + automationFlavorsVersion: 0.15.5 ocpCredentialsMode: Passthrough dependencies: - name: argo-workflows