mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 04:06:03 +00:00
Add caching for swagger schemas
This commit is contained in:
parent
bfc60709b1
commit
d9e1a00a14
@ -351,6 +351,7 @@ _kubectl_create()
|
||||
flags_with_completion=()
|
||||
flags_completion=()
|
||||
|
||||
flags+=("--cache-schema")
|
||||
flags+=("--filename=")
|
||||
flags_with_completion+=("--filename")
|
||||
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
|
||||
@ -377,6 +378,7 @@ _kubectl_replace()
|
||||
flags_with_completion=()
|
||||
flags_completion=()
|
||||
|
||||
flags+=("--cache-schema")
|
||||
flags+=("--cascade")
|
||||
flags+=("--filename=")
|
||||
flags_with_completion+=("--filename")
|
||||
@ -519,6 +521,7 @@ _kubectl_rolling-update()
|
||||
flags_with_completion=()
|
||||
flags_completion=()
|
||||
|
||||
flags+=("--cache-schema")
|
||||
flags+=("--deployment-label-key=")
|
||||
flags+=("--dry-run")
|
||||
flags+=("--filename=")
|
||||
|
@ -20,6 +20,10 @@ JSON and YAML formats are accepted.
|
||||
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\fB\-\-cache\-schema\fP=true
|
||||
If true, use/store local schema files
|
||||
|
||||
.PP
|
||||
\fB\-f\fP, \fB\-\-filename\fP=[]
|
||||
Filename, directory, or URL to file to use to create the resource
|
||||
|
@ -26,6 +26,10 @@ Please refer to the models in
|
||||
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\fB\-\-cache\-schema\fP=true
|
||||
If true, use/store local schema files
|
||||
|
||||
.PP
|
||||
\fB\-\-cascade\fP=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).
|
||||
|
@ -22,6 +22,10 @@ existing replication controller and overwrite at least one (common) label in its
|
||||
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\fB\-\-cache\-schema\fP=true
|
||||
If true, use/store local schema files
|
||||
|
||||
.PP
|
||||
\fB\-\-deployment\-label\-key\fP="deployment"
|
||||
The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when \-\-image is specified, ignored otherwise
|
||||
|
@ -59,6 +59,7 @@ $ cat pod.json | kubectl create -f -
|
||||
### Options
|
||||
|
||||
```
|
||||
--cache-schema[=true]: If true, use/store local schema files
|
||||
-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).
|
||||
--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-10 22:01:09.789168223 +0000 UTC
|
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
||||
[]()
|
||||
|
@ -69,6 +69,7 @@ kubectl replace --force -f ./pod.json
|
||||
### Options
|
||||
|
||||
```
|
||||
--cache-schema[=true]: If true, use/store local schema files
|
||||
--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).
|
||||
-f, --filename=[]: Filename, directory, or URL to file to use to replace the resource.
|
||||
--force[=false]: Delete and re-create the specified resource
|
||||
@ -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-10 22:01:09.789498374 +0000 UTC
|
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
||||
[]()
|
||||
|
@ -69,6 +69,7 @@ $ kubectl rolling-update frontend --image=image:v2
|
||||
### Options
|
||||
|
||||
```
|
||||
--cache-schema[=true]: If true, use/store local schema files
|
||||
--deployment-label-key="deployment": The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise
|
||||
--dry-run[=false]: If true, print out the changes that would be made, but don't actually make them.
|
||||
-f, --filename=[]: Filename or URL to file to use to create the new replication controller.
|
||||
@ -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-10 22:01:09.791014946 +0000 UTC
|
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
||||
[]()
|
||||
|
@ -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, cacheSchema bool) (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, cacheSchema bool) (validation.Schema, error) {
|
||||
return t.Validator, t.Err
|
||||
},
|
||||
DefaultNamespace: func() (string, bool, error) {
|
||||
|
@ -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.GetFlagBool(cmd, "cache-schema"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -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.GetFlagBool(cmd, "cache-schema"))
|
||||
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.GetFlagBool(cmd, "cache-schema"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -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.GetFlagBool(cmd, "cache-schema"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -20,7 +20,9 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -76,7 +78,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, cacheSchema bool) (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 +216,17 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
|
||||
}
|
||||
return kubectl.ReaperFor(mapping.Kind, client)
|
||||
},
|
||||
Validator: func(validate bool) (validation.Schema, error) {
|
||||
Validator: func(validate, cacheSchema bool) (validation.Schema, error) {
|
||||
if validate {
|
||||
client, err := clients.ClientForVersion("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientSwaggerSchema{client, client.ExperimentalClient, api.Scheme}, nil
|
||||
var cacheDir string
|
||||
if cacheSchema {
|
||||
cacheDir = "/tmp"
|
||||
}
|
||||
return &clientSwaggerSchema{client, client.ExperimentalClient, cacheDir}, nil
|
||||
}
|
||||
return validation.NullSchema{}, nil
|
||||
},
|
||||
@ -273,18 +279,42 @@ 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
|
||||
}
|
||||
if err = ioutil.WriteFile(cacheFile, schemaData, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
schema, err := validation.NewSwaggerSchemaFromBytes(schemaData)
|
||||
if err != nil {
|
||||
@ -305,9 +335,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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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().Bool("cache-schema", true, "If true, use/store local schema files")
|
||||
}
|
||||
|
||||
func ReadConfigDataFromReader(reader io.Reader, source string) ([]byte, error) {
|
||||
|
Loading…
Reference in New Issue
Block a user