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

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
main Release add-codeql dependabot/go_modules/github.com/docker/distribution-2.8.2incompatible dependabot/go_modules/github.com/lib/pq-1.10.9 dependabot/go_modules/github.com/rubenv/sql-migrate-1.4.0 dependabot/go_modules/golang.org/x/crypto-0.9.0 dependabot/go_modules/golang.org/x/term-0.8.0 dependabot/go_modules/k8s.io/klog/v2-2.100.1 dev-v2 feat-v3/event-emitter-lua release-2.1 release-2.10 release-2.11 release-2.12 release-2.13 release-2.14 release-2.15 release-2.16 release-2.17 release-2.2 release-2.3 release-2.4 release-2.5 release-2.6 release-2.7 release-2.8 release-2.9 release-3.0 release-3.1 release-3.10 release-3.11 release-3.12 release-3.2 release-3.3 release-3.4 release-3.5 release-3.6 release-3.6.1 release-3.6.2 release-3.7 release-3.8 release-3.9 release-v3.0.0-beta.4 v3.12.0 v3.12.0-rc.1 v3.12.0-dev.1 v3.11.3 v3.11.2 v3.11.1 v3.11.0 v3.11.0-rc.2 v3.11.0-rc.1 v3.10.3 v3.10.2 v3.10.1 v3.10.0 v3.10.0-rc.1 v3.9.4 v3.9.3 v3.9.2 v3.9.1 v3.9.0 v3.9.0-rc.1 v3.8.2 v3.8.1 v3.8.0 v3.8.0-rc.2 v3.8.0-rc.1 v3.7.2 v3.7.1 v3.7.0 v3.7.0-rc.3 v3.7.0-rc.2 v3.7.0-rc.1 v3.6.3 v3.6.2 v3.6.1 v3.6.0 v3.6.0-rc.1 v3.5.4 v3.5.3 v3.5.2 v3.5.1 v3.5.0 v3.5.0-rc.2 v3.5.0-rc.1 v3.4.2 v3.4.1 v3.4.0 v3.4.0-rc.1 v3.3.4 v3.3.3 v3.3.2 v3.3.1 v3.3.0 v3.3.0-rc.2 v3.3.0-rc.1 v3.2.4 v3.2.3 v3.2.2 v3.2.1 v3.2.0 v3.2.0-rc.1 v3.1.3 v3.1.2 v3.1.1 v3.1.0 v3.1.0-rc.3 v3.1.0-rc.2 v3.1.0-rc.1 v3.0.3 v3.0.2 v3.0.1 v3.0.0 v3.0.0-rc.4 v3.0.0-rc.3 v3.0.0-rc.2 v3.0.0-rc.1 v3.0.0-beta.5 v3.0.0-beta.4 v3.0.0-beta.3 v3.0.0-beta.2 v3.0.0-beta.1 v3.0.0-alpha.2 v3.0.0-alpha.1 v2.17.0 v2.17.0-rc.1 v2.16.12 v2.16.11 v2.16.10 v2.16.9 v2.16.8 v2.16.7 v2.16.6 v2.16.5 v2.16.4 v2.16.3 v2.16.2 v2.16.1 v2.16.0 v2.16.0-rc.2 v2.16.0-rc.1 v2.15.2 v2.15.1 v2.15.0 v2.15.0-rc.2 v2.15.0-rc.1 v2.14.3 v2.14.2 v2.14.1 v2.14.0 v2.14.0-rc.2 v2.14.0-rc.1 v2.13.1 v2.13.1-rc.1 v2.13.0 v2.13.0-rc.2 v2.13.0-rc.1 v2.12.3 v2.12.2 v2.12.1 v2.12.0 v2.12.0-rc.2 v2.12.0-rc.1 v2.11.0 v2.11.0-rc.4 v2.11.0-rc.3 v2.11.0-rc.2 v2.11.0-rc.1 v2.10.0 v2.10.0-rc.3 v2.10.0-rc.2 v2.10.0-rc.1 v2.9.1 v2.9.0 v2.9.0-rc5 v2.9.0-rc4 v2.9.0-rc3 v2.9.0-rc2 v2.9.0-rc1 v2.8.2 v2.8.2-rc1 v2.8.1 v2.8.0 v2.8.0-rc.1 v2.7.2 v2.7.1 v2.7.0 v2.7.0-rc1 v2.6.2 v2.6.1 v2.6.0 v2.5.1 v2.5.0 v2.4.2 v2.4.1 v2.4.0 v2.3.1 v2.3.0 v2.2.3 v2.2.2 v2.2.1 v2.2.0 v2.1.3 v2.1.2 v2.1.1 v2.1.0
No related merge requests found
Showing with 56 additions and 7 deletions
+56 -7
...@@ -54,6 +54,10 @@ type Client struct { ...@@ -54,6 +54,10 @@ type Client struct {
// a client will still attempt to contact a live server. In these situations, // a client will still attempt to contact a live server. In these situations,
// this flag may need to be disabled. // this flag may need to be disabled.
IncludeThirdPartyAPIs bool 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 // New create a new Client
...@@ -61,6 +65,8 @@ func New(config clientcmd.ClientConfig) *Client { ...@@ -61,6 +65,8 @@ func New(config clientcmd.ClientConfig) *Client {
return &Client{ return &Client{
Factory: cmdutil.NewFactory(config), Factory: cmdutil.NewFactory(config),
IncludeThirdPartyAPIs: true, IncludeThirdPartyAPIs: true,
Validate: true,
SchemaCacheDir: clientcmd.RecommendedSchemaFile,
} }
} }
...@@ -97,8 +103,13 @@ func (c *Client) Create(namespace string, reader io.Reader) error { ...@@ -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 { 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). return c.NewBuilder(c.IncludeThirdPartyAPIs).
ContinueOnError(). ContinueOnError().
Schema(schema).
NamespaceParam(namespace). NamespaceParam(namespace).
DefaultNamespace(). DefaultNamespace().
Stream(reader, ""). Stream(reader, "").
...@@ -280,7 +291,7 @@ func perform(c *Client, namespace string, reader io.Reader, fn ResourceActorFunc ...@@ -280,7 +291,7 @@ func perform(c *Client, namespace string, reader io.Reader, fn ResourceActorFunc
infos, err := c.newBuilder(namespace, reader).Do().Infos() infos, err := c.newBuilder(namespace, reader).Do().Infos()
switch { switch {
case err != nil: case err != nil:
return err return scrubValidationError(err)
case len(infos) == 0: case len(infos) == 0:
return ErrNoObjectsVisited return ErrNoObjectsVisited
} }
...@@ -449,3 +460,13 @@ func findMatchingInfo(target *resource.Info, infos []*resource.Info) (*resource. ...@@ -449,3 +460,13 @@ func findMatchingInfo(target *resource.Info, infos []*resource.Info) (*resource.
} }
return nil, false 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 ( ...@@ -26,8 +26,10 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
api "k8s.io/kubernetes/pkg/api/v1" api "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/client/unversioned/fake"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
...@@ -67,12 +69,13 @@ func TestUpdateResource(t *testing.T) { ...@@ -67,12 +69,13 @@ func TestUpdateResource(t *testing.T) {
func TestPerform(t *testing.T) { func TestPerform(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
namespace string namespace string
reader io.Reader reader io.Reader
count int count int
err bool swaggerFile string
errMessage string err bool
errMessage string
}{ }{
{ {
name: "Valid input", name: "Valid input",
...@@ -85,6 +88,13 @@ func TestPerform(t *testing.T) { ...@@ -85,6 +88,13 @@ func TestPerform(t *testing.T) {
reader: strings.NewReader(""), reader: strings.NewReader(""),
err: true, err: true,
errMessage: "no objects visited", 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) { ...@@ -105,6 +115,16 @@ func TestPerform(t *testing.T) {
c.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { c.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
return &fake.RESTClient{}, nil 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) err := perform(c, tt.namespace, tt.reader, fn)
if (err != nil) != tt.err { if (err != nil) != tt.err {
...@@ -159,6 +179,14 @@ spec: ...@@ -159,6 +179,14 @@ spec:
targetPort: 9376 targetPort: 9376
` `
const testInvalidServiceManifest = `
kind: Service
apiVersion: v1
spec:
ports:
- port: "80"
`
const testEndpointManifest = ` const testEndpointManifest = `
kind: Endpoints kind: Endpoints
apiVersion: v1 apiVersion: v1
......
Supports Markdown
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