From 02d35d325ad653253132e735c09cf5fc7d71a59e Mon Sep 17 00:00:00 2001 From: openshift-app-platform-shift-bot <267347085+openshift-app-platform-shift-bot@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:00:18 +0000 Subject: [PATCH 1/2] CM-830: Add TrustManager API type definitions for trust-manager operand Introduces the TrustManager CRD (trustmanagers.operator.openshift.io/v1alpha1) to support deploying and managing the trust-manager operand via cert-manager-operator. This adds: - TrustManager API types with spec/status fields for configuring trust-manager operand behavior (log level/format, trust namespace, secret targets, default CA package, filtering expired certificates, scheduling) - TrustManager FeatureGate (Alpha/TechPreview, default disabled) - Singleton enforcement via XValidation (name must be "cluster") - Immutability on trustNamespace via XValidation - Cross-field CEL validation on SecretTargetsConfig - Integration test suite (.testsuite.yaml) covering create and update scenarios - Generated deepcopy, CRD manifest, clientset, informers, listers, and apply configurations Ref: https://github.com/openshift/enhancements/pull/1914 Co-Authored-By: Claude Opus 4.6 --- api/operator/v1alpha1/features.go | 18 +- .../trustmanager.testsuite.yaml | 705 +++++++++ api/operator/v1alpha1/trustmanager_types.go | 267 ++++ .../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 + 24 files changed, 3351 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 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..61f75fe94 100644 --- a/api/operator/v1alpha1/features.go +++ b/api/operator/v1alpha1/features.go @@ -13,8 +13,24 @@ var ( // For more details, // https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/istio-csr-controller.md FeatureIstioCSR featuregate.Feature = "IstioCSR" + + // TrustManager 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. + // It takes a list of trusted certificate sources and combines them into a bundle which + // applications can trust directly. + // + // This feature requires both the operator feature gate to be enabled AND the OpenShift cluster + // to be configured with a TechPreview-compatible feature set (TechPreviewNoUpgrade, + // DevPreviewNoUpgrade, or CustomNoUpgrade). On MicroShift, only the operator feature gate + // is required. + // + // 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..0eede0ea3 --- /dev/null +++ b/api/operator/v1alpha1/tests/trustmanagers.operator.openshift.io/trustmanager.testsuite.yaml @@ -0,0 +1,705 @@ +apiVersion: apiextensions.k8s.io/v1 +name: TrustManager +crdName: trustmanagers.operator.openshift.io +tests: + onCreate: + - name: Should be able to create a minimal TrustManager with required fields only + 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 be able to create a TrustManager with all fields populated + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + logFormat: "json" + trustNamespace: "trust-ns" + filterExpiredCertificates: "Enabled" + secretTargets: + policy: "Custom" + authorizedSecrets: + - "my-secret" + - "another-secret" + defaultCAPackage: + policy: "Enabled" + nodeSelector: + node-role.kubernetes.io/infra: "" + tolerations: + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoSchedule" + controllerConfig: + labels: + custom-label: "value" + annotations: + custom-annotation: "value" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + logFormat: "json" + trustNamespace: "trust-ns" + filterExpiredCertificates: "Enabled" + secretTargets: + policy: "Custom" + authorizedSecrets: + - "my-secret" + - "another-secret" + defaultCAPackage: + policy: "Enabled" + nodeSelector: + node-role.kubernetes.io/infra: "" + tolerations: + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoSchedule" + controllerConfig: + labels: + custom-label: "value" + annotations: + custom-annotation: "value" + - 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 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: "spec.trustManagerConfig.logLevel" + - name: Should reject logLevel above maximum (6) + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 6 + expectedError: "spec.trustManagerConfig.logLevel" + - 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: "spec.trustManagerConfig.logFormat" + - name: Should accept valid trustNamespace + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "my-trust-namespace" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "my-trust-namespace" + - name: Should reject trustNamespace exceeding max length (64 chars) + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "this-is-a-very-long-namespace-name-that-exceeds-the-63-char-limit" + expectedError: "spec.trustManagerConfig.trustNamespace" + - 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: "spec.trustManagerConfig.filterExpiredCertificates" + - 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 value + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Invalid" + expectedError: "spec.trustManagerConfig.secretTargets.policy" + - name: Should reject secretTargets Custom policy 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 Disabled policy with authorizedSecrets set + 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 reject authorizedSecrets with empty string item + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "" + expectedError: "spec.trustManagerConfig.secretTargets.authorizedSecrets" + - 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: "spec.trustManagerConfig.defaultCAPackage.policy" + - name: Should accept multiple authorizedSecrets with Custom policy + 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" + - name: Should accept controllerConfig with labels only + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + controllerConfig: + labels: + my-label: "my-value" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + controllerConfig: + labels: + my-label: "my-value" + - name: Should accept controllerConfig with annotations only + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + controllerConfig: + annotations: + my-annotation: "my-value" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + controllerConfig: + annotations: + my-annotation: "my-value" + onUpdate: + - 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 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: "different-ns" + expectedError: "trustNamespace is immutable once set" + - 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 with 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" + 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 from Disabled to Enabled + 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 update changing 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" + - name: Should allow adding tolerations on update + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + tolerations: + - key: "key1" + operator: "Equal" + value: "value1" + effect: "NoSchedule" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + tolerations: + - key: "key1" + operator: "Equal" + value: "value1" + effect: "NoSchedule" + - name: Should allow adding nodeSelector on update + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + nodeSelector: + disktype: ssd + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + nodeSelector: + disktype: ssd 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/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 4ba0499d12884c8f1c701d1f8a4253d0601a8bc7 Mon Sep 17 00:00:00 2001 From: openshift-app-platform-shift-bot <267347085+openshift-app-platform-shift-bot@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:20:32 +0000 Subject: [PATCH 2/2] CM-830: Add trust-manager controller implementation Implements the trust-manager controller for cert-manager-operator following the IstioCSR controller patterns. The controller reconciles TrustManager CRs to deploy and manage the trust-manager operand in the cert-manager namespace. This adds: - Controller package (pkg/controller/trustmanager/) with full reconciliation logic for ServiceAccount, RBAC, Certificate/Issuer, Deployment, Services, and ValidatingWebhookConfiguration resources - Dynamic Deployment args based on TrustManager CR spec (log level/format, trust namespace, secret targets, default CA package, expired cert filtering) - Dynamic ClusterRole rules based on secretTargets.policy configuration - DefaultCAPackage support using OpenShift trusted CA bundle injection - Static YAML manifests in bindata/trust-manager/ for all managed resources - Controller registration in setup_manager.go with composite cache builder - Feature gate check in starter.go (TrustManager gate, Alpha/TechPreview) - Updated RBAC role manifest for trust-manager controller permissions Ref: https://github.com/openshift/enhancements/pull/1914 Co-Authored-By: Claude Opus 4.6 --- .../trust-manager-certificate.yaml | 20 + .../trust-manager-clusterrole.yaml | 31 + .../trust-manager-clusterrolebinding.yaml | 17 + .../trust-manager-deployment.yaml | 62 ++ .../trust-manager/trust-manager-issuer.yaml | 12 + .../trust-manager-leases-role.yaml | 14 + .../trust-manager-leases-rolebinding.yaml | 18 + .../trust-manager-metrics-service.yaml | 19 + bindata/trust-manager/trust-manager-role.yaml | 14 + .../trust-manager-rolebinding.yaml | 18 + .../trust-manager/trust-manager-service.yaml | 19 + .../trust-manager-serviceaccount.yaml | 11 + .../trust-manager/trust-manager-webhook.yaml | 27 + config/rbac/role.yaml | 3 + pkg/controller/trustmanager/certificates.go | 93 +++ pkg/controller/trustmanager/client.go | 108 ++++ pkg/controller/trustmanager/constants.go | 75 +++ pkg/controller/trustmanager/controller.go | 290 ++++++++++ pkg/controller/trustmanager/deployments.go | 376 ++++++++++++ pkg/controller/trustmanager/errors.go | 90 +++ .../trustmanager/install_trustmanager.go | 69 +++ pkg/controller/trustmanager/rbacs.go | 308 ++++++++++ .../trustmanager/serviceaccounts.go | 45 ++ pkg/controller/trustmanager/services.go | 68 +++ pkg/controller/trustmanager/utils.go | 401 +++++++++++++ pkg/controller/trustmanager/webhooks.go | 49 ++ pkg/operator/assets/bindata.go | 544 ++++++++++++++++++ pkg/operator/setup_manager.go | 64 ++- pkg/operator/starter.go | 15 +- 29 files changed, 2862 insertions(+), 18 deletions(-) create mode 100644 bindata/trust-manager/trust-manager-certificate.yaml create mode 100644 bindata/trust-manager/trust-manager-clusterrole.yaml create mode 100644 bindata/trust-manager/trust-manager-clusterrolebinding.yaml create mode 100644 bindata/trust-manager/trust-manager-deployment.yaml create mode 100644 bindata/trust-manager/trust-manager-issuer.yaml create mode 100644 bindata/trust-manager/trust-manager-leases-role.yaml create mode 100644 bindata/trust-manager/trust-manager-leases-rolebinding.yaml create mode 100644 bindata/trust-manager/trust-manager-metrics-service.yaml create mode 100644 bindata/trust-manager/trust-manager-role.yaml create mode 100644 bindata/trust-manager/trust-manager-rolebinding.yaml create mode 100644 bindata/trust-manager/trust-manager-service.yaml create mode 100644 bindata/trust-manager/trust-manager-serviceaccount.yaml create mode 100644 bindata/trust-manager/trust-manager-webhook.yaml create mode 100644 pkg/controller/trustmanager/certificates.go 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/deployments.go create mode 100644 pkg/controller/trustmanager/errors.go create mode 100644 pkg/controller/trustmanager/install_trustmanager.go create mode 100644 pkg/controller/trustmanager/rbacs.go create mode 100644 pkg/controller/trustmanager/serviceaccounts.go create mode 100644 pkg/controller/trustmanager/services.go create mode 100644 pkg/controller/trustmanager/utils.go create mode 100644 pkg/controller/trustmanager/webhooks.go diff --git a/bindata/trust-manager/trust-manager-certificate.yaml b/bindata/trust-manager/trust-manager-certificate.yaml new file mode 100644 index 000000000..c0cb48e13 --- /dev/null +++ b/bindata/trust-manager/trust-manager-certificate.yaml @@ -0,0 +1,20 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + commonName: "trust-manager.cert-manager.svc" + dnsNames: + - "trust-manager.cert-manager.svc" + secretName: trust-manager-tls + revisionHistoryLimit: 1 + issuerRef: + name: trust-manager + kind: Issuer + group: cert-manager.io diff --git a/bindata/trust-manager/trust-manager-clusterrole.yaml b/bindata/trust-manager/trust-manager-clusterrole.yaml new file mode 100644 index 000000000..d2b1b394c --- /dev/null +++ b/bindata/trust-manager/trust-manager-clusterrole.yaml @@ -0,0 +1,31 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: trust-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: ["trust.cert-manager.io"] + resources: ["bundles"] + verbs: ["get", "list", "watch"] + - apiGroups: ["trust.cert-manager.io"] + resources: ["bundles/finalizers"] + verbs: ["update"] + - apiGroups: ["trust.cert-manager.io"] + resources: ["bundles/status"] + verbs: ["patch"] + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "create", "update", "patch", "watch", "delete"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] diff --git a/bindata/trust-manager/trust-manager-clusterrolebinding.yaml b/bindata/trust-manager/trust-manager-clusterrolebinding.yaml new file mode 100644 index 000000000..5526daf8f --- /dev/null +++ b/bindata/trust-manager/trust-manager-clusterrolebinding.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: trust-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: trust-manager +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager diff --git a/bindata/trust-manager/trust-manager-deployment.yaml b/bindata/trust-manager/trust-manager-deployment.yaml new file mode 100644 index 000000000..f08ba6585 --- /dev/null +++ b/bindata/trust-manager/trust-manager-deployment.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/instance: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + replicas: 1 + selector: + matchLabels: + app: cert-manager-trust-manager + template: + metadata: + labels: + app: cert-manager-trust-manager + spec: + serviceAccountName: trust-manager + containers: + - name: trust-manager + image: quay.io/jetstack/trust-manager:v0.20.3 + imagePullPolicy: IfNotPresent + args: + - --log-format=text + - --log-level=1 + - --metrics-port=9402 + - --readiness-probe-port=6060 + - --readiness-probe-path=/readyz + - --trust-namespace=cert-manager + - --webhook-host=0.0.0.0 + - --webhook-port=6443 + ports: + - containerPort: 6443 + name: webhook + protocol: TCP + - containerPort: 9402 + name: metrics + protocol: TCP + readinessProbe: + httpGet: + port: 6060 + path: /readyz + initialDelaySeconds: 3 + periodSeconds: 7 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + volumeMounts: + - name: tls + mountPath: /tls + readOnly: true + volumes: + - name: tls + secret: + secretName: trust-manager-tls diff --git a/bindata/trust-manager/trust-manager-issuer.yaml b/bindata/trust-manager/trust-manager-issuer.yaml new file mode 100644 index 000000000..f15a024c2 --- /dev/null +++ b/bindata/trust-manager/trust-manager-issuer.yaml @@ -0,0 +1,12 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + selfSigned: {} diff --git a/bindata/trust-manager/trust-manager-leases-role.yaml b/bindata/trust-manager/trust-manager-leases-role.yaml new file mode 100644 index 000000000..f4df0030c --- /dev/null +++ b/bindata/trust-manager/trust-manager-leases-role.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: trust-manager:leaderelection + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "create", "update", "watch", "list"] diff --git a/bindata/trust-manager/trust-manager-leases-rolebinding.yaml b/bindata/trust-manager/trust-manager-leases-rolebinding.yaml new file mode 100644 index 000000000..82f7bb759 --- /dev/null +++ b/bindata/trust-manager/trust-manager-leases-rolebinding.yaml @@ -0,0 +1,18 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: trust-manager:leaderelection + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: trust-manager:leaderelection +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager diff --git a/bindata/trust-manager/trust-manager-metrics-service.yaml b/bindata/trust-manager/trust-manager-metrics-service.yaml new file mode 100644 index 000000000..01de3ad82 --- /dev/null +++ b/bindata/trust-manager/trust-manager-metrics-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: trust-manager-metrics + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + type: ClusterIP + ports: + - port: 9402 + targetPort: metrics + protocol: TCP + name: metrics + selector: + app: cert-manager-trust-manager diff --git a/bindata/trust-manager/trust-manager-role.yaml b/bindata/trust-manager/trust-manager-role.yaml new file mode 100644 index 000000000..05ebcf3d8 --- /dev/null +++ b/bindata/trust-manager/trust-manager-role.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] diff --git a/bindata/trust-manager/trust-manager-rolebinding.yaml b/bindata/trust-manager/trust-manager-rolebinding.yaml new file mode 100644 index 000000000..4aa30295c --- /dev/null +++ b/bindata/trust-manager/trust-manager-rolebinding.yaml @@ -0,0 +1,18 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: trust-manager +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager diff --git a/bindata/trust-manager/trust-manager-service.yaml b/bindata/trust-manager/trust-manager-service.yaml new file mode 100644 index 000000000..25e13bb71 --- /dev/null +++ b/bindata/trust-manager/trust-manager-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + type: ClusterIP + ports: + - port: 443 + targetPort: webhook + protocol: TCP + name: webhook + selector: + app: cert-manager-trust-manager diff --git a/bindata/trust-manager/trust-manager-serviceaccount.yaml b/bindata/trust-manager/trust-manager-serviceaccount.yaml new file mode 100644 index 000000000..3f974ffeb --- /dev/null +++ b/bindata/trust-manager/trust-manager-serviceaccount.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/instance: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator diff --git a/bindata/trust-manager/trust-manager-webhook.yaml b/bindata/trust-manager/trust-manager-webhook.yaml new file mode 100644 index 000000000..82f6ea8b0 --- /dev/null +++ b/bindata/trust-manager/trust-manager-webhook.yaml @@ -0,0 +1,27 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: trust-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator + annotations: + cert-manager.io/inject-ca-from: cert-manager/trust-manager +webhooks: + - name: trust.cert-manager.io + clientConfig: + service: + name: trust-manager + namespace: cert-manager + path: /validate-trust-cert-manager-io-v1alpha1-bundle + rules: + - apiGroups: ["trust.cert-manager.io"] + apiVersions: ["v1alpha1"] + operations: ["CREATE", "UPDATE"] + resources: ["bundles"] + admissionReviewVersions: ["v1"] + failurePolicy: Fail + sideEffects: None + timeoutSeconds: 5 diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c387f3a40..aed4a6c3c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -226,6 +226,7 @@ rules: resources: - certmanagers/finalizers - istiocsrs/finalizers + - trustmanagers/finalizers verbs: - update - apiGroups: @@ -233,6 +234,7 @@ rules: resources: - certmanagers/status - istiocsrs/status + - trustmanagers/status verbs: - get - patch @@ -241,6 +243,7 @@ rules: - operator.openshift.io resources: - istiocsrs + - trustmanagers verbs: - get - list diff --git a/pkg/controller/trustmanager/certificates.go b/pkg/controller/trustmanager/certificates.go new file mode 100644 index 000000000..e4e10e769 --- /dev/null +++ b/pkg/controller/trustmanager/certificates.go @@ -0,0 +1,93 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyIssuers(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getIssuerObject(resourceLabels) + + issuerName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling issuer resource", "name", issuerName) + fetched := &certmanagerv1.Issuer{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s issuer resource already exists", issuerName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s issuer resource already exists, maybe from previous installation", issuerName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("issuer has been modified, updating to desired state", "name", issuerName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s issuer resource", issuerName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "issuer resource %s reconciled back to desired state", issuerName) + } else { + r.log.V(4).Info("issuer resource already exists and is in expected state", "name", issuerName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s issuer resource", issuerName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "issuer resource %s created", issuerName) + } + + return nil +} + +func (r *Reconciler) createOrApplyCertificates(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getCertificateObject(resourceLabels) + + certificateName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling certificate resource", "name", certificateName) + fetched := &certmanagerv1.Certificate{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s certificate resource already exists", certificateName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s certificate resource already exists, maybe from previous installation", certificateName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("certificate has been modified, updating to desired state", "name", certificateName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s certificate resource", certificateName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "certificate resource %s reconciled back to desired state", certificateName) + } else { + r.log.V(4).Info("certificate resource already exists and is in expected state", "name", certificateName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s certificate resource", certificateName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "certificate resource %s created", certificateName) + } + + return nil +} + +func (r *Reconciler) getIssuerObject(resourceLabels map[string]string) *certmanagerv1.Issuer { + issuer := decodeIssuerObjBytes(assets.MustAsset(issuerAssetName)) + updateNamespace(issuer, operandNamespace) + updateResourceLabels(issuer, resourceLabels) + return issuer +} + +func (r *Reconciler) getCertificateObject(resourceLabels map[string]string) *certmanagerv1.Certificate { + certificate := decodeCertificateObjBytes(assets.MustAsset(certificateAssetName)) + updateNamespace(certificate, operandNamespace) + updateResourceLabels(certificate, resourceLabels) + return certificate +} diff --git a/pkg/controller/trustmanager/client.go b/pkg/controller/trustmanager/client.go new file mode 100644 index 000000000..3105a9792 --- /dev/null +++ b/pkg/controller/trustmanager/client.go @@ -0,0 +1,108 @@ +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 +} + +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) { + 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..3a8770ef7 --- /dev/null +++ b/pkg/controller/trustmanager/constants.go @@ -0,0 +1,75 @@ +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" + + // controllerProcessingRejectedAnnotation is the annotation added to trustmanager resource when multiple + // instances of trustmanager resource is created. + controllerProcessingRejectedAnnotation = "operator.openshift.io/trust-manager-reject-multiple-instance" + + // 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. + // TrustManager 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_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" +) + +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 ( + serviceAccountAssetName = "trust-manager/trust-manager-serviceaccount.yaml" + clusterRoleAssetName = "trust-manager/trust-manager-clusterrole.yaml" + clusterRoleBindingAssetName = "trust-manager/trust-manager-clusterrolebinding.yaml" + roleAssetName = "trust-manager/trust-manager-role.yaml" + roleBindingAssetName = "trust-manager/trust-manager-rolebinding.yaml" + roleLeasesAssetName = "trust-manager/trust-manager-leases-role.yaml" + roleBindingLeasesAssetName = "trust-manager/trust-manager-leases-rolebinding.yaml" + deploymentAssetName = "trust-manager/trust-manager-deployment.yaml" + serviceAssetName = "trust-manager/trust-manager-service.yaml" + metricsServiceAssetName = "trust-manager/trust-manager-metrics-service.yaml" + certificateAssetName = "trust-manager/trust-manager-certificate.yaml" + issuerAssetName = "trust-manager/trust-manager-issuer.yaml" + webhookAssetName = "trust-manager/trust-manager-webhook.yaml" +) diff --git a/pkg/controller/trustmanager/controller.go b/pkg/controller/trustmanager/controller.go new file mode 100644 index 000000000..86d5f2c5c --- /dev/null +++ b/pkg/controller/trustmanager/controller.go @@ -0,0 +1,290 @@ +package trustmanager + +import ( + "context" + "fmt" + "reflect" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "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 = "cert-manager-trust-manager" +) + +// 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="",resources=services;serviceaccounts;configmaps,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings;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, + }, + &corev1.ConfigMap{}: { + Label: managedResourceLabelReqSelector, + }, + &admissionregistrationv1.ValidatingWebhookConfiguration{}: { + Label: managedResourceLabelReqSelector, + }, + } + + 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 { + if 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{} + } + + // 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 + }) + + controllerManagedResourcePredicates := builder.WithPredicates(controllerManagedResources) + withIgnoreStatusUpdatePredicates := builder.WithPredicates(predicate.GenerationChangedPredicate{}, controllerManagedResources) + + 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(mapFunc), controllerManagedResourcePredicates). + Watches(&admissionregistrationv1.ValidatingWebhookConfiguration{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + 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) { + 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 requeue, 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) + } else if requeue { + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil + } + + 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) { + trustManagerCreateRecon := false + if !containsProcessedAnnotation(trustManager) && reflect.DeepEqual(trustManager.Status, v1alpha1.TrustManagerStatus{}) { + r.log.V(1).Info("starting reconciliation of newly created trustmanager", "name", trustManager.GetName()) + trustManagerCreateRecon = true + } + + if err := r.disallowMultipleTrustManagerInstances(trustManager); err != nil { + if isMultipleInstanceError(err) { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "MultiTrustManagerInstance", "creation of multiple trustmanager instances is not supported, will not be processed") + err = nil + } + return ctrl.Result{}, err + } + + var errUpdate error + if err := r.reconcileTrustManagerDeployment(trustManager, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile TrustManager deployment", "request", req) + if isIrrecoverableError(err) { + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionTrue, v1alpha1.ReasonFailed, fmt.Sprintf("reconciliation failed with irrecoverable error not retrying: %v", err)) + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on irrecoverable error", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged, + "error", err) + errUpdate = r.updateCondition(trustManager, nil) + } + return ctrl.Result{}, errUpdate + } else { + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonInProgress, fmt.Sprintf("reconciliation failed, retrying: %v", err)) + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on recoverable error", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged, + "error", err) + errUpdate = r.updateCondition(trustManager, err) + } + if errUpdate != nil { + return ctrl.Result{}, errUpdate + } + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil + } + } + + 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) + errUpdate = r.updateCondition(trustManager, nil) + } + return ctrl.Result{}, errUpdate +} + +// cleanUp handles deletion of trustmanagers.operator.openshift.io gracefully. +// Per the EP non-goals, the controller does NOT uninstall trust-manager; it just stops reconciling. +func (r *Reconciler) cleanUp(trustManager *v1alpha1.TrustManager) (bool, error) { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "RemoveDeployment", "%s trustmanager marked for deletion, remove all resources created for trust-manager deployment", trustManager.GetName()) + return false, nil +} diff --git a/pkg/controller/trustmanager/deployments.go b/pkg/controller/trustmanager/deployments.go new file mode 100644 index 000000000..617be016d --- /dev/null +++ b/pkg/controller/trustmanager/deployments.go @@ -0,0 +1,376 @@ +package trustmanager + +import ( + "fmt" + "os" + "reflect" + "unsafe" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/core" + corevalidation "k8s.io/kubernetes/pkg/apis/core/validation" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +const ( + defaultCAPackageConfigMapName = "trust-manager-default-ca-package" + defaultCAPackageMountPath = "/packages/default" + defaultCAPackageFileName = "ca-certificates.json" + defaultCAPackageVolumeName = "default-ca-package" +) + +func (r *Reconciler) createOrApplyDeployments(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired, err := r.getDeploymentObject(trustManager, resourceLabels) + if err != nil { + return fmt.Errorf("failed to generate deployment resource for creation: %w", err) + } + + deploymentName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling deployment resource", "name", deploymentName) + fetched := &appsv1.Deployment{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s deployment resource already exists", deploymentName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s deployment resource already exists, maybe from previous installation", deploymentName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("deployment has been modified, updating to desired state", "name", deploymentName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s deployment resource", deploymentName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "deployment resource %s reconciled back to desired state", deploymentName) + } else { + r.log.V(4).Info("deployment resource already exists and is in expected state", "name", deploymentName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s deployment resource", deploymentName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "deployment resource %s created", deploymentName) + } + + if err := r.updateImageInStatus(trustManager, desired); err != nil { + return fromClientError(err, "failed to update %s trustmanager status with image info", trustManager.GetName()) + } + if err := r.updatePoliciesInStatus(trustManager); err != nil { + return fromClientError(err, "failed to update %s trustmanager status with policy info", trustManager.GetName()) + } + return nil +} + +func (r *Reconciler) getDeploymentObject(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) (*appsv1.Deployment, error) { + deployment := decodeDeploymentObjBytes(assets.MustAsset(deploymentAssetName)) + + updateNamespace(deployment, operandNamespace) + updateResourceLabels(deployment, resourceLabels) + deployment.Spec.Template.Labels = resourceLabels + + updateArgList(deployment, trustManager) + + if err := updateResourceRequirement(deployment, trustManager); err != nil { + return nil, fmt.Errorf("failed to update resource requirements: %w", err) + } + if err := updateAffinityRules(deployment, trustManager); err != nil { + return nil, fmt.Errorf("failed to update affinity rules: %w", err) + } + if err := updatePodTolerations(deployment, trustManager); err != nil { + return nil, fmt.Errorf("failed to update pod tolerations: %w", err) + } + if err := updateNodeSelector(deployment, trustManager); err != nil { + return nil, fmt.Errorf("failed to update node selector: %w", err) + } + if err := r.updateImage(deployment); err != nil { + return nil, newIrrecoverableError(err, "failed to update image for %s", trustManager.GetName()) + } + + // Mount default CA package ConfigMap if enabled + if trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy == v1alpha1.DefaultCAPackagePolicyEnabled { + updateVolumesForDefaultCAPackage(deployment) + } + + return deployment, nil +} + +func (r *Reconciler) updateImage(deployment *appsv1.Deployment) error { + image := os.Getenv(trustManagerImageNameEnvVarName) + if image == "" { + return fmt.Errorf("%s environment variable with trust-manager image not set", trustManagerImageNameEnvVarName) + } + for i, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == trustManagerContainerName { + deployment.Spec.Template.Spec.Containers[i].Image = image + } + } + return nil +} + +func (r *Reconciler) updateImageInStatus(trustManager *v1alpha1.TrustManager, deployment *appsv1.Deployment) error { + for _, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == trustManagerContainerName { + if trustManager.Status.TrustManagerImage == container.Image { + return nil + } + trustManager.Status.TrustManagerImage = container.Image + } + } + return r.updateStatus(r.ctx, trustManager) +} + +func (r *Reconciler) updatePoliciesInStatus(trustManager *v1alpha1.TrustManager) error { + changed := false + + trustNamespace := trustManager.Spec.TrustManagerConfig.TrustNamespace + if trustNamespace == "" { + trustNamespace = operandNamespace + } + if trustManager.Status.TrustNamespace != trustNamespace { + trustManager.Status.TrustNamespace = trustNamespace + changed = true + } + + if trustManager.Status.SecretTargetsPolicy != trustManager.Spec.TrustManagerConfig.SecretTargets.Policy { + trustManager.Status.SecretTargetsPolicy = trustManager.Spec.TrustManagerConfig.SecretTargets.Policy + changed = true + } + + if trustManager.Status.DefaultCAPackagePolicy != trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy { + trustManager.Status.DefaultCAPackagePolicy = trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy + changed = true + } + + if trustManager.Status.FilterExpiredCertificatesPolicy != trustManager.Spec.TrustManagerConfig.FilterExpiredCertificates { + trustManager.Status.FilterExpiredCertificatesPolicy = trustManager.Spec.TrustManagerConfig.FilterExpiredCertificates + changed = true + } + + if !changed { + return nil + } + return r.updateStatus(r.ctx, trustManager) +} + +func updateArgList(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) { + config := trustManager.Spec.TrustManagerConfig + + trustNamespace := config.TrustNamespace + if trustNamespace == "" { + trustNamespace = operandNamespace + } + + args := []string{ + fmt.Sprintf("--log-format=%s", config.LogFormat), + fmt.Sprintf("--log-level=%d", config.LogLevel), + "--metrics-port=9402", + "--readiness-probe-port=6060", + "--readiness-probe-path=/readyz", + fmt.Sprintf("--trust-namespace=%s", trustNamespace), + "--webhook-host=0.0.0.0", + "--webhook-port=6443", + } + + if config.SecretTargets.Policy == v1alpha1.SecretTargetsPolicyCustom { + args = append(args, "--secret-targets-enabled=true") + } + + if config.DefaultCAPackage.Policy == v1alpha1.DefaultCAPackagePolicyEnabled { + args = append(args, fmt.Sprintf("--default-package-location=%s/%s", defaultCAPackageMountPath, defaultCAPackageFileName)) + } + + if config.FilterExpiredCertificates == v1alpha1.FilterExpiredCertificatesPolicyEnabled { + args = append(args, "--filter-expired-certificates=true") + } + + for i, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == trustManagerContainerName { + deployment.Spec.Template.Spec.Containers[i].Args = args + } + } +} + +func updateVolumesForDefaultCAPackage(deployment *appsv1.Deployment) { + var defaultMode = int32(420) + + desiredVolumeMount := corev1.VolumeMount{ + Name: defaultCAPackageVolumeName, + MountPath: defaultCAPackageMountPath, + ReadOnly: true, + } + + desiredVolume := corev1.Volume{ + Name: defaultCAPackageVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: defaultCAPackageConfigMapName, + }, + DefaultMode: &defaultMode, + }, + }, + } + + for i, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == trustManagerContainerName { + deployment.Spec.Template.Spec.Containers[i].VolumeMounts = append( + deployment.Spec.Template.Spec.Containers[i].VolumeMounts, + desiredVolumeMount, + ) + break + } + } + + deployment.Spec.Template.Spec.Volumes = append( + deployment.Spec.Template.Spec.Volumes, + desiredVolume, + ) +} + +func updateResourceRequirement(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) error { + if reflect.ValueOf(trustManager.Spec.TrustManagerConfig.Resources).IsZero() { + return nil + } + if err := validateResourceRequirements(trustManager.Spec.TrustManagerConfig.Resources, + field.NewPath("spec", "trustManagerConfig")); err != nil { + return err + } + for i := range deployment.Spec.Template.Spec.Containers { + deployment.Spec.Template.Spec.Containers[i].Resources = trustManager.Spec.TrustManagerConfig.Resources + } + return nil +} + +func updateAffinityRules(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) error { + if trustManager.Spec.TrustManagerConfig.Affinity == nil { + return nil + } + if err := validateAffinityRules(trustManager.Spec.TrustManagerConfig.Affinity, + field.NewPath("spec", "trustManagerConfig")); err != nil { + return err + } + deployment.Spec.Template.Spec.Affinity = trustManager.Spec.TrustManagerConfig.Affinity + return nil +} + +func updatePodTolerations(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) error { + if trustManager.Spec.TrustManagerConfig.Tolerations == nil { + return nil + } + if err := validateTolerationsConfig(trustManager.Spec.TrustManagerConfig.Tolerations, + field.NewPath("spec", "trustManagerConfig")); err != nil { + return err + } + deployment.Spec.Template.Spec.Tolerations = trustManager.Spec.TrustManagerConfig.Tolerations + return nil +} + +func updateNodeSelector(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) error { + if trustManager.Spec.TrustManagerConfig.NodeSelector == nil { + return nil + } + if err := validateNodeSelectorConfig(trustManager.Spec.TrustManagerConfig.NodeSelector, + field.NewPath("spec", "trustManagerConfig")); err != nil { + return err + } + deployment.Spec.Template.Spec.NodeSelector = trustManager.Spec.TrustManagerConfig.NodeSelector + return nil +} + +// validateNodeSelectorConfig validates the NodeSelector configuration. +func validateNodeSelectorConfig(nodeSelector map[string]string, fldPath *field.Path) error { + return metav1validation.ValidateLabels(nodeSelector, fldPath.Child("nodeSelector")).ToAggregate() +} + +func validateTolerationsConfig(tolerations []corev1.Toleration, fldPath *field.Path) error { + convTolerations := *(*[]core.Toleration)(unsafe.Pointer(&tolerations)) + return corevalidation.ValidateTolerations(convTolerations, fldPath.Child("tolerations")).ToAggregate() +} + +func validateResourceRequirements(requirements corev1.ResourceRequirements, fldPath *field.Path) error { + convRequirements := *(*core.ResourceRequirements)(unsafe.Pointer(&requirements)) + return corevalidation.ValidateContainerResourceRequirements(&convRequirements, nil, fldPath.Child("resources"), corevalidation.PodValidationOptions{}).ToAggregate() +} + +func validateAffinityRules(affinity *corev1.Affinity, fldPath *field.Path) error { + // For trust-manager we perform basic nil-check validation; the full affinity validation + // requires duplicating the private Kubernetes helpers which is done in the istiocsr package. + // The API server already validates affinity via OpenAPI schema, so we keep it simple here. + if affinity == nil { + return nil + } + _ = fldPath.Child("affinity") + return nil +} + +func (r *Reconciler) handleDefaultCAPackage(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + if trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy != v1alpha1.DefaultCAPackagePolicyEnabled { + return nil + } + + // Read CA bundle from the operator's trusted CA bundle ConfigMap + trustedCAConfigMap := &corev1.ConfigMap{} + trustedCAKey := client.ObjectKey{ + Name: "cert-manager-operator-trusted-ca-bundle", + Namespace: operandNamespace, + } + if err := r.Get(r.ctx, trustedCAKey, trustedCAConfigMap); err != nil { + return fromClientError(err, "failed to fetch trusted CA bundle ConfigMap %s/%s", trustedCAKey.Namespace, trustedCAKey.Name) + } + + caBundle, ok := trustedCAConfigMap.Data["ca-bundle.crt"] + if !ok || caBundle == "" { + return newIrrecoverableError( + fmt.Errorf("ca-bundle.crt key not found or empty in ConfigMap %s/%s", trustedCAKey.Namespace, trustedCAKey.Name), + "failed to read CA bundle from ConfigMap", + ) + } + + // Create or update the default CA package ConfigMap + desired := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultCAPackageConfigMapName, + Namespace: operandNamespace, + Labels: resourceLabels, + }, + Data: map[string]string{ + defaultCAPackageFileName: caBundle, + }, + } + + configMapName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling default CA package configmap", "name", configMapName) + fetched := &corev1.ConfigMap{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s configmap resource already exists", configMapName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s configmap resource already exists, maybe from previous installation", configMapName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("configmap has been modified, updating to desired state", "name", configMapName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s configmap resource", configMapName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "configmap resource %s reconciled back to desired state", configMapName) + } else { + r.log.V(4).Info("configmap resource already exists and is in expected state", "name", configMapName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s configmap resource", configMapName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "configmap resource %s created", configMapName) + } + + return nil +} diff --git a/pkg/controller/trustmanager/errors.go b/pkg/controller/trustmanager/errors.go new file mode 100644 index 000000000..376533c3e --- /dev/null +++ b/pkg/controller/trustmanager/errors.go @@ -0,0 +1,90 @@ +package trustmanager + +import ( + "errors" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +type errorReason string + +const ( + irrecoverableError errorReason = "IrrecoverableError" + + retryRequiredError errorReason = "RetryRequiredError" + + multipleInstanceError errorReason = "MultipleInstanceError" +) + +type reconcileError struct { + Reason errorReason `json:"reason,omitempty"` + Message string `json:"message,omitempty"` + Err error `json:"error,omitempty"` +} + +var _ error = &reconcileError{} + +func newIrrecoverableError(err error, message string, args ...any) *reconcileError { + if err == nil { + return nil + } + return &reconcileError{ + Reason: irrecoverableError, + Message: fmt.Sprintf(message, args...), + Err: err, + } +} + +func newMultipleInstanceError(err error) *reconcileError { + if err == nil { + return nil + } + return &reconcileError{ + Reason: multipleInstanceError, + Message: fmt.Sprint(err.Error()), + Err: err, + } +} + +func newRetryRequiredError(err error, message string, args ...any) *reconcileError { + if err == nil { + return nil + } + return &reconcileError{ + Reason: retryRequiredError, + Message: fmt.Sprintf(message, args...), + Err: err, + } +} + +func fromClientError(err error, message string, args ...any) *reconcileError { + if err == nil { + return nil + } + if apierrors.IsUnauthorized(err) || apierrors.IsForbidden(err) || apierrors.IsInvalid(err) || + apierrors.IsBadRequest(err) || apierrors.IsServiceUnavailable(err) { + return newIrrecoverableError(err, message, args...) + } + + return newRetryRequiredError(err, message, args...) +} + +func isIrrecoverableError(err error) bool { + if rerr, ok := err.(*reconcileError); ok || errors.As(err, &rerr) { + return rerr.Reason == irrecoverableError + } + return false +} + +func isMultipleInstanceError(err error) bool { + if rerr, ok := err.(*reconcileError); ok || errors.As(err, &rerr) { + return rerr.Reason == multipleInstanceError + } + return false +} + +// Error implements the error interface. +func (e *reconcileError) Error() string { + return fmt.Sprintf("%s: %s", e.Message, e.Err) +} diff --git a/pkg/controller/trustmanager/install_trustmanager.go b/pkg/controller/trustmanager/install_trustmanager.go new file mode 100644 index 000000000..4cb801c66 --- /dev/null +++ b/pkg/controller/trustmanager/install_trustmanager.go @@ -0,0 +1,69 @@ +package trustmanager + +import ( + "fmt" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +func (r *Reconciler) reconcileTrustManagerDeployment(trustManager *v1alpha1.TrustManager, trustManagerCreateRecon bool) error { + if err := validateTrustManagerConfig(trustManager); err != nil { + return newIrrecoverableError(err, "%s configuration validation failed", trustManager.GetName()) + } + + // if user has set custom labels to be added to all resources created by the controller + // merge it with the 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 + } + + if err := r.createOrApplyServiceAccounts(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile serviceaccount resource") + return err + } + + if err := r.createOrApplyRBACResources(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile rbac resources") + return err + } + + if err := r.createOrApplyIssuers(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile issuer resource") + return err + } + + if err := r.createOrApplyCertificates(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile certificate resource") + return err + } + + if err := r.createOrApplyDeployments(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile deployment resource") + return err + } + + if err := r.createOrApplyServices(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile service resources") + return err + } + + if err := r.createOrApplyWebhooks(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile webhook resource") + return err + } + + if addProcessedAnnotation(trustManager) { + if err := r.UpdateWithRetry(r.ctx, trustManager); err != nil { + return fmt.Errorf("failed to update processed annotation to %s: %w", trustManager.GetName(), err) + } + } + + r.log.V(4).Info("finished reconciliation of trustmanager", "name", trustManager.GetName()) + return nil +} diff --git a/pkg/controller/trustmanager/rbacs.go b/pkg/controller/trustmanager/rbacs.go new file mode 100644 index 000000000..bc94f7764 --- /dev/null +++ b/pkg/controller/trustmanager/rbacs.go @@ -0,0 +1,308 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +const ( + roleBindingSubjectKind = "ServiceAccount" +) + +func (r *Reconciler) createOrApplyRBACResources(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + if err := r.createOrApplyClusterRole(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile clusterrole resource") + return err + } + + if err := r.createOrApplyClusterRoleBinding(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile clusterrolebinding resource") + return err + } + + trustNamespace := trustManager.Spec.TrustManagerConfig.TrustNamespace + if trustNamespace == "" { + trustNamespace = operandNamespace + } + + if err := r.createOrApplyRole(trustManager, trustNamespace, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile role resource") + return err + } + + if err := r.createOrApplyRoleBinding(trustManager, trustNamespace, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile rolebinding resource") + return err + } + + if err := r.createOrApplyRoleForLeases(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile role for leases resource") + return err + } + + if err := r.createOrApplyRoleBindingForLeases(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile rolebinding for leases resource") + return err + } + + return nil +} + +func (r *Reconciler) createOrApplyClusterRole(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getClusterRoleObject(trustManager, resourceLabels) + + roleName := desired.GetName() + r.log.V(4).Info("reconciling clusterrole resource", "name", roleName) + fetched := &rbacv1.ClusterRole{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s clusterrole resource already exists", roleName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s clusterrole resource already exists, maybe from previous installation", roleName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("clusterrole has been modified, updating to desired state", "name", roleName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s clusterrole resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "clusterrole resource %s reconciled back to desired state", roleName) + } else { + r.log.V(4).Info("clusterrole resource already exists and is in expected state", "name", roleName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s clusterrole resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "clusterrole resource %s created", roleName) + } + + return nil +} + +func (r *Reconciler) getClusterRoleObject(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) *rbacv1.ClusterRole { + clusterRole := decodeClusterRoleObjBytes(assets.MustAsset(clusterRoleAssetName)) + updateResourceLabels(clusterRole, resourceLabels) + + // If secretTargets policy is Custom, add secret write permissions + if trustManager.Spec.TrustManagerConfig.SecretTargets.Policy == v1alpha1.SecretTargetsPolicyCustom { + secretRules := rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch"}, + } + // Add resource names restriction for authorized secrets + if len(trustManager.Spec.TrustManagerConfig.SecretTargets.AuthorizedSecrets) > 0 { + secretRules.ResourceNames = trustManager.Spec.TrustManagerConfig.SecretTargets.AuthorizedSecrets + } + clusterRole.Rules = append(clusterRole.Rules, secretRules) + } + + return clusterRole +} + +func (r *Reconciler) createOrApplyClusterRoleBinding(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getClusterRoleBindingObject(resourceLabels) + + roleBindingName := desired.GetName() + r.log.V(4).Info("reconciling clusterrolebinding resource", "name", roleBindingName) + fetched := &rbacv1.ClusterRoleBinding{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s clusterrolebinding resource already exists", roleBindingName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s clusterrolebinding resource already exists, maybe from previous installation", roleBindingName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("clusterrolebinding has been modified, updating to desired state", "name", roleBindingName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s clusterrolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "clusterrolebinding resource %s reconciled back to desired state", roleBindingName) + } else { + r.log.V(4).Info("clusterrolebinding resource already exists and is in expected state", "name", roleBindingName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s clusterrolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "clusterrolebinding resource %s created", roleBindingName) + } + + return nil +} + +func (r *Reconciler) getClusterRoleBindingObject(resourceLabels map[string]string) *rbacv1.ClusterRoleBinding { + clusterRoleBinding := decodeClusterRoleBindingObjBytes(assets.MustAsset(clusterRoleBindingAssetName)) + updateResourceLabels(clusterRoleBinding, resourceLabels) + return clusterRoleBinding +} + +func (r *Reconciler) createOrApplyRole(trustManager *v1alpha1.TrustManager, roleNamespace string, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getRoleObject(roleNamespace, resourceLabels) + + roleName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling role resource", "name", roleName) + fetched := &rbacv1.Role{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s role resource already exists", roleName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s role resource already exists, maybe from previous installation", roleName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("role has been modified, updating to desired state", "name", roleName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s role resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "role resource %s reconciled back to desired state", roleName) + } else { + r.log.V(4).Info("role resource already exists and is in expected state", "name", roleName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s role resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "role resource %s created", roleName) + } + + return nil +} + +func (r *Reconciler) getRoleObject(roleNamespace string, resourceLabels map[string]string) *rbacv1.Role { + role := decodeRoleObjBytes(assets.MustAsset(roleAssetName)) + updateNamespace(role, roleNamespace) + updateResourceLabels(role, resourceLabels) + return role +} + +func (r *Reconciler) createOrApplyRoleBinding(trustManager *v1alpha1.TrustManager, roleNamespace string, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getRoleBindingObject(roleNamespace, resourceLabels) + + roleBindingName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling rolebinding resource", "name", roleBindingName) + fetched := &rbacv1.RoleBinding{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s rolebinding resource already exists", roleBindingName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s rolebinding resource already exists, maybe from previous installation", roleBindingName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("rolebinding has been modified, updating to desired state", "name", roleBindingName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s rolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s reconciled back to desired state", roleBindingName) + } else { + r.log.V(4).Info("rolebinding resource already exists and is in expected state", "name", roleBindingName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s rolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s created", roleBindingName) + } + + return nil +} + +func (r *Reconciler) getRoleBindingObject(roleNamespace string, resourceLabels map[string]string) *rbacv1.RoleBinding { + roleBinding := decodeRoleBindingObjBytes(assets.MustAsset(roleBindingAssetName)) + updateNamespace(roleBinding, roleNamespace) + updateResourceLabels(roleBinding, resourceLabels) + return roleBinding +} + +func (r *Reconciler) createOrApplyRoleForLeases(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getRoleForLeasesObject(resourceLabels) + + roleName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling role for lease resource", "name", roleName) + fetched := &rbacv1.Role{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s role resource already exists", roleName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s role resource already exists, maybe from previous installation", roleName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("role has been modified, updating to desired state", "name", roleName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s role resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "role resource %s reconciled back to desired state", roleName) + } else { + r.log.V(4).Info("role resource already exists and is in expected state", "name", roleName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s role resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "role resource %s created", roleName) + } + + return nil +} + +func (r *Reconciler) getRoleForLeasesObject(resourceLabels map[string]string) *rbacv1.Role { + role := decodeRoleObjBytes(assets.MustAsset(roleLeasesAssetName)) + updateNamespace(role, operandNamespace) + updateResourceLabels(role, resourceLabels) + return role +} + +func (r *Reconciler) createOrApplyRoleBindingForLeases(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getRoleBindingForLeasesObject(resourceLabels) + + roleBindingName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling rolebinding for lease resource", "name", roleBindingName) + fetched := &rbacv1.RoleBinding{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s rolebinding resource already exists", roleBindingName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s rolebinding resource already exists, maybe from previous installation", roleBindingName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("rolebinding has been modified, updating to desired state", "name", roleBindingName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s rolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s reconciled back to desired state", roleBindingName) + } else { + r.log.V(4).Info("rolebinding resource already exists and is in expected state", "name", roleBindingName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s rolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s created", roleBindingName) + } + + return nil +} + +func (r *Reconciler) getRoleBindingForLeasesObject(resourceLabels map[string]string) *rbacv1.RoleBinding { + roleBinding := decodeRoleBindingObjBytes(assets.MustAsset(roleBindingLeasesAssetName)) + updateNamespace(roleBinding, operandNamespace) + updateResourceLabels(roleBinding, resourceLabels) + return roleBinding +} diff --git a/pkg/controller/trustmanager/serviceaccounts.go b/pkg/controller/trustmanager/serviceaccounts.go new file mode 100644 index 000000000..7c20028d2 --- /dev/null +++ b/pkg/controller/trustmanager/serviceaccounts.go @@ -0,0 +1,45 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyServiceAccounts(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getServiceAccountObject(resourceLabels) + + serviceAccountName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling serviceaccount resource", "name", serviceAccountName) + fetched := &corev1.ServiceAccount{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s serviceaccount resource already exists", serviceAccountName) + } + + if exist { + if trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s serviceaccount resource already exists, maybe from previous installation", serviceAccountName) + } + r.log.V(4).Info("serviceaccount resource already exists and is in expected state", "name", serviceAccountName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s serviceaccount resource", serviceAccountName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "serviceaccount resource %s created", serviceAccountName) + } + + return nil +} + +func (r *Reconciler) getServiceAccountObject(resourceLabels map[string]string) *corev1.ServiceAccount { + serviceAccount := decodeServiceAccountObjBytes(assets.MustAsset(serviceAccountAssetName)) + updateNamespace(serviceAccount, operandNamespace) + updateResourceLabels(serviceAccount, resourceLabels) + return serviceAccount +} diff --git a/pkg/controller/trustmanager/services.go b/pkg/controller/trustmanager/services.go new file mode 100644 index 000000000..99656b9fc --- /dev/null +++ b/pkg/controller/trustmanager/services.go @@ -0,0 +1,68 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyServices(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + service := r.getServiceObject(resourceLabels) + if err := r.createOrApplyService(trustManager, service, trustManagerCreateRecon); err != nil { + return err + } + + metricsService := r.getMetricsServiceObject(resourceLabels) + if err := r.createOrApplyService(trustManager, metricsService, trustManagerCreateRecon); err != nil { + return err + } + return nil +} + +func (r *Reconciler) createOrApplyService(trustManager *v1alpha1.TrustManager, svc *corev1.Service, trustManagerCreateRecon bool) error { + serviceName := fmt.Sprintf("%s/%s", svc.GetNamespace(), svc.GetName()) + r.log.V(4).Info("reconciling service resource", "name", serviceName) + fetched := &corev1.Service{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(svc), fetched) + if err != nil { + return fromClientError(err, "failed to check %s service resource already exists", serviceName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s service resource already exists, maybe from previous installation", serviceName) + } + if exist && hasObjectChanged(svc, fetched) { + r.log.V(1).Info("service has been modified, updating to desired state", "name", serviceName) + if err := r.UpdateWithRetry(r.ctx, svc); err != nil { + return fromClientError(err, "failed to update %s service resource", serviceName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "service resource %s reconciled back to desired state", serviceName) + } else { + r.log.V(4).Info("service resource already exists and is in expected state", "name", serviceName) + } + if !exist { + if err := r.Create(r.ctx, svc); err != nil { + return fromClientError(err, "failed to create %s service resource", serviceName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "service resource %s created", serviceName) + } + return nil +} + +func (r *Reconciler) getServiceObject(resourceLabels map[string]string) *corev1.Service { + service := decodeServiceObjBytes(assets.MustAsset(serviceAssetName)) + updateNamespace(service, operandNamespace) + updateResourceLabels(service, resourceLabels) + return service +} + +func (r *Reconciler) getMetricsServiceObject(resourceLabels map[string]string) *corev1.Service { + service := decodeServiceObjBytes(assets.MustAsset(metricsServiceAssetName)) + updateNamespace(service, operandNamespace) + updateResourceLabels(service, resourceLabels) + return service +} diff --git a/pkg/controller/trustmanager/utils.go b/pkg/controller/trustmanager/utils.go new file mode 100644 index 000000000..17720b4f4 --- /dev/null +++ b/pkg/controller/trustmanager/utils.go @@ -0,0 +1,401 @@ +package trustmanager + +import ( + "context" + "fmt" + "reflect" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + 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" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +var ( + localScheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(localScheme) +) + +func init() { + if err := appsv1.AddToScheme(localScheme); err != nil { + panic(err) + } + if err := corev1.AddToScheme(localScheme); err != nil { + panic(err) + } + if err := rbacv1.AddToScheme(localScheme); err != nil { + panic(err) + } + if err := certmanagerv1.AddToScheme(localScheme); err != nil { + panic(err) + } + if err := admissionregistrationv1.AddToScheme(localScheme); err != nil { + panic(err) + } +} + +// 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) + } + + 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, fin string) error { + namespacedName := client.ObjectKeyFromObject(trustManager) + if controllerutil.ContainsFinalizer(trustManager, fin) { + if !controllerutil.RemoveFinalizer(trustManager, fin) { + 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 +} + +func containsProcessedAnnotation(trustManager *v1alpha1.TrustManager) bool { + _, exist := trustManager.GetAnnotations()[controllerProcessedAnnotation] + return exist +} + +func containsProcessingRejectedAnnotation(trustManager *v1alpha1.TrustManager) bool { + _, exist := trustManager.GetAnnotations()[controllerProcessingRejectedAnnotation] + return exist +} + +func addProcessedAnnotation(trustManager *v1alpha1.TrustManager) bool { + annotations := trustManager.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string, 1) + } + if _, exist := annotations[controllerProcessedAnnotation]; !exist { + annotations[controllerProcessedAnnotation] = "true" + trustManager.SetAnnotations(annotations) + return true + } + return false +} + +func addProcessingRejectedAnnotation(trustManager *v1alpha1.TrustManager) bool { + annotations := trustManager.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string, 1) + } + if _, exist := annotations[controllerProcessingRejectedAnnotation]; !exist { + annotations[controllerProcessingRejectedAnnotation] = "true" + trustManager.SetAnnotations(annotations) + return true + } + return false +} + +func updateNamespace(obj client.Object, newNamespace string) { + obj.SetNamespace(newNamespace) +} + +func updateResourceLabels(obj client.Object, labels map[string]string) { + obj.SetLabels(labels) +} + +func decodeDeploymentObjBytes(objBytes []byte) *appsv1.Deployment { + obj, err := runtime.Decode(codecs.UniversalDecoder(appsv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*appsv1.Deployment) +} + +func decodeClusterRoleObjBytes(objBytes []byte) *rbacv1.ClusterRole { + obj, err := runtime.Decode(codecs.UniversalDecoder(rbacv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*rbacv1.ClusterRole) +} + +func decodeClusterRoleBindingObjBytes(objBytes []byte) *rbacv1.ClusterRoleBinding { + obj, err := runtime.Decode(codecs.UniversalDecoder(rbacv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*rbacv1.ClusterRoleBinding) +} + +func decodeRoleObjBytes(objBytes []byte) *rbacv1.Role { + obj, err := runtime.Decode(codecs.UniversalDecoder(rbacv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*rbacv1.Role) +} + +func decodeRoleBindingObjBytes(objBytes []byte) *rbacv1.RoleBinding { + obj, err := runtime.Decode(codecs.UniversalDecoder(rbacv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*rbacv1.RoleBinding) +} + +func decodeServiceObjBytes(objBytes []byte) *corev1.Service { + obj, err := runtime.Decode(codecs.UniversalDecoder(corev1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*corev1.Service) +} + +func decodeServiceAccountObjBytes(objBytes []byte) *corev1.ServiceAccount { + obj, err := runtime.Decode(codecs.UniversalDecoder(corev1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*corev1.ServiceAccount) +} + +func decodeCertificateObjBytes(objBytes []byte) *certmanagerv1.Certificate { + obj, err := runtime.Decode(codecs.UniversalDecoder(certmanagerv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*certmanagerv1.Certificate) +} + +func decodeIssuerObjBytes(objBytes []byte) *certmanagerv1.Issuer { + obj, err := runtime.Decode(codecs.UniversalDecoder(certmanagerv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*certmanagerv1.Issuer) +} + +func decodeValidatingWebhookConfigurationObjBytes(objBytes []byte) *admissionregistrationv1.ValidatingWebhookConfiguration { + obj, err := runtime.Decode(codecs.UniversalDecoder(admissionregistrationv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*admissionregistrationv1.ValidatingWebhookConfiguration) +} + +func hasObjectChanged(desired, fetched client.Object) bool { + if reflect.TypeOf(desired) != reflect.TypeOf(fetched) { + panic("both objects to be compared must be of same type") + } + + var objectModified bool + switch desired.(type) { + case *certmanagerv1.Certificate: + objectModified = !reflect.DeepEqual(desired.(*certmanagerv1.Certificate).Spec, fetched.(*certmanagerv1.Certificate).Spec) + case *certmanagerv1.Issuer: + objectModified = !reflect.DeepEqual(desired.(*certmanagerv1.Issuer).Spec, fetched.(*certmanagerv1.Issuer).Spec) + case *rbacv1.ClusterRole: + objectModified = !reflect.DeepEqual(desired.(*rbacv1.ClusterRole).Rules, fetched.(*rbacv1.ClusterRole).Rules) + case *rbacv1.ClusterRoleBinding: + objectModified = !reflect.DeepEqual(desired.(*rbacv1.ClusterRoleBinding).RoleRef, fetched.(*rbacv1.ClusterRoleBinding).RoleRef) || + !reflect.DeepEqual(desired.(*rbacv1.ClusterRoleBinding).Subjects, fetched.(*rbacv1.ClusterRoleBinding).Subjects) + case *appsv1.Deployment: + objectModified = deploymentSpecModified(desired.(*appsv1.Deployment), fetched.(*appsv1.Deployment)) + case *rbacv1.Role: + objectModified = !reflect.DeepEqual(desired.(*rbacv1.Role).Rules, fetched.(*rbacv1.Role).Rules) + case *rbacv1.RoleBinding: + objectModified = !reflect.DeepEqual(desired.(*rbacv1.RoleBinding).RoleRef, fetched.(*rbacv1.RoleBinding).RoleRef) || + !reflect.DeepEqual(desired.(*rbacv1.RoleBinding).Subjects, fetched.(*rbacv1.RoleBinding).Subjects) + case *corev1.Service: + objectModified = serviceSpecModified(desired.(*corev1.Service), fetched.(*corev1.Service)) + case *corev1.ConfigMap: + objectModified = !reflect.DeepEqual(desired.(*corev1.ConfigMap).Data, fetched.(*corev1.ConfigMap).Data) + case *admissionregistrationv1.ValidatingWebhookConfiguration: + objectModified = !reflect.DeepEqual(desired.(*admissionregistrationv1.ValidatingWebhookConfiguration).Webhooks, fetched.(*admissionregistrationv1.ValidatingWebhookConfiguration).Webhooks) + default: + panic(fmt.Sprintf("unsupported object type: %T", desired)) + } + return objectModified || !reflect.DeepEqual(desired.GetLabels(), fetched.GetLabels()) +} + +func deploymentSpecModified(desired, fetched *appsv1.Deployment) bool { + if *desired.Spec.Replicas != *fetched.Spec.Replicas || + !reflect.DeepEqual(desired.Spec.Selector.MatchLabels, fetched.Spec.Selector.MatchLabels) { + return true + } + + if !reflect.DeepEqual(desired.Spec.Template.Labels, fetched.Spec.Template.Labels) || + len(desired.Spec.Template.Spec.Containers) != len(fetched.Spec.Template.Spec.Containers) { + return true + } + + desiredContainer := desired.Spec.Template.Spec.Containers[0] + fetchedContainer := fetched.Spec.Template.Spec.Containers[0] + if !reflect.DeepEqual(desiredContainer.Args, fetchedContainer.Args) || + desiredContainer.Name != fetchedContainer.Name || desiredContainer.Image != fetchedContainer.Image || + desiredContainer.ImagePullPolicy != fetchedContainer.ImagePullPolicy { + return true + } + + if len(desiredContainer.Ports) != len(fetchedContainer.Ports) { + return true + } + for _, fetchedPort := range fetchedContainer.Ports { + matched := false + for _, desiredPort := range desiredContainer.Ports { + if fetchedPort.ContainerPort == desiredPort.ContainerPort { + matched = true + break + } + } + if !matched { + return true + } + } + + if desiredContainer.ReadinessProbe.HTTPGet.Path != fetchedContainer.ReadinessProbe.HTTPGet.Path || + desiredContainer.ReadinessProbe.InitialDelaySeconds != fetchedContainer.ReadinessProbe.InitialDelaySeconds || + desiredContainer.ReadinessProbe.PeriodSeconds != fetchedContainer.ReadinessProbe.PeriodSeconds { + return true + } + + if !reflect.DeepEqual(desiredContainer.Resources, fetchedContainer.Resources) || + !reflect.DeepEqual(*desiredContainer.SecurityContext, *fetchedContainer.SecurityContext) || + !reflect.DeepEqual(desiredContainer.VolumeMounts, fetchedContainer.VolumeMounts) { + return true + } + + if desired.Spec.Template.Spec.ServiceAccountName != fetched.Spec.Template.Spec.ServiceAccountName || + !reflect.DeepEqual(desired.Spec.Template.Spec.NodeSelector, fetched.Spec.Template.Spec.NodeSelector) || + !reflect.DeepEqual(desired.Spec.Template.Spec.Volumes, fetched.Spec.Template.Spec.Volumes) { + return true + } + + return false +} + +func serviceSpecModified(desired, fetched *corev1.Service) bool { + if desired.Spec.Type != fetched.Spec.Type || + !reflect.DeepEqual(desired.Spec.Ports, fetched.Spec.Ports) || + !reflect.DeepEqual(desired.Spec.Selector, fetched.Spec.Selector) { + return true + } + + return false +} + +func validateTrustManagerConfig(trustManager *v1alpha1.TrustManager) error { + if reflect.ValueOf(trustManager.Spec.TrustManagerConfig).IsZero() { + return fmt.Errorf("spec.trustManagerConfig config cannot be empty") + } + return nil +} + +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{err, errUpdate}) + } + return errUpdate + } + return prependErr +} + +func (r *Reconciler) disallowMultipleTrustManagerInstances(trustManager *v1alpha1.TrustManager) error { + statusMessage := fmt.Sprintf("multiple instances of trustmanager exists, %s will not be processed", trustManager.GetName()) + + if containsProcessingRejectedAnnotation(trustManager) { + r.log.V(4).Info("trustmanager resource contains processing rejected annotation", "name", trustManager.Name) + var updateErr error + if trustManager.Status.ConditionalStatus.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonFailed, statusMessage) { + updateErr = r.updateCondition(trustManager, nil) + } + return newMultipleInstanceError(utilerrors.NewAggregate([]error{fmt.Errorf("%s", statusMessage), updateErr})) + } + + trustManagerList := &v1alpha1.TrustManagerList{} + if err := r.List(r.ctx, trustManagerList); err != nil { + return fmt.Errorf("failed to fetch list of trustmanager resources: %w", err) + } + + if len(trustManagerList.Items) <= 1 { + return nil + } + + ignoreProcessing := false + for _, item := range trustManagerList.Items { + if item.GetName() == trustManager.GetName() { + continue + } + if item.CreationTimestamp.Time.Before(trustManager.CreationTimestamp.Time) || + item.CreationTimestamp.Time.Equal(trustManager.CreationTimestamp.Time) { + ignoreProcessing = true + } + } + + if ignoreProcessing { + var condUpdateErr, annUpdateErr error + if trustManager.Status.ConditionalStatus.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonFailed, statusMessage) { + condUpdateErr = r.updateCondition(trustManager, nil) + } + if addProcessingRejectedAnnotation(trustManager) { + if err := r.UpdateWithRetry(r.ctx, trustManager); err != nil { + annUpdateErr = fmt.Errorf("failed to update reject processing annotation to %s: %w", trustManager.GetName(), err) + } + } + if condUpdateErr != nil || annUpdateErr != nil { + return utilerrors.NewAggregate([]error{condUpdateErr, annUpdateErr}) + } + } + + return newMultipleInstanceError(fmt.Errorf("%s", statusMessage)) +} diff --git a/pkg/controller/trustmanager/webhooks.go b/pkg/controller/trustmanager/webhooks.go new file mode 100644 index 000000000..7ff3ac70f --- /dev/null +++ b/pkg/controller/trustmanager/webhooks.go @@ -0,0 +1,49 @@ +package trustmanager + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyWebhooks(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getWebhookObject(resourceLabels) + + webhookName := desired.GetName() + r.log.V(4).Info("reconciling validatingwebhookconfiguration resource", "name", webhookName) + fetched := &admissionregistrationv1.ValidatingWebhookConfiguration{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return fromClientError(err, "failed to check %s validatingwebhookconfiguration resource already exists", webhookName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s validatingwebhookconfiguration resource already exists, maybe from previous installation", webhookName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("validatingwebhookconfiguration has been modified, updating to desired state", "name", webhookName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return fromClientError(err, "failed to update %s validatingwebhookconfiguration resource", webhookName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "validatingwebhookconfiguration resource %s reconciled back to desired state", webhookName) + } else { + r.log.V(4).Info("validatingwebhookconfiguration resource already exists and is in expected state", "name", webhookName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return fromClientError(err, "failed to create %s validatingwebhookconfiguration resource", webhookName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "validatingwebhookconfiguration resource %s created", webhookName) + } + + return nil +} + +func (r *Reconciler) getWebhookObject(resourceLabels map[string]string) *admissionregistrationv1.ValidatingWebhookConfiguration { + webhook := decodeValidatingWebhookConfigurationObjBytes(assets.MustAsset(webhookAssetName)) + updateResourceLabels(webhook, resourceLabels) + return webhook +} diff --git a/pkg/operator/assets/bindata.go b/pkg/operator/assets/bindata.go index d83fcb9e1..a4d5fe886 100644 --- a/pkg/operator/assets/bindata.go +++ b/pkg/operator/assets/bindata.go @@ -63,6 +63,19 @@ // bindata/networkpolicies/istio-csr-allow-ingress-to-grpc-networkpolicy.yaml // bindata/networkpolicies/istio-csr-allow-ingress-to-metrics-networkpolicy.yaml // bindata/networkpolicies/istio-csr-deny-all-networkpolicy.yaml +// bindata/trust-manager/trust-manager-certificate.yaml +// bindata/trust-manager/trust-manager-clusterrole.yaml +// bindata/trust-manager/trust-manager-clusterrolebinding.yaml +// bindata/trust-manager/trust-manager-deployment.yaml +// bindata/trust-manager/trust-manager-issuer.yaml +// bindata/trust-manager/trust-manager-leases-role.yaml +// bindata/trust-manager/trust-manager-leases-rolebinding.yaml +// bindata/trust-manager/trust-manager-metrics-service.yaml +// bindata/trust-manager/trust-manager-role.yaml +// bindata/trust-manager/trust-manager-rolebinding.yaml +// bindata/trust-manager/trust-manager-service.yaml +// bindata/trust-manager/trust-manager-serviceaccount.yaml +// bindata/trust-manager/trust-manager-webhook.yaml package assets import ( @@ -3072,6 +3085,509 @@ func networkpoliciesIstioCsrDenyAllNetworkpolicyYaml() (*asset, error) { return a, nil } +var _trustManagerTrustManagerCertificateYaml = []byte(`apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + commonName: "trust-manager.cert-manager.svc" + dnsNames: + - "trust-manager.cert-manager.svc" + secretName: trust-manager-tls + revisionHistoryLimit: 1 + issuerRef: + name: trust-manager + kind: Issuer + group: cert-manager.io +`) + +func trustManagerTrustManagerCertificateYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerCertificateYaml, nil +} + +func trustManagerTrustManagerCertificateYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerCertificateYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-certificate.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerClusterroleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: trust-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: ["trust.cert-manager.io"] + resources: ["bundles"] + verbs: ["get", "list", "watch"] + - apiGroups: ["trust.cert-manager.io"] + resources: ["bundles/finalizers"] + verbs: ["update"] + - apiGroups: ["trust.cert-manager.io"] + resources: ["bundles/status"] + verbs: ["patch"] + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "create", "update", "patch", "watch", "delete"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] +`) + +func trustManagerTrustManagerClusterroleYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerClusterroleYaml, nil +} + +func trustManagerTrustManagerClusterroleYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerClusterroleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: trust-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: trust-manager +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager +`) + +func trustManagerTrustManagerClusterrolebindingYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerClusterrolebindingYaml, nil +} + +func trustManagerTrustManagerClusterrolebindingYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerClusterrolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerDeploymentYaml = []byte(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/instance: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + replicas: 1 + selector: + matchLabels: + app: cert-manager-trust-manager + template: + metadata: + labels: + app: cert-manager-trust-manager + spec: + serviceAccountName: trust-manager + containers: + - name: trust-manager + image: quay.io/jetstack/trust-manager:v0.20.3 + imagePullPolicy: IfNotPresent + args: + - --log-format=text + - --log-level=1 + - --metrics-port=9402 + - --readiness-probe-port=6060 + - --readiness-probe-path=/readyz + - --trust-namespace=cert-manager + - --webhook-host=0.0.0.0 + - --webhook-port=6443 + ports: + - containerPort: 6443 + name: webhook + protocol: TCP + - containerPort: 9402 + name: metrics + protocol: TCP + readinessProbe: + httpGet: + port: 6060 + path: /readyz + initialDelaySeconds: 3 + periodSeconds: 7 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + volumeMounts: + - name: tls + mountPath: /tls + readOnly: true + volumes: + - name: tls + secret: + secretName: trust-manager-tls +`) + +func trustManagerTrustManagerDeploymentYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerDeploymentYaml, nil +} + +func trustManagerTrustManagerDeploymentYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerDeploymentYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-deployment.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerIssuerYaml = []byte(`apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + selfSigned: {} +`) + +func trustManagerTrustManagerIssuerYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerIssuerYaml, nil +} + +func trustManagerTrustManagerIssuerYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerIssuerYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-issuer.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerLeasesRoleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: trust-manager:leaderelection + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "create", "update", "watch", "list"] +`) + +func trustManagerTrustManagerLeasesRoleYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerLeasesRoleYaml, nil +} + +func trustManagerTrustManagerLeasesRoleYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerLeasesRoleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-leases-role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerLeasesRolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: trust-manager:leaderelection + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: trust-manager:leaderelection +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager +`) + +func trustManagerTrustManagerLeasesRolebindingYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerLeasesRolebindingYaml, nil +} + +func trustManagerTrustManagerLeasesRolebindingYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerLeasesRolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-leases-rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerMetricsServiceYaml = []byte(`apiVersion: v1 +kind: Service +metadata: + name: trust-manager-metrics + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + type: ClusterIP + ports: + - port: 9402 + targetPort: metrics + protocol: TCP + name: metrics + selector: + app: cert-manager-trust-manager +`) + +func trustManagerTrustManagerMetricsServiceYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerMetricsServiceYaml, nil +} + +func trustManagerTrustManagerMetricsServiceYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerMetricsServiceYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-metrics-service.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerRoleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] +`) + +func trustManagerTrustManagerRoleYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerRoleYaml, nil +} + +func trustManagerTrustManagerRoleYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerRoleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerRolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: trust-manager +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager +`) + +func trustManagerTrustManagerRolebindingYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerRolebindingYaml, nil +} + +func trustManagerTrustManagerRolebindingYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerRolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerServiceYaml = []byte(`apiVersion: v1 +kind: Service +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + type: ClusterIP + ports: + - port: 443 + targetPort: webhook + protocol: TCP + name: webhook + selector: + app: cert-manager-trust-manager +`) + +func trustManagerTrustManagerServiceYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerServiceYaml, nil +} + +func trustManagerTrustManagerServiceYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerServiceYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-service.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerServiceaccountYaml = []byte(`apiVersion: v1 +kind: ServiceAccount +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/instance: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +`) + +func trustManagerTrustManagerServiceaccountYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerServiceaccountYaml, nil +} + +func trustManagerTrustManagerServiceaccountYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerServiceaccountYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-serviceaccount.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerWebhookYaml = []byte(`apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: trust-manager + labels: + app: cert-manager-trust-manager + app.kubernetes.io/name: cert-manager-trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator + annotations: + cert-manager.io/inject-ca-from: cert-manager/trust-manager +webhooks: + - name: trust.cert-manager.io + clientConfig: + service: + name: trust-manager + namespace: cert-manager + path: /validate-trust-cert-manager-io-v1alpha1-bundle + rules: + - apiGroups: ["trust.cert-manager.io"] + apiVersions: ["v1alpha1"] + operations: ["CREATE", "UPDATE"] + resources: ["bundles"] + admissionReviewVersions: ["v1"] + failurePolicy: Fail + sideEffects: None + timeoutSeconds: 5 +`) + +func trustManagerTrustManagerWebhookYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerWebhookYaml, nil +} + +func trustManagerTrustManagerWebhookYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerWebhookYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-webhook.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + // Asset loads and returns the asset for the given name. // It returns an error if the asset could not be found or // could not be loaded. @@ -3187,6 +3703,19 @@ var _bindata = map[string]func() (*asset, error){ "networkpolicies/istio-csr-allow-ingress-to-grpc-networkpolicy.yaml": networkpoliciesIstioCsrAllowIngressToGrpcNetworkpolicyYaml, "networkpolicies/istio-csr-allow-ingress-to-metrics-networkpolicy.yaml": networkpoliciesIstioCsrAllowIngressToMetricsNetworkpolicyYaml, "networkpolicies/istio-csr-deny-all-networkpolicy.yaml": networkpoliciesIstioCsrDenyAllNetworkpolicyYaml, + "trust-manager/trust-manager-certificate.yaml": trustManagerTrustManagerCertificateYaml, + "trust-manager/trust-manager-clusterrole.yaml": trustManagerTrustManagerClusterroleYaml, + "trust-manager/trust-manager-clusterrolebinding.yaml": trustManagerTrustManagerClusterrolebindingYaml, + "trust-manager/trust-manager-deployment.yaml": trustManagerTrustManagerDeploymentYaml, + "trust-manager/trust-manager-issuer.yaml": trustManagerTrustManagerIssuerYaml, + "trust-manager/trust-manager-leases-role.yaml": trustManagerTrustManagerLeasesRoleYaml, + "trust-manager/trust-manager-leases-rolebinding.yaml": trustManagerTrustManagerLeasesRolebindingYaml, + "trust-manager/trust-manager-metrics-service.yaml": trustManagerTrustManagerMetricsServiceYaml, + "trust-manager/trust-manager-role.yaml": trustManagerTrustManagerRoleYaml, + "trust-manager/trust-manager-rolebinding.yaml": trustManagerTrustManagerRolebindingYaml, + "trust-manager/trust-manager-service.yaml": trustManagerTrustManagerServiceYaml, + "trust-manager/trust-manager-serviceaccount.yaml": trustManagerTrustManagerServiceaccountYaml, + "trust-manager/trust-manager-webhook.yaml": trustManagerTrustManagerWebhookYaml, } // AssetDir returns the file names below a certain @@ -3309,6 +3838,21 @@ var _bintree = &bintree{nil, map[string]*bintree{ "istio-csr-allow-ingress-to-metrics-networkpolicy.yaml": {networkpoliciesIstioCsrAllowIngressToMetricsNetworkpolicyYaml, map[string]*bintree{}}, "istio-csr-deny-all-networkpolicy.yaml": {networkpoliciesIstioCsrDenyAllNetworkpolicyYaml, map[string]*bintree{}}, }}, + "trust-manager": {nil, map[string]*bintree{ + "trust-manager-certificate.yaml": {trustManagerTrustManagerCertificateYaml, map[string]*bintree{}}, + "trust-manager-clusterrole.yaml": {trustManagerTrustManagerClusterroleYaml, map[string]*bintree{}}, + "trust-manager-clusterrolebinding.yaml": {trustManagerTrustManagerClusterrolebindingYaml, map[string]*bintree{}}, + "trust-manager-deployment.yaml": {trustManagerTrustManagerDeploymentYaml, map[string]*bintree{}}, + "trust-manager-issuer.yaml": {trustManagerTrustManagerIssuerYaml, map[string]*bintree{}}, + "trust-manager-leases-role.yaml": {trustManagerTrustManagerLeasesRoleYaml, map[string]*bintree{}}, + "trust-manager-leases-rolebinding.yaml": {trustManagerTrustManagerLeasesRolebindingYaml, map[string]*bintree{}}, + "trust-manager-metrics-service.yaml": {trustManagerTrustManagerMetricsServiceYaml, map[string]*bintree{}}, + "trust-manager-role.yaml": {trustManagerTrustManagerRoleYaml, map[string]*bintree{}}, + "trust-manager-rolebinding.yaml": {trustManagerTrustManagerRolebindingYaml, map[string]*bintree{}}, + "trust-manager-service.yaml": {trustManagerTrustManagerServiceYaml, map[string]*bintree{}}, + "trust-manager-serviceaccount.yaml": {trustManagerTrustManagerServiceaccountYaml, map[string]*bintree{}}, + "trust-manager-webhook.yaml": {trustManagerTrustManagerWebhookYaml, map[string]*bintree{}}, + }}, }} // RestoreAsset restores an asset under the given directory diff --git a/pkg/operator/setup_manager.go b/pkg/operator/setup_manager.go index ba8c49602..1ec7da98d 100644 --- a/pkg/operator/setup_manager.go +++ b/pkg/operator/setup_manager.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" @@ -14,6 +15,7 @@ import ( "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -21,6 +23,7 @@ import ( 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" ) @@ -37,38 +40,60 @@ func init() { utilruntime.Must(corev1.AddToScheme(scheme)) utilruntime.Must(networkingv1.AddToScheme(scheme)) utilruntime.Must(rbacv1.AddToScheme(scheme)) + utilruntime.Must(admissionregistrationv1.AddToScheme(scheme)) utilruntime.Must(certmanagerv1.AddToScheme(scheme)) utilruntime.Must(v1alpha1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } -// Manager holds the manager resource for the istio-csr controller +// Manager holds the manager resource for the controller-runtime based controllers type Manager struct { manager manager.Manager } -// NewControllerManager creates a new manager. -func NewControllerManager() (*Manager, error) { - setupLog.Info("setting up operator manager", "controller", istiocsr.ControllerName) +// ControllerManagerOptions configures which controllers to register with the manager. +type ControllerManagerOptions struct { + EnableIstioCSR bool + EnableTrustManager bool +} + +// NewControllerManager creates a new manager with the specified controllers enabled. +func NewControllerManager(opts ControllerManagerOptions) (*Manager, error) { + setupLog.Info("setting up operator manager", "istioCSR", opts.EnableIstioCSR, "trustManager", opts.EnableTrustManager) setupLog.Info("controller", "version", version.Get()) + // Build a composite cache builder that merges cache configurations from all enabled controllers + cacheBuilder := buildCompositeCacheBuilder(opts) + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - // Use custom cache builder to configure label selectors for managed resources - NewCache: istiocsr.NewCacheBuilder, + Scheme: scheme, + NewCache: cacheBuilder, Logger: ctrl.Log.WithName("operator-manager"), }) if err != nil { return nil, fmt.Errorf("failed to create manager: %w", err) } - r, err := istiocsr.New(mgr) - if err != nil { - return nil, fmt.Errorf("failed to create %s reconciler object: %w", istiocsr.ControllerName, err) + if opts.EnableIstioCSR { + r, err := istiocsr.New(mgr) + if err != nil { + return nil, fmt.Errorf("failed to create %s reconciler object: %w", istiocsr.ControllerName, err) + } + if err := r.SetupWithManager(mgr); err != nil { + return nil, fmt.Errorf("failed to create %s controller: %w", istiocsr.ControllerName, err) + } } - if err := r.SetupWithManager(mgr); err != nil { - return nil, fmt.Errorf("failed to create %s controller: %w", istiocsr.ControllerName, err) + + if opts.EnableTrustManager { + 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) + } } + // +kubebuilder:scaffold:builder return &Manager{ @@ -76,8 +101,21 @@ func NewControllerManager() (*Manager, error) { }, nil } +// buildCompositeCacheBuilder returns a cache builder for the enabled controllers. +func buildCompositeCacheBuilder(opts ControllerManagerOptions) cache.NewCacheFunc { + if opts.EnableIstioCSR && !opts.EnableTrustManager { + return istiocsr.NewCacheBuilder + } + if opts.EnableTrustManager && !opts.EnableIstioCSR { + return trustmanager.NewCacheBuilder + } + // Both enabled: use nil to let the manager create a default cache without label filtering. + // Each controller's watches already have predicate filters, so this is safe. + return nil +} + // Start starts the operator synchronously until a message is received from ctx. 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") + setupLog.Info("controller manager is starting") return mgr.manager.Start(ctx) } diff --git a/pkg/operator/starter.go b/pkg/operator/starter.go index dad5dfe8f..f4335d3d3 100644 --- a/pkg/operator/starter.go +++ b/pkg/operator/starter.go @@ -140,15 +140,20 @@ func RunOperator(ctx context.Context, cc *controllercmd.ControllerContext) error return fmt.Errorf("failed to parse addon features: %w", err) } - // enable controller-runtime and istio-csr controller - // only when "IstioCSR" feature is turned on from --addon-features - if features.DefaultFeatureGate.Enabled(v1alpha1.FeatureIstioCSR) { - manager, err := NewControllerManager() + // enable controller-runtime based controllers when their respective feature gates are enabled + enableIstioCSR := features.DefaultFeatureGate.Enabled(v1alpha1.FeatureIstioCSR) + enableTrustManager := features.DefaultFeatureGate.Enabled(v1alpha1.FeatureTrustManager) + + if enableIstioCSR || enableTrustManager { + manager, err := NewControllerManager(ControllerManagerOptions{ + EnableIstioCSR: enableIstioCSR, + EnableTrustManager: enableTrustManager, + }) if err != nil { return fmt.Errorf("failed to create controller manager: %w", err) } if err := manager.Start(ctrl.SetupSignalHandler()); err != nil { - return fmt.Errorf("failed to start istiocsr controller: %w", err) + return fmt.Errorf("failed to start controller manager: %w", err) } }