Merge pull request #10493 from krousey/namespace

Unify command line namespace resolution
This commit is contained in:
Zach Loafman 2015-07-01 19:33:44 -07:00
commit 5dbe90f0ba
23 changed files with 120 additions and 57 deletions

View File

@ -47,8 +47,10 @@ type ClientConfig interface {
RawConfig() (clientcmdapi.Config, error)
// ClientConfig returns a complete client config
ClientConfig() (*client.Config, error)
// Namespace returns the namespace resulting from the merged result of all overrides
Namespace() (string, error)
// Namespace returns the namespace resulting from the merged
// result of all overrides and a boolean indicating if it was
// overridden
Namespace() (string, bool, error)
}
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
@ -207,17 +209,22 @@ func canIdentifyUser(config client.Config) bool {
}
// Namespace implements KubeConfig
func (config DirectClientConfig) Namespace() (string, error) {
func (config DirectClientConfig) Namespace() (string, bool, error) {
if err := config.ConfirmUsable(); err != nil {
return "", err
return "", false, err
}
configContext := config.getContext()
if len(configContext.Namespace) == 0 {
return api.NamespaceDefault, nil
return api.NamespaceDefault, false, nil
}
return configContext.Namespace, nil
overridden := false
if config.overrides != nil && config.overrides.Context.Namespace != "" {
overridden = true
}
return configContext.Namespace, overridden, nil
}
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,

View File

@ -52,17 +52,32 @@ func TestMergeContext(t *testing.T) {
const namespace = "overriden-namespace"
config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
_, overridden, err := clientBuilder.Namespace()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if overridden {
t.Error("Expected namespace to not be overridden")
}
clientBuilder = NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
Context: clientcmdapi.Context{
Namespace: namespace,
},
})
actual, err := clientBuilder.Namespace()
actual, overridden, err := clientBuilder.Namespace()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !overridden {
t.Error("Expected namespace to be overridden")
}
matchStringArg(namespace, actual, t)
}

View File

