diff --git a/manifests/0000_26_cloud-controller-manager-operator_60_clusteroperator.yaml b/manifests/0000_26_cloud-controller-manager-operator_60_clusteroperator.yaml index 18ca4d325..b8cf24a03 100644 --- a/manifests/0000_26_cloud-controller-manager-operator_60_clusteroperator.yaml +++ b/manifests/0000_26_cloud-controller-manager-operator_60_clusteroperator.yaml @@ -17,8 +17,65 @@ status: name: cloud-controller-manager resource: clusteroperators - group: "" - name: openshift-cloud-controller-manager + name: openshift-cloud-controller-manager-operator resource: namespaces - group: "" - name: openshift-cloud-controller-manager-operator + name: openshift-cloud-controller-manager resource: namespaces + - group: rbac.authorization.k8s.io + name: system:openshift:operator:cloud-controller-manager + resource: clusterroles + - group: rbac.authorization.k8s.io + name: cloud-controller-manager + resource: clusterroles + - group: rbac.authorization.k8s.io + name: cloud-node-manager + resource: clusterroles + - group: rbac.authorization.k8s.io + name: system:openshift:operator:cloud-controller-manager + resource: clusterrolebindings + - group: rbac.authorization.k8s.io + name: cloud-controller-manager + resource: clusterrolebindings + - group: rbac.authorization.k8s.io + name: cloud-node-manager + resource: clusterrolebindings + - group: rbac.authorization.k8s.io + name: system:openshift:scc:hostaccess:cloud-controller-manager-operator + resource: clusterrolebindings + - group: rbac.authorization.k8s.io + name: cluster-cloud-controller-manager + namespace: openshift-config + resource: roles + - group: rbac.authorization.k8s.io + name: cluster-cloud-controller-manager + namespace: openshift-config-managed + resource: roles + - group: rbac.authorization.k8s.io + name: cluster-cloud-controller-manager + namespace: kube-system + resource: roles + - group: rbac.authorization.k8s.io + name: cloud-controller-manager + namespace: kube-system + resource: roles + - group: rbac.authorization.k8s.io + name: cluster-cloud-controller-manager + namespace: openshift-config + resource: rolebindings + - group: rbac.authorization.k8s.io + name: cluster-cloud-controller-manager + namespace: openshift-config-managed + resource: rolebindings + - group: rbac.authorization.k8s.io + name: cluster-cloud-controller-manager + namespace: kube-system + resource: rolebindings + - group: rbac.authorization.k8s.io + name: cloud-controller-manager + namespace: kube-system + resource: rolebindings + - group: rbac.authorization.k8s.io + name: cloud-controller-manager:apiserver-authentication-reader + namespace: kube-system + resource: rolebindings diff --git a/pkg/controllers/clusteroperator_controller_test.go b/pkg/controllers/clusteroperator_controller_test.go index e33be174f..519686a30 100644 --- a/pkg/controllers/clusteroperator_controller_test.go +++ b/pkg/controllers/clusteroperator_controller_test.go @@ -112,7 +112,7 @@ var _ = Describe("Cluster Operator status controller", func() { Expect(v1helpers.FindStatusCondition(getOp.Status.Conditions, cloudControllerOwnershipCondition)).To(BeNil()) // check related objects. - Expect(getOp.Status.RelatedObjects).To(Equal(operatorController.relatedObjects())) + Expect(getOp.Status.RelatedObjects).To(Equal(operatorController.relatedObjects(context.Background()))) }, Entry("when there's no existing cluster operator nor release version", testCase{ releaseVersionEnvVariableValue: "unknown", diff --git a/pkg/controllers/related_objects_test.go b/pkg/controllers/related_objects_test.go new file mode 100644 index 000000000..191f4b840 --- /dev/null +++ b/pkg/controllers/related_objects_test.go @@ -0,0 +1,27 @@ +package controllers + +import ( + "os" + "path/filepath" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + configv1 "github.com/openshift/api/config/v1" + "sigs.k8s.io/yaml" +) + +var _ = Describe("relatedObjects", func() { + It("static core should stay in sync with the ClusterOperator manifest", func() { + manifestPath := filepath.Join("..", "..", "manifests", "0000_26_cloud-controller-manager-operator_60_clusteroperator.yaml") + data, err := os.ReadFile(manifestPath) + Expect(err).ToNot(HaveOccurred(), "should be able to read ClusterOperator manifest") + + co := &configv1.ClusterOperator{} + Expect(yaml.Unmarshal(data, co)).To(Succeed(), "should be able to unmarshal ClusterOperator manifest") + + statusClient := &ClusterOperatorStatusClient{ + ManagedNamespace: DefaultManagedNamespace, + } + Expect(co.Status.RelatedObjects).To(Equal(statusClient.relatedObjectsStatic()), "Go relatedObjectsStatic() must match the static ClusterOperator manifest") + }) +}) diff --git a/pkg/controllers/status.go b/pkg/controllers/status.go index d2e78c8c1..9d10ad9d4 100644 --- a/pkg/controllers/status.go +++ b/pkg/controllers/status.go @@ -215,15 +215,101 @@ func (r *ClusterOperatorStatusClient) getOrCreateClusterOperator(ctx context.Con return co, nil } -func (r *ClusterOperatorStatusClient) relatedObjects() []configv1.ObjectReference { - // TBD: Add an actual set of object references from getResources method +// relatedObjectsStatic returns the platform-agnostic relatedObjects that must +// stay in sync with the ClusterOperator CVO seed manifest. +func (r *ClusterOperatorStatusClient) relatedObjectsStatic() []configv1.ObjectReference { return []configv1.ObjectReference{ - {Resource: "namespaces", Name: defaultManagementNamespace}, {Group: configv1.GroupName, Resource: "clusteroperators", Name: clusterOperatorName}, - {Resource: "namespaces", Name: r.ManagedNamespace}, + {Group: "", Resource: "namespaces", Name: defaultManagementNamespace}, + {Group: "", Resource: "namespaces", Name: r.ManagedNamespace}, + // Operator and provider ClusterRoles / ClusterRoleBindings. + {Group: "rbac.authorization.k8s.io", Resource: "clusterroles", Name: "system:openshift:operator:" + clusterOperatorName}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterroles", Name: clusterOperatorName}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterroles", Name: "cloud-node-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "system:openshift:operator:" + clusterOperatorName}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: clusterOperatorName}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "cloud-node-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "system:openshift:scc:hostaccess:cloud-controller-manager-operator"}, + // Foreign-namespace Roles / RoleBindings (not covered by CCCMO namespace relatedObjects). + {Group: "rbac.authorization.k8s.io", Resource: "roles", Name: "cluster-cloud-controller-manager", Namespace: "openshift-config"}, + {Group: "rbac.authorization.k8s.io", Resource: "roles", Name: "cluster-cloud-controller-manager", Namespace: "openshift-config-managed"}, + {Group: "rbac.authorization.k8s.io", Resource: "roles", Name: "cluster-cloud-controller-manager", Namespace: "kube-system"}, + {Group: "rbac.authorization.k8s.io", Resource: "roles", Name: "cloud-controller-manager", Namespace: "kube-system"}, + {Group: "rbac.authorization.k8s.io", Resource: "rolebindings", Name: "cluster-cloud-controller-manager", Namespace: "openshift-config"}, + {Group: "rbac.authorization.k8s.io", Resource: "rolebindings", Name: "cluster-cloud-controller-manager", Namespace: "openshift-config-managed"}, + {Group: "rbac.authorization.k8s.io", Resource: "rolebindings", Name: "cluster-cloud-controller-manager", Namespace: "kube-system"}, + {Group: "rbac.authorization.k8s.io", Resource: "rolebindings", Name: "cloud-controller-manager", Namespace: "kube-system"}, + {Group: "rbac.authorization.k8s.io", Resource: "rolebindings", Name: "cloud-controller-manager:apiserver-authentication-reader", Namespace: "kube-system"}, } } +// relatedObjectsForPlatform returns cluster-scoped / foreign-NS objects from +// platform asset YAMLs. Managed-namespace Deployments/DaemonSets/Roles are omitted +// because namespace relatedObjects already gather those. +func relatedObjectsForPlatform(platform configv1.PlatformType) []configv1.ObjectReference { + switch platform { + case configv1.AWSPlatformType: + return []configv1.ObjectReference{ + {Group: "admissionregistration.k8s.io", Resource: "validatingadmissionpolicies", Name: "openshift-cloud-controller-manager-cloud-provider-aws"}, + {Group: "admissionregistration.k8s.io", Resource: "validatingadmissionpolicybindings", Name: "openshift-cloud-controller-manager-cloud-provider-aws"}, + } + case configv1.AzurePlatformType: + return []configv1.ObjectReference{ + {Group: "rbac.authorization.k8s.io", Resource: "clusterroles", Name: "azure-cloud-controller-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "cloud-controller-manager:azure-cloud-controller-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "roles", Name: "azure-cloud-provider", Namespace: "kube-system"}, + {Group: "rbac.authorization.k8s.io", Resource: "rolebindings", Name: "azure-cloud-provider:azure-cloud-provider", Namespace: "kube-system"}, + {Group: "admissionregistration.k8s.io", Resource: "validatingadmissionpolicies", Name: "openshift-cloud-controller-manager-cloud-provider-azure-node-admission"}, + {Group: "admissionregistration.k8s.io", Resource: "validatingadmissionpolicybindings", Name: "openshift-cloud-controller-manager-cloud-provider-azure-node-admission"}, + {Group: "admissionregistration.k8s.io", Resource: "validatingadmissionpolicies", Name: "azure-load-balancer-tcp-idle-timeout-annotation-validation-policy"}, + {Group: "admissionregistration.k8s.io", Resource: "validatingadmissionpolicybindings", Name: "azure-load-balancer-tcp-idle-timeout-validation-annotation-binding"}, + } + case configv1.GCPPlatformType: + return []configv1.ObjectReference{ + {Group: "rbac.authorization.k8s.io", Resource: "clusterroles", Name: "gcp-cloud-controller-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "gcp-cloud-controller-manager:cloud-provider"}, + {Group: "admissionregistration.k8s.io", Resource: "validatingadmissionpolicies", Name: "network-tier-annotation-validation-policy"}, + {Group: "admissionregistration.k8s.io", Resource: "validatingadmissionpolicybindings", Name: "network-tier-annotation-binding"}, + } + case configv1.NutanixPlatformType: + return []configv1.ObjectReference{ + {Group: "rbac.authorization.k8s.io", Resource: "clusterroles", Name: "nutanix-cloud-controller-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "nutanix-cloud-controller-manager:nutanix-cloud-controller-manager"}, + } + case configv1.VSpherePlatformType: + return []configv1.ObjectReference{ + {Group: "rbac.authorization.k8s.io", Resource: "clusterroles", Name: "vsphere-cloud-controller-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "vsphere-cloud-controller-manager:vsphere-cloud-controller-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "vsphere-cloud-controller-manager:cloud-controller-manager"}, + } + case configv1.OpenStackPlatformType: + return []configv1.ObjectReference{ + {Group: "rbac.authorization.k8s.io", Resource: "clusterroles", Name: "openstack-cloud-controller-manager"}, + {Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings", Name: "openstack-cloud-controller-manager"}, + {Group: "", Resource: "serviceaccounts", Name: "cloud-controller-manager", Namespace: "kube-system"}, + } + default: + return nil + } +} + +// relatedObjects returns the static relatedObjects plus any platform-specific +// objects derived from the Infrastructure platform type. If Infrastructure is +// missing or cannot be read, only the static core is returned. +func (r *ClusterOperatorStatusClient) relatedObjects(ctx context.Context) []configv1.ObjectReference { + objs := r.relatedObjectsStatic() + + infra := &configv1.Infrastructure{} + if err := r.Get(ctx, client.ObjectKey{Name: infrastructureResourceName}, infra); err != nil { + return objs + } + if infra.Status.PlatformStatus == nil { + return objs + } + + return append(objs, relatedObjectsForPlatform(infra.Status.PlatformStatus.Type)...) +} + // syncStatus applies the new condition to the ClusterOperator object. func (r *ClusterOperatorStatusClient) syncStatus(ctx context.Context, co *configv1.ClusterOperator, conds, overrides []configv1.ClusterOperatorStatusCondition) error { for _, c := range conds { @@ -235,8 +321,9 @@ func (r *ClusterOperatorStatusClient) syncStatus(ctx context.Context, co *config v1helpers.SetStatusCondition(&co.Status.Conditions, c, r.Clock) } - if !equality.Semantic.DeepEqual(co.Status.RelatedObjects, r.relatedObjects()) { - co.Status.RelatedObjects = r.relatedObjects() + related := r.relatedObjects(ctx) + if !equality.Semantic.DeepEqual(co.Status.RelatedObjects, related) { + co.Status.RelatedObjects = related } return r.Status().Update(ctx, co)