From b215e396a513881da960ca1731bf8ad36355466f Mon Sep 17 00:00:00 2001 From: openshift-app-platform-shift-bot <267347085+openshift-app-platform-shift-bot@users.noreply.github.com> Date: Fri, 8 May 2026 12:46:39 +0000 Subject: [PATCH 1/2] CM-830: Add TrustManager API types for trust-manager operand management Introduces the TrustManager CRD (trustmanagers.operator.openshift.io/v1alpha1) as specified in the enhancement proposal (EP-1914). This cluster-scoped singleton resource enables day-2 installation and configuration of the trust-manager operand. Key additions: - TrustManager types with spec fields: logLevel, logFormat, trustNamespace, secretTargets, filterExpiredCertificates, defaultCAPackage, resources, affinity, tolerations, nodeSelector - SecretTargetsConfig with Custom/Disabled policy and authorizedSecrets - DefaultCAPackageConfig for OpenShift trusted CA bundle integration - FeatureTrustManager feature gate (Alpha, default disabled) - CRD manifest, deepcopy, client/lister/informer generation - Integration tests for CRD validation (status defaults, singleton, scope, immutability) - YAML test suite covering create/update validation scenarios Co-Authored-By: Claude Opus 4.6 --- api/operator/v1alpha1/features.go | 13 +- .../trustmanager.testsuite.yaml | 697 +++++++++ api/operator/v1alpha1/trustmanager_types.go | 267 ++++ .../v1alpha1/trustmanager_types_test.go | 177 +++ .../v1alpha1/zz_generated.deepcopy.go | 193 +++ .../operator.openshift.io_trustmanagers.yaml | 1327 +++++++++++++++++ .../applyconfigurations/internal/internal.go | 10 + .../v1alpha1/defaultcapackageconfig.go | 27 + .../operator/v1alpha1/secrettargetsconfig.go | 38 + .../operator/v1alpha1/trustmanager.go | 246 +++ .../operator/v1alpha1/trustmanagerconfig.go | 117 ++ .../v1alpha1/trustmanagercontrollerconfig.go | 44 + .../operator/v1alpha1/trustmanagerspec.go | 32 + .../operator/v1alpha1/trustmanagerstatus.go | 78 + pkg/operator/applyconfigurations/utils.go | 14 + .../v1alpha1/fake/fake_operator_client.go | 4 + .../v1alpha1/fake/fake_trustmanager.go | 37 + .../operator/v1alpha1/generated_expansion.go | 2 + .../operator/v1alpha1/operator_client.go | 5 + .../typed/operator/v1alpha1/trustmanager.go | 58 + .../informers/externalversions/generic.go | 2 + .../operator/v1alpha1/interface.go | 7 + .../operator/v1alpha1/trustmanager.go | 85 ++ .../operator/v1alpha1/expansion_generated.go | 4 + .../listers/operator/v1alpha1/trustmanager.go | 32 + 25 files changed, 3515 insertions(+), 1 deletion(-) create mode 100644 api/operator/v1alpha1/tests/trustmanagers.operator.openshift.io/trustmanager.testsuite.yaml create mode 100644 api/operator/v1alpha1/trustmanager_types.go create mode 100644 api/operator/v1alpha1/trustmanager_types_test.go create mode 100644 config/crd/bases/operator.openshift.io_trustmanagers.yaml create mode 100644 pkg/operator/applyconfigurations/operator/v1alpha1/defaultcapackageconfig.go create mode 100644 pkg/operator/applyconfigurations/operator/v1alpha1/secrettargetsconfig.go create mode 100644 pkg/operator/applyconfigurations/operator/v1alpha1/trustmanager.go create mode 100644 pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerconfig.go create mode 100644 pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagercontrollerconfig.go create mode 100644 pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerspec.go create mode 100644 pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerstatus.go create mode 100644 pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_trustmanager.go create mode 100644 pkg/operator/clientset/versioned/typed/operator/v1alpha1/trustmanager.go create mode 100644 pkg/operator/informers/externalversions/operator/v1alpha1/trustmanager.go create mode 100644 pkg/operator/listers/operator/v1alpha1/trustmanager.go diff --git a/api/operator/v1alpha1/features.go b/api/operator/v1alpha1/features.go index d28e35132..5e74f661f 100644 --- a/api/operator/v1alpha1/features.go +++ b/api/operator/v1alpha1/features.go @@ -13,8 +13,19 @@ var ( // For more details, // https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/istio-csr-controller.md FeatureIstioCSR featuregate.Feature = "IstioCSR" + + // FeatureTrustManager enables the controller for trustmanagers.operator.openshift.io resource, + // which extends cert-manager-operator to deploy and manage the trust-manager operand. + // trust-manager provides a way to manage trust bundles in Kubernetes and OpenShift + // clusters by combining trusted certificate sources into bundles that applications + // can trust directly. + // + // For more details, + // https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/trust-manager-controller.md + FeatureTrustManager featuregate.Feature = "TrustManager" ) var OperatorFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ - FeatureIstioCSR: {Default: true, PreRelease: featuregate.GA}, + FeatureIstioCSR: {Default: true, PreRelease: featuregate.GA}, + FeatureTrustManager: {Default: false, PreRelease: featuregate.Alpha}, } diff --git a/api/operator/v1alpha1/tests/trustmanagers.operator.openshift.io/trustmanager.testsuite.yaml b/api/operator/v1alpha1/tests/trustmanagers.operator.openshift.io/trustmanager.testsuite.yaml new file mode 100644 index 000000000..924905bd1 --- /dev/null +++ b/api/operator/v1alpha1/tests/trustmanagers.operator.openshift.io/trustmanager.testsuite.yaml @@ -0,0 +1,697 @@ +apiVersion: apiextensions.k8s.io/v1 +name: TrustManager +crdName: trustmanagers.operator.openshift.io +tests: + onCreate: + - name: Should be able to create a minimal TrustManager with only required fields + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 1 + logFormat: "text" + trustNamespace: "cert-manager" + filterExpiredCertificates: "Disabled" + secretTargets: + policy: "Disabled" + defaultCAPackage: + policy: "Disabled" + + - name: Should reject TrustManager with name other than cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: not-cluster + spec: + trustManagerConfig: {} + expectedError: "TrustManager is a singleton, .metadata.name must be 'cluster'" + + - name: Should accept logFormat text + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "text" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "text" + + - name: Should accept logFormat json + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "json" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "json" + + - name: Should reject invalid logFormat value + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "xml" + expectedError: "Unsupported value" + + - name: Should accept logLevel at minimum boundary (1) + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 1 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 1 + + - name: Should accept logLevel at maximum boundary (5) + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 5 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 5 + + - name: Should reject logLevel below minimum (0) + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 0 + expectedError: "should be greater than or equal to 1" + + - name: Should reject logLevel above maximum (6) + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 6 + expectedError: "should be less than or equal to 5" + + - name: Should accept valid trustNamespace + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "my-trust-ns" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "my-trust-ns" + + - name: Should reject trustNamespace exceeding maxLength + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "this-is-a-very-long-namespace-name-that-exceeds-the-maximum-length-allowed" + expectedError: "should be at most 63" + + - name: Should accept filterExpiredCertificates Enabled + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Enabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Enabled" + + - name: Should accept filterExpiredCertificates Disabled + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Disabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Disabled" + + - name: Should reject invalid filterExpiredCertificates value + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Invalid" + expectedError: "Unsupported value" + + - name: Should accept secretTargets policy Disabled + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Disabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Disabled" + + - name: Should accept secretTargets policy Custom with authorizedSecrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "my-secret" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "my-secret" + + - name: Should reject invalid secretTargets policy + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Invalid" + expectedError: "Unsupported value" + + - name: Should reject secretTargets policy Custom without authorizedSecrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + expectedError: "authorizedSecrets must not be empty when policy is Custom" + + - name: Should reject secretTargets policy Custom with empty authorizedSecrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: [] + expectedError: "authorizedSecrets must not be empty when policy is Custom" + + - name: Should reject authorizedSecrets when policy is not Custom + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Disabled" + authorizedSecrets: + - "my-secret" + expectedError: "authorizedSecrets must be empty when policy is not Custom" + + - name: Should accept defaultCAPackage policy Enabled + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Enabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Enabled" + + - name: Should accept defaultCAPackage policy Disabled + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Disabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Disabled" + + - name: Should reject invalid defaultCAPackage policy value + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Invalid" + expectedError: "Unsupported value" + + - name: Should accept tolerations + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + tolerations: + - key: "example-key" + operator: "Exists" + effect: "NoSchedule" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + tolerations: + - key: "example-key" + operator: "Exists" + effect: "NoSchedule" + + - name: Should accept nodeSelector + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + nodeSelector: + kubernetes.io/os: linux + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + nodeSelector: + kubernetes.io/os: linux + + - name: Should accept controllerConfig with labels and annotations + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + controllerConfig: + labels: + custom-label: value + annotations: + custom-annotation: value + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + controllerConfig: + labels: + custom-label: value + annotations: + custom-annotation: value + + - name: Should accept full configuration + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + logFormat: "json" + trustNamespace: "custom-trust-ns" + filterExpiredCertificates: "Enabled" + secretTargets: + policy: "Custom" + authorizedSecrets: + - "secret-a" + - "secret-b" + defaultCAPackage: + policy: "Enabled" + nodeSelector: + kubernetes.io/os: linux + controllerConfig: + labels: + env: prod + annotations: + note: test + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + logFormat: "json" + trustNamespace: "custom-trust-ns" + filterExpiredCertificates: "Enabled" + secretTargets: + policy: "Custom" + authorizedSecrets: + - "secret-a" + - "secret-b" + defaultCAPackage: + policy: "Enabled" + nodeSelector: + kubernetes.io/os: linux + controllerConfig: + labels: + env: prod + annotations: + note: test + + - name: Should accept secretTargets with multiple authorizedSecrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "secret-one" + - "secret-two" + - "secret-three" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "secret-one" + - "secret-two" + - "secret-three" + + onUpdate: + - name: Should not allow changing immutable trustNamespace + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "original-ns" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "changed-ns" + expectedError: "trustNamespace is immutable once set" + + - name: Should allow updating logLevel + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 1 + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + + - name: Should allow updating logFormat + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "text" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "json" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "json" + + - name: Should allow updating filterExpiredCertificates + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Disabled" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Enabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Enabled" + + - name: Should allow updating secretTargets from Disabled to Custom + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Disabled" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "new-secret" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "new-secret" + + - name: Should allow updating defaultCAPackage policy + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Disabled" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Enabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Enabled" + + - name: Should reject updating secretTargets to Custom without authorizedSecrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Disabled" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + expectedError: "authorizedSecrets must not be empty when policy is Custom" diff --git a/api/operator/v1alpha1/trustmanager_types.go b/api/operator/v1alpha1/trustmanager_types.go new file mode 100644 index 000000000..a44e06ace --- /dev/null +++ b/api/operator/v1alpha1/trustmanager_types.go @@ -0,0 +1,267 @@ +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true + +// TrustManagerList is a list of TrustManager objects. +type TrustManagerList struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard list's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.ListMeta `json:"metadata"` + Items []TrustManager `json:"items"` +} + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=trustmanagers,scope=Cluster,categories={cert-manager-operator} +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:metadata:labels={"app.kubernetes.io/name=trustmanager", "app.kubernetes.io/part-of=cert-manager-operator"} + +// TrustManager describes the configuration and information about the managed trust-manager deployment. +// The name must be `cluster` to make TrustManager a singleton, allowing only one instance per cluster. +// +// When a TrustManager is created, trust-manager is deployed in the cert-manager namespace. +// +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'cluster'",message="TrustManager is a singleton, .metadata.name must be 'cluster'" +// +operator-sdk:csv:customresourcedefinitions:displayName="TrustManager" +type TrustManager struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec is the specification of the desired behavior of the TrustManager. + // +kubebuilder:validation:Required + // +required + Spec TrustManagerSpec `json:"spec"` + + // status is the most recently observed status of the TrustManager. + // +kubebuilder:validation:Optional + // +optional + Status TrustManagerStatus `json:"status,omitempty"` +} + +// TrustManagerSpec defines the desired state of TrustManager. +// Note: trust-manager operand is always deployed in the cert-manager namespace. +type TrustManagerSpec struct { + // trustManagerConfig configures the trust-manager operand's behavior. + // +kubebuilder:validation:Required + // +required + TrustManagerConfig TrustManagerConfig `json:"trustManagerConfig"` + + // controllerConfig configures the operator's behavior for resource creation. + // +kubebuilder:validation:Optional + // +optional + ControllerConfig TrustManagerControllerConfig `json:"controllerConfig,omitempty"` +} + +// TrustManagerConfig configures the trust-manager operand's behavior. +type TrustManagerConfig struct { + // logLevel configures the verbosity of trust-manager logging. + // Follows Kubernetes logging guidelines: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#what-method-to-use + // +kubebuilder:default:=1 + // +kubebuilder:validation:Minimum:=1 + // +kubebuilder:validation:Maximum:=5 + // +kubebuilder:validation:Optional + // +optional + LogLevel int32 `json:"logLevel,omitempty"` + + // logFormat specifies the output format for trust-manager logging. + // Supported formats are "text" and "json". + // +kubebuilder:validation:Enum:="text";"json" + // +kubebuilder:default:="text" + // +kubebuilder:validation:Optional + // +optional + LogFormat string `json:"logFormat,omitempty"` + + // trustNamespace is the namespace where trust-manager looks for trust sources + // (ConfigMaps and Secrets containing CA certificates). + // Defaults to "cert-manager" if not specified. + // This field is immutable once set. + // This field can have a maximum of 63 characters. + // +kubebuilder:default:="cert-manager" + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=63 + // +kubebuilder:validation:XValidation:rule="oldSelf == '' || self == oldSelf",message="trustNamespace is immutable once set" + // +kubebuilder:validation:Optional + // +optional + TrustNamespace string `json:"trustNamespace,omitempty"` + + // secretTargets configures whether trust-manager can write trust bundles to Secrets. + // +kubebuilder:validation:Optional + // +optional + SecretTargets SecretTargetsConfig `json:"secretTargets,omitempty"` + + // filterExpiredCertificates controls whether trust-manager filters out + // expired certificates from trust bundles before distributing them. + // When set to "Enabled", expired certificates are removed from bundles. + // When set to "Disabled", expired certificates are included (default behavior). + // +kubebuilder:default:="Disabled" + // +kubebuilder:validation:Optional + // +optional + FilterExpiredCertificates FilterExpiredCertificatesPolicy `json:"filterExpiredCertificates,omitempty"` + + // defaultCAPackage configures the default CA package for trust-manager. + // When enabled, the operator will use OpenShift's trusted CA bundle injection mechanism. + // +kubebuilder:validation:Optional + // +optional + DefaultCAPackage DefaultCAPackageConfig `json:"defaultCAPackage,omitempty"` + + // resources defines the compute resource requirements for the trust-manager pod. + // ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +kubebuilder:validation:Optional + // +optional + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + + // affinity defines scheduling constraints for the trust-manager pod. + // ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ + // +kubebuilder:validation:Optional + // +optional + Affinity *corev1.Affinity `json:"affinity,omitempty"` + + // tolerations allows the trust-manager pod to be scheduled on tainted nodes. + // This field can have a maximum of 50 entries. + // ref: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + // +listType=atomic + // +kubebuilder:validation:MinItems:=0 + // +kubebuilder:validation:MaxItems:=50 + // +kubebuilder:validation:Optional + // +optional + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + + // nodeSelector restricts which nodes the trust-manager pod can be scheduled on. + // This field can have a maximum of 50 entries. + // ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + // +mapType=atomic + // +kubebuilder:validation:MinProperties:=0 + // +kubebuilder:validation:MaxProperties:=50 + // +kubebuilder:validation:Optional + // +optional + NodeSelector map[string]string `json:"nodeSelector,omitempty"` +} + +// SecretTargetsConfig configures whether and how trust-manager can write +// trust bundles to Secrets. +// +// +kubebuilder:validation:XValidation:rule="self.policy != 'Custom' || (has(self.authorizedSecrets) && size(self.authorizedSecrets) > 0)",message="authorizedSecrets must not be empty when policy is Custom" +// +kubebuilder:validation:XValidation:rule="self.policy == 'Custom' || !has(self.authorizedSecrets) || size(self.authorizedSecrets) == 0",message="authorizedSecrets must be empty when policy is not Custom" +type SecretTargetsConfig struct { + // policy controls whether and how trust-manager can write trust bundles to Secrets. + // Allowed values are "Disabled" or "Custom". + // "Disabled" means trust-manager cannot write trust bundles to Secrets (default behavior). + // "Custom" grants trust-manager permission to create and update only the secrets listed in authorizedSecrets. + // +kubebuilder:default:="Disabled" + // +kubebuilder:validation:Optional + // +optional + Policy SecretTargetsPolicy `json:"policy,omitempty"` + + // authorizedSecrets is a list of specific secret names that trust-manager + // is authorized to create and update. This field is only valid when policy is "Custom". + // +listType=set + // +kubebuilder:validation:MinItems:=0 + // +kubebuilder:validation:items:MinLength:=1 + // +kubebuilder:validation:Optional + // +optional + AuthorizedSecrets []string `json:"authorizedSecrets,omitempty"` +} + +// DefaultCAPackageConfig configures the default CA package feature for trust-manager. +type DefaultCAPackageConfig struct { + // policy controls whether the default CA package feature is enabled. + // When set to "Enabled", the operator will inject OpenShift's trusted CA bundle + // into trust-manager, enabling the "useDefaultCAs: true" source in Bundle resources. + // When set to "Disabled", no default CA package is configured and Bundles cannot use useDefaultCAs (default behavior). + // +kubebuilder:default:="Disabled" + // +kubebuilder:validation:Optional + // +optional + Policy DefaultCAPackagePolicy `json:"policy,omitempty"` +} + +// TrustManagerControllerConfig configures the operator's behavior for +// creating trust-manager resources. +type TrustManagerControllerConfig struct { + // labels to apply to all resources created for the trust-manager deployment. + // +mapType=granular + // +kubebuilder:validation:MinProperties:=0 + // +kubebuilder:validation:Optional + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // annotations to apply to all resources created for the trust-manager deployment. + // +mapType=granular + // +kubebuilder:validation:MinProperties:=0 + // +kubebuilder:validation:Optional + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + +// FilterExpiredCertificatesPolicy defines the policy for filtering expired certificates. +// +kubebuilder:validation:Enum:=Enabled;Disabled +type FilterExpiredCertificatesPolicy string + +const ( + // FilterExpiredCertificatesPolicyEnabled filters out expired certificates from bundles. + FilterExpiredCertificatesPolicyEnabled FilterExpiredCertificatesPolicy = "Enabled" + // FilterExpiredCertificatesPolicyDisabled includes expired certificates in bundles. + FilterExpiredCertificatesPolicyDisabled FilterExpiredCertificatesPolicy = "Disabled" +) + +// SecretTargetsPolicy defines the policy for writing trust bundles to Secrets. +// +kubebuilder:validation:Enum:=Disabled;Custom +type SecretTargetsPolicy string + +const ( + // SecretTargetsPolicyDisabled means trust-manager cannot write trust bundles to Secrets. + SecretTargetsPolicyDisabled SecretTargetsPolicy = "Disabled" + // SecretTargetsPolicyCustom grants trust-manager permission to write to specific secrets only. + SecretTargetsPolicyCustom SecretTargetsPolicy = "Custom" +) + +// DefaultCAPackagePolicy defines the policy for the default CA package feature. +// +kubebuilder:validation:Enum:=Enabled;Disabled +type DefaultCAPackagePolicy string + +const ( + // DefaultCAPackagePolicyEnabled enables the default CA package feature. + DefaultCAPackagePolicyEnabled DefaultCAPackagePolicy = "Enabled" + // DefaultCAPackagePolicyDisabled disables the default CA package feature. + DefaultCAPackagePolicyDisabled DefaultCAPackagePolicy = "Disabled" +) + +// TrustManagerStatus defines the observed state of TrustManager. +type TrustManagerStatus struct { + // conditions holds information about the current state of the trust-manager deployment. + ConditionalStatus `json:",inline,omitempty"` + + // trustManagerImage is the container image (name:tag) used for trust-manager. + TrustManagerImage string `json:"trustManagerImage,omitempty"` + + // trustNamespace is the namespace where trust-manager looks for trust sources. + TrustNamespace string `json:"trustNamespace,omitempty"` + + // secretTargetsPolicy indicates the current secret targets policy. + SecretTargetsPolicy SecretTargetsPolicy `json:"secretTargetsPolicy,omitempty"` + + // defaultCAPackagePolicy indicates the current default CA package policy. + DefaultCAPackagePolicy DefaultCAPackagePolicy `json:"defaultCAPackagePolicy,omitempty"` + + // filterExpiredCertificatesPolicy indicates the current policy for filtering expired certificates. + FilterExpiredCertificatesPolicy FilterExpiredCertificatesPolicy `json:"filterExpiredCertificatesPolicy,omitempty"` +} + +func init() { + SchemeBuilder.Register(&TrustManager{}, &TrustManagerList{}) +} diff --git a/api/operator/v1alpha1/trustmanager_types_test.go b/api/operator/v1alpha1/trustmanager_types_test.go new file mode 100644 index 000000000..ae7314a95 --- /dev/null +++ b/api/operator/v1alpha1/trustmanager_types_test.go @@ -0,0 +1,177 @@ +package v1alpha1 + +import ( + "os" + "path" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "sigs.k8s.io/yaml" +) + +const ( + trustmanagerCRDFile = "operator.openshift.io_trustmanagers.yaml" + trustmanagerCRDFilePath = "../../../config/crd/bases" +) + +// TestTrustManagerStatusDefault verifies that the trustmanager CR status does not have default value +// The admission code is expecting that the trustmanager status +// field will not have a default value. +// It allows separating between clean installation and the roll-back to the previous version of the cluster +func TestTrustManagerStatusDefault(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + status, exists, err := unstructured.NestedMap(trustmanagerCRDVersion, "schema", "openAPIV3Schema", "properties", "status") + if err != nil { + t.Fatalf("failed to get nested map: %v", err) + } + + if !exists { + t.Fatalf("one of fields does not exist under the CRD") + } + + if _, ok := status["default"]; ok { + t.Fatalf("expected no default for the trustmanager CRD status") + } + } +} + +// TestTrustManagerCRDSingleton verifies that the trustmanager CRD has the singleton validation rule +func TestTrustManagerCRDSingleton(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + schema, exists, err := unstructured.NestedMap(trustmanagerCRDVersion, "schema", "openAPIV3Schema") + if err != nil { + t.Fatalf("failed to get nested map: %v", err) + } + if !exists { + t.Fatalf("openAPIV3Schema does not exist under the CRD version") + } + + // Check for x-kubernetes-validations rules containing the singleton rule + validations, exists, err := unstructured.NestedSlice(schema, "x-kubernetes-validations") + if err != nil { + t.Fatalf("failed to get x-kubernetes-validations: %v", err) + } + if !exists { + t.Fatalf("x-kubernetes-validations does not exist on TrustManager CRD schema") + } + + found := false + for _, v := range validations { + rule, ok := v.(map[string]interface{}) + if !ok { + continue + } + if ruleStr, ok := rule["rule"].(string); ok { + if ruleStr == "self.metadata.name == 'cluster'" { + found = true + break + } + } + } + if !found { + t.Fatalf("expected singleton validation rule 'self.metadata.name == cluster' not found") + } + } +} + +// TestTrustManagerCRDScope verifies that the trustmanager CRD is cluster-scoped +func TestTrustManagerCRDScope(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + scope, ok := trustmanagerCRDSpec["scope"].(string) + if !ok { + t.Fatalf("scope field not found in CRD spec") + } + if scope != "Cluster" { + t.Fatalf("expected CRD scope to be 'Cluster', got %q", scope) + } +} + +// TestTrustManagerCRDTrustNamespaceImmutability verifies that trustNamespace has an immutability XValidation rule +func TestTrustManagerCRDTrustNamespaceImmutability(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + trustNamespace, exists, err := unstructured.NestedMap(trustmanagerCRDVersion, + "schema", "openAPIV3Schema", "properties", "spec", "properties", + "trustManagerConfig", "properties", "trustNamespace") + if err != nil { + t.Fatalf("failed to get trustNamespace: %v", err) + } + if !exists { + t.Fatalf("trustNamespace field does not exist in the CRD") + } + + validations, exists, err := unstructured.NestedSlice(trustNamespace, "x-kubernetes-validations") + if err != nil { + t.Fatalf("failed to get x-kubernetes-validations on trustNamespace: %v", err) + } + if !exists { + t.Fatalf("trustNamespace does not have x-kubernetes-validations") + } + + found := false + for _, val := range validations { + rule, ok := val.(map[string]interface{}) + if !ok { + continue + } + if msg, ok := rule["message"].(string); ok { + if msg == "trustNamespace is immutable once set" { + found = true + break + } + } + } + if !found { + t.Fatalf("expected immutability validation rule for trustNamespace not found") + } + } +} diff --git a/api/operator/v1alpha1/zz_generated.deepcopy.go b/api/operator/v1alpha1/zz_generated.deepcopy.go index d878e25ce..eddbd4337 100644 --- a/api/operator/v1alpha1/zz_generated.deepcopy.go +++ b/api/operator/v1alpha1/zz_generated.deepcopy.go @@ -278,6 +278,21 @@ func (in *ControllerConfig) DeepCopy() *ControllerConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DefaultCAPackageConfig) DeepCopyInto(out *DefaultCAPackageConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultCAPackageConfig. +func (in *DefaultCAPackageConfig) DeepCopy() *DefaultCAPackageConfig { + if in == nil { + return nil + } + out := new(DefaultCAPackageConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeploymentConfig) DeepCopyInto(out *DeploymentConfig) { *out = *in @@ -535,6 +550,26 @@ func (in *NetworkPolicy) DeepCopy() *NetworkPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretTargetsConfig) DeepCopyInto(out *SecretTargetsConfig) { + *out = *in + if in.AuthorizedSecrets != nil { + in, out := &in.AuthorizedSecrets, &out.AuthorizedSecrets + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretTargetsConfig. +func (in *SecretTargetsConfig) DeepCopy() *SecretTargetsConfig { + if in == nil { + return nil + } + out := new(SecretTargetsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerConfig) DeepCopyInto(out *ServerConfig) { *out = *in @@ -550,6 +585,164 @@ func (in *ServerConfig) DeepCopy() *ServerConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManager) DeepCopyInto(out *TrustManager) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManager. +func (in *TrustManager) DeepCopy() *TrustManager { + if in == nil { + return nil + } + out := new(TrustManager) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrustManager) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManagerConfig) DeepCopyInto(out *TrustManagerConfig) { + *out = *in + in.SecretTargets.DeepCopyInto(&out.SecretTargets) + out.DefaultCAPackage = in.DefaultCAPackage + in.Resources.DeepCopyInto(&out.Resources) + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerConfig. +func (in *TrustManagerConfig) DeepCopy() *TrustManagerConfig { + if in == nil { + return nil + } + out := new(TrustManagerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManagerControllerConfig) DeepCopyInto(out *TrustManagerControllerConfig) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerControllerConfig. +func (in *TrustManagerControllerConfig) DeepCopy() *TrustManagerControllerConfig { + if in == nil { + return nil + } + out := new(TrustManagerControllerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManagerList) DeepCopyInto(out *TrustManagerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TrustManager, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerList. +func (in *TrustManagerList) DeepCopy() *TrustManagerList { + if in == nil { + return nil + } + out := new(TrustManagerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrustManagerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManagerSpec) DeepCopyInto(out *TrustManagerSpec) { + *out = *in + in.TrustManagerConfig.DeepCopyInto(&out.TrustManagerConfig) + in.ControllerConfig.DeepCopyInto(&out.ControllerConfig) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerSpec. +func (in *TrustManagerSpec) DeepCopy() *TrustManagerSpec { + if in == nil { + return nil + } + out := new(TrustManagerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManagerStatus) DeepCopyInto(out *TrustManagerStatus) { + *out = *in + in.ConditionalStatus.DeepCopyInto(&out.ConditionalStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerStatus. +func (in *TrustManagerStatus) DeepCopy() *TrustManagerStatus { + if in == nil { + return nil + } + out := new(TrustManagerStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UnsupportedConfigOverrides) DeepCopyInto(out *UnsupportedConfigOverrides) { *out = *in diff --git a/config/crd/bases/operator.openshift.io_trustmanagers.yaml b/config/crd/bases/operator.openshift.io_trustmanagers.yaml new file mode 100644 index 000000000..ce0f61ea7 --- /dev/null +++ b/config/crd/bases/operator.openshift.io_trustmanagers.yaml @@ -0,0 +1,1327 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: trustmanager + app.kubernetes.io/part-of: cert-manager-operator + name: trustmanagers.operator.openshift.io +spec: + group: operator.openshift.io + names: + categories: + - cert-manager-operator + kind: TrustManager + listKind: TrustManagerList + plural: trustmanagers + singular: trustmanager + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Message + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + TrustManager describes the configuration and information about the managed trust-manager deployment. + The name must be `cluster` to make TrustManager a singleton, allowing only one instance per cluster. + + When a TrustManager is created, trust-manager is deployed in the cert-manager namespace. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is the specification of the desired behavior of the + TrustManager. + properties: + controllerConfig: + description: controllerConfig configures the operator's behavior for + resource creation. + properties: + annotations: + additionalProperties: + type: string + description: annotations to apply to all resources created for + the trust-manager deployment. + minProperties: 0 + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: labels to apply to all resources created for the + trust-manager deployment. + minProperties: 0 + type: object + x-kubernetes-map-type: granular + type: object + trustManagerConfig: + description: trustManagerConfig configures the trust-manager operand's + behavior. + properties: + affinity: + description: |- + affinity defines scheduling constraints for the trust-manager pod. + ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + defaultCAPackage: + description: |- + defaultCAPackage configures the default CA package for trust-manager. + When enabled, the operator will use OpenShift's trusted CA bundle injection mechanism. + properties: + policy: + default: Disabled + description: |- + policy controls whether the default CA package feature is enabled. + When set to "Enabled", the operator will inject OpenShift's trusted CA bundle + into trust-manager, enabling the "useDefaultCAs: true" source in Bundle resources. + When set to "Disabled", no default CA package is configured and Bundles cannot use useDefaultCAs (default behavior). + enum: + - Enabled + - Disabled + type: string + type: object + filterExpiredCertificates: + default: Disabled + description: |- + filterExpiredCertificates controls whether trust-manager filters out + expired certificates from trust bundles before distributing them. + When set to "Enabled", expired certificates are removed from bundles. + When set to "Disabled", expired certificates are included (default behavior). + enum: + - Enabled + - Disabled + type: string + logFormat: + default: text + description: |- + logFormat specifies the output format for trust-manager logging. + Supported formats are "text" and "json". + enum: + - text + - json + type: string + logLevel: + default: 1 + description: |- + logLevel configures the verbosity of trust-manager logging. + Follows Kubernetes logging guidelines: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#what-method-to-use + format: int32 + maximum: 5 + minimum: 1 + type: integer + nodeSelector: + additionalProperties: + type: string + description: |- + nodeSelector restricts which nodes the trust-manager pod can be scheduled on. + This field can have a maximum of 50 entries. + ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + maxProperties: 50 + minProperties: 0 + type: object + x-kubernetes-map-type: atomic + resources: + description: |- + resources defines the compute resource requirements for the trust-manager pod. + ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + secretTargets: + description: secretTargets configures whether trust-manager can + write trust bundles to Secrets. + properties: + authorizedSecrets: + description: |- + authorizedSecrets is a list of specific secret names that trust-manager + is authorized to create and update. This field is only valid when policy is "Custom". + items: + minLength: 1 + type: string + minItems: 0 + type: array + x-kubernetes-list-type: set + policy: + default: Disabled + description: |- + policy controls whether and how trust-manager can write trust bundles to Secrets. + Allowed values are "Disabled" or "Custom". + "Disabled" means trust-manager cannot write trust bundles to Secrets (default behavior). + "Custom" grants trust-manager permission to create and update only the secrets listed in authorizedSecrets. + enum: + - Disabled + - Custom + type: string + type: object + x-kubernetes-validations: + - message: authorizedSecrets must not be empty when policy is + Custom + rule: self.policy != 'Custom' || (has(self.authorizedSecrets) + && size(self.authorizedSecrets) > 0) + - message: authorizedSecrets must be empty when policy is not + Custom + rule: self.policy == 'Custom' || !has(self.authorizedSecrets) + || size(self.authorizedSecrets) == 0 + tolerations: + description: |- + tolerations allows the trust-manager pod to be scheduled on tainted nodes. + This field can have a maximum of 50 entries. + ref: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + maxItems: 50 + minItems: 0 + type: array + x-kubernetes-list-type: atomic + trustNamespace: + default: cert-manager + description: |- + trustNamespace is the namespace where trust-manager looks for trust sources + (ConfigMaps and Secrets containing CA certificates). + Defaults to "cert-manager" if not specified. + This field is immutable once set. + This field can have a maximum of 63 characters. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: trustNamespace is immutable once set + rule: oldSelf == '' || self == oldSelf + type: object + required: + - trustManagerConfig + type: object + status: + description: status is the most recently observed status of the TrustManager. + properties: + conditions: + description: conditions holds information about the current state + of the istio-csr agent deployment. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + defaultCAPackagePolicy: + description: defaultCAPackagePolicy indicates the current default + CA package policy. + enum: + - Enabled + - Disabled + type: string + filterExpiredCertificatesPolicy: + description: filterExpiredCertificatesPolicy indicates the current + policy for filtering expired certificates. + enum: + - Enabled + - Disabled + type: string + secretTargetsPolicy: + description: secretTargetsPolicy indicates the current secret targets + policy. + enum: + - Disabled + - Custom + type: string + trustManagerImage: + description: trustManagerImage is the container image (name:tag) used + for trust-manager. + type: string + trustNamespace: + description: trustNamespace is the namespace where trust-manager looks + for trust sources. + type: string + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: TrustManager is a singleton, .metadata.name must be 'cluster' + rule: self.metadata.name == 'cluster' + served: true + storage: true + subresources: + status: {} diff --git a/pkg/operator/applyconfigurations/internal/internal.go b/pkg/operator/applyconfigurations/internal/internal.go index ab48e360d..cf84b3f02 100644 --- a/pkg/operator/applyconfigurations/internal/internal.go +++ b/pkg/operator/applyconfigurations/internal/internal.go @@ -43,6 +43,16 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: __untyped_deduced_ elementRelationship: separable +- name: com.github.openshift.cert-manager-operator.api.operator.v1alpha1.TrustManager + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable - name: __untyped_atomic_ scalar: untyped list: diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/defaultcapackageconfig.go b/pkg/operator/applyconfigurations/operator/v1alpha1/defaultcapackageconfig.go new file mode 100644 index 000000000..a45123bee --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/defaultcapackageconfig.go @@ -0,0 +1,27 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +// DefaultCAPackageConfigApplyConfiguration represents a declarative configuration of the DefaultCAPackageConfig type for use +// with apply. +type DefaultCAPackageConfigApplyConfiguration struct { + Policy *operatorv1alpha1.DefaultCAPackagePolicy `json:"policy,omitempty"` +} + +// DefaultCAPackageConfigApplyConfiguration constructs a declarative configuration of the DefaultCAPackageConfig type for use with +// apply. +func DefaultCAPackageConfig() *DefaultCAPackageConfigApplyConfiguration { + return &DefaultCAPackageConfigApplyConfiguration{} +} + +// WithPolicy sets the Policy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Policy field is set to the value of the last call. +func (b *DefaultCAPackageConfigApplyConfiguration) WithPolicy(value operatorv1alpha1.DefaultCAPackagePolicy) *DefaultCAPackageConfigApplyConfiguration { + b.Policy = &value + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/secrettargetsconfig.go b/pkg/operator/applyconfigurations/operator/v1alpha1/secrettargetsconfig.go new file mode 100644 index 000000000..c272e6bd7 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/secrettargetsconfig.go @@ -0,0 +1,38 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +// SecretTargetsConfigApplyConfiguration represents a declarative configuration of the SecretTargetsConfig type for use +// with apply. +type SecretTargetsConfigApplyConfiguration struct { + Policy *operatorv1alpha1.SecretTargetsPolicy `json:"policy,omitempty"` + AuthorizedSecrets []string `json:"authorizedSecrets,omitempty"` +} + +// SecretTargetsConfigApplyConfiguration constructs a declarative configuration of the SecretTargetsConfig type for use with +// apply. +func SecretTargetsConfig() *SecretTargetsConfigApplyConfiguration { + return &SecretTargetsConfigApplyConfiguration{} +} + +// WithPolicy sets the Policy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Policy field is set to the value of the last call. +func (b *SecretTargetsConfigApplyConfiguration) WithPolicy(value operatorv1alpha1.SecretTargetsPolicy) *SecretTargetsConfigApplyConfiguration { + b.Policy = &value + return b +} + +// WithAuthorizedSecrets adds the given value to the AuthorizedSecrets field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the AuthorizedSecrets field. +func (b *SecretTargetsConfigApplyConfiguration) WithAuthorizedSecrets(values ...string) *SecretTargetsConfigApplyConfiguration { + for i := range values { + b.AuthorizedSecrets = append(b.AuthorizedSecrets, values[i]) + } + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanager.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanager.go new file mode 100644 index 000000000..2ab528196 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanager.go @@ -0,0 +1,246 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + internal "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// TrustManagerApplyConfiguration represents a declarative configuration of the TrustManager type for use +// with apply. +type TrustManagerApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *TrustManagerSpecApplyConfiguration `json:"spec,omitempty"` + Status *TrustManagerStatusApplyConfiguration `json:"status,omitempty"` +} + +// TrustManager constructs a declarative configuration of the TrustManager type for use with +// apply. +func TrustManager(name string) *TrustManagerApplyConfiguration { + b := &TrustManagerApplyConfiguration{} + b.WithName(name) + b.WithKind("TrustManager") + b.WithAPIVersion("operator.openshift.io/v1alpha1") + return b +} + +// ExtractTrustManager extracts the applied configuration owned by fieldManager from +// trustManager. If no managedFields are found in trustManager for fieldManager, a +// TrustManagerApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// trustManager must be a unmodified TrustManager API object that was retrieved from the Kubernetes API. +// ExtractTrustManager provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractTrustManager(trustManager *operatorv1alpha1.TrustManager, fieldManager string) (*TrustManagerApplyConfiguration, error) { + return extractTrustManager(trustManager, fieldManager, "") +} + +// ExtractTrustManagerStatus is the same as ExtractTrustManager except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractTrustManagerStatus(trustManager *operatorv1alpha1.TrustManager, fieldManager string) (*TrustManagerApplyConfiguration, error) { + return extractTrustManager(trustManager, fieldManager, "status") +} + +func extractTrustManager(trustManager *operatorv1alpha1.TrustManager, fieldManager string, subresource string) (*TrustManagerApplyConfiguration, error) { + b := &TrustManagerApplyConfiguration{} + err := managedfields.ExtractInto(trustManager, internal.Parser().Type("com.github.openshift.cert-manager-operator.api.operator.v1alpha1.TrustManager"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(trustManager.Name) + + b.WithKind("TrustManager") + b.WithAPIVersion("operator.openshift.io/v1alpha1") + return b, nil +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithKind(value string) *TrustManagerApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithAPIVersion(value string) *TrustManagerApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithName(value string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithGenerateName(value string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithNamespace(value string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithUID(value types.UID) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithResourceVersion(value string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithGeneration(value int64) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *TrustManagerApplyConfiguration) WithLabels(entries map[string]string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *TrustManagerApplyConfiguration) WithAnnotations(entries map[string]string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *TrustManagerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *TrustManagerApplyConfiguration) WithFinalizers(values ...string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *TrustManagerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithSpec(value *TrustManagerSpecApplyConfiguration) *TrustManagerApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithStatus(value *TrustManagerStatusApplyConfiguration) *TrustManagerApplyConfiguration { + b.Status = value + return b +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *TrustManagerApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerconfig.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerconfig.go new file mode 100644 index 000000000..9f1e59efa --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerconfig.go @@ -0,0 +1,117 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + v1 "k8s.io/api/core/v1" +) + +// TrustManagerConfigApplyConfiguration represents a declarative configuration of the TrustManagerConfig type for use +// with apply. +type TrustManagerConfigApplyConfiguration struct { + LogLevel *int32 `json:"logLevel,omitempty"` + LogFormat *string `json:"logFormat,omitempty"` + TrustNamespace *string `json:"trustNamespace,omitempty"` + SecretTargets *SecretTargetsConfigApplyConfiguration `json:"secretTargets,omitempty"` + FilterExpiredCertificates *operatorv1alpha1.FilterExpiredCertificatesPolicy `json:"filterExpiredCertificates,omitempty"` + DefaultCAPackage *DefaultCAPackageConfigApplyConfiguration `json:"defaultCAPackage,omitempty"` + Resources *v1.ResourceRequirements `json:"resources,omitempty"` + Affinity *v1.Affinity `json:"affinity,omitempty"` + Tolerations []v1.Toleration `json:"tolerations,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` +} + +// TrustManagerConfigApplyConfiguration constructs a declarative configuration of the TrustManagerConfig type for use with +// apply. +func TrustManagerConfig() *TrustManagerConfigApplyConfiguration { + return &TrustManagerConfigApplyConfiguration{} +} + +// WithLogLevel sets the LogLevel field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LogLevel field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithLogLevel(value int32) *TrustManagerConfigApplyConfiguration { + b.LogLevel = &value + return b +} + +// WithLogFormat sets the LogFormat field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LogFormat field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithLogFormat(value string) *TrustManagerConfigApplyConfiguration { + b.LogFormat = &value + return b +} + +// WithTrustNamespace sets the TrustNamespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustNamespace field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithTrustNamespace(value string) *TrustManagerConfigApplyConfiguration { + b.TrustNamespace = &value + return b +} + +// WithSecretTargets sets the SecretTargets field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretTargets field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithSecretTargets(value *SecretTargetsConfigApplyConfiguration) *TrustManagerConfigApplyConfiguration { + b.SecretTargets = value + return b +} + +// WithFilterExpiredCertificates sets the FilterExpiredCertificates field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FilterExpiredCertificates field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithFilterExpiredCertificates(value operatorv1alpha1.FilterExpiredCertificatesPolicy) *TrustManagerConfigApplyConfiguration { + b.FilterExpiredCertificates = &value + return b +} + +// WithDefaultCAPackage sets the DefaultCAPackage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultCAPackage field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithDefaultCAPackage(value *DefaultCAPackageConfigApplyConfiguration) *TrustManagerConfigApplyConfiguration { + b.DefaultCAPackage = value + return b +} + +// WithResources sets the Resources field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resources field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithResources(value v1.ResourceRequirements) *TrustManagerConfigApplyConfiguration { + b.Resources = &value + return b +} + +// WithAffinity sets the Affinity field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Affinity field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithAffinity(value v1.Affinity) *TrustManagerConfigApplyConfiguration { + b.Affinity = &value + return b +} + +// WithTolerations adds the given value to the Tolerations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Tolerations field. +func (b *TrustManagerConfigApplyConfiguration) WithTolerations(values ...v1.Toleration) *TrustManagerConfigApplyConfiguration { + for i := range values { + b.Tolerations = append(b.Tolerations, values[i]) + } + return b +} + +// WithNodeSelector puts the entries into the NodeSelector field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the NodeSelector field, +// overwriting an existing map entries in NodeSelector field with the same key. +func (b *TrustManagerConfigApplyConfiguration) WithNodeSelector(entries map[string]string) *TrustManagerConfigApplyConfiguration { + if b.NodeSelector == nil && len(entries) > 0 { + b.NodeSelector = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.NodeSelector[k] = v + } + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagercontrollerconfig.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagercontrollerconfig.go new file mode 100644 index 000000000..031986672 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagercontrollerconfig.go @@ -0,0 +1,44 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// TrustManagerControllerConfigApplyConfiguration represents a declarative configuration of the TrustManagerControllerConfig type for use +// with apply. +type TrustManagerControllerConfigApplyConfiguration struct { + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` +} + +// TrustManagerControllerConfigApplyConfiguration constructs a declarative configuration of the TrustManagerControllerConfig type for use with +// apply. +func TrustManagerControllerConfig() *TrustManagerControllerConfigApplyConfiguration { + return &TrustManagerControllerConfigApplyConfiguration{} +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *TrustManagerControllerConfigApplyConfiguration) WithLabels(entries map[string]string) *TrustManagerControllerConfigApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *TrustManagerControllerConfigApplyConfiguration) WithAnnotations(entries map[string]string) *TrustManagerControllerConfigApplyConfiguration { + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerspec.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerspec.go new file mode 100644 index 000000000..7f8659cda --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerspec.go @@ -0,0 +1,32 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// TrustManagerSpecApplyConfiguration represents a declarative configuration of the TrustManagerSpec type for use +// with apply. +type TrustManagerSpecApplyConfiguration struct { + TrustManagerConfig *TrustManagerConfigApplyConfiguration `json:"trustManagerConfig,omitempty"` + ControllerConfig *TrustManagerControllerConfigApplyConfiguration `json:"controllerConfig,omitempty"` +} + +// TrustManagerSpecApplyConfiguration constructs a declarative configuration of the TrustManagerSpec type for use with +// apply. +func TrustManagerSpec() *TrustManagerSpecApplyConfiguration { + return &TrustManagerSpecApplyConfiguration{} +} + +// WithTrustManagerConfig sets the TrustManagerConfig field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustManagerConfig field is set to the value of the last call. +func (b *TrustManagerSpecApplyConfiguration) WithTrustManagerConfig(value *TrustManagerConfigApplyConfiguration) *TrustManagerSpecApplyConfiguration { + b.TrustManagerConfig = value + return b +} + +// WithControllerConfig sets the ControllerConfig field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ControllerConfig field is set to the value of the last call. +func (b *TrustManagerSpecApplyConfiguration) WithControllerConfig(value *TrustManagerControllerConfigApplyConfiguration) *TrustManagerSpecApplyConfiguration { + b.ControllerConfig = value + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerstatus.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerstatus.go new file mode 100644 index 000000000..fce632a82 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerstatus.go @@ -0,0 +1,78 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// TrustManagerStatusApplyConfiguration represents a declarative configuration of the TrustManagerStatus type for use +// with apply. +type TrustManagerStatusApplyConfiguration struct { + ConditionalStatusApplyConfiguration `json:",omitempty,inline"` + TrustManagerImage *string `json:"trustManagerImage,omitempty"` + TrustNamespace *string `json:"trustNamespace,omitempty"` + SecretTargetsPolicy *operatorv1alpha1.SecretTargetsPolicy `json:"secretTargetsPolicy,omitempty"` + DefaultCAPackagePolicy *operatorv1alpha1.DefaultCAPackagePolicy `json:"defaultCAPackagePolicy,omitempty"` + FilterExpiredCertificatesPolicy *operatorv1alpha1.FilterExpiredCertificatesPolicy `json:"filterExpiredCertificatesPolicy,omitempty"` +} + +// TrustManagerStatusApplyConfiguration constructs a declarative configuration of the TrustManagerStatus type for use with +// apply. +func TrustManagerStatus() *TrustManagerStatusApplyConfiguration { + return &TrustManagerStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *TrustManagerStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *TrustManagerStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.ConditionalStatusApplyConfiguration.Conditions = append(b.ConditionalStatusApplyConfiguration.Conditions, *values[i]) + } + return b +} + +// WithTrustManagerImage sets the TrustManagerImage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustManagerImage field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithTrustManagerImage(value string) *TrustManagerStatusApplyConfiguration { + b.TrustManagerImage = &value + return b +} + +// WithTrustNamespace sets the TrustNamespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustNamespace field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithTrustNamespace(value string) *TrustManagerStatusApplyConfiguration { + b.TrustNamespace = &value + return b +} + +// WithSecretTargetsPolicy sets the SecretTargetsPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretTargetsPolicy field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithSecretTargetsPolicy(value operatorv1alpha1.SecretTargetsPolicy) *TrustManagerStatusApplyConfiguration { + b.SecretTargetsPolicy = &value + return b +} + +// WithDefaultCAPackagePolicy sets the DefaultCAPackagePolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultCAPackagePolicy field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithDefaultCAPackagePolicy(value operatorv1alpha1.DefaultCAPackagePolicy) *TrustManagerStatusApplyConfiguration { + b.DefaultCAPackagePolicy = &value + return b +} + +// WithFilterExpiredCertificatesPolicy sets the FilterExpiredCertificatesPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FilterExpiredCertificatesPolicy field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithFilterExpiredCertificatesPolicy(value operatorv1alpha1.FilterExpiredCertificatesPolicy) *TrustManagerStatusApplyConfiguration { + b.FilterExpiredCertificatesPolicy = &value + return b +} diff --git a/pkg/operator/applyconfigurations/utils.go b/pkg/operator/applyconfigurations/utils.go index 11e43885f..50f42a187 100644 --- a/pkg/operator/applyconfigurations/utils.go +++ b/pkg/operator/applyconfigurations/utils.go @@ -34,6 +34,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &operatorv1alpha1.ConfigMapReferenceApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ControllerConfig"): return &operatorv1alpha1.ControllerConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("DefaultCAPackageConfig"): + return &operatorv1alpha1.DefaultCAPackageConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("DeploymentConfig"): return &operatorv1alpha1.DeploymentConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("IstioConfig"): @@ -50,8 +52,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &operatorv1alpha1.IstiodTLSConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("NetworkPolicy"): return &operatorv1alpha1.NetworkPolicyApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SecretTargetsConfig"): + return &operatorv1alpha1.SecretTargetsConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerConfig"): return &operatorv1alpha1.ServerConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManager"): + return &operatorv1alpha1.TrustManagerApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManagerConfig"): + return &operatorv1alpha1.TrustManagerConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManagerControllerConfig"): + return &operatorv1alpha1.TrustManagerControllerConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManagerSpec"): + return &operatorv1alpha1.TrustManagerSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManagerStatus"): + return &operatorv1alpha1.TrustManagerStatusApplyConfiguration{} } return nil diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go index cc50d82f9..aaca26cb7 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go @@ -20,6 +20,10 @@ func (c *FakeOperatorV1alpha1) IstioCSRs(namespace string) v1alpha1.IstioCSRInte return newFakeIstioCSRs(c, namespace) } +func (c *FakeOperatorV1alpha1) TrustManagers() v1alpha1.TrustManagerInterface { + return newFakeTrustManagers(c) +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeOperatorV1alpha1) RESTClient() rest.Interface { diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_trustmanager.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_trustmanager.go new file mode 100644 index 000000000..ce9077b56 --- /dev/null +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_trustmanager.go @@ -0,0 +1,37 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + operatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/operator/v1alpha1" + typedoperatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned/typed/operator/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeTrustManagers implements TrustManagerInterface +type fakeTrustManagers struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.TrustManager, *v1alpha1.TrustManagerList, *operatorv1alpha1.TrustManagerApplyConfiguration] + Fake *FakeOperatorV1alpha1 +} + +func newFakeTrustManagers(fake *FakeOperatorV1alpha1) typedoperatorv1alpha1.TrustManagerInterface { + return &fakeTrustManagers{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.TrustManager, *v1alpha1.TrustManagerList, *operatorv1alpha1.TrustManagerApplyConfiguration]( + fake.Fake, + "", + v1alpha1.SchemeGroupVersion.WithResource("trustmanagers"), + v1alpha1.SchemeGroupVersion.WithKind("TrustManager"), + func() *v1alpha1.TrustManager { return &v1alpha1.TrustManager{} }, + func() *v1alpha1.TrustManagerList { return &v1alpha1.TrustManagerList{} }, + func(dst, src *v1alpha1.TrustManagerList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.TrustManagerList) []*v1alpha1.TrustManager { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.TrustManagerList, items []*v1alpha1.TrustManager) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go index 56f852de5..df39e06da 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go @@ -5,3 +5,5 @@ package v1alpha1 type CertManagerExpansion interface{} type IstioCSRExpansion interface{} + +type TrustManagerExpansion interface{} diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go index 67d7b0aee..9eabd32fe 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go @@ -14,6 +14,7 @@ type OperatorV1alpha1Interface interface { RESTClient() rest.Interface CertManagersGetter IstioCSRsGetter + TrustManagersGetter } // OperatorV1alpha1Client is used to interact with features provided by the operator.openshift.io group. @@ -29,6 +30,10 @@ func (c *OperatorV1alpha1Client) IstioCSRs(namespace string) IstioCSRInterface { return newIstioCSRs(c, namespace) } +func (c *OperatorV1alpha1Client) TrustManagers() TrustManagerInterface { + return newTrustManagers(c) +} + // NewForConfig creates a new OperatorV1alpha1Client for the given config. // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), // where httpClient was generated with rest.HTTPClientFor(c). diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/trustmanager.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/trustmanager.go new file mode 100644 index 000000000..cea6d2742 --- /dev/null +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/trustmanager.go @@ -0,0 +1,58 @@ +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + applyconfigurationsoperatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/operator/v1alpha1" + scheme "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// TrustManagersGetter has a method to return a TrustManagerInterface. +// A group's client should implement this interface. +type TrustManagersGetter interface { + TrustManagers() TrustManagerInterface +} + +// TrustManagerInterface has methods to work with TrustManager resources. +type TrustManagerInterface interface { + Create(ctx context.Context, trustManager *operatorv1alpha1.TrustManager, opts v1.CreateOptions) (*operatorv1alpha1.TrustManager, error) + Update(ctx context.Context, trustManager *operatorv1alpha1.TrustManager, opts v1.UpdateOptions) (*operatorv1alpha1.TrustManager, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, trustManager *operatorv1alpha1.TrustManager, opts v1.UpdateOptions) (*operatorv1alpha1.TrustManager, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*operatorv1alpha1.TrustManager, error) + List(ctx context.Context, opts v1.ListOptions) (*operatorv1alpha1.TrustManagerList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *operatorv1alpha1.TrustManager, err error) + Apply(ctx context.Context, trustManager *applyconfigurationsoperatorv1alpha1.TrustManagerApplyConfiguration, opts v1.ApplyOptions) (result *operatorv1alpha1.TrustManager, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, trustManager *applyconfigurationsoperatorv1alpha1.TrustManagerApplyConfiguration, opts v1.ApplyOptions) (result *operatorv1alpha1.TrustManager, err error) + TrustManagerExpansion +} + +// trustManagers implements TrustManagerInterface +type trustManagers struct { + *gentype.ClientWithListAndApply[*operatorv1alpha1.TrustManager, *operatorv1alpha1.TrustManagerList, *applyconfigurationsoperatorv1alpha1.TrustManagerApplyConfiguration] +} + +// newTrustManagers returns a TrustManagers +func newTrustManagers(c *OperatorV1alpha1Client) *trustManagers { + return &trustManagers{ + gentype.NewClientWithListAndApply[*operatorv1alpha1.TrustManager, *operatorv1alpha1.TrustManagerList, *applyconfigurationsoperatorv1alpha1.TrustManagerApplyConfiguration]( + "trustmanagers", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *operatorv1alpha1.TrustManager { return &operatorv1alpha1.TrustManager{} }, + func() *operatorv1alpha1.TrustManagerList { return &operatorv1alpha1.TrustManagerList{} }, + ), + } +} diff --git a/pkg/operator/informers/externalversions/generic.go b/pkg/operator/informers/externalversions/generic.go index 0c542fe66..7dc954ca9 100644 --- a/pkg/operator/informers/externalversions/generic.go +++ b/pkg/operator/informers/externalversions/generic.go @@ -41,6 +41,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().CertManagers().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("istiocsrs"): return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().IstioCSRs().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("trustmanagers"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().TrustManagers().Informer()}, nil } diff --git a/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go b/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go index 5eb8c8ede..422750840 100644 --- a/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go +++ b/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go @@ -12,6 +12,8 @@ type Interface interface { CertManagers() CertManagerInformer // IstioCSRs returns a IstioCSRInformer. IstioCSRs() IstioCSRInformer + // TrustManagers returns a TrustManagerInformer. + TrustManagers() TrustManagerInformer } type version struct { @@ -34,3 +36,8 @@ func (v *version) CertManagers() CertManagerInformer { func (v *version) IstioCSRs() IstioCSRInformer { return &istioCSRInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } + +// TrustManagers returns a TrustManagerInformer. +func (v *version) TrustManagers() TrustManagerInformer { + return &trustManagerInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/operator/informers/externalversions/operator/v1alpha1/trustmanager.go b/pkg/operator/informers/externalversions/operator/v1alpha1/trustmanager.go new file mode 100644 index 000000000..cdb0943ba --- /dev/null +++ b/pkg/operator/informers/externalversions/operator/v1alpha1/trustmanager.go @@ -0,0 +1,85 @@ +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + apioperatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + versioned "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned" + internalinterfaces "github.com/openshift/cert-manager-operator/pkg/operator/informers/externalversions/internalinterfaces" + operatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/listers/operator/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TrustManagerInformer provides access to a shared informer and lister for +// TrustManagers. +type TrustManagerInformer interface { + Informer() cache.SharedIndexInformer + Lister() operatorv1alpha1.TrustManagerLister +} + +type trustManagerInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewTrustManagerInformer constructs a new informer for TrustManager type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTrustManagerInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTrustManagerInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredTrustManagerInformer constructs a new informer for TrustManager type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTrustManagerInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().TrustManagers().List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().TrustManagers().Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().TrustManagers().List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().TrustManagers().Watch(ctx, options) + }, + }, + &apioperatorv1alpha1.TrustManager{}, + resyncPeriod, + indexers, + ) +} + +func (f *trustManagerInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTrustManagerInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *trustManagerInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apioperatorv1alpha1.TrustManager{}, f.defaultInformer) +} + +func (f *trustManagerInformer) Lister() operatorv1alpha1.TrustManagerLister { + return operatorv1alpha1.NewTrustManagerLister(f.Informer().GetIndexer()) +} diff --git a/pkg/operator/listers/operator/v1alpha1/expansion_generated.go b/pkg/operator/listers/operator/v1alpha1/expansion_generated.go index c91ed34e9..1692896d0 100644 --- a/pkg/operator/listers/operator/v1alpha1/expansion_generated.go +++ b/pkg/operator/listers/operator/v1alpha1/expansion_generated.go @@ -13,3 +13,7 @@ type IstioCSRListerExpansion interface{} // IstioCSRNamespaceListerExpansion allows custom methods to be added to // IstioCSRNamespaceLister. type IstioCSRNamespaceListerExpansion interface{} + +// TrustManagerListerExpansion allows custom methods to be added to +// TrustManagerLister. +type TrustManagerListerExpansion interface{} diff --git a/pkg/operator/listers/operator/v1alpha1/trustmanager.go b/pkg/operator/listers/operator/v1alpha1/trustmanager.go new file mode 100644 index 000000000..96293ae92 --- /dev/null +++ b/pkg/operator/listers/operator/v1alpha1/trustmanager.go @@ -0,0 +1,32 @@ +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// TrustManagerLister helps list TrustManagers. +// All objects returned here must be treated as read-only. +type TrustManagerLister interface { + // List lists all TrustManagers in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*operatorv1alpha1.TrustManager, err error) + // Get retrieves the TrustManager from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*operatorv1alpha1.TrustManager, error) + TrustManagerListerExpansion +} + +// trustManagerLister implements the TrustManagerLister interface. +type trustManagerLister struct { + listers.ResourceIndexer[*operatorv1alpha1.TrustManager] +} + +// NewTrustManagerLister returns a new TrustManagerLister. +func NewTrustManagerLister(indexer cache.Indexer) TrustManagerLister { + return &trustManagerLister{listers.New[*operatorv1alpha1.TrustManager](indexer, operatorv1alpha1.Resource("trustmanager"))} +} From 5ed70e7f9d098d9cea69a3e45ace2961a7c305c4 Mon Sep 17 00:00:00 2001 From: openshift-app-platform-shift-bot <267347085+openshift-app-platform-shift-bot@users.noreply.github.com> Date: Fri, 8 May 2026 12:59:51 +0000 Subject: [PATCH 2/2] CM-830: Add TrustManager controller for trust-manager operand management Implements the controller-runtime based reconciler for the TrustManager CRD. The controller watches trustmanagers.operator.openshift.io resources and reconciles the trust-manager operand deployment in the cert-manager namespace, including ServiceAccount, RBAC, Certificates, Services, DefaultCAPackage ConfigMap, and Deployment resources. The controller follows the same patterns as the IstioCSR controller: - Feature-gated via FeatureTrustManager (Alpha, default disabled) - Separate controller-runtime manager with its own cache builder - Non-destructive cleanup on CR deletion per EP non-goals - Dynamic RBAC for SecretTargets policy - DefaultCAPackage integration with CNO trusted CA bundle injection Co-Authored-By: Claude Opus 4.6 --- pkg/controller/trustmanager/client.go | 114 +++ pkg/controller/trustmanager/constants.go | 101 +++ pkg/controller/trustmanager/controller.go | 310 +++++++ .../trustmanager/install_trustmanager.go | 758 ++++++++++++++++++ pkg/controller/trustmanager/utils.go | 234 ++++++ pkg/operator/setup_manager.go | 38 +- pkg/operator/starter.go | 25 +- 7 files changed, 1574 insertions(+), 6 deletions(-) create mode 100644 pkg/controller/trustmanager/client.go create mode 100644 pkg/controller/trustmanager/constants.go create mode 100644 pkg/controller/trustmanager/controller.go create mode 100644 pkg/controller/trustmanager/install_trustmanager.go create mode 100644 pkg/controller/trustmanager/utils.go diff --git a/pkg/controller/trustmanager/client.go b/pkg/controller/trustmanager/client.go new file mode 100644 index 000000000..5428527cc --- /dev/null +++ b/pkg/controller/trustmanager/client.go @@ -0,0 +1,114 @@ +package trustmanager + +import ( + "context" + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/util/retry" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +type ctrlClientImpl struct { + client.Client +} + +//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate +//counterfeiter:generate -o fakes . ctrlClient +type ctrlClient interface { + Get(context.Context, client.ObjectKey, client.Object) error + List(context.Context, client.ObjectList, ...client.ListOption) error + StatusUpdate(context.Context, client.Object, ...client.SubResourceUpdateOption) error + Update(context.Context, client.Object, ...client.UpdateOption) error + UpdateWithRetry(context.Context, client.Object, ...client.UpdateOption) error + Create(context.Context, client.Object, ...client.CreateOption) error + Delete(context.Context, client.Object, ...client.DeleteOption) error + Patch(context.Context, client.Object, client.Patch, ...client.PatchOption) error + Exists(context.Context, client.ObjectKey, client.Object) (bool, error) +} + +func NewClient(m manager.Manager) (ctrlClient, error) { + // Use the manager's client directly instead of creating a custom client. + // The manager's client uses the manager's cache, which ensures the reconciler + // reads from the same cache that the controller's watches use, preventing + // cache mismatch issues. + return &ctrlClientImpl{ + Client: m.GetClient(), + }, nil +} + +func (c *ctrlClientImpl) Get( + ctx context.Context, key client.ObjectKey, obj client.Object, +) error { + return c.Client.Get(ctx, key, obj) +} + +func (c *ctrlClientImpl) List( + ctx context.Context, list client.ObjectList, opts ...client.ListOption, +) error { + return c.Client.List(ctx, list, opts...) +} + +func (c *ctrlClientImpl) Create( + ctx context.Context, obj client.Object, opts ...client.CreateOption, +) error { + return c.Client.Create(ctx, obj, opts...) +} + +func (c *ctrlClientImpl) Delete( + ctx context.Context, obj client.Object, opts ...client.DeleteOption, +) error { + return c.Client.Delete(ctx, obj, opts...) +} + +func (c *ctrlClientImpl) Update( + ctx context.Context, obj client.Object, opts ...client.UpdateOption, +) error { + return c.Client.Update(ctx, obj, opts...) +} + +func (c *ctrlClientImpl) UpdateWithRetry( + ctx context.Context, obj client.Object, opts ...client.UpdateOption, +) error { + key := client.ObjectKeyFromObject(obj) + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + current := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(client.Object) + if err := c.Client.Get(ctx, key, current); err != nil { + return fmt.Errorf("failed to fetch latest %q for update: %w", key, err) + } + obj.SetResourceVersion(current.GetResourceVersion()) + if err := c.Client.Update(ctx, obj, opts...); err != nil { + return fmt.Errorf("failed to update %q resource: %w", key, err) + } + return nil + }); err != nil { + return err + } + + return nil +} + +func (c *ctrlClientImpl) StatusUpdate( + ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption, +) error { + return c.Client.Status().Update(ctx, obj, opts...) +} + +func (c *ctrlClientImpl) Patch( + ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption, +) error { + return c.Client.Patch(ctx, obj, patch, opts...) +} + +func (c *ctrlClientImpl) Exists(ctx context.Context, key client.ObjectKey, obj client.Object) (bool, error) { + if err := c.Client.Get(ctx, key, obj); err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/pkg/controller/trustmanager/constants.go b/pkg/controller/trustmanager/constants.go new file mode 100644 index 000000000..b7c5271e1 --- /dev/null +++ b/pkg/controller/trustmanager/constants.go @@ -0,0 +1,101 @@ +package trustmanager + +import ( + "os" + "time" +) + +const ( + // trustManagerCommonName is the name commonly used for naming resources. + trustManagerCommonName = "cert-manager-trust-manager" + + // ControllerName is the name of the controller used in logs and events. + ControllerName = trustManagerCommonName + "-controller" + + // controllerProcessedAnnotation is the annotation added to trustmanager resource once after + // successful reconciliation by the controller. + controllerProcessedAnnotation = "operator.openshift.io/trust-manager-processed" + + // finalizer name for trustmanagers.operator.openshift.io resource. + finalizer = "trustmanagers.operator.openshift.io/" + ControllerName + + // defaultRequeueTime is the default reconcile requeue time. + defaultRequeueTime = time.Second * 30 + + // trustManagerObjectName is the name of the trust-manager resource created by user. + // trust-manager CRD enforces name to be `cluster`. + trustManagerObjectName = "cluster" + + // trustManagerContainerName is the name of the container created for trust-manager. + trustManagerContainerName = "trust-manager" + + // trustManagerImageNameEnvVarName is the environment variable key name + // containing the image name of the trust-manager as value. + trustManagerImageNameEnvVarName = "RELATED_IMAGE_CERT_MANAGER_TRUST_MANAGER" + + // trustManagerImageVersionEnvVarName is the environment variable key name + // containing the image version of the trust-manager as value. + trustManagerImageVersionEnvVarName = "TRUST_MANAGER_OPERAND_IMAGE_VERSION" + + // operandNamespace is the namespace where trust-manager is deployed. + operandNamespace = "cert-manager" + + // operatorNamespace is the namespace where the cert-manager operator is deployed. + operatorNamespace = "cert-manager-operator" + + // defaultCAPackageConfigMapName is the name of the ConfigMap created by the controller + // containing the formatted CA package for trust-manager. + defaultCAPackageConfigMapName = "trust-manager-default-ca-package" + + // trustedCABundleConfigMapName is the name of the ConfigMap in the operator namespace + // that receives the injected CA bundle from CNO. + trustedCABundleConfigMapName = "cert-manager-operator-trusted-ca-bundle" + + // caPackageJSONName is the name of the JSON package file within the ConfigMap. + caPackageJSONName = "cert-manager-package-openshift.json" + + // caPackageMountPath is the path where the default CA package is mounted in the trust-manager container. + caPackageMountPath = "/packages" + + // defaultCAPackageHashAnnotation is the annotation on the Deployment storing the hash + // of the default CA package content, used to trigger rolling restarts when the package changes. + defaultCAPackageHashAnnotation = "operator.openshift.io/default-ca-package-hash" + + // webhookPort is the port used by the trust-manager webhook. + webhookPort int32 = 6443 + + // metricsPort is the port used by the trust-manager metrics endpoint. + metricsPort int32 = 9402 + + // readinessProbePort is the port used for the trust-manager readiness probe. + readinessProbePort int32 = 6060 +) + +var ( + controllerDefaultResourceLabels = map[string]string{ + "app": trustManagerCommonName, + "app.kubernetes.io/name": trustManagerCommonName, + "app.kubernetes.io/instance": trustManagerCommonName, + "app.kubernetes.io/version": os.Getenv(trustManagerImageVersionEnvVarName), + "app.kubernetes.io/managed-by": "cert-manager-operator", + "app.kubernetes.io/part-of": "cert-manager-operator", + } +) + +// asset names are the files present in the root bindata/ dir. Which are then loaded +// and made available by the pkg/operator/assets package. +const ( + deploymentAssetName = "trust-manager/trust-manager-deployment.yaml" + clusterRoleAssetName = "trust-manager/trust-manager-clusterrole.yaml" + clusterRoleBindingAssetName = "trust-manager/trust-manager-clusterrolebinding.yaml" + roleAssetName = "trust-manager/trust-manager-role.yaml" + roleLeasesAssetName = "trust-manager/trust-manager-leaderelection-role.yaml" + roleBindingAssetName = "trust-manager/trust-manager-rolebinding.yaml" + roleBindingLeasesAssetName = "trust-manager/trust-manager-leaderelection-rolebinding.yaml" + serviceAssetName = "trust-manager/trust-manager-service.yaml" + metricsServiceAssetName = "trust-manager/trust-manager-metrics-service.yaml" + serviceAccountAssetName = "trust-manager/trust-manager-serviceaccount.yaml" + certificateAssetName = "trust-manager/trust-manager-certificate.yaml" + issuerAssetName = "trust-manager/trust-manager-issuer.yaml" + webhookAssetName = "trust-manager/trust-manager-validatingwebhookconfiguration.yaml" +) diff --git a/pkg/controller/trustmanager/controller.go b/pkg/controller/trustmanager/controller.go new file mode 100644 index 000000000..0b1b6dd55 --- /dev/null +++ b/pkg/controller/trustmanager/controller.go @@ -0,0 +1,310 @@ +package trustmanager + +import ( + "context" + "fmt" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/go-logr/logr" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +var ( + // requestEnqueueLabelKey is the label key name used for filtering reconcile + // events to include only the resources created by the controller. + requestEnqueueLabelKey = "app" + + // requestEnqueueLabelValue is the label value used for filtering reconcile + // events to include only the resources created by the controller. + requestEnqueueLabelValue = trustManagerCommonName +) + +// Reconciler reconciles a TrustManager object +type Reconciler struct { + ctrlClient + + ctx context.Context + eventRecorder record.EventRecorder + log logr.Logger + scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers/finalizers,verbs=update +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates;issuers,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete + +// NewCacheBuilder returns a cache builder function configured with label selectors +// for managed resources. This function is used by the manager to create its cache +// to ensure the reconciler reads from the same cache that the controller's watches use. +func NewCacheBuilder(config *rest.Config, opts cache.Options) (cache.Cache, error) { + managedResourceLabelReq, err := labels.NewRequirement(requestEnqueueLabelKey, selection.Equals, []string{requestEnqueueLabelValue}) + if err != nil { + return nil, fmt.Errorf("invalid cache label requirement for %q: %w", requestEnqueueLabelKey, err) + } + managedResourceLabelReqSelector := labels.NewSelector().Add(*managedResourceLabelReq) + + // Configure cache with label selectors for managed resources + opts.ByObject = map[client.Object]cache.ByObject{ + // Explicitly include TrustManager to ensure the cache properly watches and syncs all TrustManager objects + &v1alpha1.TrustManager{}: {}, + // Resources managed by controller (with label selectors) + &certmanagerv1.Certificate{}: { + Label: managedResourceLabelReqSelector, + }, + &certmanagerv1.Issuer{}: { + Label: managedResourceLabelReqSelector, + }, + &appsv1.Deployment{}: { + Label: managedResourceLabelReqSelector, + }, + &rbacv1.ClusterRole{}: { + Label: managedResourceLabelReqSelector, + }, + &rbacv1.ClusterRoleBinding{}: { + Label: managedResourceLabelReqSelector, + }, + &rbacv1.Role{}: { + Label: managedResourceLabelReqSelector, + }, + &rbacv1.RoleBinding{}: { + Label: managedResourceLabelReqSelector, + }, + &corev1.Service{}: { + Label: managedResourceLabelReqSelector, + }, + &corev1.ServiceAccount{}: { + Label: managedResourceLabelReqSelector, + }, + // ConfigMaps in operator and operand namespaces (for DefaultCAPackage) + &corev1.ConfigMap{}: {}, + } + + return cache.New(config, opts) +} + +// New returns a new Reconciler instance. +func New(mgr ctrl.Manager) (*Reconciler, error) { + c, err := NewClient(mgr) + if err != nil { + return nil, err + } + return &Reconciler{ + ctrlClient: c, + ctx: context.Background(), + eventRecorder: mgr.GetEventRecorderFor(ControllerName), + log: ctrl.Log.WithName(ControllerName), + scheme: mgr.GetScheme(), + }, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + mapFunc := func(ctx context.Context, obj client.Object) []reconcile.Request { + r.log.V(4).Info("received reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace()) + + objLabels := obj.GetLabels() + if objLabels != nil && objLabels[requestEnqueueLabelKey] == requestEnqueueLabelValue { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: trustManagerObjectName, + }, + }, + } + } + + r.log.V(4).Info("object not of interest, ignoring reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace()) + return []reconcile.Request{} + } + + // mapConfigMapFunc maps ConfigMap changes to TrustManager reconcile requests. + // This watches the trusted CA bundle ConfigMap in operator namespace and the + // default CA package ConfigMap in operand namespace. + mapConfigMapFunc := func(ctx context.Context, obj client.Object) []reconcile.Request { + name := obj.GetName() + ns := obj.GetNamespace() + if (name == trustedCABundleConfigMapName && ns == operatorNamespace) || + (name == defaultCAPackageConfigMapName && ns == operandNamespace) { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: trustManagerObjectName, + }, + }, + } + } + + // Also reconcile on managed ConfigMaps + objLabels := obj.GetLabels() + if objLabels != nil && objLabels[requestEnqueueLabelKey] == requestEnqueueLabelValue { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: trustManagerObjectName, + }, + }, + } + } + + return []reconcile.Request{} + } + + // predicate function to ignore events for objects not managed by controller. + controllerManagedResources := predicate.NewPredicateFuncs(func(object client.Object) bool { + return object.GetLabels() != nil && object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue + }) + + withIgnoreStatusUpdatePredicates := builder.WithPredicates(predicate.GenerationChangedPredicate{}, controllerManagedResources) + controllerManagedResourcePredicates := builder.WithPredicates(controllerManagedResources) + + // ConfigMap predicate: watch managed ConfigMaps and the CA bundle ConfigMaps + configMapPredicate := predicate.NewPredicateFuncs(func(object client.Object) bool { + name := object.GetName() + ns := object.GetNamespace() + if (name == trustedCABundleConfigMapName && ns == operatorNamespace) || + (name == defaultCAPackageConfigMapName && ns == operandNamespace) { + return true + } + return object.GetLabels() != nil && object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue + }) + configMapPredicates := builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, configMapPredicate) + + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.TrustManager{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Named(ControllerName). + Watches(&certmanagerv1.Certificate{}, handler.EnqueueRequestsFromMapFunc(mapFunc), withIgnoreStatusUpdatePredicates). + Watches(&certmanagerv1.Issuer{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&appsv1.Deployment{}, handler.EnqueueRequestsFromMapFunc(mapFunc), withIgnoreStatusUpdatePredicates). + Watches(&rbacv1.ClusterRole{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&rbacv1.ClusterRoleBinding{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&rbacv1.Role{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&rbacv1.RoleBinding{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&corev1.ServiceAccount{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(mapConfigMapFunc), configMapPredicates). + Complete(r) +} + +// Reconcile function to compare the state specified by the TrustManager object against the actual cluster state, +// and to make the cluster state reflect the state specified by the user. +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.log.V(1).Info("reconciling", "request", req) + + // Fetch the trustmanagers.operator.openshift.io CR + trustManager := &v1alpha1.TrustManager{} + if err := r.Get(ctx, req.NamespacedName, trustManager); err != nil { + if errors.IsNotFound(err) { + // NotFound errors, since they can't be fixed by an immediate + // requeue (have to wait for a new notification), and can be processed + // on deleted requests. + r.log.V(1).Info("trustmanagers.operator.openshift.io object not found, skipping reconciliation", "request", req) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("failed to fetch trustmanagers.operator.openshift.io %q during reconciliation: %w", req.NamespacedName, err) + } + + if !trustManager.DeletionTimestamp.IsZero() { + r.log.V(1).Info("trustmanagers.operator.openshift.io is marked for deletion", "name", req.NamespacedName) + + if err := r.cleanUp(trustManager); err != nil { + return ctrl.Result{}, fmt.Errorf("clean up failed for %q trustmanagers.operator.openshift.io instance deletion: %w", req.NamespacedName, err) + } + + if err := r.removeFinalizer(ctx, trustManager, finalizer); err != nil { + return ctrl.Result{}, err + } + + r.log.V(1).Info("removed finalizer, cleanup complete", "request", req.NamespacedName) + return ctrl.Result{}, nil + } + + // Set finalizers on the trustmanagers.operator.openshift.io resource + if err := r.addFinalizer(ctx, trustManager); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update %q trustmanagers.operator.openshift.io with finalizers: %w", req.NamespacedName, err) + } + + return r.processReconcileRequest(trustManager, req.NamespacedName) +} + +func (r *Reconciler) processReconcileRequest(trustManager *v1alpha1.TrustManager, req types.NamespacedName) (ctrl.Result, error) { + if err := r.reconcileTrustManagerDeployment(trustManager); err != nil { + r.log.Error(err, "failed to reconcile TrustManager deployment", "request", req) + + // Set both conditions atomically before updating status + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionTrue, v1alpha1.ReasonFailed, fmt.Sprintf("reconciliation failed: %v", err)) + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonFailed, "") + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on error", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged, + "error", err) + if updateErr := r.updateCondition(trustManager, err); updateErr != nil { + return ctrl.Result{}, updateErr + } + } + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil + } + + // Update status with observed state + r.updateObservedStatus(trustManager) + + // Set both conditions atomically before updating status on success + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionTrue, v1alpha1.ReasonReady, "reconciliation successful") + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on successful reconciliation", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged) + if updateErr := r.updateCondition(trustManager, nil); updateErr != nil { + return ctrl.Result{}, updateErr + } + } + return ctrl.Result{}, nil +} + +// cleanUp handles deletion of trustmanagers.operator.openshift.io gracefully. +// Per the EP Non-Goals: deleting the CR only stops reconciliation, it does not +// remove the trust-manager deployment or its associated resources. +func (r *Reconciler) cleanUp(trustManager *v1alpha1.TrustManager) error { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "RemoveDeployment", + "trustmanager %s marked for deletion, reconciliation of trust-manager resources will stop. "+ + "Resources created for trust-manager deployment will not be removed automatically.", + trustManager.GetName()) + return nil +} diff --git a/pkg/controller/trustmanager/install_trustmanager.go b/pkg/controller/trustmanager/install_trustmanager.go new file mode 100644 index 000000000..dd9e92d1e --- /dev/null +++ b/pkg/controller/trustmanager/install_trustmanager.go @@ -0,0 +1,758 @@ +package trustmanager + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "os" + "strings" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +func (r *Reconciler) reconcileTrustManagerDeployment(trustManager *v1alpha1.TrustManager) error { + if err := validateTrustManagerConfig(trustManager); err != nil { + return fmt.Errorf("%s configuration validation failed: %w", trustManager.GetName(), err) + } + + // Merge user-provided labels with controller's own default labels. + resourceLabels := make(map[string]string) + if len(trustManager.Spec.ControllerConfig.Labels) != 0 { + for k, v := range trustManager.Spec.ControllerConfig.Labels { + resourceLabels[k] = v + } + } + for k, v := range controllerDefaultResourceLabels { + resourceLabels[k] = v + } + + // Validate trust namespace exists + trustNamespace := trustManager.Spec.TrustManagerConfig.TrustNamespace + if trustNamespace == "" { + trustNamespace = operandNamespace + } + if err := r.validateNamespaceExists(trustNamespace); err != nil { + return fmt.Errorf("trust namespace %q does not exist: %w", trustNamespace, err) + } + + // Step 1: Create ServiceAccount + if err := r.reconcileServiceAccount(trustManager, resourceLabels); err != nil { + r.log.Error(err, "failed to reconcile ServiceAccount") + return err + } + + // Step 2: Create RBAC resources + if err := r.reconcileRBAC(trustManager, resourceLabels, trustNamespace); err != nil { + r.log.Error(err, "failed to reconcile RBAC resources") + return err + } + + // Step 3: Create Certificate and Issuer for webhook TLS + if err := r.reconcileCertificates(trustManager, resourceLabels); err != nil { + r.log.Error(err, "failed to reconcile Certificate resources") + return err + } + + // Step 4: Create Services + if err := r.reconcileServices(trustManager, resourceLabels); err != nil { + r.log.Error(err, "failed to reconcile Service resources") + return err + } + + // Step 5: Handle DefaultCAPackage ConfigMap if enabled + var caPackageHash string + if trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy == v1alpha1.DefaultCAPackagePolicyEnabled { + hash, err := r.reconcileDefaultCAPackage(trustManager, resourceLabels) + if err != nil { + r.log.Error(err, "failed to reconcile DefaultCAPackage") + return err + } + caPackageHash = hash + } + + // Step 6: Create Deployment + if err := r.reconcileDeployment(trustManager, resourceLabels, trustNamespace, caPackageHash); err != nil { + r.log.Error(err, "failed to reconcile Deployment") + return err + } + + r.log.V(4).Info("finished reconciliation of trustmanager", "name", trustManager.GetName()) + return nil +} + +func (r *Reconciler) validateNamespaceExists(namespace string) error { + ns := &corev1.Namespace{} + if err := r.Get(r.ctx, types.NamespacedName{Name: namespace}, ns); err != nil { + if errors.IsNotFound(err) { + return fmt.Errorf("namespace %q does not exist, please create it before creating the TrustManager CR", namespace) + } + return fmt.Errorf("failed to check namespace %q: %w", namespace, err) + } + return nil +} + +func validateTrustManagerConfig(trustManager *v1alpha1.TrustManager) error { + if trustManager.Spec.TrustManagerConfig.SecretTargets.Policy == v1alpha1.SecretTargetsPolicyCustom { + if len(trustManager.Spec.TrustManagerConfig.SecretTargets.AuthorizedSecrets) == 0 { + return fmt.Errorf("spec.trustManagerConfig.secretTargets.authorizedSecrets must not be empty when policy is Custom") + } + } + return nil +} + +func (r *Reconciler) reconcileServiceAccount(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) error { + desired := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Namespace: operandNamespace, + Labels: resourceLabels, + }, + } + + existing := &corev1.ServiceAccount{} + exists, err := r.Exists(r.ctx, types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}, existing) + if err != nil { + return fmt.Errorf("failed to check ServiceAccount existence: %w", err) + } + + if !exists { + r.log.Info("creating ServiceAccount", "name", desired.Name, "namespace", desired.Namespace) + if err := r.Create(r.ctx, desired); err != nil { + return fmt.Errorf("failed to create ServiceAccount: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created ServiceAccount %s/%s", desired.Namespace, desired.Name) + return nil + } + + if objectMetadataModified(desired, existing) { + existing.Labels = desired.Labels + r.log.Info("updating ServiceAccount", "name", desired.Name, "namespace", desired.Namespace) + if err := r.Update(r.ctx, existing); err != nil { + return fmt.Errorf("failed to update ServiceAccount: %w", err) + } + } + + return nil +} + +func (r *Reconciler) reconcileRBAC(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustNamespace string) error { + // ClusterRole with dynamic rules based on secretTargets + clusterRoleRules := []rbacv1.PolicyRule{ + { + APIGroups: []string{"trust.cert-manager.io"}, + Resources: []string{"bundles"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"trust.cert-manager.io"}, + Resources: []string{"bundles/finalizers"}, + Verbs: []string{"update"}, + }, + { + APIGroups: []string{"trust.cert-manager.io"}, + Resources: []string{"bundles/status"}, + Verbs: []string{"patch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + Verbs: []string{"get", "list", "create", "update", "patch", "watch", "delete"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"events"}, + Verbs: []string{"create", "patch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "list", "watch"}, + }, + } + + // Add secret write rules when secretTargets policy is Custom + if trustManager.Spec.TrustManagerConfig.SecretTargets.Policy == v1alpha1.SecretTargetsPolicyCustom { + clusterRoleRules = append(clusterRoleRules, rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"secrets"}, + ResourceNames: trustManager.Spec.TrustManagerConfig.SecretTargets.AuthorizedSecrets, + Verbs: []string{"create", "update", "patch", "delete"}, + }) + } + + desiredClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Labels: resourceLabels, + }, + Rules: clusterRoleRules, + } + + if err := r.createOrUpdateClusterRole(desiredClusterRole, trustManager); err != nil { + return fmt.Errorf("failed to reconcile ClusterRole: %w", err) + } + + // ClusterRoleBinding + desiredCRB := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Labels: resourceLabels, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: trustManagerContainerName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: trustManagerContainerName, + Namespace: operandNamespace, + }, + }, + } + + if err := r.createOrUpdateClusterRoleBinding(desiredCRB, trustManager); err != nil { + return fmt.Errorf("failed to reconcile ClusterRoleBinding: %w", err) + } + + // Role in trust namespace for secret access + desiredTrustRole := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Namespace: trustNamespace, + Labels: resourceLabels, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + } + + if err := r.createOrUpdateRole(desiredTrustRole, trustManager); err != nil { + return fmt.Errorf("failed to reconcile Role in trust namespace: %w", err) + } + + // RoleBinding in trust namespace + desiredTrustRB := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Namespace: trustNamespace, + Labels: resourceLabels, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: trustManagerContainerName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: trustManagerContainerName, + Namespace: operandNamespace, + }, + }, + } + + if err := r.createOrUpdateRoleBinding(desiredTrustRB, trustManager); err != nil { + return fmt.Errorf("failed to reconcile RoleBinding in trust namespace: %w", err) + } + + // Leader election Role in operand namespace + desiredLeaderRole := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName + ":leaderelection", + Namespace: operandNamespace, + Labels: resourceLabels, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"coordination.k8s.io"}, + Resources: []string{"leases"}, + Verbs: []string{"get", "create", "update", "watch", "list"}, + }, + }, + } + + if err := r.createOrUpdateRole(desiredLeaderRole, trustManager); err != nil { + return fmt.Errorf("failed to reconcile leader election Role: %w", err) + } + + // Leader election RoleBinding in operand namespace + desiredLeaderRB := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName + ":leaderelection", + Namespace: operandNamespace, + Labels: resourceLabels, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: trustManagerContainerName + ":leaderelection", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: trustManagerContainerName, + Namespace: operandNamespace, + }, + }, + } + + if err := r.createOrUpdateRoleBinding(desiredLeaderRB, trustManager); err != nil { + return fmt.Errorf("failed to reconcile leader election RoleBinding: %w", err) + } + + return nil +} + +func (r *Reconciler) reconcileCertificates(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) error { + // Issuer for webhook TLS + desiredIssuer := &certmanagerv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Namespace: operandNamespace, + Labels: resourceLabels, + }, + Spec: certmanagerv1.IssuerSpec{ + IssuerConfig: certmanagerv1.IssuerConfig{ + SelfSigned: &certmanagerv1.SelfSignedIssuer{}, + }, + }, + } + + existingIssuer := &certmanagerv1.Issuer{} + exists, err := r.Exists(r.ctx, types.NamespacedName{Name: desiredIssuer.Name, Namespace: desiredIssuer.Namespace}, existingIssuer) + if err != nil { + return fmt.Errorf("failed to check Issuer existence: %w", err) + } + if !exists { + r.log.Info("creating Issuer", "name", desiredIssuer.Name) + if err := r.Create(r.ctx, desiredIssuer); err != nil { + return fmt.Errorf("failed to create Issuer: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created Issuer %s/%s", desiredIssuer.Namespace, desiredIssuer.Name) + } + + // Certificate for webhook TLS + desiredCert := &certmanagerv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Namespace: operandNamespace, + Labels: resourceLabels, + }, + Spec: certmanagerv1.CertificateSpec{ + CommonName: fmt.Sprintf("%s.%s.svc", trustManagerContainerName, operandNamespace), + DNSNames: []string{ + fmt.Sprintf("%s.%s.svc", trustManagerContainerName, operandNamespace), + }, + SecretName: trustManagerContainerName + "-tls", + RevisionHistoryLimit: int32Ptr(1), + IssuerRef: certmanagermetav1.ObjectReference{ + Name: trustManagerContainerName, + Kind: "Issuer", + }, + }, + } + + existingCert := &certmanagerv1.Certificate{} + exists, err = r.Exists(r.ctx, types.NamespacedName{Name: desiredCert.Name, Namespace: desiredCert.Namespace}, existingCert) + if err != nil { + return fmt.Errorf("failed to check Certificate existence: %w", err) + } + if !exists { + r.log.Info("creating Certificate", "name", desiredCert.Name) + if err := r.Create(r.ctx, desiredCert); err != nil { + return fmt.Errorf("failed to create Certificate: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created Certificate %s/%s", desiredCert.Namespace, desiredCert.Name) + } + + return nil +} + +func (r *Reconciler) reconcileServices(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) error { + // Webhook service + desiredService := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Namespace: operandNamespace, + Labels: resourceLabels, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Selector: map[string]string{ + "app": trustManagerCommonName, + }, + Ports: []corev1.ServicePort{ + { + Name: "webhook", + Port: 443, + TargetPort: intstr.FromInt32(webhookPort), + Protocol: corev1.ProtocolTCP, + }, + }, + }, + } + + existing := &corev1.Service{} + exists, err := r.Exists(r.ctx, types.NamespacedName{Name: desiredService.Name, Namespace: desiredService.Namespace}, existing) + if err != nil { + return fmt.Errorf("failed to check Service existence: %w", err) + } + if !exists { + r.log.Info("creating Service", "name", desiredService.Name) + if err := r.Create(r.ctx, desiredService); err != nil { + return fmt.Errorf("failed to create Service: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created Service %s/%s", desiredService.Namespace, desiredService.Name) + } + + // Metrics service + desiredMetricsService := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName + "-metrics", + Namespace: operandNamespace, + Labels: resourceLabels, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Selector: map[string]string{ + "app": trustManagerCommonName, + }, + Ports: []corev1.ServicePort{ + { + Name: "metrics", + Port: metricsPort, + TargetPort: intstr.FromInt32(metricsPort), + Protocol: corev1.ProtocolTCP, + }, + }, + }, + } + + existingMetrics := &corev1.Service{} + exists, err = r.Exists(r.ctx, types.NamespacedName{Name: desiredMetricsService.Name, Namespace: desiredMetricsService.Namespace}, existingMetrics) + if err != nil { + return fmt.Errorf("failed to check Metrics Service existence: %w", err) + } + if !exists { + r.log.Info("creating Metrics Service", "name", desiredMetricsService.Name) + if err := r.Create(r.ctx, desiredMetricsService); err != nil { + return fmt.Errorf("failed to create Metrics Service: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created Service %s/%s", desiredMetricsService.Namespace, desiredMetricsService.Name) + } + + return nil +} + +func (r *Reconciler) reconcileDefaultCAPackage(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) (string, error) { + // Read the injected CA bundle from operator namespace + caConfigMap := &corev1.ConfigMap{} + if err := r.Get(r.ctx, types.NamespacedName{Name: trustedCABundleConfigMapName, Namespace: operatorNamespace}, caConfigMap); err != nil { + if errors.IsNotFound(err) { + return "", fmt.Errorf("trusted CA bundle ConfigMap %s/%s not found, waiting for CNO injection", operatorNamespace, trustedCABundleConfigMapName) + } + return "", fmt.Errorf("failed to get trusted CA bundle ConfigMap: %w", err) + } + + caBundle, ok := caConfigMap.Data["ca-bundle.crt"] + if !ok || caBundle == "" { + return "", fmt.Errorf("trusted CA bundle ConfigMap %s/%s does not contain ca-bundle.crt or is empty, waiting for CNO injection", operatorNamespace, trustedCABundleConfigMapName) + } + + // Format into trust-manager expected JSON format + packageJSON := map[string]string{ + "name": "cert-manager-package-openshift", + "bundle": caBundle, + "version": caConfigMap.ResourceVersion, + } + packageBytes, err := json.Marshal(packageJSON) + if err != nil { + return "", fmt.Errorf("failed to marshal CA package JSON: %w", err) + } + + // Compute hash of the package for deployment annotation + hash := fmt.Sprintf("%x", sha256.Sum256(packageBytes)) + + // Create or update the package ConfigMap in operand namespace + desiredCM := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultCAPackageConfigMapName, + Namespace: operandNamespace, + Labels: resourceLabels, + }, + Data: map[string]string{ + caPackageJSONName: string(packageBytes), + }, + } + + existingCM := &corev1.ConfigMap{} + exists, err := r.Exists(r.ctx, types.NamespacedName{Name: desiredCM.Name, Namespace: desiredCM.Namespace}, existingCM) + if err != nil { + return "", fmt.Errorf("failed to check DefaultCAPackage ConfigMap existence: %w", err) + } + + if !exists { + r.log.Info("creating DefaultCAPackage ConfigMap", "name", desiredCM.Name, "namespace", desiredCM.Namespace) + if err := r.Create(r.ctx, desiredCM); err != nil { + return "", fmt.Errorf("failed to create DefaultCAPackage ConfigMap: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created DefaultCAPackage ConfigMap %s/%s", desiredCM.Namespace, desiredCM.Name) + } else if configMapDataModified(desiredCM, existingCM) { + existingCM.Data = desiredCM.Data + existingCM.Labels = desiredCM.Labels + r.log.Info("updating DefaultCAPackage ConfigMap", "name", desiredCM.Name, "namespace", desiredCM.Namespace) + if err := r.Update(r.ctx, existingCM); err != nil { + return "", fmt.Errorf("failed to update DefaultCAPackage ConfigMap: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Updated", "Updated DefaultCAPackage ConfigMap %s/%s", desiredCM.Namespace, desiredCM.Name) + } + + return hash, nil +} + +func (r *Reconciler) reconcileDeployment(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustNamespace string, caPackageHash string) error { + image := os.Getenv(trustManagerImageNameEnvVarName) + if image == "" { + return fmt.Errorf("environment variable %s is not set", trustManagerImageNameEnvVarName) + } + + // Build container args based on spec + args := buildContainerArgs(trustManager, trustNamespace) + + // Build volumes and volume mounts + volumes := []corev1.Volume{ + { + Name: "tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: trustManagerContainerName + "-tls", + }, + }, + }, + } + + volumeMounts := []corev1.VolumeMount{ + { + Name: "tls", + MountPath: "/tls", + ReadOnly: true, + }, + } + + // Add default CA package volume if enabled + if trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy == v1alpha1.DefaultCAPackagePolicyEnabled { + volumes = append(volumes, corev1.Volume{ + Name: "default-ca-package", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: defaultCAPackageConfigMapName, + }, + }, + }, + }) + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: "default-ca-package", + MountPath: caPackageMountPath, + ReadOnly: true, + }) + } + + // Build resource requirements + resourceRequirements := trustManager.Spec.TrustManagerConfig.Resources + if resourceRequirements.Requests == nil && resourceRequirements.Limits == nil { + // Set sensible defaults if none provided + resourceRequirements = corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + } + } + + replicas := int32(1) + podTemplateAnnotations := make(map[string]string) + if caPackageHash != "" { + podTemplateAnnotations[defaultCAPackageHashAnnotation] = caPackageHash + } + + desired := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: trustManagerContainerName, + Namespace: operandNamespace, + Labels: resourceLabels, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": trustManagerCommonName, + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: resourceLabels, + Annotations: podTemplateAnnotations, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: trustManagerContainerName, + NodeSelector: trustManager.Spec.TrustManagerConfig.NodeSelector, + Tolerations: trustManager.Spec.TrustManagerConfig.Tolerations, + Affinity: trustManager.Spec.TrustManagerConfig.Affinity, + Containers: []corev1.Container{ + { + Name: trustManagerContainerName, + Image: image, + ImagePullPolicy: corev1.PullIfNotPresent, + Args: args, + Ports: []corev1.ContainerPort{ + { + ContainerPort: webhookPort, + }, + { + ContainerPort: metricsPort, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt32(readinessProbePort), + Path: "/readyz", + }, + }, + InitialDelaySeconds: 5, + PeriodSeconds: 10, + }, + Resources: resourceRequirements, + VolumeMounts: volumeMounts, + }, + }, + Volumes: volumes, + }, + }, + }, + } + + existingDeploy := &appsv1.Deployment{} + exists, err := r.Exists(r.ctx, types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}, existingDeploy) + if err != nil { + return fmt.Errorf("failed to check Deployment existence: %w", err) + } + + if !exists { + r.log.Info("creating Deployment", "name", desired.Name, "namespace", desired.Namespace) + if err := r.Create(r.ctx, desired); err != nil { + return fmt.Errorf("failed to create Deployment: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created Deployment %s/%s", desired.Namespace, desired.Name) + return nil + } + + if deploymentNeedsUpdate(desired, existingDeploy) { + existingDeploy.Spec = desired.Spec + existingDeploy.Labels = desired.Labels + r.log.Info("updating Deployment", "name", desired.Name, "namespace", desired.Namespace) + if err := r.Update(r.ctx, existingDeploy); err != nil { + return fmt.Errorf("failed to update Deployment: %w", err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Updated", "Updated Deployment %s/%s", desired.Namespace, desired.Name) + } + + return nil +} + +func buildContainerArgs(trustManager *v1alpha1.TrustManager, trustNamespace string) []string { + args := []string{ + fmt.Sprintf("--log-format=%s", trustManager.Spec.TrustManagerConfig.LogFormat), + fmt.Sprintf("--log-level=%d", trustManager.Spec.TrustManagerConfig.LogLevel), + fmt.Sprintf("--metrics-port=%d", metricsPort), + fmt.Sprintf("--readiness-probe-port=%d", readinessProbePort), + "--readiness-probe-path=/readyz", + fmt.Sprintf("--trust-namespace=%s", trustNamespace), + "--webhook-host=0.0.0.0", + fmt.Sprintf("--webhook-port=%d", webhookPort), + "--webhook-certificate-dir=/tls", + } + + if trustManager.Spec.TrustManagerConfig.SecretTargets.Policy == v1alpha1.SecretTargetsPolicyCustom { + args = append(args, "--secret-targets-enabled=true") + } + + if trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy == v1alpha1.DefaultCAPackagePolicyEnabled { + args = append(args, fmt.Sprintf("--default-package-location=%s/%s", caPackageMountPath, caPackageJSONName)) + } + + if trustManager.Spec.TrustManagerConfig.FilterExpiredCertificates == v1alpha1.FilterExpiredCertificatesPolicyEnabled { + args = append(args, "--filter-expired-certificates=true") + } + + return args +} + +func deploymentNeedsUpdate(desired, existing *appsv1.Deployment) bool { + if len(desired.Spec.Template.Spec.Containers) == 0 || len(existing.Spec.Template.Spec.Containers) == 0 { + return true + } + + desiredContainer := desired.Spec.Template.Spec.Containers[0] + existingContainer := existing.Spec.Template.Spec.Containers[0] + + if desiredContainer.Image != existingContainer.Image { + return true + } + + if !stringSlicesEqual(desiredContainer.Args, existingContainer.Args) { + return true + } + + // Check pod template annotations (for CA package hash) + desiredAnnotations := desired.Spec.Template.Annotations + existingAnnotations := existing.Spec.Template.Annotations + if desiredAnnotations == nil { + desiredAnnotations = map[string]string{} + } + if existingAnnotations == nil { + existingAnnotations = map[string]string{} + } + if desiredAnnotations[defaultCAPackageHashAnnotation] != existingAnnotations[defaultCAPackageHashAnnotation] { + return true + } + + return false +} + +func stringSlicesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + aStr := strings.Join(a, ",") + bStr := strings.Join(b, ",") + return aStr == bStr +} + +func int32Ptr(val int32) *int32 { + return &val +} diff --git a/pkg/controller/trustmanager/utils.go b/pkg/controller/trustmanager/utils.go new file mode 100644 index 000000000..16cfacfc7 --- /dev/null +++ b/pkg/controller/trustmanager/utils.go @@ -0,0 +1,234 @@ +package trustmanager + +import ( + "context" + "fmt" + "os" + "reflect" + + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +// updateStatus is for updating the status subresource of trustmanagers.operator.openshift.io. +func (r *Reconciler) updateStatus(ctx context.Context, changed *v1alpha1.TrustManager) error { + namespacedName := client.ObjectKeyFromObject(changed) + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + r.log.V(4).Info("updating trustmanagers.operator.openshift.io status", "request", namespacedName) + current := &v1alpha1.TrustManager{} + if err := r.Get(ctx, namespacedName, current); err != nil { + return fmt.Errorf("failed to fetch trustmanagers.operator.openshift.io %q for status update: %w", namespacedName, err) + } + changed.Status.DeepCopyInto(¤t.Status) + + if err := r.StatusUpdate(ctx, current); err != nil { + return fmt.Errorf("failed to update trustmanagers.operator.openshift.io %q status: %w", namespacedName, err) + } + + return nil + }); err != nil { + return err + } + + return nil +} + +// addFinalizer adds finalizer to trustmanagers.operator.openshift.io resource. +func (r *Reconciler) addFinalizer(ctx context.Context, trustManager *v1alpha1.TrustManager) error { + namespacedName := client.ObjectKeyFromObject(trustManager) + if !controllerutil.ContainsFinalizer(trustManager, finalizer) { + if !controllerutil.AddFinalizer(trustManager, finalizer) { + return fmt.Errorf("failed to create %q trustmanagers.operator.openshift.io object with finalizers added", namespacedName) + } + + // update trustmanagers.operator.openshift.io on adding finalizer. + if err := r.UpdateWithRetry(ctx, trustManager); err != nil { + return fmt.Errorf("failed to add finalizers on %q trustmanagers.operator.openshift.io with %w", namespacedName, err) + } + + updated := &v1alpha1.TrustManager{} + if err := r.Get(ctx, namespacedName, updated); err != nil { + return fmt.Errorf("failed to fetch trustmanagers.operator.openshift.io %q after updating finalizers: %w", namespacedName, err) + } + updated.DeepCopyInto(trustManager) + return nil + } + return nil +} + +// removeFinalizer removes finalizers added to trustmanagers.operator.openshift.io resource. +func (r *Reconciler) removeFinalizer(ctx context.Context, trustManager *v1alpha1.TrustManager, finalizer string) error { + namespacedName := client.ObjectKeyFromObject(trustManager) + if controllerutil.ContainsFinalizer(trustManager, finalizer) { + if !controllerutil.RemoveFinalizer(trustManager, finalizer) { + return fmt.Errorf("failed to create %q trustmanagers.operator.openshift.io object with finalizers removed", namespacedName) + } + + if err := r.UpdateWithRetry(ctx, trustManager); err != nil { + return fmt.Errorf("failed to remove finalizers on %q trustmanagers.operator.openshift.io with %w", namespacedName, err) + } + return nil + } + + return nil +} + +// updateCondition updates the status of the trustmanagers.operator.openshift.io resource. +func (r *Reconciler) updateCondition(trustManager *v1alpha1.TrustManager, prependErr error) error { + if err := r.updateStatus(r.ctx, trustManager); err != nil { + errUpdate := fmt.Errorf("failed to update %s status: %w", trustManager.GetName(), err) + if prependErr != nil { + return utilerrors.NewAggregate([]error{prependErr, errUpdate}) + } + return errUpdate + } + return prependErr +} + +// updateObservedStatus updates the observed state fields in the TrustManager status. +func (r *Reconciler) updateObservedStatus(trustManager *v1alpha1.TrustManager) { + trustManager.Status.TrustManagerImage = os.Getenv(trustManagerImageNameEnvVarName) + trustManager.Status.TrustNamespace = trustManager.Spec.TrustManagerConfig.TrustNamespace + if trustManager.Status.TrustNamespace == "" { + trustManager.Status.TrustNamespace = operandNamespace + } + trustManager.Status.SecretTargetsPolicy = trustManager.Spec.TrustManagerConfig.SecretTargets.Policy + trustManager.Status.DefaultCAPackagePolicy = trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy + trustManager.Status.FilterExpiredCertificatesPolicy = trustManager.Spec.TrustManagerConfig.FilterExpiredCertificates +} + +func objectMetadataModified(desired, fetched client.Object) bool { + return !reflect.DeepEqual(desired.GetLabels(), fetched.GetLabels()) +} + +func configMapDataModified(desired, fetched *corev1.ConfigMap) bool { + return !reflect.DeepEqual(desired.Data, fetched.Data) +} + +func (r *Reconciler) createOrUpdateClusterRole(desired *rbacv1.ClusterRole, trustManager *v1alpha1.TrustManager) error { + existing := &rbacv1.ClusterRole{} + exists, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), existing) + if err != nil { + return fmt.Errorf("failed to check ClusterRole %q existence: %w", desired.Name, err) + } + + if !exists { + r.log.Info("creating ClusterRole", "name", desired.Name) + if err := r.Create(r.ctx, desired); err != nil { + return fmt.Errorf("failed to create ClusterRole %q: %w", desired.Name, err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created ClusterRole %s", desired.Name) + return nil + } + + if !reflect.DeepEqual(existing.Rules, desired.Rules) || objectMetadataModified(desired, existing) { + existing.Rules = desired.Rules + existing.Labels = desired.Labels + r.log.Info("updating ClusterRole", "name", desired.Name) + if err := r.Update(r.ctx, existing); err != nil { + return fmt.Errorf("failed to update ClusterRole %q: %w", desired.Name, err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Updated", "Updated ClusterRole %s", desired.Name) + } + + return nil +} + +func (r *Reconciler) createOrUpdateClusterRoleBinding(desired *rbacv1.ClusterRoleBinding, trustManager *v1alpha1.TrustManager) error { + existing := &rbacv1.ClusterRoleBinding{} + exists, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), existing) + if err != nil { + return fmt.Errorf("failed to check ClusterRoleBinding %q existence: %w", desired.Name, err) + } + + if !exists { + r.log.Info("creating ClusterRoleBinding", "name", desired.Name) + if err := r.Create(r.ctx, desired); err != nil { + return fmt.Errorf("failed to create ClusterRoleBinding %q: %w", desired.Name, err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created ClusterRoleBinding %s", desired.Name) + return nil + } + + if !reflect.DeepEqual(existing.RoleRef, desired.RoleRef) || + !reflect.DeepEqual(existing.Subjects, desired.Subjects) || + objectMetadataModified(desired, existing) { + existing.RoleRef = desired.RoleRef + existing.Subjects = desired.Subjects + existing.Labels = desired.Labels + r.log.Info("updating ClusterRoleBinding", "name", desired.Name) + if err := r.Update(r.ctx, existing); err != nil { + return fmt.Errorf("failed to update ClusterRoleBinding %q: %w", desired.Name, err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Updated", "Updated ClusterRoleBinding %s", desired.Name) + } + + return nil +} + +func (r *Reconciler) createOrUpdateRole(desired *rbacv1.Role, trustManager *v1alpha1.TrustManager) error { + existing := &rbacv1.Role{} + exists, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), existing) + if err != nil { + return fmt.Errorf("failed to check Role %s/%s existence: %w", desired.Namespace, desired.Name, err) + } + + if !exists { + r.log.Info("creating Role", "name", desired.Name, "namespace", desired.Namespace) + if err := r.Create(r.ctx, desired); err != nil { + return fmt.Errorf("failed to create Role %s/%s: %w", desired.Namespace, desired.Name, err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created Role %s/%s", desired.Namespace, desired.Name) + return nil + } + + if !reflect.DeepEqual(existing.Rules, desired.Rules) || objectMetadataModified(desired, existing) { + existing.Rules = desired.Rules + existing.Labels = desired.Labels + r.log.Info("updating Role", "name", desired.Name, "namespace", desired.Namespace) + if err := r.Update(r.ctx, existing); err != nil { + return fmt.Errorf("failed to update Role %s/%s: %w", desired.Namespace, desired.Name, err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Updated", "Updated Role %s/%s", desired.Namespace, desired.Name) + } + + return nil +} + +func (r *Reconciler) createOrUpdateRoleBinding(desired *rbacv1.RoleBinding, trustManager *v1alpha1.TrustManager) error { + existing := &rbacv1.RoleBinding{} + exists, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), existing) + if err != nil { + return fmt.Errorf("failed to check RoleBinding %s/%s existence: %w", desired.Namespace, desired.Name, err) + } + + if !exists { + r.log.Info("creating RoleBinding", "name", desired.Name, "namespace", desired.Namespace) + if err := r.Create(r.ctx, desired); err != nil { + return fmt.Errorf("failed to create RoleBinding %s/%s: %w", desired.Namespace, desired.Name, err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Created", "Created RoleBinding %s/%s", desired.Namespace, desired.Name) + return nil + } + + if !reflect.DeepEqual(existing.RoleRef, desired.RoleRef) || + !reflect.DeepEqual(existing.Subjects, desired.Subjects) || + objectMetadataModified(desired, existing) { + existing.RoleRef = desired.RoleRef + existing.Subjects = desired.Subjects + existing.Labels = desired.Labels + r.log.Info("updating RoleBinding", "name", desired.Name, "namespace", desired.Namespace) + if err := r.Update(r.ctx, existing); err != nil { + return fmt.Errorf("failed to update RoleBinding %s/%s: %w", desired.Namespace, desired.Name, err) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Updated", "Updated RoleBinding %s/%s", desired.Namespace, desired.Name) + } + + return nil +} diff --git a/pkg/operator/setup_manager.go b/pkg/operator/setup_manager.go index ba8c49602..4c9566851 100644 --- a/pkg/operator/setup_manager.go +++ b/pkg/operator/setup_manager.go @@ -19,8 +19,11 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr" + "github.com/openshift/cert-manager-operator/pkg/controller/trustmanager" "github.com/openshift/cert-manager-operator/pkg/version" ) @@ -42,7 +45,7 @@ func init() { // +kubebuilder:scaffold:scheme } -// Manager holds the manager resource for the istio-csr controller +// Manager holds the manager resource for a controller-runtime based controller. type Manager struct { manager manager.Manager } @@ -81,3 +84,36 @@ func (mgr *Manager) Start(ctx context.Context) error { mgr.manager.GetEventRecorderFor("cert-manager-istio-csr-controller").Event(&v1alpha1.IstioCSR{}, corev1.EventTypeNormal, "ControllerStarted", "controller is starting") return mgr.manager.Start(ctx) } + +// NewTrustManagerControllerManager creates a new manager for the trust-manager controller. +func NewTrustManagerControllerManager() (*Manager, error) { + setupLog.Info("setting up operator manager", "controller", trustmanager.ControllerName) + setupLog.Info("controller", "version", version.Get()) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + NewCache: trustmanager.NewCacheBuilder, + Logger: ctrl.Log.WithName("trustmanager-operator-manager"), + // Disable health and metrics endpoints to avoid port conflicts + // with the IstioCSR controller manager. + HealthProbeBindAddress: "0", + Metrics: metricsserver.Options{ + BindAddress: "0", + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to create trust-manager manager: %w", err) + } + + r, err := trustmanager.New(mgr) + if err != nil { + return nil, fmt.Errorf("failed to create %s reconciler object: %w", trustmanager.ControllerName, err) + } + if err := r.SetupWithManager(mgr); err != nil { + return nil, fmt.Errorf("failed to create %s controller: %w", trustmanager.ControllerName, err) + } + + return &Manager{ + manager: mgr, + }, nil +} diff --git a/pkg/operator/starter.go b/pkg/operator/starter.go index dad5dfe8f..db3a3e445 100644 --- a/pkg/operator/starter.go +++ b/pkg/operator/starter.go @@ -7,8 +7,7 @@ import ( apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" - - ctrl "sigs.k8s.io/controller-runtime" + "k8s.io/klog/v2" configv1 "github.com/openshift/api/config/v1" configv1client "github.com/openshift/client-go/config/clientset/versioned" @@ -145,11 +144,27 @@ func RunOperator(ctx context.Context, cc *controllercmd.ControllerContext) error if features.DefaultFeatureGate.Enabled(v1alpha1.FeatureIstioCSR) { manager, err := NewControllerManager() if err != nil { - return fmt.Errorf("failed to create controller manager: %w", err) + return fmt.Errorf("failed to create istiocsr controller manager: %w", err) } - if err := manager.Start(ctrl.SetupSignalHandler()); err != nil { - return fmt.Errorf("failed to start istiocsr controller: %w", err) + go func() { + if err := manager.Start(ctx); err != nil { + klog.Errorf("failed to start istiocsr controller: %v", err) + } + }() + } + + // enable controller-runtime and trust-manager controller + // only when "TrustManager" feature is turned on from --addon-features + if features.DefaultFeatureGate.Enabled(v1alpha1.FeatureTrustManager) { + manager, err := NewTrustManagerControllerManager() + if err != nil { + return fmt.Errorf("failed to create trustmanager controller manager: %w", err) } + go func() { + if err := manager.Start(ctx); err != nil { + klog.Errorf("failed to start trustmanager controller: %v", err) + } + }() } <-ctx.Done()