kube: unify clients into single RESTClientGetter
This drops the twofold implementation in favor of a single `MemoryRESTClientGetter` which can work with an arbitrary `rest.Config`. The new `MemoryRESTClientGetter` lazy-loads and caches the objects it initializes, thereby creating at most one instance of each object for the duration of the reconcile of a single `HelmRelease` object. Based on some initial tests, this seems to reduce the overal memory footprint of the controller. Signed-off-by: Hidde Beydals <hidde@hhh.computer>
This commit is contained in:
parent
90a03d05f6
commit
34d87ccc24
|
|
@ -520,7 +520,8 @@ func (r *HelmReleaseReconciler) checkDependencies(hr v2.HelmRelease) error {
|
|||
}
|
||||
|
||||
func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, hr v2.HelmRelease) (genericclioptions.RESTClientGetter, error) {
|
||||
opts := []kube.ClientGetterOption{
|
||||
opts := []kube.Option{
|
||||
kube.WithNamespace(hr.GetReleaseNamespace()),
|
||||
kube.WithClientOptions(r.ClientOpts),
|
||||
// When ServiceAccountName is empty, it will fall back to the configured default.
|
||||
// If this is not configured either, this option will result in a no-op.
|
||||
|
|
@ -535,13 +536,13 @@ func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, hr v2
|
|||
if err := r.Get(ctx, secretName, &secret); err != nil {
|
||||
return nil, fmt.Errorf("could not find KubeConfig secret '%s': %w", secretName, err)
|
||||
}
|
||||
kubeConfig, err := kube.ConfigFromSecret(&secret, hr.Spec.KubeConfig.SecretRef.Key)
|
||||
kubeConfig, err := kube.ConfigFromSecret(&secret, hr.Spec.KubeConfig.SecretRef.Key, r.KubeConfigOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, kube.WithKubeConfig(kubeConfig, r.KubeConfigOpts))
|
||||
return kube.NewMemoryRESTClientGetter(kubeConfig, opts...), nil
|
||||
}
|
||||
return kube.BuildClientGetter(hr.GetReleaseNamespace(), opts...)
|
||||
return kube.NewInClusterMemoryRESTClientGetter(opts...)
|
||||
}
|
||||
|
||||
// composeValues attempts to resolve all v2beta1.ValuesReference resources
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -24,7 +24,6 @@ require (
|
|||
k8s.io/apimachinery v0.26.2
|
||||
k8s.io/cli-runtime v0.26.2
|
||||
k8s.io/client-go v0.26.2
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5
|
||||
sigs.k8s.io/cli-utils v0.34.0
|
||||
sigs.k8s.io/controller-runtime v0.14.5
|
||||
sigs.k8s.io/kustomize/api v0.12.1
|
||||
|
|
@ -159,6 +158,7 @@ require (
|
|||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70 // indirect
|
||||
k8s.io/kubectl v0.26.0 // indirect
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
|
||||
oras.land/oras-go v1.2.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kube
|
||||
|
||||
import (
|
||||
"github.com/fluxcd/pkg/runtime/client"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultKubeConfigSecretKey is the default data key ConfigFromSecret
|
||||
// looks at when no data key is provided.
|
||||
DefaultKubeConfigSecretKey = "value"
|
||||
// DefaultKubeConfigSecretKeyExt is the default data key ConfigFromSecret
|
||||
// looks at when no data key is provided, and DefaultKubeConfigSecretKey
|
||||
// does not exist.
|
||||
DefaultKubeConfigSecretKeyExt = DefaultKubeConfigSecretKey + ".yaml"
|
||||
)
|
||||
|
||||
// clientGetterOptions used to BuildClientGetter.
|
||||
type clientGetterOptions struct {
|
||||
namespace string
|
||||
kubeConfig []byte
|
||||
impersonateAccount string
|
||||
impersonateNamespace string
|
||||
clientOptions client.Options
|
||||
kubeConfigOptions client.KubeConfigOptions
|
||||
}
|
||||
|
||||
// ClientGetterOption configures a genericclioptions.RESTClientGetter.
|
||||
type ClientGetterOption func(o *clientGetterOptions)
|
||||
|
||||
// WithKubeConfig creates a MemoryRESTClientGetter configured with the provided
|
||||
// KubeConfig and other values.
|
||||
func WithKubeConfig(kubeConfig []byte, opts client.KubeConfigOptions) func(o *clientGetterOptions) {
|
||||
return func(o *clientGetterOptions) {
|
||||
o.kubeConfig = kubeConfig
|
||||
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 with the given account name in the provided namespace.
|
||||
// If the account name is empty, DefaultServiceAccountName is assumed.
|
||||
func WithImpersonate(accountName, namespace string) func(o *clientGetterOptions) {
|
||||
return func(o *clientGetterOptions) {
|
||||
o.impersonateAccount = accountName
|
||||
o.impersonateNamespace = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// BuildClientGetter builds a genericclioptions.RESTClientGetter based on the
|
||||
// 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{
|
||||
namespace: namespace,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
if len(o.kubeConfig) > 0 {
|
||||
return NewMemoryRESTClientGetter(o.kubeConfig, namespace, o.impersonateAccount, o.impersonateNamespace, o.clientOptions, o.kubeConfigOptions), nil
|
||||
}
|
||||
return NewInClusterRESTClientGetter(namespace, o.impersonateAccount, o.impersonateNamespace, &o.clientOptions)
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
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"
|
||||
)
|
||||
|
||||
func TestBuildClientGetter(t *testing.T) {
|
||||
t.Run("with namespace and retrieved config", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
cfg := &rest.Config{Host: "https://example.com"}
|
||||
ctrl.GetConfig = func() (*rest.Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
namespace := "a-namespace"
|
||||
getter, err := BuildClientGetter(namespace)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(getter).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
|
||||
|
||||
flags := getter.(*genericclioptions.ConfigFlags)
|
||||
g.Expect(flags.Namespace).ToNot(BeNil())
|
||||
g.Expect(*flags.Namespace).To(Equal(namespace))
|
||||
g.Expect(flags.APIServer).ToNot(BeNil())
|
||||
g.Expect(*flags.APIServer).To(Equal(cfg.Host))
|
||||
})
|
||||
|
||||
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
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://example.com
|
||||
name: example-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: example-cluster
|
||||
namespace: flux-system
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:`)
|
||||
clientOpts := client.Options{QPS: 600, Burst: 1000}
|
||||
cfgOpts := client.KubeConfigOptions{InsecureTLS: true}
|
||||
impersonate := "jane"
|
||||
|
||||
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.clientOpts).To(Equal(clientOpts))
|
||||
g.Expect(got.kubeConfigOpts).To(Equal(cfgOpts))
|
||||
g.Expect(got.impersonateAccount).To(Equal(impersonate))
|
||||
})
|
||||
|
||||
t.Run("with impersonate account", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ctrl.GetConfig = mockGetConfig
|
||||
|
||||
namespace := "a-namespace"
|
||||
impersonate := "frank"
|
||||
impersonateNS := "other-namespace"
|
||||
getter, err := BuildClientGetter(namespace, WithImpersonate(impersonate, impersonateNS))
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(getter).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
|
||||
|
||||
flags := getter.(*genericclioptions.ConfigFlags)
|
||||
g.Expect(flags.Namespace).ToNot(BeNil())
|
||||
g.Expect(*flags.Namespace).To(Equal(namespace))
|
||||
g.Expect(flags.Impersonate).ToNot(BeNil())
|
||||
g.Expect(*flags.Impersonate).To(Equal("system:serviceaccount:other-namespace:frank"))
|
||||
})
|
||||
|
||||
t.Run("with impersonate DefaultServiceAccount", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ctrl.GetConfig = mockGetConfig
|
||||
|
||||
namespace := "a-namespace"
|
||||
DefaultServiceAccountName = "frank"
|
||||
impersonateNS := "other-namespace"
|
||||
getter, err := BuildClientGetter(namespace, WithImpersonate("", impersonateNS))
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(getter).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
|
||||
|
||||
flags := getter.(*genericclioptions.ConfigFlags)
|
||||
g.Expect(flags.Namespace).ToNot(BeNil())
|
||||
g.Expect(*flags.Namespace).To(Equal(namespace))
|
||||
g.Expect(flags.Impersonate).ToNot(BeNil())
|
||||
g.Expect(*flags.Impersonate).To(Equal("system:serviceaccount:other-namespace:frank"))
|
||||
})
|
||||
}
|
||||
|
||||
func mockGetConfig() (*rest.Config, error) {
|
||||
return &rest.Config{}, nil
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2023 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
@ -18,142 +18,165 @@ package kube
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/discovery/cached/memory"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/utils/pointer"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
ctrl "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, client options, and the provided namespace.
|
||||
// It returns an error if it fails to retrieve a rest.Config.
|
||||
func NewInClusterRESTClientGetter(namespace, impersonateAccount, impersonateNamespace string, opts *client.Options) (genericclioptions.RESTClientGetter, error) {
|
||||
cfg, err := controllerruntime.GetConfig()
|
||||
// Option is a function that configures an MemoryRESTClientGetter.
|
||||
type Option func(*MemoryRESTClientGetter)
|
||||
|
||||
// WithNamespace sets the namespace to use for the client.
|
||||
func WithNamespace(namespace string) Option {
|
||||
return func(c *MemoryRESTClientGetter) {
|
||||
c.namespace = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// WithImpersonate sets the service account to impersonate. It configures the
|
||||
// REST client to impersonate the service account in the given namespace, and
|
||||
// sets the service account name as the username to use in the raw KubeConfig.
|
||||
func WithImpersonate(serviceAccount, namespace string) Option {
|
||||
return func(c *MemoryRESTClientGetter) {
|
||||
if username := SetImpersonationConfig(c.cfg, namespace, serviceAccount); username != "" {
|
||||
c.impersonate = username
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientOptions sets the client options (e.g. QPS and Burst) to use for
|
||||
// the client.
|
||||
func WithClientOptions(opts client.Options) Option {
|
||||
return func(c *MemoryRESTClientGetter) {
|
||||
c.cfg.Burst = opts.Burst
|
||||
c.cfg.QPS = opts.QPS
|
||||
}
|
||||
}
|
||||
|
||||
// MemoryRESTClientGetter is a resource.RESTClientGetter that uses an
|
||||
// in-memory REST config, REST mapper, and discovery client. The REST config,
|
||||
// REST mapper, and discovery client are lazily initialized, and cached for
|
||||
// subsequent calls.
|
||||
type MemoryRESTClientGetter struct {
|
||||
// namespace is the namespace to use for the client.
|
||||
namespace string
|
||||
// impersonate is the username to use for the client.
|
||||
impersonate string
|
||||
|
||||
cfg *rest.Config
|
||||
|
||||
restMapper meta.RESTMapper
|
||||
restMapperMu sync.Mutex
|
||||
|
||||
discoveryClient discovery.CachedDiscoveryInterface
|
||||
discoveryMu sync.Mutex
|
||||
|
||||
clientCfg clientcmd.ClientConfig
|
||||
clientCfgMu sync.Mutex
|
||||
}
|
||||
|
||||
// setDefaults sets the default values for the MemoryRESTClientGetter.
|
||||
func (c *MemoryRESTClientGetter) setDefaults() {
|
||||
if c.namespace == "" {
|
||||
c.namespace = "default"
|
||||
}
|
||||
}
|
||||
|
||||
// NewMemoryRESTClientGetter returns a new MemoryRESTClientGetter.
|
||||
func NewMemoryRESTClientGetter(cfg *rest.Config, opts ...Option) *MemoryRESTClientGetter {
|
||||
g := &MemoryRESTClientGetter{
|
||||
cfg: cfg,
|
||||
}
|
||||
for _, opts := range opts {
|
||||
opts(g)
|
||||
}
|
||||
g.setDefaults()
|
||||
return g
|
||||
}
|
||||
|
||||
// NewInClusterMemoryRESTClientGetter returns a new MemoryRESTClientGetter
|
||||
// that uses the in-cluster REST config. It returns an error if the in-cluster
|
||||
// REST config cannot be obtained.
|
||||
func NewInClusterMemoryRESTClientGetter(opts ...Option) (*MemoryRESTClientGetter, error) {
|
||||
cfg, err := ctrl.GetConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get config for in-cluster REST client: %w", err)
|
||||
}
|
||||
SetImpersonationConfig(cfg, impersonateNamespace, 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)
|
||||
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, nil
|
||||
return NewMemoryRESTClientGetter(cfg, opts...), 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 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
|
||||
// impersonateAccount configures the rest.ImpersonationConfig account namespace.
|
||||
impersonateNamespace string
|
||||
}
|
||||
|
||||
// NewMemoryRESTClientGetter returns a MemoryRESTClientGetter configured with
|
||||
// the provided values and client.KubeConfigOptions. The provided KubeConfig is
|
||||
// sanitized, configure the settings for this using client.KubeConfigOptions.
|
||||
func NewMemoryRESTClientGetter(
|
||||
kubeConfig []byte,
|
||||
namespace string,
|
||||
impersonateAccount string,
|
||||
impersonateNamespace string,
|
||||
clientOpts client.Options,
|
||||
kubeConfigOpts client.KubeConfigOptions) genericclioptions.RESTClientGetter {
|
||||
return &MemoryRESTClientGetter{
|
||||
kubeConfig: kubeConfig,
|
||||
namespace: namespace,
|
||||
impersonateAccount: impersonateAccount,
|
||||
impersonateNamespace: impersonateNamespace,
|
||||
clientOpts: clientOpts,
|
||||
kubeConfigOpts: kubeConfigOpts,
|
||||
}
|
||||
}
|
||||
|
||||
// ToRESTConfig creates a rest.Config with the rest.ImpersonationConfig configured
|
||||
// with to the impersonation account. It loads the config the KubeConfig bytes and
|
||||
// sanitizes it using the client.KubeConfigOptions.
|
||||
// ToRESTConfig returns the in-memory REST config.
|
||||
func (c *MemoryRESTClientGetter) ToRESTConfig() (*rest.Config, error) {
|
||||
cfg, err := clientcmd.RESTConfigFromKubeConfig(c.kubeConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if c.cfg == nil {
|
||||
return nil, fmt.Errorf("MemoryRESTClientGetter has no REST config")
|
||||
}
|
||||
cfg = client.KubeConfig(cfg, c.kubeConfigOpts)
|
||||
SetImpersonationConfig(cfg, c.impersonateNamespace, c.impersonateAccount)
|
||||
return cfg, nil
|
||||
return c.cfg, nil
|
||||
}
|
||||
|
||||
// ToDiscoveryClient returns a discovery.CachedDiscoveryInterface configured
|
||||
// with ToRESTConfig, and the QPS and Burst settings.
|
||||
// ToDiscoveryClient returns a memory cached discovery client. Calling it
|
||||
// multiple times will return the same instance.
|
||||
func (c *MemoryRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
||||
config, err := c.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.clientCfgMu.Lock()
|
||||
defer c.clientCfgMu.Unlock()
|
||||
|
||||
config.QPS = c.clientOpts.QPS
|
||||
config.Burst = c.clientOpts.Burst
|
||||
if c.discoveryClient == nil {
|
||||
config, err := c.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.discoveryClient = memory.NewMemCacheClient(discoveryClient)
|
||||
}
|
||||
return memory.NewMemCacheClient(discoveryClient), nil
|
||||
return c.discoveryClient, nil
|
||||
}
|
||||
|
||||
// ToRESTMapper returns a RESTMapper constructed from ToDiscoveryClient.
|
||||
// ToRESTMapper returns a meta.RESTMapper using the discovery client. Calling
|
||||
// it multiple times will return the same instance.
|
||||
func (c *MemoryRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
|
||||
discoveryClient, err := c.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.discoveryMu.Lock()
|
||||
defer c.discoveryMu.Unlock()
|
||||
|
||||
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
|
||||
expander := restmapper.NewShortcutExpander(mapper, discoveryClient)
|
||||
return expander, nil
|
||||
if c.restMapper == nil {
|
||||
discoveryClient, err := c.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
|
||||
c.restMapper = restmapper.NewShortcutExpander(mapper, discoveryClient)
|
||||
}
|
||||
return c.restMapper, nil
|
||||
}
|
||||
|
||||
// ToRawKubeConfigLoader returns a clientcmd.ClientConfig using
|
||||
// clientcmd.DefaultClientConfig. With clientcmd.ClusterDefaults, namespace, and
|
||||
// impersonate configured as overwrites.
|
||||
func (c *MemoryRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
// use the standard defaults for this client command
|
||||
// DEPRECATED: remove and replace with something more accurate
|
||||
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
|
||||
c.clientCfgMu.Lock()
|
||||
defer c.clientCfgMu.Unlock()
|
||||
|
||||
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
|
||||
overrides.Context.Namespace = c.namespace
|
||||
if c.clientCfg == nil {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
// use the standard defaults for this client command
|
||||
// DEPRECATED: remove and replace with something more accurate
|
||||
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
|
||||
|
||||
if c.impersonateAccount != "" {
|
||||
overrides.AuthInfo.Impersonate = c.impersonateAccount
|
||||
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
|
||||
overrides.Context.Namespace = c.namespace
|
||||
overrides.AuthInfo.Impersonate = c.impersonate
|
||||
|
||||
c.clientCfg = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
|
||||
}
|
||||
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
|
||||
return c.clientCfg
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
Copyright 2023 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
@ -20,178 +20,220 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/fluxcd/pkg/runtime/client"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"github.com/fluxcd/pkg/runtime/client"
|
||||
)
|
||||
|
||||
var cfg = []byte(`current-context: federal-context
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
api-version: v1
|
||||
server: http://cow.org:8080
|
||||
insecure-skip-tls-verify: true
|
||||
name: cow-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: cow-cluster
|
||||
user: blue-user
|
||||
name: federal-context
|
||||
kind: Config
|
||||
users:
|
||||
- name: blue-user
|
||||
user:
|
||||
token: foo`)
|
||||
|
||||
func TestNewInClusterRESTClientGetter(t *testing.T) {
|
||||
t.Run("discover config", func(t *testing.T) {
|
||||
func TestWithNamespace(t *testing.T) {
|
||||
t.Run("sets the namespace", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
cfg := &rest.Config{
|
||||
Host: "https://example.com",
|
||||
BearerToken: "chase-the-honey",
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
CAFile: "afile",
|
||||
c := &MemoryRESTClientGetter{}
|
||||
WithNamespace("foo")(c)
|
||||
g.Expect(c.namespace).To(Equal("foo"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithImpersonate(t *testing.T) {
|
||||
t.Run("sets the impersonate", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
c := &MemoryRESTClientGetter{
|
||||
cfg: &rest.Config{
|
||||
Host: "https://example.com",
|
||||
},
|
||||
}
|
||||
ctrl.GetConfig = func() (*rest.Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
got, err := NewInClusterRESTClientGetter("", "", "", nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
|
||||
WithImpersonate("foo", "bar")(c)
|
||||
g.Expect(c.impersonate).To(Equal(fmt.Sprintf(userNameFormat, "bar", "foo")))
|
||||
g.Expect(c.cfg.Impersonate.UserName).To(Equal(c.impersonate))
|
||||
})
|
||||
}
|
||||
|
||||
flags := got.(*genericclioptions.ConfigFlags)
|
||||
fields := map[*string]*string{
|
||||
flags.APIServer: &cfg.Host,
|
||||
flags.BearerToken: &cfg.BearerToken,
|
||||
flags.CAFile: &cfg.CAFile,
|
||||
}
|
||||
for f, ff := range fields {
|
||||
g.Expect(f).ToNot(BeNil())
|
||||
g.Expect(f).To(Equal(ff))
|
||||
g.Expect(f).ToNot(BeIdenticalTo(ff))
|
||||
func TestWithClientOptions(t *testing.T) {
|
||||
t.Run("sets the client options", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
c := &MemoryRESTClientGetter{
|
||||
cfg: &rest.Config{
|
||||
Host: "https://example.com",
|
||||
},
|
||||
}
|
||||
WithClientOptions(client.Options{
|
||||
Burst: 10,
|
||||
QPS: 5,
|
||||
})(c)
|
||||
g.Expect(c.cfg.Burst).To(Equal(10))
|
||||
g.Expect(c.cfg.QPS).To(Equal(float32(5)))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewMemoryRESTClientGetter(t *testing.T) {
|
||||
t.Run("returns a new MemoryRESTClientGetter", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
c := NewMemoryRESTClientGetter(&rest.Config{
|
||||
Host: "https://example.com",
|
||||
})
|
||||
g.Expect(c).ToNot(BeNil())
|
||||
g.Expect(c.cfg).ToNot(BeNil())
|
||||
g.Expect(c.cfg.Host).To(Equal("https://example.com"))
|
||||
})
|
||||
|
||||
t.Run("config retrieval error", func(t *testing.T) {
|
||||
t.Run("returns a new MemoryRESTClientGetter with default options", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
c := NewMemoryRESTClientGetter(&rest.Config{
|
||||
Host: "https://example.com",
|
||||
})
|
||||
g.Expect(c).ToNot(BeNil())
|
||||
g.Expect(c.cfg).ToNot(BeNil())
|
||||
g.Expect(c.namespace).To(Equal("default"))
|
||||
})
|
||||
|
||||
t.Run("returns a new MemoryRESTClientGetter with options", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
c := NewMemoryRESTClientGetter(&rest.Config{
|
||||
Host: "https://example.com",
|
||||
}, WithNamespace("foo"))
|
||||
g.Expect(c).ToNot(BeNil())
|
||||
g.Expect(c.cfg).ToNot(BeNil())
|
||||
g.Expect(c.namespace).To(Equal("foo"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewInClusterMemoryRESTClientGetter(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
cfg := ctrl.GetConfig
|
||||
ctrl.GetConfig = cfg
|
||||
})
|
||||
|
||||
t.Run("discovers the in cluster config", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
mockCfg := &rest.Config{
|
||||
Host: "https://example.com",
|
||||
}
|
||||
ctrl.GetConfig = func() (*rest.Config, error) {
|
||||
return mockCfg, nil
|
||||
}
|
||||
|
||||
c, err := NewInClusterMemoryRESTClientGetter()
|
||||
g.Expect(c).ToNot(BeNil())
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(c.cfg).To(Equal(mockCfg))
|
||||
})
|
||||
|
||||
t.Run("returns an error if the in cluster config cannot be discovered", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
ctrl.GetConfig = func() (*rest.Config, error) {
|
||||
return nil, fmt.Errorf("error")
|
||||
}
|
||||
got, err := NewInClusterRESTClientGetter("", "", "", nil)
|
||||
|
||||
c, err := NewInClusterMemoryRESTClientGetter()
|
||||
g.Expect(c).To(BeNil())
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("failed to get config for in-cluster REST client"))
|
||||
g.Expect(got).To(BeNil())
|
||||
})
|
||||
|
||||
t.Run("namespace", func(t *testing.T) {
|
||||
t.Run("configures the client with options", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
ctrl.GetConfig = mockGetConfig
|
||||
namespace := "a-space"
|
||||
got, err := NewInClusterRESTClientGetter(namespace, "", "", nil)
|
||||
mockCfg := &rest.Config{
|
||||
Host: "https://example.com",
|
||||
}
|
||||
ctrl.GetConfig = func() (*rest.Config, error) {
|
||||
return mockCfg, nil
|
||||
}
|
||||
|
||||
c, err := NewInClusterMemoryRESTClientGetter(WithNamespace("foo"))
|
||||
g.Expect(c).ToNot(BeNil())
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
|
||||
|
||||
flags := got.(*genericclioptions.ConfigFlags)
|
||||
g.Expect(flags.Namespace).ToNot(BeNil())
|
||||
g.Expect(*flags.Namespace).To(Equal(namespace))
|
||||
})
|
||||
|
||||
t.Run("impersonation", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
ctrl.GetConfig = mockGetConfig
|
||||
ns := "a-namespace"
|
||||
accountName := "foo"
|
||||
accountNamespace := "another-namespace"
|
||||
got, err := NewInClusterRESTClientGetter(ns, accountName, accountNamespace, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
|
||||
|
||||
flags := got.(*genericclioptions.ConfigFlags)
|
||||
g.Expect(flags.Impersonate).ToNot(BeNil())
|
||||
g.Expect(*flags.Impersonate).To(Equal(fmt.Sprintf("system:serviceaccount:%s:%s", accountNamespace, accountName)))
|
||||
g.Expect(c.cfg).To(Equal(mockCfg))
|
||||
g.Expect(c.namespace).To(Equal("foo"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMemoryRESTClientGetter_ToRESTConfig(t *testing.T) {
|
||||
t.Run("loads REST config from KubeConfig", func(t *testing.T) {
|
||||
t.Run("returns a REST config", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
getter := NewMemoryRESTClientGetter(cfg, "", "", "", client.Options{}, client.KubeConfigOptions{})
|
||||
got, err := getter.ToRESTConfig()
|
||||
|
||||
c := &MemoryRESTClientGetter{
|
||||
cfg: &rest.Config{
|
||||
Host: "https://example.com",
|
||||
},
|
||||
}
|
||||
cfg, err := c.ToRESTConfig()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got.Host).To(Equal("http://cow.org:8080"))
|
||||
g.Expect(got.TLSClientConfig.Insecure).To(BeFalse())
|
||||
g.Expect(cfg).To(BeIdenticalTo(c.cfg))
|
||||
})
|
||||
|
||||
t.Run("sets ImpersonationConfig", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
getter := NewMemoryRESTClientGetter(cfg, "", "someone", "a-namespace", client.Options{}, client.KubeConfigOptions{})
|
||||
|
||||
got, err := getter.ToRESTConfig()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got.Impersonate.UserName).To(Equal("system:serviceaccount:a-namespace:someone"))
|
||||
})
|
||||
|
||||
t.Run("uses KubeConfigOptions", func(t *testing.T) {
|
||||
t.Run("error on nil REST config", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
agent := "a static string forever," +
|
||||
"but static strings can have dreams and hope too"
|
||||
getter := NewMemoryRESTClientGetter(cfg, "", "someone", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{
|
||||
UserAgent: agent,
|
||||
})
|
||||
|
||||
got, err := getter.ToRESTConfig()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got.UserAgent).To(Equal(agent))
|
||||
})
|
||||
|
||||
t.Run("invalid config", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
getter := NewMemoryRESTClientGetter([]byte("invalid"), "", "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
|
||||
got, err := getter.ToRESTConfig()
|
||||
c := &MemoryRESTClientGetter{}
|
||||
cfg, err := c.ToRESTConfig()
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(got).To(BeNil())
|
||||
g.Expect(cfg).To(BeNil())
|
||||
})
|
||||
}
|
||||
|
||||
func TestMemoryRESTClientGetter_ToDiscoveryClient(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
t.Run("returns a discovery client", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
getter := NewMemoryRESTClientGetter(cfg, "", "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
|
||||
got, err := getter.ToDiscoveryClient()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).ToNot(BeNil())
|
||||
c := &MemoryRESTClientGetter{
|
||||
cfg: &rest.Config{
|
||||
Host: "https://example.com",
|
||||
},
|
||||
}
|
||||
dc, err := c.ToDiscoveryClient()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(dc).ToNot(BeNil())
|
||||
|
||||
// Calling it again should return the same instance.
|
||||
dc2, err := c.ToDiscoveryClient()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(dc2).To(BeIdenticalTo(dc))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMemoryRESTClientGetter_ToRESTMapper(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
t.Run("returns a REST mapper", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
getter := NewMemoryRESTClientGetter(cfg, "", "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
|
||||
got, err := getter.ToRESTMapper()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).ToNot(BeNil())
|
||||
c := &MemoryRESTClientGetter{
|
||||
cfg: &rest.Config{
|
||||
Host: "https://example.com",
|
||||
},
|
||||
}
|
||||
rm, err := c.ToRESTMapper()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(rm).ToNot(BeNil())
|
||||
|
||||
// Calling it again should return the same instance.
|
||||
rm2, err := c.ToRESTMapper()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(rm2).To(BeIdenticalTo(rm))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMemoryRESTClientGetter_ToRawKubeConfigLoader(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
t.Run("returns a client config", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
getter := NewMemoryRESTClientGetter(cfg, "a-namespace", "impersonate", "other-namespace", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
|
||||
got := getter.ToRawKubeConfigLoader()
|
||||
g.Expect(got).ToNot(BeNil())
|
||||
c := &MemoryRESTClientGetter{
|
||||
cfg: &rest.Config{
|
||||
Host: "https://example.com",
|
||||
},
|
||||
}
|
||||
cc := c.ToRawKubeConfigLoader()
|
||||
g.Expect(cc).ToNot(BeNil())
|
||||
|
||||
cfg, err := got.ClientConfig()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(cfg.Impersonate.UserName).To(Equal("impersonate"))
|
||||
ns, _, err := got.Namespace()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(ns).To(Equal("a-namespace"))
|
||||
// Calling it again should return the same instance.
|
||||
g.Expect(c.ToRawKubeConfigLoader()).To(BeIdenticalTo(cc))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,32 +20,54 @@ import (
|
|||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/fluxcd/pkg/runtime/client"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultKubeConfigSecretKey is the default data key ConfigFromSecret
|
||||
// looks at when no data key is provided.
|
||||
DefaultKubeConfigSecretKey = "value"
|
||||
// DefaultKubeConfigSecretKeyExt is the default data key ConfigFromSecret
|
||||
// looks at when no data key is provided, and DefaultKubeConfigSecretKey
|
||||
// does not exist.
|
||||
DefaultKubeConfigSecretKeyExt = DefaultKubeConfigSecretKey + ".yaml"
|
||||
)
|
||||
|
||||
// ConfigFromSecret returns the KubeConfig data from the provided key in the
|
||||
// given Secret, or attempts to load the data from the default `value` and
|
||||
// `value.yaml` keys. If a Secret is provided but no key with data can be
|
||||
// found, an error is returned. The secret may be nil, in which case no bytes
|
||||
// nor error are returned. Validation of the data is expected to happen while
|
||||
// decoding the bytes.
|
||||
func ConfigFromSecret(secret *corev1.Secret, key string) ([]byte, error) {
|
||||
var kubeConfig []byte
|
||||
if secret != nil {
|
||||
secretName := fmt.Sprintf("%s/%s", secret.Namespace, secret.Name)
|
||||
switch {
|
||||
case key != "":
|
||||
kubeConfig = secret.Data[key]
|
||||
if kubeConfig == nil {
|
||||
return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a '%s' key with data", secretName, key)
|
||||
}
|
||||
case secret.Data[DefaultKubeConfigSecretKey] != nil:
|
||||
kubeConfig = secret.Data[DefaultKubeConfigSecretKey]
|
||||
case secret.Data[DefaultKubeConfigSecretKeyExt] != nil:
|
||||
kubeConfig = secret.Data[DefaultKubeConfigSecretKeyExt]
|
||||
default:
|
||||
// User did not specify a key, and the 'value' key was not defined.
|
||||
return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a '%s' or '%s' key with data", secretName, DefaultKubeConfigSecretKey, DefaultKubeConfigSecretKeyExt)
|
||||
}
|
||||
// found, an error is returned.
|
||||
func ConfigFromSecret(secret *corev1.Secret, key string, opts client.KubeConfigOptions) (*rest.Config, error) {
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("KubeConfig secret is nil")
|
||||
}
|
||||
return kubeConfig, nil
|
||||
|
||||
var (
|
||||
kubeConfig []byte
|
||||
secretName = fmt.Sprintf("%s/%s", secret.Namespace, secret.Name)
|
||||
)
|
||||
switch {
|
||||
case key != "":
|
||||
kubeConfig = secret.Data[key]
|
||||
if kubeConfig == nil {
|
||||
return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a '%s' key with data", secretName, key)
|
||||
}
|
||||
case secret.Data[DefaultKubeConfigSecretKey] != nil:
|
||||
kubeConfig = secret.Data[DefaultKubeConfigSecretKey]
|
||||
case secret.Data[DefaultKubeConfigSecretKeyExt] != nil:
|
||||
kubeConfig = secret.Data[DefaultKubeConfigSecretKeyExt]
|
||||
default:
|
||||
// User did not specify a key, and the 'value' key was not defined.
|
||||
return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a '%s' or '%s' key with data", secretName, DefaultKubeConfigSecretKey, DefaultKubeConfigSecretKeyExt)
|
||||
}
|
||||
|
||||
cfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load KubeConfig from secret '%s': %w", secretName, err)
|
||||
}
|
||||
cfg = client.KubeConfig(cfg, opts)
|
||||
return cfg, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package kube
|
||||
|
||||
import (
|
||||
"github.com/fluxcd/pkg/runtime/client"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
|
|
@ -24,6 +25,29 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
kubeCfg = `apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- cluster:
|
||||
insecure-skip-tls-verify: true
|
||||
server: https://1.2.3.4
|
||||
name: development
|
||||
contexts:
|
||||
- context:
|
||||
cluster: development
|
||||
namespace: frontend
|
||||
user: developer
|
||||
name: dev-frontend
|
||||
current-context: dev-frontend
|
||||
preferences: {}
|
||||
users:
|
||||
- name: developer
|
||||
user:
|
||||
password: some-password
|
||||
username: exp`
|
||||
)
|
||||
|
||||
func TestConfigFromSecret(t *testing.T) {
|
||||
t.Run("with default key", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
|
@ -34,14 +58,14 @@ func TestConfigFromSecret(t *testing.T) {
|
|||
Namespace: "vault",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
DefaultKubeConfigSecretKey: []byte("good"),
|
||||
DefaultKubeConfigSecretKey: []byte(kubeCfg),
|
||||
// Also confirm priority.
|
||||
DefaultKubeConfigSecretKeyExt: []byte("bad"),
|
||||
},
|
||||
}
|
||||
got, err := ConfigFromSecret(secret, "")
|
||||
got, err := ConfigFromSecret(secret, "", client.KubeConfigOptions{})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(Equal(secret.Data[DefaultKubeConfigSecretKey]))
|
||||
g.Expect(got).ToNot(BeNil())
|
||||
})
|
||||
|
||||
t.Run("with default key with ext", func(t *testing.T) {
|
||||
|
|
@ -53,12 +77,12 @@ func TestConfigFromSecret(t *testing.T) {
|
|||
Namespace: "vault",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
DefaultKubeConfigSecretKeyExt: []byte("good"),
|
||||
DefaultKubeConfigSecretKeyExt: []byte(kubeCfg),
|
||||
},
|
||||
}
|
||||
got, err := ConfigFromSecret(secret, "")
|
||||
got, err := ConfigFromSecret(secret, "", client.KubeConfigOptions{})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(Equal(secret.Data[DefaultKubeConfigSecretKeyExt]))
|
||||
g.Expect(got).ToNot(BeNil())
|
||||
})
|
||||
|
||||
t.Run("with key", func(t *testing.T) {
|
||||
|
|
@ -71,12 +95,12 @@ func TestConfigFromSecret(t *testing.T) {
|
|||
Namespace: "vault",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
key: []byte("snow"),
|
||||
key: []byte(kubeCfg),
|
||||
},
|
||||
}
|
||||
got, err := ConfigFromSecret(secret, key)
|
||||
got, err := ConfigFromSecret(secret, key, client.KubeConfigOptions{})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(Equal(secret.Data[key]))
|
||||
g.Expect(got).ToNot(BeNil())
|
||||
})
|
||||
|
||||
t.Run("invalid key", func(t *testing.T) {
|
||||
|
|
@ -90,11 +114,10 @@ func TestConfigFromSecret(t *testing.T) {
|
|||
},
|
||||
Data: map[string][]byte{},
|
||||
}
|
||||
got, err := ConfigFromSecret(secret, key)
|
||||
got, err := ConfigFromSecret(secret, key, client.KubeConfigOptions{})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(got).To(BeNil())
|
||||
g.Expect(err.Error()).To(ContainSubstring("secret 'vault/super-secret' does not contain a 'black-hole' key "))
|
||||
g.Expect(got).To(Equal(secret.Data[key]))
|
||||
})
|
||||
|
||||
t.Run("key without data", func(t *testing.T) {
|
||||
|
|
@ -110,7 +133,7 @@ func TestConfigFromSecret(t *testing.T) {
|
|||
key: nil,
|
||||
},
|
||||
}
|
||||
got, err := ConfigFromSecret(secret, key)
|
||||
got, err := ConfigFromSecret(secret, key, client.KubeConfigOptions{})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(got).To(BeNil())
|
||||
g.Expect(err.Error()).To(ContainSubstring("does not contain a 'void' key with data"))
|
||||
|
|
@ -127,7 +150,7 @@ func TestConfigFromSecret(t *testing.T) {
|
|||
Data: map[string][]byte{},
|
||||
}
|
||||
|
||||
got, err := ConfigFromSecret(secret, "")
|
||||
got, err := ConfigFromSecret(secret, "", client.KubeConfigOptions{})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(got).To(BeNil())
|
||||
g.Expect(err.Error()).To(ContainSubstring("does not contain a 'value' or 'value.yaml'"))
|
||||
|
|
@ -136,8 +159,48 @@ func TestConfigFromSecret(t *testing.T) {
|
|||
t.Run("nil secret", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
got, err := ConfigFromSecret(nil, "")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
got, err := ConfigFromSecret(nil, "", client.KubeConfigOptions{})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(got).To(BeNil())
|
||||
g.Expect(err.Error()).To(ContainSubstring("secret is nil"))
|
||||
})
|
||||
|
||||
t.Run("invalid kubeconfig", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "super-secret",
|
||||
Namespace: "vault",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
DefaultKubeConfigSecretKeyExt: []byte("bad"),
|
||||
},
|
||||
}
|
||||
|
||||
got, err := ConfigFromSecret(secret, "", client.KubeConfigOptions{})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(got).To(BeNil())
|
||||
g.Expect(err.Error()).To(ContainSubstring("couldn't get version/kind"))
|
||||
})
|
||||
|
||||
t.Run("with kubeconfig options", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "super-secret",
|
||||
Namespace: "vault",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
DefaultKubeConfigSecretKey: []byte(kubeCfg),
|
||||
},
|
||||
}
|
||||
got, err := ConfigFromSecret(secret, "", client.KubeConfigOptions{
|
||||
UserAgent: "test",
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).ToNot(BeNil())
|
||||
g.Expect(got.UserAgent).To(Equal("test"))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue