From 9dc78378267a6064e3d65bc2f952c00717bfff8c Mon Sep 17 00:00:00 2001 From: splatypus-bot <282974456+splatypus-bot@users.noreply.github.com> Date: Fri, 8 May 2026 22:29:50 -0400 Subject: [PATCH 1/2] feat: add vsphere-component annotation to CredentialsRequest CR Add cloudcredential.openshift.io/vsphere-component: machineAPI annotation to the openshift-machine-api-vsphere CredentialsRequest so CCO can route per-component credentials in PerComponent mode (story #39). Add manifest annotation unit tests (test/manifest/). Co-Authored-By: Claude Sonnet 4.6 (1M context) --- ...e-api-operator_00_credentials-request.yaml | 1 + .../credentials_request_annotation_test.go | 104 ++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 test/manifest/credentials_request_annotation_test.go diff --git a/install/0000_30_machine-api-operator_00_credentials-request.yaml b/install/0000_30_machine-api-operator_00_credentials-request.yaml index 6cdf28399a..109b051f57 100644 --- a/install/0000_30_machine-api-operator_00_credentials-request.yaml +++ b/install/0000_30_machine-api-operator_00_credentials-request.yaml @@ -230,6 +230,7 @@ metadata: namespace: openshift-cloud-credential-operator annotations: capability.openshift.io/name: MachineAPI+CloudCredential + cloudcredential.openshift.io/vsphere-component: machineAPI include.release.openshift.io/self-managed-high-availability: "true" spec: secretRef: diff --git a/test/manifest/credentials_request_annotation_test.go b/test/manifest/credentials_request_annotation_test.go new file mode 100644 index 0000000000..3afd9fb788 --- /dev/null +++ b/test/manifest/credentials_request_annotation_test.go @@ -0,0 +1,104 @@ +package manifest_test + +import ( + "os" + "strings" + "testing" + + "gopkg.in/yaml.v3" +) + +const vsphereComponentAnnotation = "cloudcredential.openshift.io/vsphere-component" + +type credentialsRequest struct { + Kind string `yaml:"kind"` + Metadata struct { + Name string `yaml:"name"` + Annotations map[string]string `yaml:"annotations"` + } `yaml:"metadata"` +} + +func parseCredentialsRequests(t *testing.T, path string) []credentialsRequest { + t.Helper() + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + var out []credentialsRequest + for _, doc := range strings.Split(string(data), "\n---") { + doc = strings.TrimSpace(doc) + if doc == "" { + continue + } + var cr credentialsRequest + if err := yaml.Unmarshal([]byte(doc), &cr); err != nil { + t.Fatalf("unmarshal %s: %v", path, err) + } + if cr.Kind == "CredentialsRequest" { + out = append(out, cr) + } + } + return out +} + +func findCR(crs []credentialsRequest, name string) (credentialsRequest, bool) { + for _, cr := range crs { + if cr.Metadata.Name == name { + return cr, true + } + } + return credentialsRequest{}, false +} + +func TestMAOVSphereCredentialsRequestAnnotation(t *testing.T) { + path := "../../install/0000_30_machine-api-operator_00_credentials-request.yaml" + crs := parseCredentialsRequests(t, path) + + cr, ok := findCR(crs, "openshift-machine-api-vsphere") + if !ok { + t.Fatal("CredentialsRequest 'openshift-machine-api-vsphere' not found in manifest") + } + + got, present := cr.Metadata.Annotations[vsphereComponentAnnotation] + if !present { + t.Fatalf("annotation %q missing from openshift-machine-api-vsphere", vsphereComponentAnnotation) + } + if got != "machineAPI" { + t.Errorf("annotation value: got %q, want %q", got, "machineAPI") + } +} + +func TestNonVSphereCredentialsRequestsUnmodified(t *testing.T) { + path := "../../install/0000_30_machine-api-operator_00_credentials-request.yaml" + crs := parseCredentialsRequests(t, path) + + nonVSphere := []string{ + "openshift-machine-api-aws", + "openshift-machine-api-azure", + "openshift-machine-api-gcp", + "openshift-machine-api-openstack", + } + for _, name := range nonVSphere { + cr, ok := findCR(crs, name) + if !ok { + continue + } + if _, present := cr.Metadata.Annotations[vsphereComponentAnnotation]; present { + t.Errorf("non-vsphere CR %q must NOT carry %s", name, vsphereComponentAnnotation) + } + } +} + +func TestAnnotationValueIsNotEmpty(t *testing.T) { + path := "../../install/0000_30_machine-api-operator_00_credentials-request.yaml" + crs := parseCredentialsRequests(t, path) + + cr, ok := findCR(crs, "openshift-machine-api-vsphere") + if !ok { + t.Fatal("CredentialsRequest 'openshift-machine-api-vsphere' not found in manifest") + } + val := cr.Metadata.Annotations[vsphereComponentAnnotation] + if strings.TrimSpace(val) == "" { + t.Errorf("annotation %q must not be empty", vsphereComponentAnnotation) + } +} From 0bf1b1f82a50e38d2f9381bf0355236c645bb760 Mon Sep 17 00:00:00 2001 From: splatypus-bot <282974456+splatypus-bot@users.noreply.github.com> Date: Mon, 11 May 2026 10:29:41 -0400 Subject: [PATCH 2/2] fix(test): fail-fast when expected non-vSphere CredentialsRequest is missing Replacing 'continue' with t.Fatalf ensures the test fails immediately if an expected non-vSphere CR (aws, azure, gcp, openstack) is absent from the manifest. Using 'continue' silently hid regressions where a renamed or deleted CR would cause the annotation check to be skipped entirely. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- test/manifest/credentials_request_annotation_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/manifest/credentials_request_annotation_test.go b/test/manifest/credentials_request_annotation_test.go index 3afd9fb788..3f43448c42 100644 --- a/test/manifest/credentials_request_annotation_test.go +++ b/test/manifest/credentials_request_annotation_test.go @@ -81,7 +81,7 @@ func TestNonVSphereCredentialsRequestsUnmodified(t *testing.T) { for _, name := range nonVSphere { cr, ok := findCR(crs, name) if !ok { - continue + t.Fatalf("expected non-vsphere CredentialsRequest %q not found in manifest; file may have been renamed or removed", name) } if _, present := cr.Metadata.Annotations[vsphereComponentAnnotation]; present { t.Errorf("non-vsphere CR %q must NOT carry %s", name, vsphereComponentAnnotation)