@ -94,10 +94,10 @@ func (config DeferredLoadingClientConfig) ClientConfig() (*client.Config, error)
}
// Namespace implements KubeConfig
func (config DeferredLoadingClientConfig) Namespace() (string, error) {
func (config DeferredLoadingClientConfig) Namespace() (string, bool, error) {
mergedKubeConfig, err := config.createClientConfig()
if err != nil {
return "", err
return "", false, err
}
return mergedKubeConfig.Namespace()

View File

@ -57,7 +57,7 @@ func RunClusterInfo(factory *cmdutil.Factory, out io.Writer, cmd *cobra.Command)
printService(out, "Kubernetes master", client.Host)
mapper, typer := factory.Object()
cmdNamespace, err := factory.DefaultNamespace()
cmdNamespace, _, err := factory.DefaultNamespace()
if err != nil {
return err
}

View File

@ -146,8 +146,8 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
Validator: func() (validation.Schema, error) {
return t.Validator, t.Err
},
DefaultNamespace: func() (string, error) {
return t.Namespace, t.Err
DefaultNamespace: func() (string, bool, error) {
return t.Namespace, false, t.Err
},
ClientConfig: func() (*client.Config, error) {
return t.ClientConfig, t.Err
@ -200,8 +200,8 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
Validator: func() (validation.Schema, error) {
return t.Validator, t.Err
},
DefaultNamespace: func() (string, error) {
return t.Namespace, t.Err
DefaultNamespace: func() (string, bool, error) {
return t.Namespace, false, t.Err
},
ClientConfig: func() (*client.Config, error) {
return t.ClientConfig, t.Err

View File

@ -75,7 +75,7 @@ func RunCreate(f *cmdutil.Factory, out io.Writer, filenames util.StringList) err
return err
}
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
@ -84,8 +84,8 @@ func RunCreate(f *cmdutil.Factory, out io.Writer, filenames util.StringList) err
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(filenames...).
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, filenames...).
Flatten().
Do()
err = r.Err()

View File

@ -82,7 +82,7 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command {
}
func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error {
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
@ -90,7 +90,7 @@ func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(filenames...).
FilenameParam(enforceNamespace, filenames...).
SelectorParam(cmdutil.GetFlagString(cmd, "selector")).
SelectAllParam(cmdutil.GetFlagBool(cmd, "all")).
ResourceTypeOrNameArgs(false, args...).RequireObject(false).

View File

@ -59,7 +59,7 @@ $ kubectl describe po -l name=myLabel`,
func RunDescribe(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
selector := cmdutil.GetFlagString(cmd, "selector")
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -104,7 +104,7 @@ func extractPodAndContainer(cmd *cobra.Command, argsIn []string, p *execParams)
func RunExec(f *cmdutil.Factory, cmd *cobra.Command, cmdIn io.Reader, cmdOut, cmdErr io.Writer, p *execParams, argsIn []string, re remoteExecutor) error {
podName, containerName, args, err := extractPodAndContainer(cmd, argsIn, p)
namespace, err := f.DefaultNamespace()
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -74,7 +74,7 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command {
}
func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
namespace, err := f.DefaultNamespace()
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -95,7 +95,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string
allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces")
mapper, typer := f.Object()
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -180,7 +180,7 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
overwrite := cmdutil.GetFlagBool(cmd, "overwrite")
resourceVersion := cmdutil.GetFlagString(cmd, "resource-version")
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -100,7 +100,7 @@ func RunLog(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string
return cmdutil.UsageError(cmd, "log POD [CONTAINER]")
}
namespace, err := f.DefaultNamespace()
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -53,7 +53,7 @@ func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command {
}
func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -83,7 +83,7 @@ func RunPortForward(f *cmdutil.Factory, cmd *cobra.Command, args []string, fw po
return cmdutil.UsageError(cmd, "at least 1 PORT is required for port-forward")
}
namespace, err := f.DefaultNamespace()
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -77,7 +77,7 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st
return err
}
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
@ -95,8 +95,8 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(filenames...).
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, filenames...).
Flatten().
Do()
err = r.Err()
@ -126,7 +126,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
return err
}
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
@ -135,7 +135,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(filenames...).
FilenameParam(enforceNamespace, filenames...).
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
Flatten().
Do()
@ -159,8 +159,8 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
r = resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(filenames...).
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, filenames...).
Flatten().
Do()
err = r.Err()

View File

@ -117,7 +117,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
timeout := cmdutil.GetFlagDuration(cmd, "timeout")
dryrun := cmdutil.GetFlagBool(cmd, "dry-run")
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
@ -154,10 +154,11 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
if err != nil {
return err
}
request := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
Schema(schema).
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(filename).
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, filename).
Do()
obj, err := request.Object()
if err != nil {

View File

@ -78,7 +78,7 @@ func Run(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) e
return cmdutil.UsageError(cmd, "NAME is required for run")
}
namespace, err := f.DefaultNamespace()
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -74,7 +74,7 @@ func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
return cmdutil.UsageError(cmd, "--replicas=COUNT RESOURCE ID")
}
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

View File

@ -68,16 +68,16 @@ func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command {
}
func RunStop(f *cmdutil.Factory, cmd *cobra.Command, args []string, filenames util.StringList, out io.Writer) error {
cmdNamespace, err := f.DefaultNamespace()
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
NamespaceParam(cmdNamespace).DefaultNamespace().
ResourceTypeOrNameArgs(false, args...).
FilenameParam(filenames...).
FilenameParam(enforceNamespace, filenames...).
SelectorParam(cmdutil.GetFlagString(cmd, "selector")).
SelectAllParam(cmdutil.GetFlagBool(cmd, "all")).
Flatten().

View File

@ -78,8 +78,10 @@ type Factory struct {
LabelsForObject func(object runtime.Object) (map[string]string, error)
// Returns a schema that can validate objects stored on disk.
Validator func() (validation.Schema, error)
// Returns the default namespace to use in cases where no other namespace is specified
DefaultNamespace func() (string, error)
// Returns the default namespace to use in cases where no
// other namespace is specified and whether the namespace was
// overriden.
DefaultNamespace func() (string, bool, error)
// Returns the generator for the provided generator name
Generator func(name string) (kubectl.Generator, bool)
}
@ -209,7 +211,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
}
return validation.NullSchema{}, nil
},
DefaultNamespace: func() (string, error) {
DefaultNamespace: func() (string, bool, error) {
return clientConfig.Namespace()
},
Generator: func(name string) (kubectl.Generator, bool) {

View File

@ -87,9 +87,12 @@ func (b *Builder) Schema(schema validation.Schema) *Builder {
}
// FilenameParam groups input in two categories: URLs and files (files, directories, STDIN)
// If enforceNamespace is false, namespaces in the specs will be allowed to
// override the default namespace. If it is true, namespaces that don't match
// will cause an error.
// If ContinueOnError() is set prior to this method, objects on the path that are not
// recognized will be ignored (but logged at V(2)).
func (b *Builder) FilenameParam(paths ...string) *Builder {
func (b *Builder) FilenameParam(enforceNamespace bool, paths ...string) *Builder {
for _, s := range paths {
switch {
case s == "-":
@ -105,6 +108,11 @@ func (b *Builder) FilenameParam(paths ...string) *Builder {
b.Path(s)
}
}
if enforceNamespace {
b.RequireNamespace()
}
return b
}

View File

@ -177,7 +177,7 @@ func (v *testVisitor) Objects() []runtime.Object {
func TestPathBuilder(t *testing.T) {
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
FilenameParam("../../../examples/guestbook/redis-master-controller.yaml")
FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml")
test := &testVisitor{}
singular := false
@ -227,8 +227,8 @@ func TestNodeBuilder(t *testing.T) {
func TestPathBuilderWithMultiple(t *testing.T) {
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
FilenameParam("../../../examples/guestbook/redis-master-controller.yaml").
FilenameParam("../../../examples/guestbook/redis-master-controller.yaml").
FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").
FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").
NamespaceParam("test").DefaultNamespace()
test := &testVisitor{}
@ -247,7 +247,7 @@ func TestPathBuilderWithMultiple(t *testing.T) {
func TestDirectoryBuilder(t *testing.T) {
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
FilenameParam("../../../examples/guestbook").
FilenameParam(false, "../../../examples/guestbook").
NamespaceParam("test").DefaultNamespace()
test := &testVisitor{}
@ -269,6 +269,36 @@ func TestDirectoryBuilder(t *testing.T) {
}
}
func TestNamespaceOverride(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(runtime.EncodeOrDie(latest.Codec, &api.Pod{ObjectMeta: api.ObjectMeta{Namespace: "foo", Name: "test"}})))
}))
defer s.Close()
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
FilenameParam(false, s.URL).
NamespaceParam("test")
test := &testVisitor{}
err := b.Do().Visit(test.Handle)
if err != nil || len(test.Infos) != 1 && test.Infos[0].Namespace != "foo" {
t.Fatalf("unexpected response: %v %#v", err, test.Infos)
}
b = NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
FilenameParam(true, s.URL).
NamespaceParam("test")
test = &testVisitor{}
err = b.Do().Visit(test.Handle)
if err == nil {
t.Fatalf("expected namespace error. got: %#v", test.Infos)
}
}
func TestURLBuilder(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
@ -277,7 +307,7 @@ func TestURLBuilder(t *testing.T) {
defer s.Close()
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
FilenameParam(s.URL).
FilenameParam(false, s.URL).
NamespaceParam("test")
test := &testVisitor{}
@ -301,7 +331,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) {
defer s.Close()
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
FilenameParam(s.URL).
FilenameParam(false, s.URL).
NamespaceParam("test").RequireNamespace()
test := &testVisitor{}
@ -640,7 +670,7 @@ func TestContinueOnErrorVisitor(t *testing.T) {
func TestSingularObject(t *testing.T) {
obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
NamespaceParam("test").DefaultNamespace().
FilenameParam("../../../examples/guestbook/redis-master-controller.yaml").
FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").
Flatten().
Do().Object()
@ -751,7 +781,7 @@ func TestWatch(t *testing.T) {
}),
})).
NamespaceParam("test").DefaultNamespace().
FilenameParam("../../../examples/guestbook/redis-master-service.yaml").Flatten().
FilenameParam(false, "../../../examples/guestbook/redis-master-service.yaml").Flatten().
Do().Watch("12")
if err != nil {
@ -778,8 +808,8 @@ func TestWatch(t *testing.T) {
func TestWatchMultipleError(t *testing.T) {
_, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
NamespaceParam("test").DefaultNamespace().
FilenameParam("../../../examples/guestbook/redis-master-controller.yaml").Flatten().
FilenameParam("../../../examples/guestbook/redis-master-controller.yaml").Flatten().
FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").Flatten().
FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").Flatten().
Do().Watch("")
if err == nil {