Skip to content

Commit

Permalink
internal/kube: get REST config from runtime
Browse files Browse the repository at this point in the history
Signed-off-by: Hidde Beydals <hello@hidde.co>
  • Loading branch information
hiddeco committed May 12, 2022
1 parent 5784f06 commit c8f5400
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 78 deletions.
7 changes: 4 additions & 3 deletions controllers/helmrelease_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type HelmReleaseReconciler struct {
MetricsRecorder *metrics.Recorder
DefaultServiceAccount string
NoCrossNamespaceRef bool
ClientOpts fluxClient.Options
KubeConfigOpts fluxClient.KubeConfigOptions
}

Expand Down Expand Up @@ -473,7 +474,7 @@ func (r *HelmReleaseReconciler) checkDependencies(hr v2.HelmRelease) error {
}

func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, hr v2.HelmRelease) (genericclioptions.RESTClientGetter, error) {
var opts []kube.ClientGetterOption
opts := []kube.ClientGetterOption{kube.WithClientOptions(r.ClientOpts)}
if hr.Spec.ServiceAccountName != "" {
opts = append(opts, kube.WithImpersonate(hr.Spec.ServiceAccountName))
}
Expand All @@ -490,9 +491,9 @@ func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, hr v2
if err != nil {
return nil, err
}
opts = append(opts, kube.WithKubeConfig(kubeConfig, r.Config.QPS, r.Config.Burst, r.KubeConfigOpts))
opts = append(opts, kube.WithKubeConfig(kubeConfig, r.KubeConfigOpts))
}
return kube.BuildClientGetter(r.Config, hr.GetReleaseNamespace(), opts...), nil
return kube.BuildClientGetter(hr.GetReleaseNamespace(), opts...)
}

// composeValues attempts to resolve all v2beta1.ValuesReference resources
Expand Down
34 changes: 17 additions & 17 deletions internal/kube/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ limitations under the License.
package kube

