diff --git a/contrib/flags2yaml/README.md b/contrib/flags2yaml/README.md index 6eff0432d4e..147832a825e 100644 --- a/contrib/flags2yaml/README.md +++ b/contrib/flags2yaml/README.md @@ -4,5 +4,5 @@ ### Usage ``` -$ flags2yaml image=dockerfile/nginx | simplegen - | cluster/kubectl.sh createall -f - +$ flags2yaml image=dockerfile/nginx | simplegen - | cluster/kubectl.sh create -f - ``` diff --git a/contrib/simplegen/README.md b/contrib/simplegen/README.md index a06a5957b4f..ec64e1fad75 100644 --- a/contrib/simplegen/README.md +++ b/contrib/simplegen/README.md @@ -41,8 +41,8 @@ portSpec: 10001:6379 ``` Output: ``` -$ simplegen redismaster.yaml | cluster/kubectl.sh createall -f - -$ simplegen redisslave.yaml | cluster/kubectl.sh createall -f - +$ simplegen redismaster.yaml | cluster/kubectl.sh create -f - +$ simplegen redisslave.yaml | cluster/kubectl.sh create -f - $ cluster/kubectl.sh get services NAME LABELS SELECTOR IP PORT kubernetes-ro component=apiserver,provider=kubernetes 10.0.0.2 80 diff --git a/contrib/simplegen/simplegen.go b/contrib/simplegen/simplegen.go index d9d6d32c506..5faae984326 100644 --- a/contrib/simplegen/simplegen.go +++ b/contrib/simplegen/simplegen.go @@ -16,8 +16,8 @@ limitations under the License. // simplegen is a tool to generate simple services from a simple description // -// $ simplegen myservice.json | kubectl createall -f - -// $ simplegen myservice.yaml | kubectl createall -f - +// $ simplegen myservice.json | kubectl create -f - +// $ simplegen myservice.yaml | kubectl create -f - // // This is completely separate from kubectl at the moment, until we figure out // what the right integration approach is. diff --git a/contrib/srvexpand/srvexpand.go b/contrib/srvexpand/srvexpand.go index 073589ac7fc..512dbd815c3 100644 --- a/contrib/srvexpand/srvexpand.go +++ b/contrib/srvexpand/srvexpand.go @@ -17,8 +17,8 @@ limitations under the License. // srvexpand is a tool to generate non-trivial but regular services // from a description free of most boilerplate // -// $ srvexpand myservice.json | kubectl createall -f - -// $ srvexpand myservice.yaml | kubectl createall -f - +// $ srvexpand myservice.json | kubectl create -f - +// $ srvexpand myservice.yaml | kubectl create -f - // // This is completely separate from kubectl at the moment, until we figure out // what the right integration approach is. diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 106c6d335ba..9ff3e8c0c56 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -48,16 +48,15 @@ type Factory struct { clients *clientCache flags *pflag.FlagSet - Mapper meta.RESTMapper - Typer runtime.ObjectTyper - + // Returns interfaces for dealing with arbitrary runtime.Objects. + Object func(cmd *cobra.Command) (meta.RESTMapper, runtime.ObjectTyper) // Returns a client for accessing Kubernetes resources or an error. Client func(cmd *cobra.Command) (*client.Client, error) // Returns a client.Config for accessing the Kubernetes server. ClientConfig func(cmd *cobra.Command) (*client.Config, error) // Returns a RESTClient for working with the specified RESTMapping or an error. This is intended // for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. - RESTClient func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) + RESTClient func(cmd *cobra.Command, mapping *meta.RESTMapping) (resource.RESTClient, error) // Returns a Describer for displaying the specified RESTMapping type or an error. Describer func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error) // Returns a Printer for formatting objects of the given type or an error. @@ -81,16 +80,17 @@ func NewFactory() *Factory { clients: clients, flags: flags, - Mapper: mapper, - Typer: api.Scheme, - + Object: func(cmd *cobra.Command) (meta.RESTMapper, runtime.ObjectTyper) { + version := GetFlagString(cmd, "api-version") + return kubectl.OutputVersionMapper{mapper, version}, api.Scheme + }, Client: func(cmd *cobra.Command) (*client.Client, error) { return clients.ClientForVersion("") }, ClientConfig: func(cmd *cobra.Command) (*client.Config, error) { return clients.ClientConfigForVersion("") }, - RESTClient: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) { + RESTClient: func(cmd *cobra.Command, mapping *meta.RESTMapping) (resource.RESTClient, error) { client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err @@ -165,7 +165,6 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, cmds.AddCommand(f.NewCmdGet(out)) cmds.AddCommand(f.NewCmdDescribe(out)) cmds.AddCommand(f.NewCmdCreate(out)) - cmds.AddCommand(f.NewCmdCreateAll(out)) cmds.AddCommand(f.NewCmdUpdate(out)) cmds.AddCommand(f.NewCmdDelete(out)) diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index d8dea7ff1fb..5331ad06846 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -25,8 +25,10 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" . "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/spf13/cobra" @@ -90,19 +92,27 @@ func (t *testDescriber) Describe(namespace, name string) (output string, err err } type testFactory struct { + Mapper meta.RESTMapper + Typer runtime.ObjectTyper Client kubectl.RESTClient Describer kubectl.Describer Printer kubectl.ResourcePrinter + Validator validation.Schema Err error } func NewTestFactory() (*Factory, *testFactory, runtime.Codec) { scheme, mapper, codec := newExternalScheme() - t := &testFactory{} + t := &testFactory{ + Validator: validation.NullSchema{}, + Mapper: mapper, + Typer: scheme, + } return &Factory{ - Mapper: mapper, - Typer: scheme, - RESTClient: func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) { + Object: func(*cobra.Command) (meta.RESTMapper, runtime.ObjectTyper) { + return t.Mapper, t.Typer + }, + RESTClient: func(*cobra.Command, *meta.RESTMapping) (resource.RESTClient, error) { return t.Client, t.Err }, Describer: func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error) { @@ -111,15 +121,21 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) { Printer: func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) { return t.Printer, t.Err }, + Validator: func(cmd *cobra.Command) (validation.Schema, error) { + return t.Validator, t.Err + }, }, t, codec } func NewAPIFactory() (*Factory, *testFactory, runtime.Codec) { - t := &testFactory{} + t := &testFactory{ + Validator: validation.NullSchema{}, + } return &Factory{ - Mapper: latest.RESTMapper, - Typer: api.Scheme, - RESTClient: func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) { + Object: func(*cobra.Command) (meta.RESTMapper, runtime.ObjectTyper) { + return latest.RESTMapper, api.Scheme + }, + RESTClient: func(*cobra.Command, *meta.RESTMapping) (resource.RESTClient, error) { return t.Client, t.Err }, Describer: func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error) { @@ -128,6 +144,9 @@ func NewAPIFactory() (*Factory, *testFactory, runtime.Codec) { Printer: func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) { return t.Printer, t.Err }, + Validator: func(cmd *cobra.Command) (validation.Schema, error) { + return t.Validator, t.Err + }, }, t, latest.Codec } diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 6d5502550d3..8705dda2767 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -20,11 +20,16 @@ import ( "fmt" "io" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/spf13/cobra" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) func (f *Factory) NewCmdCreate(out io.Writer) *cobra.Command { + flags := &struct { + Filenames util.StringList + }{} cmd := &cobra.Command{ Use: "create -f filename", Short: "Create a resource by filename or stdin", @@ -39,29 +44,35 @@ Examples: $ cat pod.json | kubectl create -f - `, Run: func(cmd *cobra.Command, args []string) { - filename := GetFlagString(cmd, "filename") - if len(filename) == 0 { - usageError(cmd, "Must specify filename to create") - } schema, err := f.Validator(cmd) checkErr(err) - mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema) - client, err := f.RESTClient(cmd, mapping) - checkErr(err) - // use the default namespace if not specified, or check for conflict with the file's namespace - if len(namespace) == 0 { - namespace = GetKubeNamespace(cmd) - } else { - err = CompareNamespaceFromFile(cmd, namespace) - checkErr(err) - } + mapper, typer := f.Object(cmd) + r := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)). + ContinueOnError(). + NamespaceParam(GetKubeNamespace(cmd)).RequireNamespace(). + FilenameParam(flags.Filenames...). + Flatten(). + Do() - err = resource.NewHelper(client, mapping).Create(namespace, true, data) + err = r.Visit(func(info *resource.Info) error { + data, err := info.Mapping.Codec.Encode(info.Object) + if err != nil { + return err + } + if err := schema.ValidateBytes(data); err != nil { + return err + } + if err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, data); err != nil { + return err + } + // TODO: if generation of names added to server side, change this to use the server's name + fmt.Fprintf(out, "%s\n", info.Name) + return nil + }) checkErr(err) - fmt.Fprintf(out, "%s\n", name) }, } - cmd.Flags().StringP("filename", "f", "", "Filename or URL to file to use to create the resource") + cmd.Flags().VarP(&flags.Filenames, "filename", "f", "Filename, directory, or URL to file to use to create the resource") return cmd } diff --git a/pkg/kubectl/cmd/create_test.go b/pkg/kubectl/cmd/create_test.go new file mode 100644 index 00000000000..a8b6fa0289d --- /dev/null +++ b/pkg/kubectl/cmd/create_test.go @@ -0,0 +1,120 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 cmd_test + +import ( + "bytes" + "net/http" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" +) + +func TestCreateObject(t *testing.T) { + pods, _ := testData() + + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &client.FakeRESTClient{ + Codec: codec, + Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/ns/test/pods" && m == "POST": + return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + buf := bytes.NewBuffer([]byte{}) + + cmd := f.NewCmdCreate(buf) + cmd.Flags().String("namespace", "test", "") + cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json") + cmd.Run(cmd, []string{}) + + // uses the name from the file, not the response + if buf.String() != "redis-master\n" { + t.Errorf("unexpected output: %s", buf.String()) + } +} + +func TestCreateMultipleObject(t *testing.T) { + pods, svc := testData() + + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &client.FakeRESTClient{ + Codec: codec, + Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/ns/test/pods" && m == "POST": + return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil + case p == "/ns/test/services" && m == "POST": + return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + buf := bytes.NewBuffer([]byte{}) + + cmd := f.NewCmdCreate(buf) + cmd.Flags().String("namespace", "test", "") + cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json") + cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json") + cmd.Run(cmd, []string{}) + + if buf.String() != "redis-master\nfrontend\n" { + t.Errorf("unexpected output: %s", buf.String()) + } +} + +func TestCreateDirectory(t *testing.T) { + pods, svc := testData() + + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &client.FakeRESTClient{ + Codec: codec, + Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/ns/test/pods" && m == "POST": + return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil + case p == "/ns/test/services" && m == "POST": + return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil + case p == "/ns/test/replicationcontrollers" && m == "POST": + return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + buf := bytes.NewBuffer([]byte{}) + + cmd := f.NewCmdCreate(buf) + cmd.Flags().String("namespace", "test", "") + cmd.Flags().Set("filename", "../../../examples/guestbook") + cmd.Run(cmd, []string{}) + + if buf.String() != "frontendController\nfrontend\nredis-master\nredis-master\nredisSlaveController\nredisslave\n" { + t.Errorf("unexpected output: %s", buf.String()) + } +} diff --git a/pkg/kubectl/cmd/createall.go b/pkg/kubectl/cmd/createall.go deleted file mode 100644 index 694fdf9d542..00000000000 --- a/pkg/kubectl/cmd/createall.go +++ /dev/null @@ -1,120 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -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 cmd - -import ( - "fmt" - "io" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" - "github.com/GoogleCloudPlatform/kubernetes/pkg/config" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" - "github.com/ghodss/yaml" - "github.com/golang/glog" - "github.com/spf13/cobra" -) - -// DataToObjects converts the raw JSON data into API objects -func DataToObjects(m meta.RESTMapper, t runtime.ObjectTyper, data []byte) (result []runtime.Object, errors []error) { - configObj := []runtime.RawExtension{} - - if err := yaml.Unmarshal(data, &configObj); err != nil { - errors = append(errors, fmt.Errorf("config unmarshal: %v", err)) - return result, errors - } - - for i, in := range configObj { - version, kind, err := t.DataVersionAndKind(in.RawJSON) - if err != nil { - errors = append(errors, fmt.Errorf("item[%d] kind: %v", i, err)) - continue - } - - mapping, err := m.RESTMapping(kind, version) - if err != nil { - errors = append(errors, fmt.Errorf("item[%d] mapping: %v", i, err)) - continue - } - - obj, err := mapping.Codec.Decode(in.RawJSON) - if err != nil { - errors = append(errors, fmt.Errorf("item[%d] decode: %v", i, err)) - continue - } - result = append(result, obj) - } - return -} - -func (f *Factory) NewCmdCreateAll(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "createall [-d directory] [-f filename]", - Short: "Create all resources specified in a directory, filename or stdin", - Long: `Create all resources contained in JSON file specified in a directory, filename or stdin - -JSON and YAML formats are accepted. - -Examples: - $ kubectl createall -d configs/ - - - $ kubectl createall -f config.json - - - $ cat config.json | kubectl apply -f - - `, - Run: func(cmd *cobra.Command, args []string) { - clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) { - client, err := f.RESTClient(cmd, mapper) - checkErr(err) - return client, nil - } - - filename := GetFlagString(cmd, "filename") - directory := GetFlagString(cmd, "directory") - if (len(filename) == 0 && len(directory) == 0) || (len(filename) != 0 && len(directory) != 0) { - usageError(cmd, "Must pass a directory or filename to update") - } - - files := []string{} - if len(filename) != 0 { - files = append(files, filename) - - } else { - files = append(GetFilesFromDir(directory, ".json"), GetFilesFromDir(directory, ".yaml")...) - } - - for _, filename := range files { - data, err := ReadConfigData(filename) - checkErr(err) - - items, errs := DataToObjects(f.Mapper, f.Typer, data) - applyErrs := config.CreateObjects(f.Typer, f.Mapper, clientFunc, items) - - errs = append(errs, applyErrs...) - if len(errs) > 0 { - for _, e := range errs { - glog.Error(e) - } - } - } - }, - } - cmd.Flags().StringP("directory", "d", "", "Directory of JSON or YAML files to use to update the resource") - cmd.Flags().StringP("filename", "f", "", "Filename or URL to file to use to update the resource") - return cmd -} diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index ab6bfcd77eb..f7a45dc8a8a 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -59,7 +59,8 @@ Examples: checkErr(err) selector := GetFlagString(cmd, "selector") found := 0 - ResourcesFromArgsOrFile(cmd, args, filename, selector, f.Typer, f.Mapper, f.RESTClient, schema, true).Visit(func(r *resource.Info) error { + mapper, typer := f.Object(cmd) + ResourcesFromArgsOrFile(cmd, args, filename, selector, typer, mapper, f.RESTClient, schema, true).Visit(func(r *resource.Info) error { found++ if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil { return err diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index b66e8ea6d53..e5d27984ab2 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -32,7 +32,8 @@ func (f *Factory) NewCmdDescribe(out io.Writer) *cobra.Command { This command joins many API calls together to form a detailed description of a given resource.`, Run: func(cmd *cobra.Command, args []string) { - mapping, namespace, name := ResourceFromArgs(cmd, args, f.Mapper) + mapper, _ := f.Object(cmd) + mapping, namespace, name := ResourceFromArgs(cmd, args, mapper) describer, err := f.Describer(cmd, mapping) checkErr(err) diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 16b3d1b7f7f..bc9f0d1579b 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -74,11 +74,12 @@ Examples: // TODO: return an error instead of using glog.Fatal and checkErr func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { selector := GetFlagString(cmd, "selector") + mapper, typer := f.Object(cmd) // handle watch separately since we cannot watch multiple resource types isWatch, isWatchOnly := GetFlagBool(cmd, "watch"), GetFlagBool(cmd, "watch-only") if isWatch || isWatchOnly { - r := resource.NewBuilder(f.Mapper, f.Typer, ClientMapperForCommand(cmd, f)). + r := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)). NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace(). SelectorParam(selector). ResourceTypeOrNameArgs(args...). @@ -117,7 +118,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { printer, generic, err := printerForCommand(cmd) checkErr(err) - b := resource.NewBuilder(f.Mapper, f.Typer, ClientMapperForCommand(cmd, f)). + b := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)). NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace(). SelectorParam(selector). ResourceTypeOrNameArgs(args...). diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index d0ea2f0920c..9ea81450883 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -90,8 +90,8 @@ func TestGetUnknownSchemaObject(t *testing.T) { // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. func TestGetSchemaObject(t *testing.T) { f, tf, _ := NewTestFactory() - f.Mapper = latest.RESTMapper - f.Typer = api.Scheme + tf.Mapper = latest.RESTMapper + tf.Typer = api.Scheme codec := latest.Codec tf.Printer = &testPrinter{} tf.Client = &client.FakeRESTClient{ diff --git a/pkg/kubectl/cmd/resource.go b/pkg/kubectl/cmd/resource.go index 43a41fee79a..060e9f966d7 100644 --- a/pkg/kubectl/cmd/resource.go +++ b/pkg/kubectl/cmd/resource.go @@ -24,7 +24,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -39,7 +38,7 @@ func ResourcesFromArgsOrFile( filename, selector string, typer runtime.ObjectTyper, mapper meta.RESTMapper, - clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error), + clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (resource.RESTClient, error), schema validation.Schema, requireNames bool, ) resource.Visitor { diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index 5a8ed9818a6..1f20afb8239 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -61,7 +61,8 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f - oldName := args[0] schema, err := f.Validator(cmd) checkErr(err) - mapping, namespace, newName, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema) + mapper, typer := f.Object(cmd) + mapping, namespace, newName, data := ResourceFromFile(cmd, filename, typer, mapper, schema) if mapping.Kind != "ReplicationController" { usageError(cmd, "%s does not specify a valid ReplicationController", filename) } diff --git a/pkg/kubectl/cmd/update.go b/pkg/kubectl/cmd/update.go index eb7d05b1bbe..13403b20bf1 100644 --- a/pkg/kubectl/cmd/update.go +++ b/pkg/kubectl/cmd/update.go @@ -45,7 +45,8 @@ Examples: } schema, err := f.Validator(cmd) checkErr(err) - mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema) + mapper, typer := f.Object(cmd) + mapping, namespace, name, data := ResourceFromFile(cmd, filename, typer, mapper, schema) client, err := f.RESTClient(cmd, mapping) checkErr(err) diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go index ea09542047d..07530b4ef25 100644 --- a/pkg/kubectl/kubectl.go +++ b/pkg/kubectl/kubectl.go @@ -112,6 +112,19 @@ func makeImageList(spec *api.PodSpec) string { return strings.Join(listOfImages(spec), ",") } +// OutputVersionMapper is a RESTMapper that will prefer mappings that +// correspond to a preferred output version (if feasible) +type OutputVersionMapper struct { + meta.RESTMapper + OutputVersion string +} + +// RESTMapping implements meta.RESTMapper by prepending the output version to the preferred version list. +func (m OutputVersionMapper) RESTMapping(kind string, versions ...string) (*meta.RESTMapping, error) { + preferred := append([]string{m.OutputVersion}, versions...) + return m.RESTMapper.RESTMapping(kind, preferred...) +} + // ShortcutExpander is a RESTMapper that can be used for Kubernetes // resources. type ShortcutExpander struct { diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 87553d6d2dd..e589aa9325f 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -262,6 +262,8 @@ func (b *Builder) ContinueOnError() *Builder { return b } +// SingleResourceType will cause the builder to error if the user specifies more than a single type +// of resource. func (b *Builder) SingleResourceType() *Builder { b.singleResourceType = true return b