Make validation optional, handle YAML

This commit is contained in:
Brendan Burns 2014-11-25 20:10:21 -08:00
parent c29f5db81c
commit 18cfac0d31
4 changed files with 100 additions and 13 deletions

View File

@ -54,6 +54,7 @@ var (
json = flag.Bool("json", false, "If true, print raw JSON for responses")
yaml = flag.Bool("yaml", false, "If true, print raw YAML for responses")
verbose = flag.Bool("verbose", false, "If true, print extra information")
validate = flag.Bool("validate", false, "If true, try to validate the passed in object using a swagger schema on the api server")
proxy = flag.Bool("proxy", false, "If true, run a proxy to the api server")
www = flag.String("www", "", "If -proxy is true, use this directory to serve static files")
templateFile = flag.String("template_file", "", "If present, load this file as a golang template and use it for output printing")
@ -159,9 +160,11 @@ func readConfig(storage string, c *client.Client) []byte {
}
dataInput := readConfigData()
err := kubecfg.ValidateObject(dataInput, c)
if err != nil {
glog.Fatalf("Error validating %v as an object for %v: %v\n", *config, storage, err)
if *validate {
err := kubecfg.ValidateObject(dataInput, c)
if err != nil {
glog.Fatalf("Error validating %v as an object for %v: %v\n", *config, storage, err)
}
}
data, err := parser.ToWireFormat(dataInput, storage, latest.Codec, serverCodec)

View File

@ -24,6 +24,7 @@ import (
"github.com/emicklei/go-restful/swagger"
"github.com/golang/glog"
"gopkg.in/v2/yaml"
)
type InvalidTypeError struct {
@ -64,11 +65,11 @@ func NewSwaggerSchemaFromBytes(data []byte) (Schema, error) {
func (s *SwaggerSchema) ValidateBytes(data []byte) error {
var obj interface{}
err := json.Unmarshal(data, &obj)
err := yaml.Unmarshal(data, &obj)
if err != nil {
return err
}
fields := obj.(map[string]interface{})
fields := obj.(map[interface{}]interface{})
apiVersion := fields["apiVersion"].(string)
kind := fields["kind"].(string)
return s.ValidateObject(obj, apiVersion, "", apiVersion+"."+kind)
@ -83,12 +84,12 @@ func (s *SwaggerSchema) ValidateObject(obj interface{}, apiVersion, fieldName, t
return nil
}
properties := model.Properties
fields := obj.(map[string]interface{})
fields := obj.(map[interface{}]interface{})
if len(fieldName) > 0 {
fieldName = fieldName + "."
}
for key, value := range fields {
details, ok := properties[key]
details, ok := properties[key.(string)]
if !ok {
glog.V(2).Infof("couldn't find properties for %s, skipping", key)
continue
@ -98,7 +99,7 @@ func (s *SwaggerSchema) ValidateObject(obj interface{}, apiVersion, fieldName, t
glog.V(2).Infof("Skipping nil field: %s", key)
continue
}
err := s.validateField(value, apiVersion, fieldName+key, fieldType, &details)
err := s.validateField(value, apiVersion, fieldName+key.(string), fieldType, &details)
if err != nil {
glog.Errorf("Validation failed for: %s, %v", key, value)
return err
@ -116,7 +117,8 @@ func (s *SwaggerSchema) validateField(value interface{}, apiVersion, fieldName,
// Be loose about what we accept for 'string' since we use IntOrString in a couple of places
_, isString := value.(string)
_, isNumber := value.(float64)
if !isString && !isNumber {
_, isInteger := value.(int)
if !isString && !isNumber && !isInteger {
return NewInvalidTypeError(reflect.String, reflect.TypeOf(value).Kind(), fieldName)
}
case "array":
@ -133,7 +135,9 @@ func (s *SwaggerSchema) validateField(value interface{}, apiVersion, fieldName,
}
case "uint64":
case "integer":
if _, ok := value.(float64); !ok {
_, isNumber := value.(float64)
_, isInteger := value.(int)
if !isNumber && !isInteger {
return NewInvalidTypeError(reflect.Int, reflect.TypeOf(value).Kind(), fieldName)
}
case "float64":

View File

@ -251,12 +251,63 @@ var invalidPod3 = `{
}
`
var invalidYaml = `
id: name
kind: Pod
apiVersion: v1beta1
desiredState:
manifest:
version: v1beta1
id: redis-master
containers:
- name: "master"
image: "dockerfile/redis"
command: "this is a bad command"
labels:
name: "redis-master"
`
func TestInvalid(t *testing.T) {
schema, err := LoadSchemaForTest("v1beta1-swagger.json")
if err != nil {
t.Errorf("Failed to load: %v", err)
}
tests := []string{invalidPod, invalidPod2}
tests := []string{invalidPod, invalidPod2, invalidPod3, invalidYaml}
for _, test := range tests {
err = schema.ValidateBytes([]byte(test))
if err == nil {
t.Errorf("unexpected non-error\n%s", test)
}
}
}
var validYaml = `
id: name
kind: Pod
apiVersion: v1beta1
desiredState:
manifest:
version: v1beta1
id: redis-master
containers:
- name: "master"
image: "dockerfile/redis"
command:
- this
- is
- an
- ok
- command
labels:
name: "redis-master"
`
func TestValid(t *testing.T) {
schema, err := LoadSchemaForTest("v1beta1-swagger.json")
if err != nil {
t.Errorf("Failed to load: %v", err)
}
tests := []string{validYaml}
for _, test := range tests {
err = schema.ValidateBytes([]byte(test))
if err == nil {

View File

@ -52,9 +52,13 @@ func NewFactory(clientBuilder clientcmd.Builder) *Factory {
ClientBuilder: clientBuilder,
Mapper: latest.RESTMapper,
Typer: api.Scheme,
Validator: func(cmd *cobra.Command) (validation.Schema, error) {
Validator: func(cmd *cobra.Command) (validation.Schema, error) {
if GetFlagBool(cmd, "validate") {
return &clientSwaggerSchema{getKubeClient(cmd), api.Scheme}, nil
client, err := clientBuilder.Client()
if err != nil {
return nil, err
}
return &clientSwaggerSchema{client, api.Scheme}, nil
} else {
return validation.NullSchema{}, nil
}
@ -165,3 +169,28 @@ func GetExplicitKubeNamespace(cmd *cobra.Command) (string, bool) {
// value and return its value and true.
return "", false
}
type clientSwaggerSchema struct {
c *client.Client
t runtime.ObjectTyper
}
func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
version, _, err := c.t.DataVersionAndKind(data)
if err != nil {
return err
}
schemaData, err := c.c.RESTClient.Get().
AbsPath("/swaggerapi/api").
Path(version).
Do().
Raw()
if err != nil {
return err
}
schema, err := validation.NewSwaggerSchemaFromBytes(schemaData)
if err != nil {
return err
}
return schema.ValidateBytes(data)
}