import (
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"

"github.com/fluxcd/pkg/runtime/client"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

const (
Expand All @@ -35,12 +33,10 @@ const (

// clientGetterOptions used to BuildClientGetter.
type clientGetterOptions struct {
config *rest.Config
namespace string
kubeConfig []byte
burst int
qps float32
impersonateAccount string
clientOptions client.Options
kubeConfigOptions client.KubeConfigOptions
}

Expand All @@ -49,15 +45,21 @@ type ClientGetterOption func(o *clientGetterOptions)

// WithKubeConfig creates a MemoryRESTClientGetter configured with the provided
// KubeConfig and other values.
func WithKubeConfig(kubeConfig []byte, qps float32, burst int, opts client.KubeConfigOptions) func(o *clientGetterOptions) {
func WithKubeConfig(kubeConfig []byte, opts client.KubeConfigOptions) func(o *clientGetterOptions) {
return func(o *clientGetterOptions) {
o.kubeConfig = kubeConfig
o.qps = qps
o.burst = burst
o.kubeConfigOptions = opts
}
}

// WithClientOptions configures the genericclioptions.RESTClientGetter with
// provided options.
func WithClientOptions(opts client.Options) func(o *clientGetterOptions) {
return func(o *clientGetterOptions) {
o.clientOptions = opts
}
}

// WithImpersonate configures the genericclioptions.RESTClientGetter to
// impersonate the provided account name.
func WithImpersonate(accountName string) func(o *clientGetterOptions) {
Expand All @@ -67,20 +69,18 @@ func WithImpersonate(accountName string) func(o *clientGetterOptions) {
}

// BuildClientGetter builds a genericclioptions.RESTClientGetter based on the
// provided options and returns the result. config and namespace are mandatory,
// and not expected to be nil or empty.
func BuildClientGetter(config *rest.Config, namespace string, opts ...ClientGetterOption) genericclioptions.RESTClientGetter {
// provided options and returns the result. Namespace is not expected to be
// empty. In case it fails to construct using NewInClusterRESTClientGetter, it
// returns an error.
func BuildClientGetter(namespace string, opts ...ClientGetterOption) (genericclioptions.RESTClientGetter, error) {
o := &clientGetterOptions{
config: config,
namespace: namespace,
}
for _, opt := range opts {
opt(o)
}
if len(o.kubeConfig) > 0 {
return NewMemoryRESTClientGetter(o.kubeConfig, namespace, o.impersonateAccount, o.qps, o.burst, o.kubeConfigOptions)
return NewMemoryRESTClientGetter(o.kubeConfig, namespace, o.impersonateAccount, o.clientOptions, o.kubeConfigOptions), nil
}
cfg := *config
SetImpersonationConfig(&cfg, namespace, o.impersonateAccount)
return NewInClusterRESTClientGetter(&cfg, namespace)
return NewInClusterRESTClientGetter(namespace, o.impersonateAccount, &o.clientOptions)
}
43 changes: 24 additions & 19 deletions internal/kube/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,33 @@ limitations under the License.
package kube

import (
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"testing"

"github.com/fluxcd/pkg/runtime/client"
. "github.com/onsi/gomega"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"
)

func TestBuildClientGetter(t *testing.T) {
t.Run("with config and namespace", func(t *testing.T) {
t.Run("with namespace", func(t *testing.T) {
g := NewWithT(t)
ctrl.GetConfig = mockGetConfig

cfg := &rest.Config{
BearerToken: "a-token",
}
namespace := "a-namespace"
getter := BuildClientGetter(cfg, namespace)
getter, err := BuildClientGetter(namespace)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(getter).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))

flags := getter.(*genericclioptions.ConfigFlags)
g.Expect(flags.BearerToken).ToNot(BeNil())
g.Expect(*flags.BearerToken).To(Equal(cfg.BearerToken))
g.Expect(flags.Namespace).ToNot(BeNil())
g.Expect(*flags.Namespace).To(Equal(namespace))
})

t.Run("with kubeconfig and impersonate", func(t *testing.T) {
t.Run("with kubeconfig, impersonate and client options", func(t *testing.T) {
g := NewWithT(t)
ctrl.GetConfig = mockGetConfig

namespace := "a-namespace"
cfg := []byte(`apiVersion: v1
Expand All @@ -59,30 +58,30 @@ contexts:
kind: Config
preferences: {}
users:`)
qps := float32(600)
burst := 1000
clientOpts := client.Options{QPS: 600, Burst: 1000}
cfgOpts := client.KubeConfigOptions{InsecureTLS: true}

impersonate := "jane"

getter := BuildClientGetter(&rest.Config{}, namespace, WithKubeConfig(cfg, qps, burst, cfgOpts), WithImpersonate(impersonate))
getter, err := BuildClientGetter(namespace, WithClientOptions(clientOpts), WithKubeConfig(cfg, cfgOpts), WithImpersonate(impersonate))
g.Expect(err).ToNot(HaveOccurred())
g.Expect(getter).To(BeAssignableToTypeOf(&MemoryRESTClientGetter{}))

got := getter.(*MemoryRESTClientGetter)
g.Expect(got.namespace).To(Equal(namespace))
g.Expect(got.kubeConfig).To(Equal(cfg))
g.Expect(got.qps).To(Equal(qps))
g.Expect(got.burst).To(Equal(burst))
g.Expect(got.clientOpts).To(Equal(clientOpts))
g.Expect(got.kubeConfigOpts).To(Equal(cfgOpts))
g.Expect(got.impersonateAccount).To(Equal(impersonate))
})

t.Run("with config and impersonate account", func(t *testing.T) {
t.Run("with impersonate account", func(t *testing.T) {
g := NewWithT(t)
ctrl.GetConfig = mockGetConfig

namespace := "a-namespace"
impersonate := "frank"
getter := BuildClientGetter(&rest.Config{}, namespace, WithImpersonate(impersonate))
getter, err := BuildClientGetter(namespace, WithImpersonate(impersonate))
g.Expect(err).ToNot(HaveOccurred())
g.Expect(getter).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))

flags := getter.(*genericclioptions.ConfigFlags)
Expand All @@ -92,12 +91,14 @@ users:`)
g.Expect(*flags.Impersonate).To(Equal("system:serviceaccount:a-namespace:frank"))
})

t.Run("with config and DefaultServiceAccount", func(t *testing.T) {
t.Run("with DefaultServiceAccount", func(t *testing.T) {
g := NewWithT(t)
ctrl.GetConfig = mockGetConfig

namespace := "a-namespace"
DefaultServiceAccountName = "frank"
getter := BuildClientGetter(&rest.Config{}, namespace)
getter, err := BuildClientGetter(namespace)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(getter).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))

flags := getter.(*genericclioptions.ConfigFlags)
Expand All @@ -107,3 +108,7 @@ users:`)
g.Expect(*flags.Impersonate).To(Equal("system:serviceaccount:a-namespace:frank"))
})
}

func mockGetConfig() (*rest.Config, error) {
return &rest.Config{}, nil
}
41 changes: 23 additions & 18 deletions internal/kube/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package kube

import (
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/discovery"
Expand All @@ -25,46 +26,53 @@ import (
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/utils/pointer"
controllerruntime "sigs.k8s.io/controller-runtime"

"github.com/fluxcd/pkg/runtime/client"
)

// NewInClusterRESTClientGetter creates a new genericclioptions.RESTClientGetter
// using genericclioptions.NewConfigFlags, and configures it with the server,
// authentication, impersonation, and burst and QPS settings, and the provided
// namespace.
func NewInClusterRESTClientGetter(cfg *rest.Config, namespace string) genericclioptions.RESTClientGetter {
// authentication, impersonation, client options, and the provided namespace.
// It returns an error if it fails to retrieve a rest.Config.
func NewInClusterRESTClientGetter(namespace, impersonateAccount string, opts *client.Options) (genericclioptions.RESTClientGetter, error) {
cfg, err := controllerruntime.GetConfig()
if err != nil {
return nil, fmt.Errorf("failed to get config for in-cluster REST client: %w", err)
}
SetImpersonationConfig(cfg, namespace, impersonateAccount)

flags := genericclioptions.NewConfigFlags(false)
flags.APIServer = pointer.String(cfg.Host)
flags.BearerToken = pointer.String(cfg.BearerToken)
flags.CAFile = pointer.String(cfg.CAFile)
flags.Namespace = pointer.String(namespace)
flags.WithDiscoveryBurst(cfg.Burst)
flags.WithDiscoveryQPS(cfg.QPS)
if opts != nil {
flags.WithDiscoveryBurst(opts.Burst)
flags.WithDiscoveryQPS(opts.QPS)
}
if sa := cfg.Impersonate.UserName; sa != "" {
flags.Impersonate = pointer.String(sa)
}
// In a container, we are not expected to be able to write to the
// home dir default. However, explicitly disabling this is better.
flags.CacheDir = nil
return flags
return flags, nil
}

// MemoryRESTClientGetter is an implementation of the genericclioptions.RESTClientGetter,
// capable of working with an in-memory kubeconfig file.
type MemoryRESTClientGetter struct {
// kubeConfig used to load a rest.Config, after being sanitized.
kubeConfig []byte
// kubeConfigOpts control the sanitization of the kubeConfig.
// kubeConfigOpts controls the sanitization of the kubeConfig.
kubeConfigOpts client.KubeConfigOptions
// clientOpts controls the kube client configuration.
clientOpts client.Options
// namespace specifies the namespace the client is configured to.
namespace string
// impersonateAccount configures the rest.ImpersonationConfig account name.
impersonateAccount string
// qps configures the QPS on the discovery.DiscoveryClient.
qps float32
// burst configures the burst on the discovery.DiscoveryClient.
burst int
}

// NewMemoryRESTClientGetter returns a MemoryRESTClientGetter configured with
Expand All @@ -74,15 +82,13 @@ func NewMemoryRESTClientGetter(
kubeConfig []byte,
namespace string,
impersonate string,
qps float32,
burst int,
clientOpts client.Options,
kubeConfigOpts client.KubeConfigOptions) genericclioptions.RESTClientGetter {
return &MemoryRESTClientGetter{
kubeConfig: kubeConfig,
namespace: namespace,
impersonateAccount: impersonate,
qps: qps,
burst: burst,
clientOpts: clientOpts,
kubeConfigOpts: kubeConfigOpts,
}
}
Expand Down Expand Up @@ -110,8 +116,8 @@ func (c *MemoryRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryI
return nil, err
}

config.QPS = c.qps
config.Burst = c.burst
config.QPS = c.clientOpts.QPS
config.Burst = c.clientOpts.Burst

discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
Expand Down Expand Up @@ -147,6 +153,5 @@ func (c *MemoryRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig
if c.impersonateAccount != "" {
overrides.AuthInfo.Impersonate = c.impersonateAccount
}

return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
}
Loading

0 comments on commit c8f5400

Please sign in to comment.