diff --git a/pkg/kube/client.go b/pkg/kube/client.go index ae66d95d8785634ce96ae098549b67deb8f297a8..d74a1d03040d2331de4630142bce6a303ea67465 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -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 +} diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 7cf5b824660b31f87a1ccb81e6b4309e0a8663be..afd95a5a0487ac56e85ca9f4f5046f44a08e217c 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -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" @@ -67,12 +69,13 @@ func TestUpdateResource(t *testing.T) { func TestPerform(t *testing.T) { tests := []struct { - name string - namespace string - reader io.Reader - count int - err bool - errMessage string + name string + namespace string + reader io.Reader + count int + swaggerFile string + err bool + errMessage string }{ { name: "Valid input", @@ -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