diff --git a/cmd/machine-remediation-operator/BUILD.bazel b/cmd/machine-remediation-operator/BUILD.bazel index c3e53e48..5fd90e6d 100644 --- a/cmd/machine-remediation-operator/BUILD.bazel +++ b/cmd/machine-remediation-operator/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "//pkg/consts:go_default_library", "//pkg/controllers:go_default_library", "//pkg/operator:go_default_library", + "//pkg/utils/mapper:go_default_library", "//pkg/version:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/openshift/api/config/v1:go_default_library", diff --git a/cmd/machine-remediation-operator/main.go b/cmd/machine-remediation-operator/main.go index 8cdd2b68..40b5b2e3 100644 --- a/cmd/machine-remediation-operator/main.go +++ b/cmd/machine-remediation-operator/main.go @@ -14,6 +14,7 @@ import ( "kubevirt.io/machine-remediation-operator/pkg/consts" "kubevirt.io/machine-remediation-operator/pkg/controllers" "kubevirt.io/machine-remediation-operator/pkg/operator" + "kubevirt.io/machine-remediation-operator/pkg/utils/mapper" "kubevirt.io/machine-remediation-operator/pkg/version" "sigs.k8s.io/controller-runtime/pkg/cache" @@ -45,7 +46,8 @@ func main() { namespaces = append(namespaces, *namespace) } opts := manager.Options{ - NewCache: cache.MultiNamespacedCacheBuilder(namespaces), + NewCache: cache.MultiNamespacedCacheBuilder(namespaces), + MapperProvider: mapper.NewDynamicRESTMapper, } // Create a new Cmd to provide shared dependencies and start components diff --git a/manifests/generated/machine-remediation-operator-csv.yaml.in b/manifests/generated/machine-remediation-operator-csv.yaml.in index 43d9f5d1..9869731f 100644 --- a/manifests/generated/machine-remediation-operator-csv.yaml.in +++ b/manifests/generated/machine-remediation-operator-csv.yaml.in @@ -108,6 +108,22 @@ spec: - clusterrolebindings verbs: - '*' + - apiGroups: + - config.openshift.io + resources: + - infrastructures + - infrastructures/status + verbs: + - get + - list + - watch + - apiGroups: + - machineremediation.kubevirt.io + resources: + - machinedisruptionbudgets + - machinehealthchecks + verbs: + - '*' serviceAccountName: machine-remediation-operator deployments: - name: machine-remediation-operator diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index e212d33b..d870c81b 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -34,8 +34,6 @@ type ReconcileMachineRemediationOperator struct { // This client, initialized using mgr.Client() above, is a split client // that reads objects from the cache and writes to the apiserver client client.Client - updateClient client.Client - mgr manager.Manager namespace string operatorVersion string crdsManifestsDir string @@ -54,7 +52,6 @@ func Add(mgr manager.Manager, opts manager.Options) error { func newReconciler(mgr manager.Manager, opts manager.Options) (reconcile.Reconciler, error) { return &ReconcileMachineRemediationOperator{ client: mgr.GetClient(), - mgr: mgr, namespace: opts.Namespace, operatorVersion: os.Getenv(components.EnvVarOperatorVersion), crdsManifestsDir: "/data", @@ -195,19 +192,6 @@ func (r *ReconcileMachineRemediationOperator) createOrUpdateComponents(mro *mrv1 // deploy masters MachineHealthCheck and MachineDisruptionBudget only for BareMetal environment if baremetal { - // TODO(alukiano): ugly W/A to get client that already has registred MHC and MDB CRD's - // this client does not use cache at all - if r.updateClient == nil { - opts := client.Options{ - Scheme: r.mgr.GetScheme(), - } - c, err := client.New(r.mgr.GetConfig(), opts) - if err != nil { - return err - } - r.updateClient = c - } - if err := r.createOrUpdateMachineHealthCheck(consts.MasterMachineHealthCheck, consts.NamespaceOpenshiftMachineAPI); err != nil { return err } diff --git a/pkg/operator/operator_test.go b/pkg/operator/operator_test.go index a50584c7..46acea52 100644 --- a/pkg/operator/operator_test.go +++ b/pkg/operator/operator_test.go @@ -81,7 +81,6 @@ func newFakeReconciler(initObjects ...runtime.Object) *ReconcileMachineRemediati fakeClient := fake.NewFakeClient(initObjects...) return &ReconcileMachineRemediationOperator{ client: fakeClient, - updateClient: fakeClient, namespace: consts.NamespaceOpenshiftMachineAPI, operatorVersion: imageTag, crdsManifestsDir: "../../manifests/generated/crds", @@ -89,10 +88,10 @@ func newFakeReconciler(initObjects ...runtime.Object) *ReconcileMachineRemediati } func testReconcile(t *testing.T, platform osconfigv1.PlatformType) { - ifrastructure := mrotesting.NewInfrastructure("cluster", platform) + infrastructure := mrotesting.NewInfrastructure("cluster", platform) mro := newMachineRemediationOperator("mro") - r := newFakeReconciler(mro, ifrastructure) + r := newFakeReconciler(mro, infrastructure) request := reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: consts.NamespaceOpenshiftMachineAPI, diff --git a/pkg/operator/resources.go b/pkg/operator/resources.go index 7cd6d7de..b2938ecc 100644 --- a/pkg/operator/resources.go +++ b/pkg/operator/resources.go @@ -331,7 +331,7 @@ func (r *ReconcileMachineRemediationOperator) getMachineHealthCheck(name string, Name: name, Namespace: namespace, } - if err := r.updateClient.Get(context.TODO(), key, mhc); err != nil { + if err := r.client.Get(context.TODO(), key, mhc); err != nil { return nil, err } return mhc, nil @@ -342,7 +342,7 @@ func (r *ReconcileMachineRemediationOperator) createOrUpdateMachineHealthCheck(n oldMachineHealthCheck, err := r.getMachineHealthCheck(name, namespace) if errors.IsNotFound(err) { - if err := r.updateClient.Create(context.TODO(), newMachineHealthCheck); err != nil { + if err := r.client.Create(context.TODO(), newMachineHealthCheck); err != nil { return err } return nil @@ -353,7 +353,7 @@ func (r *ReconcileMachineRemediationOperator) createOrUpdateMachineHealthCheck(n } newMachineHealthCheck.ResourceVersion = oldMachineHealthCheck.ResourceVersion - return r.updateClient.Update(context.TODO(), newMachineHealthCheck) + return r.client.Update(context.TODO(), newMachineHealthCheck) } func (r *ReconcileMachineRemediationOperator) deleteMachineHealthCheck(name string, namespace string) error { @@ -364,7 +364,7 @@ func (r *ReconcileMachineRemediationOperator) deleteMachineHealthCheck(name stri if err != nil { return err } - return r.updateClient.Delete(context.TODO(), mhc) + return r.client.Delete(context.TODO(), mhc) } func (r *ReconcileMachineRemediationOperator) getMachineDisruptionBudget(name string, namespace string) (*mrv1.MachineDisruptionBudget, error) { @@ -373,7 +373,7 @@ func (r *ReconcileMachineRemediationOperator) getMachineDisruptionBudget(name st Name: name, Namespace: namespace, } - if err := r.updateClient.Get(context.TODO(), key, mdb); err != nil { + if err := r.client.Get(context.TODO(), key, mdb); err != nil { return nil, err } return mdb, nil @@ -384,7 +384,7 @@ func (r *ReconcileMachineRemediationOperator) createOrUpdateMachineDisruptionBud oldMachineDisruptionBudget, err := r.getMachineDisruptionBudget(name, namespace) if errors.IsNotFound(err) { - if err := r.updateClient.Create(context.TODO(), newMachineDisruptionBudget); err != nil { + if err := r.client.Create(context.TODO(), newMachineDisruptionBudget); err != nil { return err } return nil @@ -395,7 +395,7 @@ func (r *ReconcileMachineRemediationOperator) createOrUpdateMachineDisruptionBud } newMachineDisruptionBudget.ResourceVersion = oldMachineDisruptionBudget.ResourceVersion - return r.updateClient.Update(context.TODO(), newMachineDisruptionBudget) + return r.client.Update(context.TODO(), newMachineDisruptionBudget) } func (r *ReconcileMachineRemediationOperator) deleteMachineDisruptionBudget(name string, namespace string) error { @@ -406,7 +406,7 @@ func (r *ReconcileMachineRemediationOperator) deleteMachineDisruptionBudget(name if err != nil { return err } - return r.updateClient.Delete(context.TODO(), mdb) + return r.client.Delete(context.TODO(), mdb) } func (r *ReconcileMachineRemediationOperator) getInfrastructure(name string) (*osconfigv1.Infrastructure, error) { diff --git a/pkg/utils/mapper/BUILD.bazel b/pkg/utils/mapper/BUILD.bazel new file mode 100644 index 00000000..2e9196ea --- /dev/null +++ b/pkg/utils/mapper/BUILD.bazel @@ -0,0 +1,16 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["mapper.go"], + importpath = "kubevirt.io/machine-remediation-operator/pkg/utils/mapper", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/restmapper:go_default_library", + ], +) diff --git a/pkg/utils/mapper/mapper.go b/pkg/utils/mapper/mapper.go new file mode 100644 index 00000000..3ec8e9d9 --- /dev/null +++ b/pkg/utils/mapper/mapper.go @@ -0,0 +1,118 @@ +package mapper + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/discovery" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" +) + +// DynamicRESTMapper defines dynamic REST mapper entity +type DynamicRESTMapper struct { + client discovery.DiscoveryInterface + delegate meta.RESTMapper +} + +// NewDynamicRESTMapper returns a RESTMapper that dynamically discovers resource +// types at runtime. This is in contrast to controller-manager's default RESTMapper, which +// only checks resource types at startup, and so can't handle the case of first creating a +// CRD and then creating an instance of that CRD. +func NewDynamicRESTMapper(cfg *rest.Config) (meta.RESTMapper, error) { + client, err := discovery.NewDiscoveryClientForConfig(cfg) + if err != nil { + return nil, err + } + + drm := &DynamicRESTMapper{client: client} + if err := drm.reload(); err != nil { + return nil, err + } + return drm, nil +} + +func (drm *DynamicRESTMapper) reload() error { + gr, err := restmapper.GetAPIGroupResources(drm.client) + if err != nil { + return err + } + drm.delegate = restmapper.NewDiscoveryRESTMapper(gr) + return nil +} + +// reloadOnError checks if an error indicates that the delegated RESTMapper needs to be +// reloaded, and if so, reloads it and returns true. +func (drm *DynamicRESTMapper) reloadOnError(err error) bool { + if _, matches := err.(*meta.NoKindMatchError); !matches { + return false + } + err = drm.reload() + if err != nil { + utilruntime.HandleError(err) + } + return err == nil +} + +// KindFor returns the kind by GroupVersionResource +func (drm *DynamicRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + gvk, err := drm.delegate.KindFor(resource) + if drm.reloadOnError(err) { + gvk, err = drm.delegate.KindFor(resource) + } + return gvk, err +} + +// KindsFor returns kinds by GroupVersionResource +func (drm *DynamicRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { + gvks, err := drm.delegate.KindsFor(resource) + if drm.reloadOnError(err) { + gvks, err = drm.delegate.KindsFor(resource) + } + return gvks, err +} + +// ResourceFor returns resource by GroupVersionResource +func (drm *DynamicRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) { + gvr, err := drm.delegate.ResourceFor(input) + if drm.reloadOnError(err) { + gvr, err = drm.delegate.ResourceFor(input) + } + return gvr, err +} + +// ResourcesFor returns resources by GroupVersionResource +func (drm *DynamicRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + gvrs, err := drm.delegate.ResourcesFor(input) + if drm.reloadOnError(err) { + gvrs, err = drm.delegate.ResourcesFor(input) + } + return gvrs, err +} + +// RESTMapping returns RESTMapping from kind and versions +func (drm *DynamicRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) { + m, err := drm.delegate.RESTMapping(gk, versions...) + if drm.reloadOnError(err) { + m, err = drm.delegate.RESTMapping(gk, versions...) + } + return m, err +} + +// RESTMappings returns RESTMappings from kind and versions +func (drm *DynamicRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) { + ms, err := drm.delegate.RESTMappings(gk, versions...) + if drm.reloadOnError(err) { + ms, err = drm.delegate.RESTMappings(gk, versions...) + } + return ms, err +} + +// ResourceSingularizer returns resource singular +func (drm *DynamicRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + s, err := drm.delegate.ResourceSingularizer(resource) + if drm.reloadOnError(err) { + s, err = drm.delegate.ResourceSingularizer(resource) + } + return s, err +}