Commit 2ae88214 authored by Adam Reese's avatar Adam Reese

feat(kube): add schema validation

Adds validation against the swagger schema.

Example error:
Error: release telling-wildebeest failed: error validating "": error
validating data: expected type int, for field
spec.template.spec.containers[0].ports[0].containerPort, got string

Current error:
unable to decode "": [pos 177]: json: expect char '"' but got char 'n'"'
parent e77d564b
......@@ -54,6 +54,10 @@ type Client struct {
// a client will still attempt to contact a live server. In these situations,
// this flag may need to be disabled.
IncludeThirdPartyAPIs bool
// Validate idicates whether to load a schema for validation.
Validate bool
// SchemaCacheDir is the path for loading cached schema.
SchemaCacheDir string
}
// New create a new Client
......@@ -61,6 +65,8 @@ func New(config clientcmd.ClientConfig) *Client {
return &Client{
Factory: cmdutil.NewFactory(config),
IncludeThirdPartyAPIs: true,
Validate: true,
SchemaCacheDir: clientcmd.RecommendedSchemaFile,
}
}
......@@ -97,8 +103,13 @@ func (c *Client) Create(namespace string, reader io.Reader) error {
}
func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Builder {
schema, err := c.Validator(c.Validate, c.SchemaCacheDir)
if err != nil {
log.Printf("warning: failed to load schema: %s", err)
}
return c.NewBuilder(c.IncludeThirdPartyAPIs).
ContinueOnError().
Schema(schema).
NamespaceParam(namespace).
DefaultNamespace().
Stream(reader, "").
......@@ -280,7 +291,7 @@ func perform(c *Client, namespace string, reader io.Reader, fn ResourceActorFunc
infos, err := c.newBuilder(namespace, reader).Do().Infos()
switch {
case err != nil:
return err
return scrubValidationError(err)
case len(infos) == 0:
return ErrNoObjectsVisited
}
......@@ -449,3 +460,13 @@ func findMatchingInfo(target *resource.Info, infos []*resource.Info) (*resource.
}
return nil, false
}
// scrubValidationError removes kubectl info from the message
func scrubValidationError(err error) error {
const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false"
if strings.Contains(err.Error(), stopValidateMessage) {
return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1))
}
return err
}
......@@ -26,8 +26,10 @@ import (
"testing"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
api "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/client/unversioned/fake"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
......@@ -71,6 +73,7 @@ func TestPerform(t *testing.T) {
namespace string
reader io.Reader
count int
swaggerFile string
err bool
errMessage string
}{
......@@ -85,6 +88,13 @@ func TestPerform(t *testing.T) {
reader: strings.NewReader(""),
err: true,
errMessage: "no objects visited",
}, {
name: "Invalid schema",
namespace: "test",
reader: strings.NewReader(testInvalidServiceManifest),
swaggerFile: "../../vendor/k8s.io/kubernetes/api/swagger-spec/" + testapi.Default.GroupVersion().Version + ".json",
err: true,
errMessage: `error validating "": error validating data: expected type int, for field spec.ports[0].port, got string`,
},
}
......@@ -105,6 +115,16 @@ func TestPerform(t *testing.T) {
c.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
return &fake.RESTClient{}, nil
}
c.Validator = func(validate bool, cacheDir string) (validation.Schema, error) {
if tt.swaggerFile == "" {
return validation.NullSchema{}, nil
}
data, err := ioutil.ReadFile(tt.swaggerFile)
if err != nil {
t.Fatalf("could not load swagger spec: %s", err)
}
return validation.NewSwaggerSchemaFromBytes(data, nil)
}
err := perform(c, tt.namespace, tt.reader, fn)
if (err != nil) != tt.err {
......@@ -159,6 +179,14 @@ spec:
targetPort: 9376
`
const testInvalidServiceManifest = `
kind: Service
apiVersion: v1
spec:
ports:
- port: "80"
`
const testEndpointManifest = `
kind: Endpoints
apiVersion: v1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment