Skip to content

Commit

Permalink
Cherry-pick kube changes from dev
Browse files Browse the repository at this point in the history
This is a partial cherry-pick of commit ae4f499, including
changes around `kube`. This to include some of the changes around the
construction of the ConfigFlags RESTClientGetter, as an attempt to
solve token refresh issues.

Signed-off-by: Hidde Beydals <hello@hidde.co>
  • Loading branch information
hiddeco committed May 12, 2022
1 parent e78a6f0 commit 4371610
Show file tree
Hide file tree
Showing 11 changed files with 750 additions and 71 deletions.
53 changes: 11 additions & 42 deletions controllers/helmrelease_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context,
log := ctrl.LoggerFrom(ctx)

// Initialize Helm action runner
getter, err := r.getRESTClientGetter(ctx, hr)
getter, err := r.buildRESTClientGetter(ctx, hr)
if err != nil {
return v2.HelmReleaseNotReady(hr, v2.InitFailedReason, err.Error()), err
}
Expand Down Expand Up @@ -472,23 +472,11 @@ func (r *HelmReleaseReconciler) checkDependencies(hr v2.HelmRelease) error {
return nil
}

func (r *HelmReleaseReconciler) setImpersonationConfig(restConfig *rest.Config, hr v2.HelmRelease) string {
name := r.DefaultServiceAccount
if sa := hr.Spec.ServiceAccountName; sa != "" {
name = sa
func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, hr v2.HelmRelease) (genericclioptions.RESTClientGetter, error) {
var opts []kube.ClientGetterOption
if hr.Spec.ServiceAccountName != "" {
opts = append(opts, kube.WithImpersonate(hr.Spec.ServiceAccountName))
}
if name != "" {
username := fmt.Sprintf("system:serviceaccount:%s:%s", hr.GetNamespace(), name)
restConfig.Impersonate = rest.ImpersonationConfig{UserName: username}
return username
}
return ""
}

func (r *HelmReleaseReconciler) getRESTClientGetter(ctx context.Context, hr v2.HelmRelease) (genericclioptions.RESTClientGetter, error) {
config := *r.Config
impersonateAccount := r.setImpersonationConfig(&config, hr)

if hr.Spec.KubeConfig != nil {
secretName := types.NamespacedName{
Namespace: hr.GetNamespace(),
Expand All @@ -498,32 +486,13 @@ func (r *HelmReleaseReconciler) getRESTClientGetter(ctx context.Context, hr v2.H
if err := r.Get(ctx, secretName, &secret); err != nil {
return nil, fmt.Errorf("could not find KubeConfig secret '%s': %w", secretName, err)
}

var kubeConfig []byte
switch {
case hr.Spec.KubeConfig.SecretRef.Key != "":
key := hr.Spec.KubeConfig.SecretRef.Key
kubeConfig = secret.Data[key]
if kubeConfig == nil {
return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a '%s' key with a kubeconfig", secretName, key)
}
case secret.Data["value"] != nil:
kubeConfig = secret.Data["value"]
case secret.Data["value.yaml"] != nil:
kubeConfig = secret.Data["value.yaml"]
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 'value' key with a kubeconfig", secretName)
kubeConfig, err := kube.ConfigFromSecret(&secret, hr.Spec.KubeConfig.SecretRef.Key)
if err != nil {
return nil, err
}

return kube.NewMemoryRESTClientGetter(kubeConfig, hr.GetReleaseNamespace(), impersonateAccount, r.Config.QPS, r.Config.Burst, r.KubeConfigOpts), nil
opts = append(opts, kube.WithKubeConfig(kubeConfig, r.Config.QPS, r.Config.Burst, r.KubeConfigOpts))
}

if r.DefaultServiceAccount != "" || hr.Spec.ServiceAccountName != "" {
return kube.NewInClusterRESTClientGetter(&config, hr.GetReleaseNamespace()), nil
}

return kube.NewInClusterRESTClientGetter(r.Config, hr.GetReleaseNamespace()), nil
return kube.BuildClientGetter(r.Config, hr.GetReleaseNamespace(), opts...), nil

}

Expand Down Expand Up @@ -653,7 +622,7 @@ func (r *HelmReleaseReconciler) reconcileDelete(ctx context.Context, hr v2.HelmR

// Only uninstall the Helm Release if the resource is not suspended.
if !hr.Spec.Suspend {
getter, err := r.getRESTClientGetter(ctx, hr)
getter, err := r.buildRESTClientGetter(ctx, hr)
if err != nil {
return ctrl.Result{}, err
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
k8s.io/apimachinery v0.23.6
k8s.io/cli-runtime v0.23.6
k8s.io/client-go v0.23.6
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
sigs.k8s.io/controller-runtime v0.11.2
sigs.k8s.io/kustomize/api v0.11.4
sigs.k8s.io/yaml v1.3.0
Expand Down Expand Up @@ -165,7 +166,6 @@ require (
k8s.io/klog/v2 v2.50.0 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
k8s.io/kubectl v0.23.5 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
oras.land/oras-go v1.1.1 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
Expand Down
86 changes: 86 additions & 0 deletions internal/kube/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
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/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"

"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"
)

// clientGetterOptions used to BuildClientGetter.
type clientGetterOptions struct {
config *rest.Config
namespace string
kubeConfig []byte
burst int
qps float32
impersonateAccount string
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, qps float32, burst int, opts client.KubeConfigOptions) func(o *clientGetterOptions) {
return func(o *clientGetterOptions) {
o.kubeConfig = kubeConfig
o.qps = qps
o.burst = burst
o.kubeConfigOptions = opts
}
}

// WithImpersonate configures the genericclioptions.RESTClientGetter to
// impersonate the provided account name.
func WithImpersonate(accountName string) func(o *clientGetterOptions) {
return func(o *clientGetterOptions) {
o.impersonateAccount = accountName
}
}

// 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 {
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)
}
cfg := *config
SetImpersonationConfig(&cfg, namespace, o.impersonateAccount)
return NewInClusterRESTClientGetter(&cfg, namespace)
}
109 changes: 109 additions & 0 deletions internal/kube/builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
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 (
"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) {
g := NewWithT(t)

cfg := &rest.Config{
BearerToken: "a-token",
}
namespace := "a-namespace"
getter := BuildClientGetter(cfg, namespace)
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) {
g := NewWithT(t)

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:`)
qps := float32(600)
burst := 1000
cfgOpts := client.KubeConfigOptions{InsecureTLS: true}

impersonate := "jane"

getter := BuildClientGetter(&rest.Config{}, namespace, WithKubeConfig(cfg, qps, burst, cfgOpts), WithImpersonate(impersonate))
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.kubeConfigOpts).To(Equal(cfgOpts))
g.Expect(got.impersonateAccount).To(Equal(impersonate))
})

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

namespace := "a-namespace"
impersonate := "frank"
getter := BuildClientGetter(&rest.Config{}, namespace, WithImpersonate(impersonate))
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:a-namespace:frank"))
})

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

namespace := "a-namespace"
DefaultServiceAccountName = "frank"
getter := BuildClientGetter(&rest.Config{}, namespace)
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:a-namespace:frank"))
})
}
Loading

0 comments on commit 4371610

Please sign in to comment.