Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Obtain the default namespace directly from kubeconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
2opremio committed Feb 26, 2019
1 parent 1d86d57 commit aeef2c6
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 46 deletions.
5 changes: 4 additions & 1 deletion cluster/kubernetes/cached_disco_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ func TestCachedDiscovery(t *testing.T) {

cachedDisco, store, _ := makeCachedDiscovery(coreClient.Discovery(), crdClient, shutdown, makeHandler)

namespacer, err := NewNamespacer(namespaceDefaulterFake("bar-ns"), cachedDisco)
saved := getDefaultNamespace
getDefaultNamespace = func() (string, error) { return "bar-ns", nil }
defer func() { getDefaultNamespace = saved }()
namespacer, err := NewNamespacer(cachedDisco)
if err != nil {
t.Fatal(err)
}
Expand Down
38 changes: 32 additions & 6 deletions cluster/kubernetes/namespacer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,54 @@ import (
"fmt"

"k8s.io/client-go/discovery"
"k8s.io/client-go/tools/clientcmd"

kresource "github.com/weaveworks/flux/cluster/kubernetes/resource"
)

// The namespace to presume if something doesn't have one, and we
// haven't been told what to use as a fallback. This is what
// `kubectl` uses when there's no config setting the fallback
// namespace.
const defaultFallbackNamespace = "default"

type namespaceViaDiscovery struct {
fallbackNamespace string
disco discovery.DiscoveryInterface
}

type namespaceDefaulter interface {
GetDefaultNamespace() (string, error)
}

// NewNamespacer creates an implementation of Namespacer
func NewNamespacer(ns namespaceDefaulter, d discovery.DiscoveryInterface) (*namespaceViaDiscovery, error) {
fallback, err := ns.GetDefaultNamespace()
func NewNamespacer(d discovery.DiscoveryInterface) (*namespaceViaDiscovery, error) {
fallback, err := getDefaultNamespace()
if err != nil {
return nil, err
}
return &namespaceViaDiscovery{fallbackNamespace: fallback, disco: d}, nil
}

// getDefaultNamespace returns the fallback namespace used by the
// when a namespaced resource doesn't have one specified. This is
// used when syncing to anticipate the identity of a resource in the
// cluster given the manifest from a file (which may be missing the
// namespace).
// A variable is used for mocking in tests.
var getDefaultNamespace = func() (string, error) {
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.NewDefaultClientConfigLoadingRules(),
&clientcmd.ConfigOverrides{},
).RawConfig()
if err != nil {
return "", err
}

cc := config.CurrentContext
if c, ok := config.Contexts[cc]; ok && c.Namespace != "" {
return c.Namespace, nil
}

return defaultFallbackNamespace, nil
}

// effectiveNamespace yields the namespace that would be used for this
// resource were it applied, taking into account the kind of the
// resource, and local configuration.
Expand Down
42 changes: 34 additions & 8 deletions cluster/kubernetes/namespacer_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package kubernetes

import (
"io/ioutil"
"os"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -9,12 +11,6 @@ import (
kresource "github.com/weaveworks/flux/cluster/kubernetes/resource"
)

type namespaceDefaulterFake string

func (ns namespaceDefaulterFake) GetDefaultNamespace() (string, error) {
return string(ns), nil
}

var getAndList = metav1.Verbs([]string{"get", "list"})

func makeFakeClient() *corefake.Clientset {
Expand All @@ -39,8 +35,38 @@ func makeFakeClient() *corefake.Clientset {
}

func TestNamespaceDefaulting(t *testing.T) {
testKubeconfig := `apiVersion: v1
clusters: []
contexts:
- context:
cluster: cluster
namespace: namespace
user: user
name: context
current-context: context
kind: Config
preferences: {}
users: []
`
err := ioutil.WriteFile("testkubeconfig", []byte(testKubeconfig), 0600)
if err != nil {
t.Fatal("cannot create test kubeconfig file")
}
defer os.Remove("testkubeconfig")

os.Setenv("KUBECONFIG", "testkubeconfig")
defer os.Unsetenv("KUBECONFIG")
coreClient := makeFakeClient()
nser, err := NewNamespacer(namespaceDefaulterFake("fallback-ns"), coreClient.Discovery())

ns, err := getDefaultNamespace()
if err != nil {
t.Fatal("cannot get default namespace")
}
if ns != "namespace" {
t.Fatal("unexpected default namespace", ns)
}

nser, err := NewNamespacer(coreClient.Discovery())
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -86,6 +112,6 @@ metadata:
}

assertEffectiveNamespace("foo-ns:deployment/hasNamespace", "foo-ns")
assertEffectiveNamespace("<cluster>:deployment/noNamespace", "fallback-ns")
assertEffectiveNamespace("<cluster>:deployment/noNamespace", "namespace")
assertEffectiveNamespace("spurious:namespace/notNamespaced", "")
}
29 changes: 0 additions & 29 deletions cluster/kubernetes/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ import (
const (
syncSetLabel = kresource.PolicyPrefix + "sync-set"
checksumAnnotation = kresource.PolicyPrefix + "sync-checksum"

// The namespace to presume if something doesn't have one, and we
// haven't been told what to use as a fallback. This is what
// `kubectl` uses when there's no config setting the fallback
// namespace.
DefaultFallbackNamespace = "default"
)

// Sync takes a definition of what should be running in the cluster,
Expand Down Expand Up @@ -334,29 +328,6 @@ func (c *Kubectl) connectArgs() []string {
return args
}

// GetDefaultNamespace returns the fallback namespace used by the
// applied when a namespaced resource doesn't have one specified. This
// is used when syncing to anticipate the identity of a resource in
// the cluster given the manifest from a file (which may be missing
// the namespace).
func (k *Kubectl) GetDefaultNamespace() (string, error) {
cmd := k.kubectlCommand("config", "get-contexts", "--no-headers")
out, err := cmd.Output()
if err != nil {
return "", err
}
lines := bytes.Split(out, []byte("\n"))
for _, line := range lines {
words := bytes.Fields(line)
if len(words) > 1 && string(words[0]) == "*" {
if len(words) == 5 {
return string(words[4]), nil
}
}
}
return DefaultFallbackNamespace, nil
}

// rankOfKind returns an int denoting the position of the given kind
// in the partial ordering of Kubernetes resources, according to which
// kinds depend on which (derived by hand).
Expand Down
5 changes: 4 additions & 1 deletion cluster/kubernetes/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,10 @@ metadata:
}

test := func(t *testing.T, kube *Cluster, defs, expectedAfterSync string, expectErrors bool) {
namespacer, err := NewNamespacer(namespaceDefaulterFake(defaultTestNamespace), kube.client.coreClient.Discovery())
saved := getDefaultNamespace
getDefaultNamespace = func() (string, error) { return defaultTestNamespace, nil }
defer func() { getDefaultNamespace = saved }()
namespacer, err := NewNamespacer(kube.client.coreClient.Discovery())
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/fluxd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ func main() {
// There is only one way we currently interpret a repo of
// files as manifests, and that's as Kubernetes yamels.
k8sManifests = &kubernetes.Manifests{}
k8sManifests.Namespacer, err = kubernetes.NewNamespacer(kubectlApplier, discoClientset)
k8sManifests.Namespacer, err = kubernetes.NewNamespacer(discoClientset)

if err != nil {
logger.Log("err", err)
Expand Down

0 comments on commit aeef2c6

Please sign in to comment.