mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Create should be able to accept multiple resources
This commit is contained in:
parent
2151afe334
commit
d1ab27762b
@ -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
|
||||
|
@ -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"
|
||||
@ -93,16 +95,20 @@ type testFactory struct {
|
||||
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{},
|
||||
}
|
||||
return &Factory{
|
||||
Mapper: mapper,
|
||||
Typer: scheme,
|
||||
RESTClient: func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) {
|
||||
Object: func(*cobra.Command) (meta.RESTMapper, runtime.ObjectTyper) {
|
||||
return mapper, scheme
|
||||
},
|
||||
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 +117,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 +140,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
|
||||
}
|
||||
|
||||
|
@ -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 -
|
||||
<create a pod based on the json passed into stdin>`,
|
||||
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
|
||||
}
|
||||
|
120
pkg/kubectl/cmd/create_test.go
Normal file
120
pkg/kubectl/cmd/create_test.go
Normal file
@ -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())
|
||||
}
|
||||
}
|
@ -78,8 +78,8 @@ Examples:
|
||||
$ cat config.json | kubectl apply -f -
|
||||
<creates all resources listed in config.json>`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) {
|
||||
client, err := f.RESTClient(cmd, mapper)
|
||||
clientFunc := func(mapping *meta.RESTMapping) (config.RESTClientPoster, error) {
|
||||
client, err := f.RESTClient(cmd, mapping)
|
||||
checkErr(err)
|
||||
return client, nil
|
||||
}
|
||||
@ -98,12 +98,13 @@ Examples:
|
||||
files = append(GetFilesFromDir(directory, ".json"), GetFilesFromDir(directory, ".yaml")...)
|
||||
}
|
||||
|
||||
mapper, typer := f.Object(cmd)
|
||||
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)
|
||||
items, errs := DataToObjects(mapper, typer, data)
|
||||
applyErrs := config.CreateObjects(typer, mapper, clientFunc, items)
|
||||
|
||||
errs = append(errs, applyErrs...)
|
||||
if len(errs) > 0 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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...).
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user