Merge pull request #13837 from brendandburns/kubectl2

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2015-09-11 17:18:29 -07:00
commit cb481b4a00
15 changed files with 202 additions and 28 deletions

View File

@ -359,6 +359,7 @@ _kubectl_create()
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--output=")
two_word_flags+=("-o")
flags+=("--schema-cache-dir=")
flags+=("--validate")
must_have_one_flag=()
@ -388,6 +389,7 @@ _kubectl_replace()
flags+=("--grace-period=")
flags+=("--output=")
two_word_flags+=("-o")
flags+=("--schema-cache-dir=")
flags+=("--timeout=")
flags+=("--validate")
@ -534,6 +536,7 @@ _kubectl_rolling-update()
flags+=("--output-version=")
flags+=("--poll-interval=")
flags+=("--rollback")
flags+=("--schema-cache-dir=")
flags+=("--show-all")
flags+=("-a")
flags+=("--sort-by=")

View File

@ -28,6 +28,10 @@ JSON and YAML formats are accepted.
\fB\-o\fP, \fB\-\-output\fP=""
Output mode. Use "\-o name" for shorter output (resource/name).
.PP
\fB\-\-schema\-cache\-dir\fP="/tmp/kubectl.schema"
If non\-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema'
.PP
\fB\-\-validate\fP=true
If true, use a schema to validate the input before sending it

View File

@ -46,6 +46,10 @@ Please refer to the models in
\fB\-o\fP, \fB\-\-output\fP=""
Output mode. Use "\-o name" for shorter output (resource/name).
.PP
\fB\-\-schema\-cache\-dir\fP="/tmp/kubectl.schema"
If non\-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema'
.PP
\fB\-\-timeout\fP=0
Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object

View File

@ -60,6 +60,10 @@ existing replication controller and overwrite at least one (common) label in its
\fB\-\-rollback\fP=false
If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout
.PP
\fB\-\-schema\-cache\-dir\fP="/tmp/kubectl.schema"
If non\-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema'
.PP
\fB\-a\fP, \fB\-\-show\-all\fP=false
When printing, show all resources (default hide terminated pods.)

View File

@ -61,6 +61,7 @@ $ cat pod.json | kubectl create -f -
```
-f, --filename=[]: Filename, directory, or URL to file to use to create the resource
-o, --output="": Output mode. Use "-o name" for shorter output (resource/name).
--schema-cache-dir="/tmp/kubectl.schema": If non-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema'
--validate[=true]: If true, use a schema to validate the input before sending it
```
@ -96,7 +97,7 @@ $ cat pod.json | kubectl create -f -
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-09-10 18:53:03.152429973 +0000 UTC
###### Auto generated by spf13/cobra at 2015-09-11 20:48:33.289761103 +0000 UTC
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create.md?pixel)]()

View File

