Skip to content

Commit

Permalink
update to controller-runtime 0.18.0
Browse files Browse the repository at this point in the history
  • Loading branch information
xrstf committed Apr 29, 2024
1 parent ba7524a commit 0bfa35d
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 111 deletions.
8 changes: 3 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ require (
k8s.io/code-generator v0.30.0
k8s.io/klog/v2 v2.120.1
k8s.io/utils v0.0.0-20240310230437-4693a0247e57
// Pinned due to a breaking change in k8s.io/client-go/tools/leaderelection in v0.30.0
// TODO: Update to the latest semver version when https://github.com/kubernetes-sigs/controller-runtime/pull/2693 is released
sigs.k8s.io/controller-runtime v0.17.1-0.20240418082203-04706074d2f1
sigs.k8s.io/controller-runtime v0.18.0
sigs.k8s.io/controller-tools v0.14.0
sigs.k8s.io/yaml v1.4.0
)
Expand Down Expand Up @@ -101,8 +99,8 @@ require (
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apiextensions-apiserver v0.30.0-rc.2 // indirect
k8s.io/component-base v0.30.0-rc.2 // indirect
k8s.io/apiextensions-apiserver v0.30.0 // indirect
k8s.io/component-base v0.30.0 // indirect
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect
k8s.io/klog v1.0.0 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -504,16 +504,16 @@ k8c.io/reconciler v0.5.0 h1:BHpelg1UfI/7oBFctqOq8sX6qzflXpl3SlvHe7e8wak=
k8c.io/reconciler v0.5.0/go.mod h1:pT1+SVcVXJQeBJhpJBXQ5XW64QnKKeYTnVlQf0dGE0k=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
k8s.io/apiextensions-apiserver v0.30.0-rc.2 h1:nnQg+c72aanAIrrPSyds0jtazCjOQDHo2vpazxem/TI=
k8s.io/apiextensions-apiserver v0.30.0-rc.2/go.mod h1:Vfet39CooU8WJYMintiVVNCJhHHtiJ/+ZX3CgA7O+so=
k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs=
k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ=
k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY=
k8s.io/code-generator v0.30.0 h1:3VUVqHvWFSVSm9kqL/G6kD4ZwNdHF6J/jPyo3Jgjy3k=
k8s.io/code-generator v0.30.0/go.mod h1:mBMZhfRR4IunJUh2+7LVmdcWwpouCH5+LNPkZ3t/v7Q=
k8s.io/component-base v0.30.0-rc.2 h1:0Qa6faUg01rBp9VxU76B8PmK58rBcAGB+7r4ckpLtgI=
k8s.io/component-base v0.30.0-rc.2/go.mod h1:rdQm+7+FBi+t74zJKiKBYVgQJEiNRMqvESRh8/f5z5k=
k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o=
k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ=
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo=
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
Expand All @@ -529,8 +529,8 @@ k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/controller-runtime v0.17.1-0.20240418082203-04706074d2f1 h1:W15Y5zHVUsH1YJvstRqy6lG0KquU7kS2ooGC5poLnrU=
sigs.k8s.io/controller-runtime v0.17.1-0.20240418082203-04706074d2f1/go.mod h1:umEFUKWCSYpq2U4tNN7riBXU6iiulk7bdF0XZq9LzvU=
sigs.k8s.io/controller-runtime v0.18.0 h1:Z7jKuX784TQSUL1TIyeuF7j8KXZ4RtSX0YgtjKcSTME=
sigs.k8s.io/controller-runtime v0.18.0/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw=
sigs.k8s.io/controller-tools v0.14.0 h1:rnNoCC5wSXlrNoBKKzL70LNJKIQKEzT6lloG6/LF73A=
sigs.k8s.io/controller-tools v0.14.0/go.mod h1:TV7uOtNNnnR72SpzhStvPkoS/U5ir0nMudrkrC4M9Sc=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
Expand Down
19 changes: 9 additions & 10 deletions pkg/controllers/osc/osc_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,12 @@ import (
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/tools/record"
ctrlruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)

const (
Expand Down Expand Up @@ -121,16 +120,16 @@ func Add(
nodeRegistryCredentialsSecret: nodeRegistryCredentialsSecret,
kubeletFeatureGates: kubeletFeatureGates,
}
c, err := controller.New(ControllerName, mgr, controller.Options{Reconciler: reconciler, MaxConcurrentReconciles: workerCount})
if err != nil {
return err
}

if err := c.Watch(source.Kind(mgr.GetCache(), &clusterv1alpha1.MachineDeployment{}), &handler.EnqueueRequestForObject{}, filterMachineDeploymentPredicate()); err != nil {
return fmt.Errorf("failed to watch MachineDeployments: %w", err)
}
_, err := builder.ControllerManagedBy(mgr).
Named(ControllerName).
WithOptions(controller.Options{
MaxConcurrentReconciles: workerCount,
}).
For(&clusterv1alpha1.MachineDeployment{}, builder.WithPredicates(filterMachineDeploymentPredicate())).
Build(reconciler)

return nil
return err
}

func (r *Reconciler) Reconcile(ctx context.Context, req ctrlruntime.Request) (reconcile.Result, error) {
Expand Down
172 changes: 82 additions & 90 deletions pkg/controllers/osp/osp_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package osp
import (
"context"
"fmt"
"io/fs"
"path/filepath"
"strings"

Expand All @@ -28,13 +27,10 @@ import (
"k8c.io/operating-system-manager/deploy/osps"
"k8c.io/operating-system-manager/pkg/crd/osm/v1alpha1"
"k8c.io/operating-system-manager/pkg/resources/reconciling"
predicateutil "k8c.io/operating-system-manager/pkg/util/predicate"

appsv1 "k8s.io/api/apps/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
ctrlruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/event"
Expand All @@ -51,132 +47,128 @@ const (
ospsDefaultDirName = "default"
)

type ospMap map[string]*v1alpha1.OperatingSystemProfile

type Reconciler struct {
client.Client
log *zap.SugaredLogger
defaultOSPFiles map[string][]byte
log *zap.SugaredLogger
defaultOSPs ospMap

namespace string
}

func Add(mgr manager.Manager, log *zap.SugaredLogger, namespace string, workerCount int) error {
ospDefaultDir, err := osps.FS.ReadDir(ospsDefaultDirName)
defaultOSPs, err := loadDefaultOSPs()
if err != nil {
return fmt.Errorf("failed to read osps default directory: %w", err)
}

var defaultOSPFiles = make(map[string][]byte, len(ospDefaultDir))
for _, ospFile := range ospDefaultDir {
defaultOSPFile, err := fs.ReadFile(osps.FS, filepath.Join(ospsDefaultDirName, ospFile.Name()))
if err != nil {
return fmt.Errorf("failed to read osp file %s: %w", ospFile.Name(), err)
}

defaultOSPFiles[ospFile.Name()] = defaultOSPFile
return fmt.Errorf("failed to load default OSPs: %w", err)
}

reconciler := &Reconciler{
Client: mgr.GetClient(),
log: log,
defaultOSPFiles: defaultOSPFiles,
namespace: namespace,
}

c, err := controller.New(ControllerName, mgr, controller.Options{Reconciler: reconciler, MaxConcurrentReconciles: workerCount})
if err != nil {
return err
Client: mgr.GetClient(),
log: log,
defaultOSPs: defaultOSPs,
namespace: namespace,
}

// Since the osp controller cares about only creating the default OSP resources, we need to watch for the creation
// of any random resource in the underlying namespace where osm is deployed. We picked deployments for this and added additional
// event filtering to avoid redundant reconciliation/requeues.
if err := c.Watch(source.Kind(mgr.GetCache(), &appsv1.Deployment{}),
&handler.EnqueueRequestForObject{},
filterDeploymentPredicate(),
predicateutil.ByNamespace(namespace),
); err != nil {
return fmt.Errorf("failed to create watch for deployments: %w", err)
// trigger controller once upon startup to bootstrap the default OSPs
sourceChannel := make(chan event.GenericEvent, 1)
sourceChannel <- event.GenericEvent{
Object: &v1alpha1.OperatingSystemProfile{},
}

return nil
_, err = builder.ControllerManagedBy(mgr).
Named(ControllerName).
WithOptions(controller.Options{
MaxConcurrentReconciles: workerCount,
}).
For(&v1alpha1.OperatingSystemProfile{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetNamespace() == namespace
}))).
WatchesRawSource(source.Channel(sourceChannel, &handler.EnqueueRequestForObject{})).
Build(reconciler)

return err
}

func (r *Reconciler) Reconcile(ctx context.Context, _ ctrlruntime.Request) (reconcile.Result, error) {
r.log.Info("Reconciling default OSP resource..")
func (r *Reconciler) Reconcile(ctx context.Context, req ctrlruntime.Request) (reconcile.Result, error) {
var toReconcile ospMap
if req.Name == "" {
r.log.Info("Reconciling default OSP resources...")
toReconcile = r.defaultOSPs
} else {
osp, ok := r.defaultOSPs[req.Name]
if !ok {
return reconcile.Result{}, nil
}

r.log.Infow("Reconciling OSP resource...", "osp", req.Name)
toReconcile = ospMap{req.Name: osp}
}

if err := r.reconcile(ctx); err != nil {
if err := r.reconcileOSPs(ctx, toReconcile); err != nil {
return reconcile.Result{}, err
}

return reconcile.Result{}, nil
}

func (r *Reconciler) reconcile(ctx context.Context) error {
func (r *Reconciler) reconcileOSPs(ctx context.Context, ospInstances ospMap) error {
var ospReconcilers []reconciling.NamedOperatingSystemProfileReconcilerFactory
for name, ospFile := range r.defaultOSPFiles {
osp, err := parseYAMLToObject(ospFile)
if err != nil {
return fmt.Errorf("failed to parse osp %s: %w", name, err)
}
for name, osp := range ospInstances {
ospReconcilers = append(ospReconcilers, ospReconciler(name, osp))
}

// Remove file extension .yaml from the OSP name
name = strings.ReplaceAll(name, ".yaml", "")
if err := reconciling.ReconcileOperatingSystemProfiles(ctx, ospReconcilers, r.namespace, r.Client); err != nil {
return fmt.Errorf("failed to reconcile OSPs: %w", err)
}

// Check if OSP already exists
existingOSP := &v1alpha1.OperatingSystemProfile{}
if err := r.Client.Get(ctx, types.NamespacedName{Name: name, Namespace: r.namespace}, existingOSP); err != nil && !kerrors.IsNotFound(err) {
return fmt.Errorf("failed to retrieve existing OperatingSystemProfile: %w", err)
}
return nil
}

// Since OSPs are immutable, we only want to reconcile resources when the version is different.
if osp.Spec.Version != existingOSP.Spec.Version {
// OSP already exists
osp.SetResourceVersion(existingOSP.GetResourceVersion())
osp.SetGeneration(existingOSP.GetGeneration())
func ospReconciler(name string, source *v1alpha1.OperatingSystemProfile) reconciling.NamedOperatingSystemProfileReconcilerFactory {
return func() (string, reconciling.OperatingSystemProfileReconciler) {
return name, func(osp *v1alpha1.OperatingSystemProfile) (*v1alpha1.OperatingSystemProfile, error) {
osp.Spec = source.Spec

ospReconcilers = append(ospReconcilers, ospReconciler(name, osp))
return osp, nil
}
}
}

if err := reconciling.ReconcileOperatingSystemProfiles(ctx,
ospReconcilers,
r.namespace, r.Client); err != nil {
return fmt.Errorf("failed to reconcile osps: %w", err)
func loadDefaultOSPs() (ospMap, error) {
ospDefaultDir, err := osps.FS.ReadDir(ospsDefaultDirName)
if err != nil {
return nil, fmt.Errorf("failed to read OSPs default directory: %w", err)
}

return nil
}
var defaultOSPs = make(ospMap, len(ospDefaultDir))
for _, ospFile := range ospDefaultDir {
filename := ospFile.Name()

func ospReconciler(name string, osp *v1alpha1.OperatingSystemProfile) reconciling.NamedOperatingSystemProfileReconcilerFactory {
return func() (string, reconciling.OperatingSystemProfileReconciler) {
return name, func(*v1alpha1.OperatingSystemProfile) (*v1alpha1.OperatingSystemProfile, error) {
return osp, nil
osp, err := parseOSPFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read OSP %s: %w", filename, err)
}

// Remove file extension .yaml to get OSP name
ospName := strings.ReplaceAll(filename, ".yaml", "")

defaultOSPs[ospName] = osp
}

return defaultOSPs, nil
}

func parseYAMLToObject(ospByte []byte) (*v1alpha1.OperatingSystemProfile, error) {
func parseOSPFile(filename string) (*v1alpha1.OperatingSystemProfile, error) {
content, err := osps.FS.ReadFile(filepath.Join(ospsDefaultDirName, filename))
if err != nil {
return nil, err
}

osp := &v1alpha1.OperatingSystemProfile{}
if err := yamlutil.Unmarshal(ospByte, osp); err != nil {
if err := yamlutil.Unmarshal(content, osp); err != nil {
return nil, err
}

return osp, nil
}

// filterDeploymentPredicate filters out all deployment events except the creation one.
func filterDeploymentPredicate() predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(_ event.CreateEvent) bool {
return true
},
DeleteFunc: func(_ event.DeleteEvent) bool {
return false
},
UpdateFunc: func(_ event.UpdateEvent) bool {
return false
},
GenericFunc: func(_ event.GenericEvent) bool {
return false
},
}
}

0 comments on commit 0bfa35d

Please sign in to comment.