diff --git a/cmd/fluxd/main.go b/cmd/fluxd/main.go index 8e9885f12..3e0b975af 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 39078642e..a97d95af9 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/cached_disco_test.go b/pkg/cluster/kubernetes/cached_disco_test.go index f0d9eb1f4..0e6e71ef8 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 43c2a8e73..ceb4e5725 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.go b/pkg/cluster/kubernetes/namespacer.go index db2c3ecc8..f238c2992 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 diff --git a/pkg/cluster/kubernetes/namespacer_test.go b/pkg/cluster/kubernetes/namespacer_test.go index 628613420..c853ce4af 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(":deployment/noNamespace", "namespace") - assertEffectiveNamespace("spurious:namespace/notNamespaced", "") + assertEffectiveNamespace(*defaultNser, "foo-ns:deployment/hasNamespace", "foo-ns") + assertEffectiveNamespace(*defaultNser, ":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, ":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 8dfc78c79..3880f6c90 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) }