Skip to content

Commit

Permalink
Use controller-runtime's cluster instead of manager (#256)
Browse files Browse the repository at this point in the history
## Summary

Hegel originally integrated with Kubernetes by using a tink repo defined client that handled the client construction for a service. The implementation leveraged the controller-runtime manager data structure that comes with a lot more than whats needed by a service. Consequently, it needs to mute features such as metrics endpoints etc.

This change switches to the cluster object which is much lighter weight and focus' on representing a cluster (with caching of obects) instead of a fully fledged controller manager.

## Other notes

`go mod tidy` cleaned up a bunch of dependencies probably left from the Go v1.20 update.

## Issues

Closes #195
  • Loading branch information
mergify[bot] authored May 9, 2023
2 parents b089130 + 4bda0aa commit bbf9e13
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 500 deletions.
6 changes: 0 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ require (
require (
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blendle/zapdriver v1.3.1 // indirect
github.com/bytedance/sonic v1.8.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
Expand All @@ -41,7 +40,6 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
Expand Down Expand Up @@ -93,14 +91,12 @@ require (
go.uber.org/zap v1.24.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
google.golang.org/protobuf v1.30.0 // indirect
Expand All @@ -109,11 +105,9 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.26.3 // indirect
k8s.io/apiextensions-apiserver v0.26.1 // indirect
k8s.io/component-base v0.26.1 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
knative.dev/pkg v0.0.0-20211119170723-a99300deff34 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
Expand Down
465 changes: 0 additions & 465 deletions go.sum

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions internal/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,10 @@ func New(ctx context.Context, opts Options) (Client, error) {
return flatfile.FromYAMLFile(opts.Flatfile.Path)

case opts.Kubernetes != nil:
kubeclient, err := kubernetes.NewBackend(kubernetes.BackendConfig{
kubeclient, err := kubernetes.NewBackend(ctx, kubernetes.BackendConfig{
Kubeconfig: opts.Kubernetes.Kubeconfig,
APIServerAddress: opts.Kubernetes.APIServerAddress,
Namespace: opts.Kubernetes.Namespace,
Context: ctx,
})
if err != nil {
return nil, fmt.Errorf("kubernetes client: %v", err)
Expand Down
51 changes: 32 additions & 19 deletions internal/backend/kubernetes/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@ import (

"github.com/tinkerbell/hegel/internal/frontend/ec2"
tinkv1 "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1"
tinkcontrollers "github.com/tinkerbell/tink/pkg/controllers"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
kubescheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/cluster"
)

var errNotFound = errors.New("no hardware found")

// Build the scheme as a package variable so we don't need to perform error checks.
var scheme = kubescheme.Scheme

func init() {
utilruntime.Must(tinkv1.AddToScheme(scheme))
}

// Backend is a hardware Backend backed by a Backend cluster that contains hardware resources.
type Backend struct {
client listerClient
Expand All @@ -28,12 +37,7 @@ type Backend struct {
// NewBackend creates a new Backend instance. It launches a goroutine to perform synchronization
// between the cluster and internal caches. Consumers can wait for the initial sync using WaitForCachesync().
// See k8s.io/Backend-go/tools/Backendcmd for constructing *rest.Config objects.
func NewBackend(cfg BackendConfig) (*Backend, error) {
// Default the context.
if cfg.Context == nil {
cfg.Context = context.Background()
}

func NewBackend(ctx context.Context, cfg BackendConfig) (*Backend, error) {
// If no client was specified, build one and configure the backend with it including waiting
// for the caches to sync.
if cfg.ClientConfig == nil {
Expand All @@ -44,29 +48,38 @@ func NewBackend(cfg BackendConfig) (*Backend, error) {
}
}

opts := tinkcontrollers.GetServerOptions()
opts.Namespace = cfg.Namespace
conf := func(opts *cluster.Options) {
opts.Scheme = scheme
opts.Namespace = cfg.Namespace
}

clstr, err := cluster.New(cfg.ClientConfig, conf)
if err != nil {
return nil, fmt.Errorf("create cluster: %v", err)
}

// Use a manager from the tink project so we can take advantage of the indexes and caching it
// configures. Once started, we don't really need any of the manager capabilities hence we don't
// store it in the Backend.
manager, err := tinkcontrollers.NewManager(cfg.ClientConfig, opts)
err = clstr.GetFieldIndexer().IndexField(
ctx,
&tinkv1.Hardware{},
hardwareIPAddrIndex,
hardwareIPIndexFunc,
)
if err != nil {
return nil, err
return nil, fmt.Errorf("register index: %v", err)
}

// TODO(chrisdoherty4) Stop panicing on error. This will likely require exposing Start in
// some capacity and allowing the caller to handle the error.
go func() {
if err := manager.Start(cfg.Context); err != nil {
if err := clstr.Start(ctx); err != nil {
panic(err)
}
}()

return &Backend{
closer: cfg.Context.Done(),
client: manager.GetClient(),
WaitForCacheSync: manager.GetCache().WaitForCacheSync,
closer: ctx.Done(),
client: clstr.GetClient(),
WaitForCacheSync: clstr.GetCache().WaitForCacheSync,
}, nil
}

Expand Down Expand Up @@ -128,7 +141,7 @@ func (b *Backend) GetEC2Instance(ctx context.Context, ip string) (ec2.Instance,
func (b *Backend) retrieveByIP(ctx context.Context, ip string) (tinkv1.Hardware, error) {
var hw tinkv1.HardwareList
err := b.client.List(ctx, &hw, crclient.MatchingFields{
tinkcontrollers.HardwareIPAddrIndex: ip,
hardwareIPAddrIndex: ip,
})
if err != nil {
return tinkv1.Hardware{}, err
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/kubernetes/backend_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func TestBackend(t *testing.T) {
defer cancel()

// Construct the backend and attempt to retrieve our test Hardware resource.
backend, err := NewBackend(BackendConfig{Context: ctx, ClientConfig: cfg})
backend, err := NewBackend(ctx, BackendConfig{ClientConfig: cfg})
if err != nil {
t.Fatal(err)
}
Expand Down
7 changes: 0 additions & 7 deletions internal/backend/kubernetes/config.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
package kubernetes

import (
"context"

"k8s.io/client-go/rest"
)

// BackendConfig used by the NewBackend function family.
type BackendConfig struct {
// Context is the context used by the Kubernetes client. Defaults to context.Background().
// When specified it controls the lifetime of the Kubernetes client by shutting the client
// down when it cancelled.
Context context.Context

// Kubeconfig is a path to a valid kubeconfig file. When in-cluster defaults to the in-cluster
// config. Optional.
Kubeconfig string
Expand Down
25 changes: 25 additions & 0 deletions internal/backend/kubernetes/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kubernetes

import (
"github.com/tinkerbell/tink/pkg/apis/core/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// hardwareIPAddrIndex is the index used to retrieve hardware by IP address. It is used with
// the controller-runtimes MatchingFields selector.
const hardwareIPAddrIndex = ".Spec.Interfaces.DHCP.IP"

// hardwareIPIndexFunc satisfies the controller runtimes index.
func hardwareIPIndexFunc(obj client.Object) []string {
hw, ok := obj.(*v1alpha1.Hardware)
if !ok {
return nil
}
resp := []string{}
for _, iface := range hw.Spec.Interfaces {
if iface.DHCP != nil && iface.DHCP.IP != nil && iface.DHCP.IP.Address != "" {
resp = append(resp, iface.DHCP.IP.Address)
}
}
return resp
}

0 comments on commit bbf9e13

Please sign in to comment.