@ -74,6 +74,7 @@ kubectl replace --force -f ./pod.json
--force[=false]: Delete and re-create the specified resource
--grace-period=-1: Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.
-o, --output="": Output mode. Use "-o name" for shorter output (resource/name).
--schema-cache-dir="/tmp/kubectl.schema": If non-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema'
--timeout=0: Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object
--validate[=true]: If true, use a schema to validate the input before sending it
```
@ -110,7 +111,7 @@ kubectl replace --force -f ./pod.json
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-09-10 18:53:03.153166598 +0000 UTC
###### Auto generated by spf13/cobra at 2015-09-11 20:48:33.290279625 +0000 UTC
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_replace.md?pixel)]()

View File

@ -78,6 +78,7 @@ $ kubectl rolling-update frontend --image=image:v2
--output-version="": Output the formatted object with the given version (default api-version).
--poll-interval=3s: Time delay between polling for replication controller status after the update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
--rollback[=false]: If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout
--schema-cache-dir="/tmp/kubectl.schema": If non-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema'
-a, --show-all[=false]: When printing, show all resources (default hide terminated pods.)
--sort-by="": If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. 'ObjectMeta.Name'). The field in the API resource specified by this JSONPath expression must be an integer or a string.
--template="": Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
@ -118,7 +119,7 @@ $ kubectl rolling-update frontend --image=image:v2
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-09-10 18:53:03.154895732 +0000 UTC
###### Auto generated by spf13/cobra at 2015-09-11 20:48:33.293748592 +0000 UTC
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_rolling-update.md?pixel)]()

View File

@ -230,6 +230,7 @@ root-dir
run-proxy
runtime-config
scheduler-config
schema-cache-dir
secure-port
service-account-key-file
service-account-lookup

View File

@ -160,7 +160,7 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, wide bool, showAll bool, columnLabels []string) (kubectl.ResourcePrinter, error) {
return t.Printer, t.Err
},
Validator: func(validate bool) (validation.Schema, error) {
Validator: func(validate bool, cacheDir string) (validation.Schema, error) {
return t.Validator, t.Err
},
DefaultNamespace: func() (string, bool, error) {
@ -215,7 +215,7 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, wide bool, showAll bool, columnLabels []string) (kubectl.ResourcePrinter, error) {
return t.Printer, t.Err
},
Validator: func(validate bool) (validation.Schema, error) {
Validator: func(validate bool, cacheDir string) (validation.Schema, error) {
return t.Validator, t.Err
},
DefaultNamespace: func() (string, bool, error) {

View File

@ -65,7 +65,7 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
usage := "Filename, directory, or URL to file to use to create the resource"
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlag(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddOutputFlagsForMutation(cmd)
return cmd
}
@ -78,7 +78,7 @@ func ValidateArgs(cmd *cobra.Command, args []string) error {
}
func RunCreate(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *CreateOptions) error {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}

View File

@ -81,7 +81,7 @@ func NewCmdReplace(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().Bool("cascade", false, "Only relevant during a force replace. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController).")
cmd.Flags().Int("grace-period", -1, "Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.")
cmd.Flags().Duration("timeout", 0, "Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object")
cmdutil.AddValidateFlag(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddOutputFlagsForMutation(cmd)
return cmd
}
@ -90,7 +90,7 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st
if len(os.Args) > 1 && os.Args[1] == "update" {
printDeprecationWarning("replace", "update")
}
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}
@ -143,7 +143,7 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st
}
func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *ReplaceOptions) error {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}

View File

@ -95,7 +95,7 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().String("deployment-label-key", "deployment", "The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise")
cmd.Flags().Bool("dry-run", false, "If true, print out the changes that would be made, but don't actually make them.")
cmd.Flags().Bool("rollback", false, "If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout")
cmdutil.AddValidateFlag(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
return cmd
}
@ -172,7 +172,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
mapper, typer := f.Object()
if len(filename) != 0 {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}

View File

@ -17,10 +17,13 @@ limitations under the License.
package util
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strconv"
"github.com/spf13/cobra"
@ -76,7 +79,7 @@ type Factory struct {
// LabelsForObject returns the labels associated with the provided object
LabelsForObject func(object runtime.Object) (map[string]string, error)
// Returns a schema that can validate objects stored on disk.
Validator func(validate bool) (validation.Schema, error)
Validator func(validate bool, cacheDir string) (validation.Schema, error)
// Returns the default namespace to use in cases where no
// other namespace is specified and whether the namespace was
// overriden.
@ -214,13 +217,25 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
}
return kubectl.ReaperFor(mapping.Kind, client)
},
Validator: func(validate bool) (validation.Schema, error) {
Validator: func(validate bool, cacheDir string) (validation.Schema, error) {
if validate {
client, err := clients.ClientForVersion("")
if err != nil {
return nil, err
}
return &clientSwaggerSchema{client, client.ExperimentalClient, api.Scheme}, nil
dir := cacheDir
if len(dir) > 0 {
version, err := client.ServerVersion()
if err != nil {
return nil, err
}
dir = path.Join(cacheDir, version.String())
}
return &clientSwaggerSchema{
c: client,
ec: client.ExperimentalClient,
cacheDir: dir,
}, nil
}
return validation.NullSchema{}, nil
},
@ -273,18 +288,49 @@ func getServicePorts(spec api.ServiceSpec) []string {
}
type clientSwaggerSchema struct {
c *client.Client
ec *client.ExperimentalClient
t runtime.ObjectTyper
c *client.Client
ec *client.ExperimentalClient
cacheDir string
}
func getSchemaAndValidate(c *client.RESTClient, data []byte, group, version string) error {
schemaData, err := c.Get().
AbsPath("/swaggerapi", group, version).
Do().
Raw()
if err != nil {
return err
const schemaFileName = "schema.json"
type schemaClient interface {
Get() *client.Request
}
func getSchemaAndValidate(c schemaClient, data []byte, group, version, cacheDir string) (err error) {
var schemaData []byte
cacheFile := path.Join(cacheDir, group, version, schemaFileName)
if len(cacheDir) != 0 {
if schemaData, err = ioutil.ReadFile(cacheFile); err != nil && !os.IsNotExist(err) {
return err
}
}
if schemaData == nil {
schemaData, err = c.Get().
AbsPath("/swaggerapi", group, version).
Do().
Raw()
if err != nil {
return err
}
if len(cacheDir) != 0 {
if err = os.MkdirAll(path.Join(cacheDir, group, version), 0755); err != nil {
return err
}
tmpFile, err := ioutil.TempFile(cacheDir, "schema")
if err != nil {
return err
}
if _, err := io.Copy(tmpFile, bytes.NewBuffer(schemaData)); err != nil {
return err
}
if err := os.Link(tmpFile.Name(), cacheFile); err != nil && !os.IsExist(err) {
return err
}
}
}
schema, err := validation.NewSwaggerSchemaFromBytes(schemaData)
if err != nil {
@ -305,9 +351,9 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
// If experimental fails, return error from stable api.
// TODO: Figure out which group to try once multiple group support is merged
// instead of trying everything.
err = getSchemaAndValidate(c.c.RESTClient, data, "api", version)
err = getSchemaAndValidate(c.c.RESTClient, data, "api", version, c.cacheDir)
if err != nil && c.ec != nil {
errExp := getSchemaAndValidate(c.ec.RESTClient, data, "experimental", version)
errExp := getSchemaAndValidate(c.ec.RESTClient, data, "experimental", version, c.cacheDir)
if errExp == nil {
return nil
}

View File

@ -17,10 +17,20 @@ limitations under the License.
package util
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"path"
"sort"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/validation"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl"
@ -166,3 +176,101 @@ func TestFlagUnderscoreRenaming(t *testing.T) {
t.Fatalf("Expected flag name to be valid-flag, got %s", factory.flags.Lookup("valid_flag").Name)
}
}
func loadSchemaForTest() (validation.Schema, error) {
pathToSwaggerSpec := "../../../../api/swagger-spec/" + testapi.Default.Version() + ".json"
data, err := ioutil.ReadFile(pathToSwaggerSpec)
if err != nil {
return nil, err
}
return validation.NewSwaggerSchemaFromBytes(data)
}
func TestValidateCachesSchema(t *testing.T) {
schema, err := loadSchemaForTest()
if err != nil {
t.Errorf("Error loading schema: %v", err)
t.FailNow()
}
output, err := json.Marshal(schema)
if err != nil {
t.Errorf("Error serializing schema: %v", err)
t.FailNow()
}
requests := map[string]int{}
c := &client.FakeRESTClient{
Codec: testapi.Default.Codec(),
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case strings.HasPrefix(p, "/swaggerapi") && m == "GET":
requests[p] = requests[p] + 1
return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewBuffer(output))}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
dir := os.TempDir() + "/schemaCache"
os.RemoveAll(dir)
obj := &api.Pod{}
data, err := testapi.Default.Codec().Encode(obj)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
// Initial request, should use HTTP and write
if getSchemaAndValidate(c, data, "foo", "bar", dir); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if _, err := os.Stat(path.Join(dir, "foo", "bar", schemaFileName)); err != nil {
t.Errorf("unexpected missing cache file: %v", err)
}
if requests["/swaggerapi/foo/bar"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/bar"])
}
// Same version and group, should skip HTTP
if getSchemaAndValidate(c, data, "foo", "bar", dir); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if requests["/swaggerapi/foo/bar"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/bar"])
}
// Different API group, should go to HTTP and write
if getSchemaAndValidate(c, data, "foo", "baz", dir); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if _, err := os.Stat(path.Join(dir, "foo", "baz", schemaFileName)); err != nil {
t.Errorf("unexpected missing cache file: %v", err)
}
if requests["/swaggerapi/foo/baz"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/baz"])
}
// Different version, should go to HTTP and write
if getSchemaAndValidate(c, data, "foo2", "bar", dir); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if _, err := os.Stat(path.Join(dir, "foo2", "bar", schemaFileName)); err != nil {
t.Errorf("unexpected missing cache file: %v", err)
}
if requests["/swaggerapi/foo2/bar"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo2/bar"])
}
// No cache dir, should go straight to HTTP and not write
if getSchemaAndValidate(c, data, "foo", "blah", ""); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if requests["/swaggerapi/foo/blah"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/blah"])
}
if _, err := os.Stat(path.Join(dir, "foo", "blah", schemaFileName)); err == nil || !os.IsNotExist(err) {
t.Errorf("unexpected cache file error: %v", err)
}
}

View File

@ -268,8 +268,9 @@ func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
return d
}
func AddValidateFlag(cmd *cobra.Command) {
func AddValidateFlags(cmd *cobra.Command) {
cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it")
cmd.Flags().String("schema-cache-dir", "/tmp/kubectl.schema", "If non-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema'")
}
func ReadConfigDataFromReader(reader io.Reader, source string) ([]byte, error) {