Repository: fluxcd/flux
Pull request #2625 (2019-11-21 10:17:38)

add --k8s-default-namespace argument to override default namespace
<!--
# General contribution criteria

Please have a look at our contribution guidelines: https://github.com/fluxcd/flux/blob/master/CONTRIBUTING.md
Particularly the sections about the:

 - DCO;
 - contribution workflow; and
 - how to get your fix accepted

To help the maintainers out when they're writing release notes, please
try to include a sentence or two here describing your change for end
users. See the CHANGELOG.md file in the top-level directory for examples.

Particularly for ground-breaking changes and new features, it's important to
make users and developers aware of what's changing and where those changes
were documented or discussed.

Even for smaller changes it's useful to see things documented as well, as it
gives everybody a chance to see at a glance what's coming up in the next
release. It makes the life of the project maintainer a lot easier as well.

The following short checklist can be used to make sure your PR is of good
quality, and can be merged easily:

- [ ] if it resolves an issue;
      is a reference (i.e. #1) to this issue included?
- [ ] if it introduces a new functionality or configuration flag;
      did you document this in the references or guides?
- [ ] optional but much appreciated;
      do you think many users would profit from a dedicated setting
      for this functionality in the Helm chart?
-->


2opremio: For traceability, this overrides #2588 which was mysteriously closed by GitHub

Patch:
diff --git a/cmd/fluxd/main.go b/cmd/fluxd/main.go
index 8e9885f1..3e0b975a 100644
--- a/cmd/fluxd/main.go
+++ b/cmd/fluxd/main.go
@@ -18,7 +18,6 @@ import (
 	"syscall"
 	"time"
 
-	helmopclient "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned"
 	"github.com/go-kit/kit/log"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 	"github.com/spf13/pflag"
@@ -51,6 +50,7 @@ import (
 	"github.com/fluxcd/flux/pkg/remote"
 	"github.com/fluxcd/flux/pkg/ssh"
 	fluxsync "github.com/fluxcd/flux/pkg/sync"
+	helmopclient "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned"
 )
 
 var version = "unversioned"
@@ -170,10 +170,11 @@ func main() {
 		k8sSecretDataKey         = fs.String("k8s-secret-data-key", "identity", "data key holding the private SSH key within the k8s secret")
 
 		// k8s-scope settings
-		k8sNamespaceWhitelist    = fs.StringSlice("k8s-namespace-whitelist", []string{}, "restrict the view of the cluster to the namespaces listed. All namespaces are included if this is not set")
-		k8sAllowNamespace        = fs.StringSlice("k8s-allow-namespace", []string{}, "restrict all operations to the provided namespaces")
+		k8sNamespaceWhitelist = fs.StringSlice("k8s-namespace-whitelist", []string{}, "restrict the view of the cluster to the namespaces listed. All namespaces are included if this is not set")
+		k8sAllowNamespace     = fs.StringSlice("k8s-allow-namespace", []string{}, "restrict all operations to the provided namespaces")
+		k8sDefaultNamespace   = fs.String("k8s-default-namespace", "", "the namespace to use for resources where a namespace is not specified")
 
-		k8sVerbosity             = fs.Int("k8s-verbosity", 0, "klog verbosity level")
+		k8sVerbosity = fs.Int("k8s-verbosity", 0, "klog verbosity level")
 
 		// SSH key generation
 		sshKeyBits   = optionalVar(fs, &ssh.KeyBitsValue{}, "ssh-keygen-bits", "-b argument to ssh-keygen (default unspecified)")
@@ -502,7 +503,7 @@ func main() {
 		imageCreds = k8sInst.ImagesToFetch
 		// There is only one way we currently interpret a repo of
 		// files as manifests, and that's as Kubernetes yamels.
-		namespacer, err := kubernetes.NewNamespacer(discoClientset)
+		namespacer, err := kubernetes.NewNamespacer(discoClientset, *k8sDefaultNamespace)
 		if err != nil {
 			logger.Log("err", err)
 			os.Exit(1)
diff --git a/docs/references/daemon.md b/docs/references/daemon.md
index 39078642..a97d95af 100644
--- a/docs/references/daemon.md
+++ b/docs/references/daemon.md
@@ -85,6 +85,7 @@ Version controlling of cluster manifests provides reproducibility and a historic
 | --k8s-secret-data-key                            | `identity`                         | data key holding the private SSH key within the k8s secret
 | **k8s configuration**
 | --k8s-allow-namespace                            |                                    | restrict all operations to the provided namespaces
+| --k8s-default-namespace                          |                                    | the namespace to use for resources where a namespace is not specified
 | **upstream service**
 | --connect                                        |                                    | connect to an upstream service e.g., Weave Cloud, at this base address
 | --token                                          |                                    | authentication token for upstream service
diff --git a/pkg/cluster/kubernetes/namespacer.go b/pkg/cluster/kubernetes/namespacer.go
index db2c3ecc..f238c299 100644
--- a/pkg/cluster/kubernetes/namespacer.go
+++ b/pkg/cluster/kubernetes/namespacer.go
@@ -23,21 +23,28 @@ type namespaceViaDiscovery struct {
 }
 
 // NewNamespacer creates an implementation of Namespacer
-func NewNamespacer(d discovery.DiscoveryInterface) (*namespaceViaDiscovery, error) {
-	fallback, err := getDefaultNamespace()
+// If not empty `defaultNamespaceOverride` is used as the namespace when
+// a resource doesn't have a namespace specified. If empty the namespace
+// from the context in the KUBECONFIG is used, otherwise the "default"
+// namespace is used mimicking kubectl behavior
+func NewNamespacer(d discovery.DiscoveryInterface, defaultNamespaceOverride string) (*namespaceViaDiscovery, error) {
+	if defaultNamespaceOverride != "" {
+		return &namespaceViaDiscovery{fallbackNamespace: defaultNamespaceOverride, disco: d}, nil
+	}
+	kubeconfigDefaultNamespace, err := getKubeconfigDefaultNamespace()
 	if err != nil {
 		return nil, err
 	}
-	return &namespaceViaDiscovery{fallbackNamespace: fallback, disco: d}, nil
+	if kubeconfigDefaultNamespace != "" {
+		return &namespaceViaDiscovery{fallbackNamespace: kubeconfigDefaultNamespace, disco: d}, nil
+	}
+	return &namespaceViaDiscovery{fallbackNamespace: defaultFallbackNamespace, 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).
+// getKubeconfigDefaultNamespace returns the namespace specified
+// for the current config in KUBECONFIG
 // A variable is used for mocking in tests.
-var getDefaultNamespace = func() (string, error) {
+var getKubeconfigDefaultNamespace = func() (string, error) {
 	config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
 		clientcmd.NewDefaultClientConfigLoadingRules(),
 		&clientcmd.ConfigOverrides{},
@@ -51,7 +58,7 @@ var getDefaultNamespace = func() (string, error) {
 		return c.Namespace, nil
 	}
 
-	return defaultFallbackNamespace, nil
+	return "", nil
 }
 
 // effectiveNamespace yields the namespace that would be used for this


Test patch:
diff --git a/pkg/cluster/kubernetes/cached_disco_test.go b/pkg/cluster/kubernetes/cached_disco_test.go
index f0d9eb1f..0e6e71ef 100644
--- a/pkg/cluster/kubernetes/cached_disco_test.go
+++ b/pkg/cluster/kubernetes/cached_disco_test.go
@@ -36,10 +36,10 @@ func TestCachedDiscovery(t *testing.T) {
 
 	cachedDisco := MakeCachedDiscovery(coreClient.Discovery(), crdClient, shutdown)
 
-	saved := getDefaultNamespace
-	getDefaultNamespace = func() (string, error) { return "bar-ns", nil }
-	defer func() { getDefaultNamespace = saved }()
-	namespacer, err := NewNamespacer(cachedDisco)
+	saved := getKubeconfigDefaultNamespace
+	getKubeconfigDefaultNamespace = func() (string, error) { return "bar-ns", nil }
+	defer func() { getKubeconfigDefaultNamespace = saved }()
+	namespacer, err := NewNamespacer(cachedDisco, "")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/pkg/cluster/kubernetes/manifests_test.go b/pkg/cluster/kubernetes/manifests_test.go
index 43c2a8e7..ceb4e572 100644
--- a/pkg/cluster/kubernetes/manifests_test.go
+++ b/pkg/cluster/kubernetes/manifests_test.go
@@ -17,7 +17,7 @@ import (
 func TestLocalCRDScope(t *testing.T) {
 	coreClient := makeFakeClient()
 
-	nser, err := NewNamespacer(coreClient.Discovery())
+	nser, err := NewNamespacer(coreClient.Discovery(), "")
 	assert.NoError(t, err)
 	manifests := NewManifests(nser, log.NewLogfmtLogger(os.Stdout))
 
@@ -64,7 +64,7 @@ metadata:
 func TestUnKnownCRDScope(t *testing.T) {
 	coreClient := makeFakeClient()
 
-	nser, err := NewNamespacer(coreClient.Discovery())
+	nser, err := NewNamespacer(coreClient.Discovery(), "")
 	assert.NoError(t, err)
 	logBuffer := bytes.NewBuffer(nil)
 	manifests := NewManifests(nser, log.NewLogfmtLogger(logBuffer))
diff --git a/pkg/cluster/kubernetes/namespacer_test.go b/pkg/cluster/kubernetes/namespacer_test.go
index 62861342..c853ce4a 100644
--- a/pkg/cluster/kubernetes/namespacer_test.go
+++ b/pkg/cluster/kubernetes/namespacer_test.go
@@ -64,7 +64,7 @@ users: []
 	defer os.Unsetenv("KUBECONFIG")
 	coreClient := makeFakeClient()
 
-	ns, err := getDefaultNamespace()
+	ns, err := getKubeconfigDefaultNamespace()
 	if err != nil {
 		t.Fatal("cannot get default namespace")
 	}
@@ -72,11 +72,6 @@ users: []
 		t.Fatal("unexpected default namespace", ns)
 	}
 
-	nser, err := NewNamespacer(coreClient.Discovery())
-	if err != nil {
-		t.Fatal(err)
-	}
-
 	const defs = `---
 apiVersion: apps/v1
 kind: Deployment
@@ -101,7 +96,11 @@ metadata:
 		t.Fatal(err)
 	}
 
-	assertEffectiveNamespace := func(id, expected string) {
+	defaultNser, err := NewNamespacer(coreClient.Discovery(), "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	assertEffectiveNamespace := func(nser namespaceViaDiscovery, id, expected string) {
 		res, ok := manifests[id]
 		if !ok {
 			t.Errorf("manifest for %q not found", id)
@@ -117,7 +116,17 @@ metadata:
 		}
 	}
 
-	assertEffectiveNamespace("foo-ns:deployment/hasNamespace", "foo-ns")
-	assertEffectiveNamespace("<cluster>:deployment/noNamespace", "namespace")
-	assertEffectiveNamespace("spurious:namespace/notNamespaced", "")
+	assertEffectiveNamespace(*defaultNser, "foo-ns:deployment/hasNamespace", "foo-ns")
+	assertEffectiveNamespace(*defaultNser, "<cluster>:deployment/noNamespace", "namespace")
+	assertEffectiveNamespace(*defaultNser, "spurious:namespace/notNamespaced", "")
+
+	overrideNser, err := NewNamespacer(coreClient.Discovery(), "foo-override")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	assertEffectiveNamespace(*overrideNser, "foo-ns:deployment/hasNamespace", "foo-ns")
+	assertEffectiveNamespace(*overrideNser, "<cluster>:deployment/noNamespace", "foo-override")
+	assertEffectiveNamespace(*overrideNser, "spurious:namespace/notNamespaced", "")
+
 }
diff --git a/pkg/cluster/kubernetes/sync_test.go b/pkg/cluster/kubernetes/sync_test.go
index 8dfc78c7..3880f6c9 100644
--- a/pkg/cluster/kubernetes/sync_test.go
+++ b/pkg/cluster/kubernetes/sync_test.go
@@ -7,7 +7,6 @@ import (
 	"strings"
 	"testing"
 
-	helmopfake "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/fake"
 	"github.com/ghodss/yaml"
 	"github.com/go-kit/kit/log"
 	"github.com/stretchr/testify/assert"
@@ -30,6 +29,7 @@ import (
 	kresource "github.com/fluxcd/flux/pkg/cluster/kubernetes/resource"
 	"github.com/fluxcd/flux/pkg/resource"
 	"github.com/fluxcd/flux/pkg/sync"
+	helmopfake "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/fake"
 )
 
 const (
@@ -363,10 +363,10 @@ metadata:
 	}
 
 	test := func(t *testing.T, kube *Cluster, defs, expectedAfterSync string, expectErrors bool) {
-		saved := getDefaultNamespace
-		getDefaultNamespace = func() (string, error) { return defaultTestNamespace, nil }
-		defer func() { getDefaultNamespace = saved }()
-		namespacer, err := NewNamespacer(kube.client.coreClient.Discovery())
+		saved := getKubeconfigDefaultNamespace
+		getKubeconfigDefaultNamespace = func() (string, error) { return defaultTestNamespace, nil }
+		defer func() { getKubeconfigDefaultNamespace = saved }()
+		namespacer, err := NewNamespacer(kube.client.coreClient.Discovery(), "")
 		if err != nil {
 			t.Fatal(err)
 		}