From b65ecddad44cee322b30fdb66e059ad869ccd2a6 Mon Sep 17 00:00:00 2001 From: Brandon Palm Date: Tue, 7 Jul 2026 21:55:30 -0500 Subject: [PATCH 1/2] OCPBUGS-85579: Move Console resources from bundle to runtime with capability detection Remove ConsoleYAMLSample and ConsoleQuickStart manifests from the OLM bundle and create them at runtime only when the Console capability is enabled on the cluster. On consoleless RAN/RDS clusters, the operator now skips Console resource creation instead of failing during InstallPlan execution. This follows the pattern established by cluster-monitoring-operator (PR #2011, OCPBUGS-14922) for handling optional Console resources in OLM-managed operators. Changes: - Remove 4 Console manifests from bundle/manifests/ - Remove config/console from kustomize manifests generation - Add console_resources.go controller that checks ClusterVersion capabilities before creating Console resources via dynamic client - Use library-go ApplyUnstructuredResourceImproved for idempotent create-or-update with change detection - Add Console YAML files to bindata/ for runtime embedding - Add RBAC for console.openshift.io and clusterversions via kubebuilder markers - Remove ineffective capability.openshift.io/name annotations --- .../cert-manager-acme-issuer-sample.yaml | 5 +- .../cert-manager-certificate-sample.yaml | 5 +- .../cert-manager-example-quickstart.yaml | 199 ++++++++ .../console/cert-manager-issuer-sample.yaml | 5 +- ...ole.openshift.io_v1_consolequickstart.yaml | 203 -------- ...anager-operator.clusterserviceversion.yaml | 14 + .../cert-manager-acme-issuer-sample.yaml | 2 - .../cert-manager-certificate-sample.yaml | 2 - .../cert-manager-example-quickstart.yaml | 1 - .../console/cert-manager-issuer-sample.yaml | 2 - config/manifests/kustomization.yaml | 1 - config/rbac/role.yaml | 14 + .../cert_manager_controller_set.go | 7 + .../certmanager/console_resources.go | 152 ++++++ .../certmanager/console_resources_test.go | 450 ++++++++++++++++++ pkg/operator/assets/bindata.go | 350 ++++++++++++++ pkg/operator/starter.go | 8 + test/e2e/console_resources_test.go | 125 +++++ test/go.mod | 4 +- test/go.sum | 4 + 20 files changed, 1331 insertions(+), 222 deletions(-) rename bundle/manifests/cert-manager-acme-issuer-sample_console.openshift.io_v1_consoleyamlsample.yaml => bindata/console/cert-manager-acme-issuer-sample.yaml (87%) rename bundle/manifests/cert-manager-certificate-sample_console.openshift.io_v1_consoleyamlsample.yaml => bindata/console/cert-manager-certificate-sample.yaml (90%) create mode 100644 bindata/console/cert-manager-example-quickstart.yaml rename bundle/manifests/cert-manager-issuer-sample_console.openshift.io_v1_consoleyamlsample.yaml => bindata/console/cert-manager-issuer-sample.yaml (88%) delete mode 100644 bundle/manifests/cert-manager-example_console.openshift.io_v1_consolequickstart.yaml create mode 100644 pkg/controller/certmanager/console_resources.go create mode 100644 pkg/controller/certmanager/console_resources_test.go create mode 100644 test/e2e/console_resources_test.go diff --git a/bundle/manifests/cert-manager-acme-issuer-sample_console.openshift.io_v1_consoleyamlsample.yaml b/bindata/console/cert-manager-acme-issuer-sample.yaml similarity index 87% rename from bundle/manifests/cert-manager-acme-issuer-sample_console.openshift.io_v1_consoleyamlsample.yaml rename to bindata/console/cert-manager-acme-issuer-sample.yaml index 31f27a2e0..40e3d7e4a 100644 --- a/bundle/manifests/cert-manager-acme-issuer-sample_console.openshift.io_v1_consoleyamlsample.yaml +++ b/bindata/console/cert-manager-acme-issuer-sample.yaml @@ -1,16 +1,13 @@ apiVersion: console.openshift.io/v1 kind: ConsoleYAMLSample metadata: - annotations: - capability.openshift.io/name: Console name: cert-manager-acme-issuer-sample spec: - description: An example ACME Issuer for Let's Encrypt production certificates with - HTTP-01 challenge targetResource: apiVersion: cert-manager.io/v1 kind: Issuer title: Example ACME Issuer (Let's Encrypt) + description: An example ACME Issuer for Let's Encrypt production certificates with HTTP-01 challenge yaml: |- apiVersion: cert-manager.io/v1 kind: Issuer diff --git a/bundle/manifests/cert-manager-certificate-sample_console.openshift.io_v1_consoleyamlsample.yaml b/bindata/console/cert-manager-certificate-sample.yaml similarity index 90% rename from bundle/manifests/cert-manager-certificate-sample_console.openshift.io_v1_consoleyamlsample.yaml rename to bindata/console/cert-manager-certificate-sample.yaml index 64b78b490..b83b514ac 100644 --- a/bundle/manifests/cert-manager-certificate-sample_console.openshift.io_v1_consoleyamlsample.yaml +++ b/bindata/console/cert-manager-certificate-sample.yaml @@ -1,15 +1,13 @@ apiVersion: console.openshift.io/v1 kind: ConsoleYAMLSample metadata: - annotations: - capability.openshift.io/name: Console name: cert-manager-certificate-sample spec: - description: A simple Certificate example targetResource: apiVersion: cert-manager.io/v1 kind: Certificate title: Example Certificate + description: A simple Certificate example yaml: |- apiVersion: cert-manager.io/v1 kind: Certificate @@ -24,3 +22,4 @@ spec: issuerRef: name: selfsigned-issuer kind: Issuer + diff --git a/bindata/console/cert-manager-example-quickstart.yaml b/bindata/console/cert-manager-example-quickstart.yaml new file mode 100644 index 000000000..673421ad3 --- /dev/null +++ b/bindata/console/cert-manager-example-quickstart.yaml @@ -0,0 +1,199 @@ +apiVersion: console.openshift.io/v1 +kind: ConsoleQuickStart +metadata: + name: cert-manager-example + annotations: + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + include.release.openshift.io/single-node-developer: "true" +spec: + displayName: cert-manager Operator for Red Hat OpenShift Example + tags: + - example + - operator + - certificates + durationMinutes: 10 + prerequisites: + - You completed the "Install the cert-manager Operator for Red Hat OpenShift" quick start. + - You have a namespace in which to deploy the example CRs. + description: |- + Create and issue TLS certificates using the cert-manager Operator for Red Hat OpenShift + introduction: |- + # cert-manager Operator for Red Hat OpenShift + + The cert-manager Operator for Red Hat OpenShift enables you to create and sign TLS certificates from an external PKI for your workloads + running on an OpenShift cluster. + + ### Expected Learning + + With this Quick Start, you will learn about the following CRDs provided by the cert-manager operator: + 1. **Issuer** - defines a certificate authority that can sign certificates + 2. **Certificate** - defines the desired certificate and its properties + + This Quick Start will walk you through creating your first certificate: + - Create a self-signed Issuer (for testing) + - Create a Certificate signed by that Issuer + - View the generated certificate in a Kubernetes Secret + - Learn how to use it in your applications + + **Note**: For production, you would use Let's Encrypt (ACME), HashiCorp Vault, or your organization's CA instead of self-signed certificates. + tasks: + - title: Navigate to installed cert-manager operator + description: |- + ### To navigate to the installed operator: + 1. Go to the **Installed Operators** from the [Ecosystem]{{highlight qs-nav-ecosystem}} section of the navigation. + 2. In the **Search by name** field, type `cert-manager`. + 3. Look for **cert-manager Operator for Red Hat OpenShift**. If you had completed the prerequisite Quick Start, the tile should appear. + 4. Click on the installed operator + + You will be brought to the **Operator Details** page and be presented with **Provided APIs** + review: + instructions: |- + #### Verify you see a list of **Provided APIs**: + The list should include `Issuer`, `ClusterIssuer`, and `Certificate` + failedTaskHelp: This task isn't verified yet. Try the task again. + summary: + success: You are in the right place, and ready to start the rest of the Quick Start + failed: Try the steps again. + - title: Select a project + description: |- + ### Create or select a project to work in + 1. Find the **Project** dropdown menu at the top of the screen. + 2. Select or create the project in which you want to work in. + + **Note**: For this example, we'll create an `Issuer` which is namespace-scoped. If you want to issue certificates + across multiple namespaces, you can create a `ClusterIssuer` instead. + review: + instructions: |- + #### Verify the name in the **Project** dropdown menu is the expected project + failedTaskHelp: Try the task again. + summary: + success: You are in the right place. + failed: Try the steps again. + - title: Create a self-signed Issuer + description: |- + ### To create a self-signed Issuer + + An Issuer represents a certificate authority that can sign certificates. We'll create a self-signed Issuer + for this example. This is useful for testing and development. + + 1. Find the `Issuer` Custom Resource in the list of **Provided APIs** or in the top side-scrolling menu bar. + - From the list of **Provided APIs**, click the **Create instance** link. + - From the **top side-scrolling menu bar**, click **Issuer** and then click **Create Issuer**. + + 2. Switch to **YAML view** in the editor. + + 3. On the right sidebar, look for the **Samples** section and select **"Example Self-Signed Issuer"**. + + 4. Click **Try it** to populate the editor with the sample YAML. + + 5. Click the **Create** button to create the Issuer. + review: + instructions: |- + #### Verify the Issuer was successfully created: + 1. You should see the Issuer listed with the name `selfsigned-issuer` + 2. Check that the **Ready** condition shows **True** in the Conditions section + failedTaskHelp: This task isn't verified yet. Try the task again. + summary: + success: You just created a self-signed Issuer! Now we can create certificates. + failed: Try the steps again. + - title: Create a Certificate + description: |- + ### To create a Certificate + + Now we'll create a certificate that will be signed by our Issuer. + + 1. Find the `Certificate` Custom Resource in the list of **Provided APIs** or in the top side-scrolling menu bar. + - From the list of **Provided APIs**, click the **Create instance** link. + - From the **top side-scrolling menu bar**, click **Certificate** and then click **Create Certificate**. + + 2. Switch to **YAML view** in the editor. + + 3. On the right sidebar, look for the **Samples** section and select **"Example Certificate"**. + + 4. Click **Try it** to populate the editor with the sample YAML. + + 5. Click the **Create** button to create the Certificate. + review: + instructions: |- + #### Verify the Certificate was successfully created: + 1. You should see the Certificate listed with the name `example-cert`. + 2. Check that the **Ready** condition shows **True**. + 3. Navigate to the [Workloads]{{highlight qs-nav-workloads}} section and click **Secrets** + 4. You should see a new Secret named `example-tls`. + failedTaskHelp: This task isn't verified yet. Try the task again. + summary: + success: You just created your first certificate! cert-manager has issued it and stored it in a Secret. + failed: Try the steps again. + - title: View the certificate + description: |- + ### To inspect the certificate + + Let's look at the Secret that contains the certificate. + + 1. Make sure you're in the [Workloads]{{highlight qs-nav-workloads}} section, click **Secrets**. + 2. Click on the **example-tls** Secret. + 3. You should see the certificate data with keys: + - `tls.crt` - The certificate. + - `tls.key` - The private key. + - `ca.crt` - The CA certificate. + review: + instructions: |- + #### Verify you can see the certificate data: + Is the Secret `example-tls` present with `tls.crt` and `tls.key`? + failedTaskHelp: This task isn't verified yet. Try the task again. + summary: + success: Great! Your certificate is ready to use. + failed: Try the steps again. + - title: Use the certificate + description: |- + ### How to use certificates + + Now you can use this certificate in your applications. Here's a simple example for an OpenShift Route: + + ```yaml + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + name: my-app + spec: + to: + kind: Service + name: my-service + tls: + termination: edge + externalCertificate: + name: example-tls + ``` + + Or in an Ingress: + + ```yaml + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: my-app + spec: + tls: + - secretName: example-tls + rules: + - host: example.com + ``` + + **Note**: cert-manager will automatically renew certificates before they expire! + review: + instructions: |- + #### Do you understand how to use certificates? + Certificates can be referenced in Routes and Ingress resources. + failedTaskHelp: Review the examples above. + summary: + success: You now know how to create and use certificates! + failed: Review the examples again. + conclusion: |- + Great job! You've successfully created your first Issuer and Certificate. + + ### Next Steps: + + - For production, use Let's Encrypt (ACME Issuer) instead of self-signed certificates + - Explore ClusterIssuer for cluster-wide certificate management + - Check out the [cert-manager documentation](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/cert-manager-operator-for-red-hat-openshift) to learn more diff --git a/bundle/manifests/cert-manager-issuer-sample_console.openshift.io_v1_consoleyamlsample.yaml b/bindata/console/cert-manager-issuer-sample.yaml similarity index 88% rename from bundle/manifests/cert-manager-issuer-sample_console.openshift.io_v1_consoleyamlsample.yaml rename to bindata/console/cert-manager-issuer-sample.yaml index 3e8fffad4..3bc9ebd71 100644 --- a/bundle/manifests/cert-manager-issuer-sample_console.openshift.io_v1_consoleyamlsample.yaml +++ b/bindata/console/cert-manager-issuer-sample.yaml @@ -1,15 +1,13 @@ apiVersion: console.openshift.io/v1 kind: ConsoleYAMLSample metadata: - annotations: - capability.openshift.io/name: Console name: cert-manager-issuer-sample spec: - description: A simple self-signed Issuer for development and testing targetResource: apiVersion: cert-manager.io/v1 kind: Issuer title: Example Self-Signed Issuer + description: A simple self-signed Issuer for development and testing yaml: |- apiVersion: cert-manager.io/v1 kind: Issuer @@ -18,3 +16,4 @@ spec: namespace: default spec: selfSigned: {} + diff --git a/bundle/manifests/cert-manager-example_console.openshift.io_v1_consolequickstart.yaml b/bundle/manifests/cert-manager-example_console.openshift.io_v1_consolequickstart.yaml deleted file mode 100644 index 37847c281..000000000 --- a/bundle/manifests/cert-manager-example_console.openshift.io_v1_consolequickstart.yaml +++ /dev/null @@ -1,203 +0,0 @@ -apiVersion: console.openshift.io/v1 -kind: ConsoleQuickStart -metadata: - annotations: - capability.openshift.io/name: Console - include.release.openshift.io/ibm-cloud-managed: "true" - include.release.openshift.io/self-managed-high-availability: "true" - include.release.openshift.io/single-node-developer: "true" - name: cert-manager-example -spec: - conclusion: |- - Great job! You've successfully created your first Issuer and Certificate. - - ### Next Steps: - - - For production, use Let's Encrypt (ACME Issuer) instead of self-signed certificates - - Explore ClusterIssuer for cluster-wide certificate management - - Check out the [cert-manager documentation](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/cert-manager-operator-for-red-hat-openshift) to learn more - description: Create and issue TLS certificates using the cert-manager Operator for - Red Hat OpenShift - displayName: cert-manager Operator for Red Hat OpenShift Example - durationMinutes: 10 - introduction: |- - # cert-manager Operator for Red Hat OpenShift - - The cert-manager Operator for Red Hat OpenShift enables you to create and sign TLS certificates from an external PKI for your workloads - running on an OpenShift cluster. - - ### Expected Learning - - With this Quick Start, you will learn about the following CRDs provided by the cert-manager operator: - 1. **Issuer** - defines a certificate authority that can sign certificates - 2. **Certificate** - defines the desired certificate and its properties - - This Quick Start will walk you through creating your first certificate: - - Create a self-signed Issuer (for testing) - - Create a Certificate signed by that Issuer - - View the generated certificate in a Kubernetes Secret - - Learn how to use it in your applications - - **Note**: For production, you would use Let's Encrypt (ACME), HashiCorp Vault, or your organization's CA instead of self-signed certificates. - prerequisites: - - You completed the "Install the cert-manager Operator for Red Hat OpenShift" quick - start. - - You have a namespace in which to deploy the example CRs. - tags: - - example - - operator - - certificates - tasks: - - description: |- - ### To navigate to the installed operator: - 1. Go to the **Installed Operators** from the [Ecosystem]{{highlight qs-nav-ecosystem}} section of the navigation. - 2. In the **Search by name** field, type `cert-manager`. - 3. Look for **cert-manager Operator for Red Hat OpenShift**. If you had completed the prerequisite Quick Start, the tile should appear. - 4. Click on the installed operator - - You will be brought to the **Operator Details** page and be presented with **Provided APIs** - review: - failedTaskHelp: This task isn't verified yet. Try the task again. - instructions: |- - #### Verify you see a list of **Provided APIs**: - The list should include `Issuer`, `ClusterIssuer`, and `Certificate` - summary: - failed: Try the steps again. - success: You are in the right place, and ready to start the rest of the Quick - Start - title: Navigate to installed cert-manager operator - - description: |- - ### Create or select a project to work in - 1. Find the **Project** dropdown menu at the top of the screen. - 2. Select or create the project in which you want to work in. - - **Note**: For this example, we'll create an `Issuer` which is namespace-scoped. If you want to issue certificates - across multiple namespaces, you can create a `ClusterIssuer` instead. - review: - failedTaskHelp: Try the task again. - instructions: '#### Verify the name in the **Project** dropdown menu is the - expected project' - summary: - failed: Try the steps again. - success: You are in the right place. - title: Select a project - - description: |- - ### To create a self-signed Issuer - - An Issuer represents a certificate authority that can sign certificates. We'll create a self-signed Issuer - for this example. This is useful for testing and development. - - 1. Find the `Issuer` Custom Resource in the list of **Provided APIs** or in the top side-scrolling menu bar. - - From the list of **Provided APIs**, click the **Create instance** link. - - From the **top side-scrolling menu bar**, click **Issuer** and then click **Create Issuer**. - - 2. Switch to **YAML view** in the editor. - - 3. On the right sidebar, look for the **Samples** section and select **"Example Self-Signed Issuer"**. - - 4. Click **Try it** to populate the editor with the sample YAML. - - 5. Click the **Create** button to create the Issuer. - review: - failedTaskHelp: This task isn't verified yet. Try the task again. - instructions: |- - #### Verify the Issuer was successfully created: - 1. You should see the Issuer listed with the name `selfsigned-issuer` - 2. Check that the **Ready** condition shows **True** in the Conditions section - summary: - failed: Try the steps again. - success: You just created a self-signed Issuer! Now we can create certificates. - title: Create a self-signed Issuer - - description: |- - ### To create a Certificate - - Now we'll create a certificate that will be signed by our Issuer. - - 1. Find the `Certificate` Custom Resource in the list of **Provided APIs** or in the top side-scrolling menu bar. - - From the list of **Provided APIs**, click the **Create instance** link. - - From the **top side-scrolling menu bar**, click **Certificate** and then click **Create Certificate**. - - 2. Switch to **YAML view** in the editor. - - 3. On the right sidebar, look for the **Samples** section and select **"Example Certificate"**. - - 4. Click **Try it** to populate the editor with the sample YAML. - - 5. Click the **Create** button to create the Certificate. - review: - failedTaskHelp: This task isn't verified yet. Try the task again. - instructions: |- - #### Verify the Certificate was successfully created: - 1. You should see the Certificate listed with the name `example-cert`. - 2. Check that the **Ready** condition shows **True**. - 3. Navigate to the [Workloads]{{highlight qs-nav-workloads}} section and click **Secrets** - 4. You should see a new Secret named `example-tls`. - summary: - failed: Try the steps again. - success: You just created your first certificate! cert-manager has issued it - and stored it in a Secret. - title: Create a Certificate - - description: |- - ### To inspect the certificate - - Let's look at the Secret that contains the certificate. - - 1. Make sure you're in the [Workloads]{{highlight qs-nav-workloads}} section, click **Secrets**. - 2. Click on the **example-tls** Secret. - 3. You should see the certificate data with keys: - - `tls.crt` - The certificate. - - `tls.key` - The private key. - - `ca.crt` - The CA certificate. - review: - failedTaskHelp: This task isn't verified yet. Try the task again. - instructions: |- - #### Verify you can see the certificate data: - Is the Secret `example-tls` present with `tls.crt` and `tls.key`? - summary: - failed: Try the steps again. - success: Great! Your certificate is ready to use. - title: View the certificate - - description: |- - ### How to use certificates - - Now you can use this certificate in your applications. Here's a simple example for an OpenShift Route: - - ```yaml - apiVersion: route.openshift.io/v1 - kind: Route - metadata: - name: my-app - spec: - to: - kind: Service - name: my-service - tls: - termination: edge - externalCertificate: - name: example-tls - ``` - - Or in an Ingress: - - ```yaml - apiVersion: networking.k8s.io/v1 - kind: Ingress - metadata: - name: my-app - spec: - tls: - - secretName: example-tls - rules: - - host: example.com - ``` - - **Note**: cert-manager will automatically renew certificates before they expire! - review: - failedTaskHelp: Review the examples above. - instructions: |- - #### Do you understand how to use certificates? - Certificates can be referenced in Routes and Ingress resources. - summary: - failed: Review the examples again. - success: You now know how to create and use certificates! - title: Use the certificate diff --git a/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml b/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml index 2832f06b5..eb405f098 100644 --- a/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml @@ -568,6 +568,7 @@ spec: - config.openshift.io resources: - apiservers + - clusterversions verbs: - get - list @@ -587,6 +588,19 @@ spec: - patch - update - watch + - apiGroups: + - console.openshift.io + resources: + - consolequickstarts + - consoleyamlsamples + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - coordination.k8s.io resources: diff --git a/config/console/cert-manager-acme-issuer-sample.yaml b/config/console/cert-manager-acme-issuer-sample.yaml index ae04d2fab..40e3d7e4a 100644 --- a/config/console/cert-manager-acme-issuer-sample.yaml +++ b/config/console/cert-manager-acme-issuer-sample.yaml @@ -2,8 +2,6 @@ apiVersion: console.openshift.io/v1 kind: ConsoleYAMLSample metadata: name: cert-manager-acme-issuer-sample - annotations: - capability.openshift.io/name: Console spec: targetResource: apiVersion: cert-manager.io/v1 diff --git a/config/console/cert-manager-certificate-sample.yaml b/config/console/cert-manager-certificate-sample.yaml index 23b565989..b83b514ac 100644 --- a/config/console/cert-manager-certificate-sample.yaml +++ b/config/console/cert-manager-certificate-sample.yaml @@ -2,8 +2,6 @@ apiVersion: console.openshift.io/v1 kind: ConsoleYAMLSample metadata: name: cert-manager-certificate-sample - annotations: - capability.openshift.io/name: Console spec: targetResource: apiVersion: cert-manager.io/v1 diff --git a/config/console/cert-manager-example-quickstart.yaml b/config/console/cert-manager-example-quickstart.yaml index f1f192aa3..673421ad3 100644 --- a/config/console/cert-manager-example-quickstart.yaml +++ b/config/console/cert-manager-example-quickstart.yaml @@ -3,7 +3,6 @@ kind: ConsoleQuickStart metadata: name: cert-manager-example annotations: - capability.openshift.io/name: Console include.release.openshift.io/ibm-cloud-managed: "true" include.release.openshift.io/self-managed-high-availability: "true" include.release.openshift.io/single-node-developer: "true" diff --git a/config/console/cert-manager-issuer-sample.yaml b/config/console/cert-manager-issuer-sample.yaml index 3100ed5b7..3bc9ebd71 100644 --- a/config/console/cert-manager-issuer-sample.yaml +++ b/config/console/cert-manager-issuer-sample.yaml @@ -2,8 +2,6 @@ apiVersion: console.openshift.io/v1 kind: ConsoleYAMLSample metadata: name: cert-manager-issuer-sample - annotations: - capability.openshift.io/name: Console spec: targetResource: apiVersion: cert-manager.io/v1 diff --git a/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml index 0a4b22e0f..1bbf49121 100644 --- a/config/manifests/kustomization.yaml +++ b/config/manifests/kustomization.yaml @@ -5,7 +5,6 @@ resources: - ../default - ../samples - ../scorecard -- ../console # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 60136088a..1511acc78 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -159,6 +159,7 @@ rules: - config.openshift.io resources: - apiservers + - clusterversions verbs: - get - list @@ -178,6 +179,19 @@ rules: - patch - update - watch +- apiGroups: + - console.openshift.io + resources: + - consolequickstarts + - consoleyamlsamples + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - coordination.k8s.io resources: diff --git a/pkg/controller/certmanager/cert_manager_controller_set.go b/pkg/controller/certmanager/cert_manager_controller_set.go index 08981cb33..ed4cba67a 100644 --- a/pkg/controller/certmanager/cert_manager_controller_set.go +++ b/pkg/controller/certmanager/cert_manager_controller_set.go @@ -1,9 +1,11 @@ package certmanager import ( + "k8s.io/client-go/dynamic" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" + configv1client "github.com/openshift/client-go/config/clientset/versioned" configinformers "github.com/openshift/client-go/config/informers/externalversions" "github.com/openshift/library-go/pkg/controller/factory" "github.com/openshift/library-go/pkg/operator/events" @@ -24,6 +26,7 @@ type CertManagerControllerSet struct { certManagerCAInjectorDeploymentController factory.Controller certManagerNetworkPolicyStaticResourcesController factory.Controller certManagerNetworkPolicyUserDefinedController factory.Controller + consoleResourcesController factory.Controller } func NewCertManagerControllerSet( @@ -34,6 +37,8 @@ func NewCertManagerControllerSet( operatorClient v1helpers.OperatorClientWithFinalizers, certManagerOperatorInformers certmanoperatorinformers.SharedInformerFactory, kubeClientContainer *resourceapply.ClientHolder, + configClient configv1client.Interface, + dynamicClient dynamic.Interface, eventRecorder events.Recorder, targetVersion string, versionRecorder status.VersionGetter, @@ -49,6 +54,7 @@ func NewCertManagerControllerSet( certManagerCAInjectorDeploymentController: NewCertManagerCAInjectorDeploymentController(operatorClient, certManagerOperatorInformers, infraInformers, kubeClient, kubeInformersForTargetNamespace, eventRecorder, targetVersion, versionRecorder, trustedCAConfigmapName, cloudCredentialsSecretName), certManagerNetworkPolicyStaticResourcesController: NewCertManagerNetworkPolicyStaticResourcesController(operatorClient, kubeClientContainer, kubeInformersForNamespaces, certManagerOperatorInformers, eventRecorder), certManagerNetworkPolicyUserDefinedController: NewCertManagerNetworkPolicyUserDefinedController(operatorClient, certManagerOperatorInformers, kubeClient, kubeInformersForNamespaces, eventRecorder), + consoleResourcesController: NewConsoleResourcesController(operatorClient, configClient, dynamicClient, eventRecorder), } } @@ -62,5 +68,6 @@ func (c *CertManagerControllerSet) ToArray() []factory.Controller { c.certManagerCAInjectorDeploymentController, c.certManagerNetworkPolicyStaticResourcesController, c.certManagerNetworkPolicyUserDefinedController, + c.consoleResourcesController, } } diff --git a/pkg/controller/certmanager/console_resources.go b/pkg/controller/certmanager/console_resources.go new file mode 100644 index 000000000..02683d630 --- /dev/null +++ b/pkg/controller/certmanager/console_resources.go @@ -0,0 +1,152 @@ +package certmanager + +import ( + "context" + "errors" + "fmt" + "slices" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/klog/v2" + + configv1 "github.com/openshift/api/config/v1" + configv1client "github.com/openshift/client-go/config/clientset/versioned" + "github.com/openshift/library-go/pkg/controller/factory" + "github.com/openshift/library-go/pkg/operator/events" + "github.com/openshift/library-go/pkg/operator/resource/resourceapply" + "github.com/openshift/library-go/pkg/operator/resource/resourceread" + "github.com/openshift/library-go/pkg/operator/v1helpers" + + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +const ( + consoleResourcesControllerName = operatorName + "-console-resources" +) + +var ( + ConsoleYAMLSampleGVR = schema.GroupVersionResource{ + Group: "console.openshift.io", + Version: "v1", + Resource: "consoleyamlsamples", + } + + ConsoleQuickStartGVR = schema.GroupVersionResource{ + Group: "console.openshift.io", + Version: "v1", + Resource: "consolequickstarts", + } + + consoleAssetFiles = []string{ + "console/cert-manager-acme-issuer-sample.yaml", + "console/cert-manager-certificate-sample.yaml", + "console/cert-manager-issuer-sample.yaml", + "console/cert-manager-example-quickstart.yaml", + } +) + +// +kubebuilder:rbac:groups=config.openshift.io,resources=clusterversions,verbs=get;list;watch +// +kubebuilder:rbac:groups=console.openshift.io,resources=consoleyamlsamples;consolequickstarts,verbs=get;list;watch;create;update;patch;delete + +// consoleResourcesController conditionally creates Console resources +// when the Console capability is enabled on the cluster. +type consoleResourcesController struct { + configClient configv1client.Interface + dynamicClient dynamic.Interface + eventRecorder events.Recorder + consoleAssets []consoleAsset +} + +type consoleAsset struct { + obj *unstructured.Unstructured + gvr schema.GroupVersionResource +} + +func NewConsoleResourcesController( + operatorClient v1helpers.OperatorClient, + configClient configv1client.Interface, + dynamicClient dynamic.Interface, + eventRecorder events.Recorder, +) factory.Controller { + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynamicClient, + eventRecorder: eventRecorder.WithComponentSuffix("console-resources"), + } + + for _, file := range consoleAssetFiles { + data, err := assets.Asset(file) + if err != nil { + panic(fmt.Sprintf("failed to read console asset %s: %v", file, err)) + } + obj := resourceread.ReadUnstructuredOrDie(data) + gvr, err := gvrForKind(obj.GetKind()) + if err != nil { + panic(fmt.Sprintf("console asset %s: %v", file, err)) + } + c.consoleAssets = append(c.consoleAssets, consoleAsset{obj: obj, gvr: gvr}) + } + + return factory.New(). + WithInformers(operatorClient.Informer()). + ResyncEvery(10*time.Minute). + WithSync(c.sync). + ToController(consoleResourcesControllerName, c.eventRecorder) +} + +func (c *consoleResourcesController) sync(ctx context.Context, _ factory.SyncContext) error { + enabled, err := c.hasConsoleCapability(ctx) + if err != nil { + return fmt.Errorf("failed to check Console capability: %w", err) + } + + if !enabled { + klog.V(4).Info("Console capability is not enabled, skipping Console resource creation") + return nil + } + + var errs []error + for _, asset := range c.consoleAssets { + _, _, err := resourceapply.ApplyUnstructuredResourceImproved( + ctx, c.dynamicClient, c.eventRecorder, + asset.obj, nil, asset.gvr, nil, nil, + ) + if err != nil { + errs = append(errs, fmt.Errorf("failed to apply %s/%s: %w", asset.obj.GetKind(), asset.obj.GetName(), err)) + } + } + + return errors.Join(errs...) +} + +func (c *consoleResourcesController) hasConsoleCapability(ctx context.Context) (bool, error) { + version, err := c.configClient.ConfigV1().ClusterVersions().Get(ctx, "version", metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + klog.V(4).Info("ClusterVersion resource not available (e.g. MicroShift); assuming Console capability is disabled") + return false, nil + } + if err != nil { + return false, fmt.Errorf("failed to get ClusterVersion: %w", err) + } + + return slices.Contains( + version.Status.Capabilities.EnabledCapabilities, + configv1.ClusterVersionCapabilityConsole, + ), nil +} + +func gvrForKind(kind string) (schema.GroupVersionResource, error) { + switch kind { + case "ConsoleYAMLSample": + return ConsoleYAMLSampleGVR, nil + case "ConsoleQuickStart": + return ConsoleQuickStartGVR, nil + default: + return schema.GroupVersionResource{}, fmt.Errorf("unsupported console resource kind: %s", kind) + } +} diff --git a/pkg/controller/certmanager/console_resources_test.go b/pkg/controller/certmanager/console_resources_test.go new file mode 100644 index 000000000..1b79df2c3 --- /dev/null +++ b/pkg/controller/certmanager/console_resources_test.go @@ -0,0 +1,450 @@ +package certmanager + +import ( + "context" + "fmt" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + dynamicfake "k8s.io/client-go/dynamic/fake" + clienttesting "k8s.io/client-go/testing" + "k8s.io/utils/clock" + + configv1 "github.com/openshift/api/config/v1" + configfake "github.com/openshift/client-go/config/clientset/versioned/fake" + "github.com/openshift/library-go/pkg/operator/events" + "github.com/openshift/library-go/pkg/operator/resource/resourceread" + + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func clusterVersionWithCapabilities(caps ...configv1.ClusterVersionCapability) *configv1.ClusterVersion { + return &configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{Name: "version"}, + Status: configv1.ClusterVersionStatus{ + Capabilities: configv1.ClusterVersionCapabilitiesStatus{ + EnabledCapabilities: caps, + }, + }, + } +} + +func buildConsoleAssets(t *testing.T) []consoleAsset { + t.Helper() + result := make([]consoleAsset, 0, len(consoleAssetFiles)) + for _, file := range consoleAssetFiles { + data, err := assets.Asset(file) + if err != nil { + t.Fatalf("failed to load asset %s: %v", file, err) + } + obj := resourceread.ReadUnstructuredOrDie(data) + gvr, err := gvrForKind(obj.GetKind()) + if err != nil { + t.Fatalf("gvrForKind(%s): %v", obj.GetKind(), err) + } + result = append(result, consoleAsset{obj: obj, gvr: gvr}) + } + return result +} + +func TestHasConsoleCapability(t *testing.T) { + tests := []struct { + name string + caps []configv1.ClusterVersionCapability + expected bool + }{ + { + name: "console enabled", + caps: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityConsole, configv1.ClusterVersionCapabilityIngress}, + expected: true, + }, + { + name: "console disabled", + caps: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityIngress, configv1.ClusterVersionCapabilityStorage}, + expected: false, + }, + { + name: "no capabilities", + caps: nil, + expected: false, + }, + { + name: "console only capability", + caps: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityConsole}, + expected: true, + }, + { + name: "empty capabilities list", + caps: []configv1.ClusterVersionCapability{}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cv := clusterVersionWithCapabilities(tt.caps...) + client := configfake.NewClientset(cv) + c := &consoleResourcesController{configClient: client} + + got, err := c.hasConsoleCapability(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.expected { + t.Errorf("hasConsoleCapability() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestHasConsoleCapabilityClusterVersionNotFound(t *testing.T) { + client := configfake.NewClientset() + c := &consoleResourcesController{configClient: client} + + got, err := c.hasConsoleCapability(context.Background()) + if err != nil { + t.Fatalf("expected no error when ClusterVersion is not found (e.g. MicroShift), got: %v", err) + } + if got { + t.Error("expected false when ClusterVersion does not exist") + } +} + +func TestHasConsoleCapabilityResourceNotFound(t *testing.T) { + client := configfake.NewClientset() + client.PrependReactor("get", "clusterversions", func(action clienttesting.Action) (bool, runtime.Object, error) { + return true, nil, &apierrors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: 404, + Reason: metav1.StatusReasonNotFound, + Message: "the server could not find the requested resource", + }, + } + }) + c := &consoleResourcesController{configClient: client} + + got, err := c.hasConsoleCapability(context.Background()) + if err != nil { + t.Fatalf("expected no error for resource-type-not-found (MicroShift), got: %v", err) + } + if got { + t.Error("expected false when ClusterVersion resource type does not exist") + } +} + +func TestSyncConsoleDisabledWithAssets(t *testing.T) { + cv := clusterVersionWithCapabilities(configv1.ClusterVersionCapabilityIngress, configv1.ClusterVersionCapabilityStorage) + configClient := configfake.NewClientset(cv) + + scheme := runtime.NewScheme() + dynClient := dynamicfake.NewSimpleDynamicClient(scheme) + + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynClient, + eventRecorder: events.NewInMemoryRecorder("test", clock.RealClock{}), + consoleAssets: buildConsoleAssets(t), + } + + if err := c.sync(context.Background(), nil); err != nil { + t.Fatalf("sync() returned error: %v", err) + } + + if len(dynClient.Actions()) != 0 { + t.Errorf("expected no dynamic client actions when Console disabled even with assets loaded, got %d", len(dynClient.Actions())) + } +} + +func TestSyncConsoleEnabled(t *testing.T) { + cv := clusterVersionWithCapabilities(configv1.ClusterVersionCapabilityConsole) + configClient := configfake.NewClientset(cv) + + scheme := runtime.NewScheme() + dynClient := dynamicfake.NewSimpleDynamicClient(scheme) + + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynClient, + eventRecorder: events.NewInMemoryRecorder("test", clock.RealClock{}), + consoleAssets: buildConsoleAssets(t), + } + + err := c.sync(context.Background(), nil) + // ApplyUnstructuredResourceImproved will fail with the fake dynamic client + // because it does a Get first and the fake doesn't have the right GVR scheme. + // What matters is that it ATTEMPTED to call the dynamic client (not skipped). + if len(dynClient.Actions()) == 0 && err == nil { + t.Error("expected dynamic client actions when Console is enabled") + } +} + +func TestSyncSkipsWhenClusterVersionMissing(t *testing.T) { + configClient := configfake.NewClientset() + + scheme := runtime.NewScheme() + dynClient := dynamicfake.NewSimpleDynamicClient(scheme) + + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynClient, + eventRecorder: events.NewInMemoryRecorder("test", clock.RealClock{}), + consoleAssets: buildConsoleAssets(t), + } + + err := c.sync(context.Background(), nil) + if err != nil { + t.Fatalf("sync() should succeed when ClusterVersion is missing (e.g. MicroShift), got: %v", err) + } + + if len(dynClient.Actions()) != 0 { + t.Errorf("expected no dynamic client actions when ClusterVersion is missing, got %d", len(dynClient.Actions())) + } +} + +func TestSyncErrorPropagationFromCapabilityCheck(t *testing.T) { + configClient := configfake.NewClientset() + configClient.PrependReactor("get", "clusterversions", func(action clienttesting.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf("connection refused") + }) + + scheme := runtime.NewScheme() + dynClient := dynamicfake.NewSimpleDynamicClient(scheme) + + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynClient, + eventRecorder: events.NewInMemoryRecorder("test", clock.RealClock{}), + consoleAssets: buildConsoleAssets(t), + } + + err := c.sync(context.Background(), nil) + if err == nil { + t.Fatal("sync() should return error on transient failure") + } + if !strings.Contains(err.Error(), "failed to check Console capability") { + t.Errorf("error should wrap capability check failure, got: %v", err) + } + + if len(dynClient.Actions()) != 0 { + t.Errorf("expected no dynamic client actions when capability check fails, got %d", len(dynClient.Actions())) + } +} + +func TestSyncAttemptsAllAssetsOnError(t *testing.T) { + cv := clusterVersionWithCapabilities(configv1.ClusterVersionCapabilityConsole) + configClient := configfake.NewClientset(cv) + + scheme := runtime.NewScheme() + dynClient := dynamicfake.NewSimpleDynamicClient(scheme) + + testAssets := buildConsoleAssets(t) + + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynClient, + eventRecorder: events.NewInMemoryRecorder("test", clock.RealClock{}), + consoleAssets: testAssets, + } + + _ = c.sync(context.Background(), nil) + + // With the fake client, ApplyUnstructuredResourceImproved does a Get first. + // Each asset should produce at least one Get action. With continue-on-error, + // we expect actions for ALL assets, not just the first one. + getActions := 0 + for _, action := range dynClient.Actions() { + if action.GetVerb() == "get" { + getActions++ + } + } + + if getActions < len(testAssets) { + t.Errorf("expected at least %d get actions (one per asset), got %d; sync may be aborting early", + len(testAssets), getActions) + } +} + +func TestSyncNoAssetsNoError(t *testing.T) { + cv := clusterVersionWithCapabilities(configv1.ClusterVersionCapabilityConsole) + configClient := configfake.NewClientset(cv) + + scheme := runtime.NewScheme() + dynClient := dynamicfake.NewSimpleDynamicClient(scheme) + + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynClient, + eventRecorder: events.NewInMemoryRecorder("test", clock.RealClock{}), + consoleAssets: nil, + } + + if err := c.sync(context.Background(), nil); err != nil { + t.Fatalf("sync() with no assets should succeed, got: %v", err) + } + + if len(dynClient.Actions()) != 0 { + t.Errorf("expected no dynamic client actions with empty assets, got %d", len(dynClient.Actions())) + } +} + +func TestSyncReappliesWithoutError(t *testing.T) { + cv := clusterVersionWithCapabilities(configv1.ClusterVersionCapabilityConsole) + configClient := configfake.NewClientset(cv) + + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName( + schema.GroupVersionKind{Group: "console.openshift.io", Version: "v1", Kind: "ConsoleYAMLSample"}, + &unstructured.Unstructured{}, + ) + scheme.AddKnownTypeWithName( + schema.GroupVersionKind{Group: "console.openshift.io", Version: "v1", Kind: "ConsoleYAMLSampleList"}, + &unstructured.UnstructuredList{}, + ) + scheme.AddKnownTypeWithName( + schema.GroupVersionKind{Group: "console.openshift.io", Version: "v1", Kind: "ConsoleQuickStart"}, + &unstructured.Unstructured{}, + ) + scheme.AddKnownTypeWithName( + schema.GroupVersionKind{Group: "console.openshift.io", Version: "v1", Kind: "ConsoleQuickStartList"}, + &unstructured.UnstructuredList{}, + ) + dynClient := dynamicfake.NewSimpleDynamicClient(scheme) + + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynClient, + eventRecorder: events.NewInMemoryRecorder("test", clock.RealClock{}), + consoleAssets: buildConsoleAssets(t), + } + + err1 := c.sync(context.Background(), nil) + actionsAfterFirst := len(dynClient.Actions()) + + err2 := c.sync(context.Background(), nil) + actionsAfterSecond := len(dynClient.Actions()) + + if err1 != nil { + t.Fatalf("first sync() failed: %v", err1) + } + if err2 != nil { + t.Fatalf("second sync() failed: %v", err2) + } + + if actionsAfterSecond <= actionsAfterFirst { + t.Error("second sync should have produced additional actions (idempotent re-apply)") + } +} + +func TestSyncReturnsAggregatedErrors(t *testing.T) { + cv := clusterVersionWithCapabilities(configv1.ClusterVersionCapabilityConsole) + configClient := configfake.NewClientset(cv) + + scheme := runtime.NewScheme() + dynClient := dynamicfake.NewSimpleDynamicClient(scheme) + dynClient.PrependReactor("*", "*", func(action clienttesting.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf("simulated error for %s", action.GetResource().Resource) + }) + + c := &consoleResourcesController{ + configClient: configClient, + dynamicClient: dynClient, + eventRecorder: events.NewInMemoryRecorder("test", clock.RealClock{}), + consoleAssets: buildConsoleAssets(t), + } + + err := c.sync(context.Background(), nil) + if err == nil { + t.Fatal("sync() should return error when all assets fail") + } + + errStr := err.Error() + if !strings.Contains(errStr, "ConsoleYAMLSample") { + t.Errorf("aggregated error should mention ConsoleYAMLSample, got: %v", err) + } + if !strings.Contains(errStr, "ConsoleQuickStart") { + t.Errorf("aggregated error should mention ConsoleQuickStart, got: %v", err) + } +} + +func TestGvrForKind(t *testing.T) { + tests := []struct { + kind string + want schema.GroupVersionResource + wantErr bool + }{ + { + kind: "ConsoleYAMLSample", + want: ConsoleYAMLSampleGVR, + }, + { + kind: "ConsoleQuickStart", + want: ConsoleQuickStartGVR, + }, + { + kind: "ConsolePlugin", + wantErr: true, + }, + { + kind: "Unknown", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.kind, func(t *testing.T) { + got, err := gvrForKind(tt.kind) + if tt.wantErr { + if err == nil { + t.Errorf("gvrForKind(%q) expected error, got %v", tt.kind, got) + } + return + } + if err != nil { + t.Fatalf("gvrForKind(%q) unexpected error: %v", tt.kind, err) + } + if got != tt.want { + t.Errorf("gvrForKind(%q) = %v, want %v", tt.kind, got, tt.want) + } + }) + } +} + +func TestConsoleAssetContent(t *testing.T) { + expectedAssets := []struct { + file string + kind string + name string + }{ + {"console/cert-manager-acme-issuer-sample.yaml", "ConsoleYAMLSample", "cert-manager-acme-issuer-sample"}, + {"console/cert-manager-certificate-sample.yaml", "ConsoleYAMLSample", "cert-manager-certificate-sample"}, + {"console/cert-manager-issuer-sample.yaml", "ConsoleYAMLSample", "cert-manager-issuer-sample"}, + {"console/cert-manager-example-quickstart.yaml", "ConsoleQuickStart", "cert-manager-example"}, + } + + for _, expected := range expectedAssets { + t.Run(expected.file, func(t *testing.T) { + data, err := assets.Asset(expected.file) + if err != nil { + t.Fatalf("asset not found: %v", err) + } + obj := resourceread.ReadUnstructuredOrDie(data) + + if obj.GetKind() != expected.kind { + t.Errorf("kind = %q, want %q", obj.GetKind(), expected.kind) + } + if obj.GetName() != expected.name { + t.Errorf("name = %q, want %q", obj.GetName(), expected.name) + } + if obj.GetAPIVersion() != "console.openshift.io/v1" { + t.Errorf("apiVersion = %q, want %q", obj.GetAPIVersion(), "console.openshift.io/v1") + } + }) + } +} + diff --git a/pkg/operator/assets/bindata.go b/pkg/operator/assets/bindata.go index 6183e9b47..820344cdf 100644 --- a/pkg/operator/assets/bindata.go +++ b/pkg/operator/assets/bindata.go @@ -43,6 +43,10 @@ // bindata/cert-manager-deployment/webhook/cert-manager-webhook-subjectaccessreviews-crb.yaml // bindata/cert-manager-deployment/webhook/cert-manager-webhook-svc.yaml // bindata/cert-manager-deployment/webhook/cert-manager-webhook-validatingwebhookconfiguration.yaml +// bindata/console/cert-manager-acme-issuer-sample.yaml +// bindata/console/cert-manager-certificate-sample.yaml +// bindata/console/cert-manager-example-quickstart.yaml +// bindata/console/cert-manager-issuer-sample.yaml // bindata/istio-csr/cert-manager-istio-csr-clusterrole.yaml // bindata/istio-csr/cert-manager-istio-csr-clusterrolebinding.yaml // bindata/istio-csr/cert-manager-istio-csr-deployment.yaml @@ -2295,6 +2299,342 @@ func certManagerDeploymentWebhookCertManagerWebhookValidatingwebhookconfiguratio return a, nil } +var _consoleCertManagerAcmeIssuerSampleYaml = []byte(`apiVersion: console.openshift.io/v1 +kind: ConsoleYAMLSample +metadata: + name: cert-manager-acme-issuer-sample +spec: + targetResource: + apiVersion: cert-manager.io/v1 + kind: Issuer + title: Example ACME Issuer (Let's Encrypt) + description: An example ACME Issuer for Let's Encrypt production certificates with HTTP-01 challenge + yaml: |- + apiVersion: cert-manager.io/v1 + kind: Issuer + metadata: + name: letsencrypt-prod + namespace: default + spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: letsencrypt-prod-account-key + solvers: + - http01: + ingress: + ingressClassName: openshift-default +`) + +func consoleCertManagerAcmeIssuerSampleYamlBytes() ([]byte, error) { + return _consoleCertManagerAcmeIssuerSampleYaml, nil +} + +func consoleCertManagerAcmeIssuerSampleYaml() (*asset, error) { + bytes, err := consoleCertManagerAcmeIssuerSampleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "console/cert-manager-acme-issuer-sample.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _consoleCertManagerCertificateSampleYaml = []byte(`apiVersion: console.openshift.io/v1 +kind: ConsoleYAMLSample +metadata: + name: cert-manager-certificate-sample +spec: + targetResource: + apiVersion: cert-manager.io/v1 + kind: Certificate + title: Example Certificate + description: A simple Certificate example + yaml: |- + apiVersion: cert-manager.io/v1 + kind: Certificate + metadata: + name: example-cert + namespace: default + spec: + secretName: example-tls + commonName: example.com + dnsNames: + - example.com + issuerRef: + name: selfsigned-issuer + kind: Issuer + +`) + +func consoleCertManagerCertificateSampleYamlBytes() ([]byte, error) { + return _consoleCertManagerCertificateSampleYaml, nil +} + +func consoleCertManagerCertificateSampleYaml() (*asset, error) { + bytes, err := consoleCertManagerCertificateSampleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "console/cert-manager-certificate-sample.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _consoleCertManagerExampleQuickstartYaml = []byte(`apiVersion: console.openshift.io/v1 +kind: ConsoleQuickStart +metadata: + name: cert-manager-example + annotations: + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + include.release.openshift.io/single-node-developer: "true" +spec: + displayName: cert-manager Operator for Red Hat OpenShift Example + tags: + - example + - operator + - certificates + durationMinutes: 10 + prerequisites: + - You completed the "Install the cert-manager Operator for Red Hat OpenShift" quick start. + - You have a namespace in which to deploy the example CRs. + description: |- + Create and issue TLS certificates using the cert-manager Operator for Red Hat OpenShift + introduction: |- + # cert-manager Operator for Red Hat OpenShift + + The cert-manager Operator for Red Hat OpenShift enables you to create and sign TLS certificates from an external PKI for your workloads + running on an OpenShift cluster. + + ### Expected Learning + + With this Quick Start, you will learn about the following CRDs provided by the cert-manager operator: + 1. **Issuer** - defines a certificate authority that can sign certificates + 2. **Certificate** - defines the desired certificate and its properties + + This Quick Start will walk you through creating your first certificate: + - Create a self-signed Issuer (for testing) + - Create a Certificate signed by that Issuer + - View the generated certificate in a Kubernetes Secret + - Learn how to use it in your applications + + **Note**: For production, you would use Let's Encrypt (ACME), HashiCorp Vault, or your organization's CA instead of self-signed certificates. + tasks: + - title: Navigate to installed cert-manager operator + description: |- + ### To navigate to the installed operator: + 1. Go to the **Installed Operators** from the [Ecosystem]{{highlight qs-nav-ecosystem}} section of the navigation. + 2. In the **Search by name** field, type ` + "`" + `cert-manager` + "`" + `. + 3. Look for **cert-manager Operator for Red Hat OpenShift**. If you had completed the prerequisite Quick Start, the tile should appear. + 4. Click on the installed operator + + You will be brought to the **Operator Details** page and be presented with **Provided APIs** + review: + instructions: |- + #### Verify you see a list of **Provided APIs**: + The list should include ` + "`" + `Issuer` + "`" + `, ` + "`" + `ClusterIssuer` + "`" + `, and ` + "`" + `Certificate` + "`" + ` + failedTaskHelp: This task isn't verified yet. Try the task again. + summary: + success: You are in the right place, and ready to start the rest of the Quick Start + failed: Try the steps again. + - title: Select a project + description: |- + ### Create or select a project to work in + 1. Find the **Project** dropdown menu at the top of the screen. + 2. Select or create the project in which you want to work in. + + **Note**: For this example, we'll create an ` + "`" + `Issuer` + "`" + ` which is namespace-scoped. If you want to issue certificates + across multiple namespaces, you can create a ` + "`" + `ClusterIssuer` + "`" + ` instead. + review: + instructions: |- + #### Verify the name in the **Project** dropdown menu is the expected project + failedTaskHelp: Try the task again. + summary: + success: You are in the right place. + failed: Try the steps again. + - title: Create a self-signed Issuer + description: |- + ### To create a self-signed Issuer + + An Issuer represents a certificate authority that can sign certificates. We'll create a self-signed Issuer + for this example. This is useful for testing and development. + + 1. Find the ` + "`" + `Issuer` + "`" + ` Custom Resource in the list of **Provided APIs** or in the top side-scrolling menu bar. + - From the list of **Provided APIs**, click the **Create instance** link. + - From the **top side-scrolling menu bar**, click **Issuer** and then click **Create Issuer**. + + 2. Switch to **YAML view** in the editor. + + 3. On the right sidebar, look for the **Samples** section and select **"Example Self-Signed Issuer"**. + + 4. Click **Try it** to populate the editor with the sample YAML. + + 5. Click the **Create** button to create the Issuer. + review: + instructions: |- + #### Verify the Issuer was successfully created: + 1. You should see the Issuer listed with the name ` + "`" + `selfsigned-issuer` + "`" + ` + 2. Check that the **Ready** condition shows **True** in the Conditions section + failedTaskHelp: This task isn't verified yet. Try the task again. + summary: + success: You just created a self-signed Issuer! Now we can create certificates. + failed: Try the steps again. + - title: Create a Certificate + description: |- + ### To create a Certificate + + Now we'll create a certificate that will be signed by our Issuer. + + 1. Find the ` + "`" + `Certificate` + "`" + ` Custom Resource in the list of **Provided APIs** or in the top side-scrolling menu bar. + - From the list of **Provided APIs**, click the **Create instance** link. + - From the **top side-scrolling menu bar**, click **Certificate** and then click **Create Certificate**. + + 2. Switch to **YAML view** in the editor. + + 3. On the right sidebar, look for the **Samples** section and select **"Example Certificate"**. + + 4. Click **Try it** to populate the editor with the sample YAML. + + 5. Click the **Create** button to create the Certificate. + review: + instructions: |- + #### Verify the Certificate was successfully created: + 1. You should see the Certificate listed with the name ` + "`" + `example-cert` + "`" + `. + 2. Check that the **Ready** condition shows **True**. + 3. Navigate to the [Workloads]{{highlight qs-nav-workloads}} section and click **Secrets** + 4. You should see a new Secret named ` + "`" + `example-tls` + "`" + `. + failedTaskHelp: This task isn't verified yet. Try the task again. + summary: + success: You just created your first certificate! cert-manager has issued it and stored it in a Secret. + failed: Try the steps again. + - title: View the certificate + description: |- + ### To inspect the certificate + + Let's look at the Secret that contains the certificate. + + 1. Make sure you're in the [Workloads]{{highlight qs-nav-workloads}} section, click **Secrets**. + 2. Click on the **example-tls** Secret. + 3. You should see the certificate data with keys: + - ` + "`" + `tls.crt` + "`" + ` - The certificate. + - ` + "`" + `tls.key` + "`" + ` - The private key. + - ` + "`" + `ca.crt` + "`" + ` - The CA certificate. + review: + instructions: |- + #### Verify you can see the certificate data: + Is the Secret ` + "`" + `example-tls` + "`" + ` present with ` + "`" + `tls.crt` + "`" + ` and ` + "`" + `tls.key` + "`" + `? + failedTaskHelp: This task isn't verified yet. Try the task again. + summary: + success: Great! Your certificate is ready to use. + failed: Try the steps again. + - title: Use the certificate + description: |- + ### How to use certificates + + Now you can use this certificate in your applications. Here's a simple example for an OpenShift Route: + + ` + "`" + `` + "`" + `` + "`" + `yaml + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + name: my-app + spec: + to: + kind: Service + name: my-service + tls: + termination: edge + externalCertificate: + name: example-tls + ` + "`" + `` + "`" + `` + "`" + ` + + Or in an Ingress: + + ` + "`" + `` + "`" + `` + "`" + `yaml + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: my-app + spec: + tls: + - secretName: example-tls + rules: + - host: example.com + ` + "`" + `` + "`" + `` + "`" + ` + + **Note**: cert-manager will automatically renew certificates before they expire! + review: + instructions: |- + #### Do you understand how to use certificates? + Certificates can be referenced in Routes and Ingress resources. + failedTaskHelp: Review the examples above. + summary: + success: You now know how to create and use certificates! + failed: Review the examples again. + conclusion: |- + Great job! You've successfully created your first Issuer and Certificate. + + ### Next Steps: + + - For production, use Let's Encrypt (ACME Issuer) instead of self-signed certificates + - Explore ClusterIssuer for cluster-wide certificate management + - Check out the [cert-manager documentation](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/cert-manager-operator-for-red-hat-openshift) to learn more +`) + +func consoleCertManagerExampleQuickstartYamlBytes() ([]byte, error) { + return _consoleCertManagerExampleQuickstartYaml, nil +} + +func consoleCertManagerExampleQuickstartYaml() (*asset, error) { + bytes, err := consoleCertManagerExampleQuickstartYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "console/cert-manager-example-quickstart.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _consoleCertManagerIssuerSampleYaml = []byte(`apiVersion: console.openshift.io/v1 +kind: ConsoleYAMLSample +metadata: + name: cert-manager-issuer-sample +spec: + targetResource: + apiVersion: cert-manager.io/v1 + kind: Issuer + title: Example Self-Signed Issuer + description: A simple self-signed Issuer for development and testing + yaml: |- + apiVersion: cert-manager.io/v1 + kind: Issuer + metadata: + name: selfsigned-issuer + namespace: default + spec: + selfSigned: {} + +`) + +func consoleCertManagerIssuerSampleYamlBytes() ([]byte, error) { + return _consoleCertManagerIssuerSampleYaml, nil +} + +func consoleCertManagerIssuerSampleYaml() (*asset, error) { + bytes, err := consoleCertManagerIssuerSampleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "console/cert-manager-issuer-sample.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + var _istioCsrCertManagerIstioCsrClusterroleYaml = []byte(`kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -3772,6 +4112,10 @@ var _bindata = map[string]func() (*asset, error){ "cert-manager-deployment/webhook/cert-manager-webhook-subjectaccessreviews-crb.yaml": certManagerDeploymentWebhookCertManagerWebhookSubjectaccessreviewsCrbYaml, "cert-manager-deployment/webhook/cert-manager-webhook-svc.yaml": certManagerDeploymentWebhookCertManagerWebhookSvcYaml, "cert-manager-deployment/webhook/cert-manager-webhook-validatingwebhookconfiguration.yaml": certManagerDeploymentWebhookCertManagerWebhookValidatingwebhookconfigurationYaml, + "console/cert-manager-acme-issuer-sample.yaml": consoleCertManagerAcmeIssuerSampleYaml, + "console/cert-manager-certificate-sample.yaml": consoleCertManagerCertificateSampleYaml, + "console/cert-manager-example-quickstart.yaml": consoleCertManagerExampleQuickstartYaml, + "console/cert-manager-issuer-sample.yaml": consoleCertManagerIssuerSampleYaml, "istio-csr/cert-manager-istio-csr-clusterrole.yaml": istioCsrCertManagerIstioCsrClusterroleYaml, "istio-csr/cert-manager-istio-csr-clusterrolebinding.yaml": istioCsrCertManagerIstioCsrClusterrolebindingYaml, "istio-csr/cert-manager-istio-csr-deployment.yaml": istioCsrCertManagerIstioCsrDeploymentYaml, @@ -3903,6 +4247,12 @@ var _bintree = &bintree{nil, map[string]*bintree{ "cert-manager-webhook-validatingwebhookconfiguration.yaml": {certManagerDeploymentWebhookCertManagerWebhookValidatingwebhookconfigurationYaml, map[string]*bintree{}}, }}, }}, + "console": {nil, map[string]*bintree{ + "cert-manager-acme-issuer-sample.yaml": {consoleCertManagerAcmeIssuerSampleYaml, map[string]*bintree{}}, + "cert-manager-certificate-sample.yaml": {consoleCertManagerCertificateSampleYaml, map[string]*bintree{}}, + "cert-manager-example-quickstart.yaml": {consoleCertManagerExampleQuickstartYaml, map[string]*bintree{}}, + "cert-manager-issuer-sample.yaml": {consoleCertManagerIssuerSampleYaml, map[string]*bintree{}}, + }}, "istio-csr": {nil, map[string]*bintree{ "cert-manager-istio-csr-clusterrole.yaml": {istioCsrCertManagerIstioCsrClusterroleYaml, map[string]*bintree{}}, "cert-manager-istio-csr-clusterrolebinding.yaml": {istioCsrCertManagerIstioCsrClusterrolebindingYaml, map[string]*bintree{}}, diff --git a/pkg/operator/starter.go b/pkg/operator/starter.go index 188f73d35..7663cf257 100644 --- a/pkg/operator/starter.go +++ b/pkg/operator/starter.go @@ -6,6 +6,7 @@ import ( "time" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" @@ -87,6 +88,11 @@ func RunOperator(ctx context.Context, cc *controllercmd.ControllerContext) error return err } + dynamicClient, err := dynamic.NewForConfig(cc.KubeConfig) + if err != nil { + return err + } + infraGVR := configv1.GroupVersion.WithResource("infrastructures") optInfraInformer, err := utils.InitInformerIfAvailable( utils.NewResourceDiscoverer(infraGVR, configClient.Discovery()), @@ -106,6 +112,8 @@ func RunOperator(ctx context.Context, cc *controllercmd.ControllerContext) error operatorClient, certManagerInformers, resourceapply.NewKubeClientHolder(kubeClient).WithAPIExtensionsClient(apiExtensionsClient), + configClient, + dynamicClient, cc.EventRecorder, status.VersionForOperandFromEnv(), versionRecorder, diff --git a/test/e2e/console_resources_test.go b/test/e2e/console_resources_test.go new file mode 100644 index 000000000..d64c40bbe --- /dev/null +++ b/test/e2e/console_resources_test.go @@ -0,0 +1,125 @@ +//go:build e2e +// +build e2e + +package e2e + +import ( + "context" + "fmt" + "slices" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + configapiv1 "github.com/openshift/api/config/v1" + "github.com/openshift/library-go/pkg/operator/resource/resourceread" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/openshift/cert-manager-operator/pkg/controller/certmanager" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +var consoleExpectedAssets = []struct { + file string + name string + gvr schema.GroupVersionResource +}{ + {file: "console/cert-manager-acme-issuer-sample.yaml", name: "cert-manager-acme-issuer-sample", gvr: certmanager.ConsoleYAMLSampleGVR}, + {file: "console/cert-manager-certificate-sample.yaml", name: "cert-manager-certificate-sample", gvr: certmanager.ConsoleYAMLSampleGVR}, + {file: "console/cert-manager-issuer-sample.yaml", name: "cert-manager-issuer-sample", gvr: certmanager.ConsoleYAMLSampleGVR}, + {file: "console/cert-manager-example-quickstart.yaml", name: "cert-manager-example", gvr: certmanager.ConsoleQuickStartGVR}, +} + +func hasConsoleCapability(ctx context.Context) (bool, error) { + version, err := configClient.ClusterVersions().Get(ctx, "version", metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("failed to get ClusterVersion: %w", err) + } + + return slices.Contains( + version.Status.Capabilities.EnabledCapabilities, + configapiv1.ClusterVersionCapabilityConsole, + ), nil +} + +var _ = Describe("Console Resources", Label("Platform:Generic"), Ordered, func() { + var ( + ctx context.Context + consoleEnabled bool + ) + + BeforeAll(func() { + ctx = context.Background() + + By("detecting Console capability on the cluster") + enabled, err := hasConsoleCapability(ctx) + Expect(err).NotTo(HaveOccurred(), "failed to check Console capability") + consoleEnabled = enabled + + if consoleEnabled { + fmt.Fprintf(GinkgoWriter, "Console capability is ENABLED on this cluster\n") + } else { + fmt.Fprintf(GinkgoWriter, "Console capability is DISABLED on this cluster\n") + } + }) + + BeforeEach(func() { + By("waiting for operator status to become available") + err := VerifyHealthyOperatorConditions(certmanageroperatorclient.OperatorV1alpha1()) + Expect(err).NotTo(HaveOccurred(), "operator is expected to be available") + }) + + Context("when Console capability is enabled", func() { + It("should create all console resources with correct content", func() { + if !consoleEnabled { + Skip("Console capability is not enabled on this cluster") + } + + for _, asset := range consoleExpectedAssets { + By(fmt.Sprintf("verifying %s exists", asset.name)) + + var live *unstructured.Unstructured + err := wait.PollUntilContextTimeout(ctx, fastPollInterval, lowTimeout, true, func(ctx context.Context) (bool, error) { + obj, getErr := loader.DynamicClient.Resource(asset.gvr).Get(ctx, asset.name, metav1.GetOptions{}) + if getErr != nil { + if apierrors.IsNotFound(getErr) { + return false, nil + } + return false, getErr + } + live = obj + return true, nil + }) + Expect(err).NotTo(HaveOccurred(), "timed out waiting for %s to be created", asset.name) + + By(fmt.Sprintf("validating content of %s", asset.name)) + data, err := assets.Asset(asset.file) + Expect(err).NotTo(HaveOccurred(), "failed to load bindata asset %s", asset.file) + expected := resourceread.ReadUnstructuredOrDie(data) + + Expect(live.Object["spec"]).To(Equal(expected.Object["spec"]), + "spec mismatch for %s", asset.name) + } + }) + }) + + Context("when Console capability is disabled", func() { + It("should not create any console resources", func() { + if consoleEnabled { + Skip("Console capability is enabled on this cluster") + } + + for _, asset := range consoleExpectedAssets { + By(fmt.Sprintf("verifying %s does not exist", asset.name)) + _, err := loader.DynamicClient.Resource(asset.gvr).Get(ctx, asset.name, metav1.GetOptions{}) + Expect(apierrors.IsNotFound(err)).To(BeTrue(), + "expected %s to not exist on consoleless cluster, but got: %v", asset.name, err) + } + }) + }) +}) diff --git a/test/go.mod b/test/go.mod index 77c538f77..9c4a16281 100644 --- a/test/go.mod +++ b/test/go.mod @@ -22,6 +22,7 @@ require ( github.com/openshift/api v0.0.0-20260513085653-694421e64aee github.com/openshift/cert-manager-operator v0.0.0-00010101000000-000000000000 github.com/openshift/client-go v0.0.0-20260330134249-7e1499aaacd7 + github.com/openshift/library-go v0.0.0-20260512161954-889c2cd3e381 github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.18.0 github.com/vmware-archive/yaml-patch v0.0.11 @@ -102,7 +103,6 @@ require ( github.com/nxadm/tail v1.4.11 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/openshift/library-go v0.0.0-20260512161954-889c2cd3e381 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect @@ -147,11 +147,13 @@ require ( k8s.io/component-helpers v0.35.2 // indirect k8s.io/controller-manager v0.35.2 // indirect k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-aggregator v0.35.2 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/kubelet v0.35.2 // indirect k8s.io/kubernetes v1.35.2 // indirect sigs.k8s.io/gateway-api v1.5.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect ) diff --git a/test/go.sum b/test/go.sum index 393df543e..326e381eb 100644 --- a/test/go.sum +++ b/test/go.sum @@ -412,6 +412,8 @@ k8s.io/controller-manager v0.35.2 h1:EpLIwm4bBgoFwXiVULgNxtpUF9cbXUkcGkZ6vUlurYQ k8s.io/controller-manager v0.35.2/go.mod h1:CknUpFd1A0S9h+J9eQZXvZt05ssXMnsYCCLRghm/gp4= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-aggregator v0.35.2 h1:bnF7E238wUOVaPpTyKrqGCAEXOAJ6HRTARvJTZ0UIC0= +k8s.io/kube-aggregator v0.35.2/go.mod h1:7Xl9zFJFsFIrPnwBfu7hve+G5QgLsDZRIedc8gA1mq4= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/kubelet v0.35.2 h1:qF9jOe1j6vT4bVQZ6nnTTA5uu5NCnyR10o9IkW8Z0JQ= @@ -426,6 +428,8 @@ sigs.k8s.io/gateway-api v1.5.0 h1:duoo14Ky/fJXpjpmyMISE2RTBGnfCg8zICfTYLTnBJA= sigs.k8s.io/gateway-api v1.5.0/go.mod h1:GvCETiaMAlLym5CovLxGjS0NysqFk3+Yuq3/rh6QL2o= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 h1:PFWFSkpArPNJxFX4ZKWAk9NSeRoZaXschn+ULa4xVek= +sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96/go.mod h1:EOBQyBowOUsd7U4CJnMHNE0ri+zCXyouGdLwC/jZU+I= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= From a00f47d19d18438879a6ab6a35803a74e0e32ce8 Mon Sep 17 00:00:00 2001 From: Brandon Palm Date: Mon, 20 Jul 2026 15:42:02 -0500 Subject: [PATCH 2/2] OCPBUGS-85579: Fix go mod tidy and gofmt issues Run go mod tidy in test/ and gofmt on console_resources_test.go to fix CI verify failures. --- pkg/controller/certmanager/console_resources_test.go | 3 +-- test/go.mod | 2 ++ test/go.sum | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/controller/certmanager/console_resources_test.go b/pkg/controller/certmanager/console_resources_test.go index 1b79df2c3..910151bb1 100644 --- a/pkg/controller/certmanager/console_resources_test.go +++ b/pkg/controller/certmanager/console_resources_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -447,4 +447,3 @@ func TestConsoleAssetContent(t *testing.T) { }) } } - diff --git a/test/go.mod b/test/go.mod index 9c4a16281..d9dc68f9b 100644 --- a/test/go.mod +++ b/test/go.mod @@ -103,12 +103,14 @@ require ( github.com/nxadm/tail v1.4.11 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/operator-framework/operator-lib v0.19.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.19.2 // indirect + github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/test/go.sum b/test/go.sum index 326e381eb..9731197f0 100644 --- a/test/go.sum +++ b/test/go.sum @@ -237,6 +237,8 @@ github.com/openshift/jetstack-cert-manager v1.20.3 h1:WAOKjcWJxXLZ2VBXdDLXuf2RB6 github.com/openshift/jetstack-cert-manager v1.20.3/go.mod h1:Aqf5P0xRh9aey1p10m2c3UAk/Vb/FBPyH3WQxJRm+7Y= github.com/openshift/library-go v0.0.0-20260512161954-889c2cd3e381 h1:DuRbvQFYd/2HzMCL+s5Z1kCXh+mdL7Q9UZG99/Sa/ys= github.com/openshift/library-go v0.0.0-20260512161954-889c2cd3e381/go.mod h1:3bi4pLpYRdVd1aEhsHfRTJkwxwPLfRZ+ZePn3RmJd2k= +github.com/operator-framework/operator-lib v0.19.0 h1:az6ogYj21rtU0SF9uYctRLyKp2dtlqTsmpfehFy6Ce8= +github.com/operator-framework/operator-lib v0.19.0/go.mod h1:KxycAjFnHt0DBtHmH3Jm7yHcY5sdrshPKTqM/HKAQ08= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -244,6 +246,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.74.0 h1:AHzMWDxNiAVscJL6+4wkvFRTpMnJqiaZFEKA/osaBXE= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.74.0/go.mod h1:wAR5JopumPtAZnu0Cjv2PSqV4p4QB09LMhc6fZZTXuA= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -252,6 +256,8 @@ github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTU github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=