From 572b92dc8aa4adf90337578a108ee5f4c501e3c6 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 8 Oct 2019 12:55:19 -0700 Subject: [PATCH] feat(pkg/kube): add openapi validation for k8s objects Add back OpenAPI validation for kubernetes objects. Fixes: #6382 Signed-off-by: Adam Reese --- pkg/action/hooks.go | 4 ++-- pkg/action/install.go | 4 ++-- pkg/action/release_testing.go | 2 +- pkg/action/rollback.go | 4 ++-- pkg/action/uninstall.go | 2 +- pkg/action/upgrade.go | 6 +++--- pkg/kube/client.go | 7 ++++++- pkg/kube/client_test.go | 16 ++++++++-------- pkg/kube/fake/fake.go | 4 ++-- pkg/kube/fake/printer.go | 2 +- pkg/kube/interface.go | 4 +++- 11 files changed, 31 insertions(+), 24 deletions(-) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index a8a27a5a2..a84cd2b51 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -44,7 +44,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, return err } - resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest)) + resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) if err != nil { return errors.Wrapf(err, "unable to build kubernetes object for %s hook %s", hook, h.Path) } @@ -111,7 +111,7 @@ func (x hookByWeight) Less(i, j int) bool { // deleteHookByPolicy deletes a hook if the hook policy instructs it to func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy) error { if hookHasDeletePolicy(h, policy) { - resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest)) + resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) if err != nil { return errors.Wrapf(err, "unable to build kubernetes object for deleting hook %s", h.Path) } diff --git a/pkg/action/install.go b/pkg/action/install.go index b8f9eb585..c9d4d65b5 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -110,7 +110,7 @@ func (i *Install) installCRDs(crds []*chart.File) error { totalItems := []*resource.Info{} for _, obj := range crds { // Read in the resources - res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.Data)) + res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.Data), false) if err != nil { return errors.Wrapf(err, "failed to install CRD %s", obj.Name) } @@ -219,7 +219,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // Mark this release as in-progress rel.SetStatus(release.StatusPendingInstall, "Initial install underway") - resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest)) + resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), false) if err != nil { return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") } diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 40dddf061..0f594512b 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -74,7 +74,7 @@ func (r *ReleaseTesting) Run(name string) error { } } } - hooks, err := r.cfg.KubeClient.Build(bytes.NewBufferString(manifestsToDelete.String())) + hooks, err := r.cfg.KubeClient.Build(bytes.NewBufferString(manifestsToDelete.String()), false) if err != nil { return fmt.Errorf("unable to build test hooks: %v", err) } diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 3d1ecb295..b565aa9b0 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -140,11 +140,11 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas return targetRelease, nil } - current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest)) + current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest), false) if err != nil { return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") } - target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest)) + target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest), false) if err != nil { return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") } diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 22123ad82..c5aaba629 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -188,7 +188,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (string, []error) { for _, file := range filesToDelete { builder.WriteString("\n---\n" + file.Content) } - resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String())) + resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) if err != nil { return "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")} } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 1b7b19557..e89ad6050 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -189,11 +189,11 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin } func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) { - current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest)) + current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest), false) if err != nil { return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") } - target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest)) + target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), true) if err != nil { return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") } @@ -372,7 +372,7 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV } func validateManifest(c kube.Interface, manifest []byte) error { - _, err := c.Build(bytes.NewReader(manifest)) + _, err := c.Build(bytes.NewReader(manifest), true) return err } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 3f228a583..1513c72d2 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -122,9 +122,14 @@ func (c *Client) newBuilder() *resource.Builder { } // Build validates for Kubernetes objects and returns unstructured infos. -func (c *Client) Build(reader io.Reader) (ResourceList, error) { +func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) { + schema, err := c.Factory.Validator(validate) + if err != nil { + return nil, err + } result, err := c.newBuilder(). Unstructured(). + Schema(schema). Stream(reader, ""). Do().Infos() return result, scrubValidationError(err) diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index a425a8e12..a0c678b50 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -153,11 +153,11 @@ func TestUpdate(t *testing.T) { } }), } - first, err := c.Build(objBody(&listA)) + first, err := c.Build(objBody(&listA), false) if err != nil { t.Fatal(err) } - second, err := c.Build(objBody(&listB)) + second, err := c.Build(objBody(&listB), false) if err != nil { t.Fatal(err) } @@ -222,7 +222,7 @@ func TestBuild(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test for an invalid manifest - infos, err := c.Build(tt.reader) + infos, err := c.Build(tt.reader, false) if err != nil && !tt.err { t.Errorf("Got error message when no error should have occurred: %v", err) } else if err != nil && strings.Contains(err.Error(), "--validate=false") { @@ -266,7 +266,7 @@ func TestPerform(t *testing.T) { } c := newTestClient() - infos, err := c.Build(tt.reader) + infos, err := c.Build(tt.reader, false) if err != nil && err.Error() != tt.errMessage { t.Errorf("Error while building manifests: %v", err) } @@ -289,7 +289,7 @@ func TestPerform(t *testing.T) { func TestReal(t *testing.T) { t.Skip("This is a live test, comment this line to run") c := New(nil) - resources, err := c.Build(strings.NewReader(guestbookManifest)) + resources, err := c.Build(strings.NewReader(guestbookManifest), false) if err != nil { t.Fatal(err) } @@ -299,7 +299,7 @@ func TestReal(t *testing.T) { testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest c = New(nil) - resources, err = c.Build(strings.NewReader(testSvcEndpointManifest)) + resources, err = c.Build(strings.NewReader(testSvcEndpointManifest), false) if err != nil { t.Fatal(err) } @@ -307,7 +307,7 @@ func TestReal(t *testing.T) { t.Fatal(err) } - resources, err = c.Build(strings.NewReader(testEndpointManifest)) + resources, err = c.Build(strings.NewReader(testEndpointManifest), false) if err != nil { t.Fatal(err) } @@ -316,7 +316,7 @@ func TestReal(t *testing.T) { t.Fatal(errs) } - resources, err = c.Build(strings.NewReader(testSvcEndpointManifest)) + resources, err = c.Build(strings.NewReader(testSvcEndpointManifest), false) if err != nil { t.Fatal(err) } diff --git a/pkg/kube/fake/fake.go b/pkg/kube/fake/fake.go index 938f43364..b3f7a393b 100644 --- a/pkg/kube/fake/fake.go +++ b/pkg/kube/fake/fake.go @@ -83,11 +83,11 @@ func (f *FailingKubeClient) Update(r, modified kube.ResourceList, ignoreMe bool) } // Build returns the configured error if set or prints -func (f *FailingKubeClient) Build(r io.Reader) (kube.ResourceList, error) { +func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error) { if f.BuildError != nil { return []*resource.Info{}, f.BuildError } - return f.PrintingKubeClient.Build(r) + return f.PrintingKubeClient.Build(r, false) } // WaitAndGetCompletedPodPhase returns the configured error if set or prints diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go index 4e8344171..ec75aa790 100644 --- a/pkg/kube/fake/printer.go +++ b/pkg/kube/fake/printer.go @@ -82,7 +82,7 @@ func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ bool) (*kub } // Build implements KubeClient Build. -func (p *PrintingKubeClient) Build(_ io.Reader) (kube.ResourceList, error) { +func (p *PrintingKubeClient) Build(_ io.Reader, _ bool) (kube.ResourceList, error) { return []*resource.Info{}, nil } diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index fc4189cc9..4bf61211e 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -51,7 +51,9 @@ type Interface interface { // // reader must contain a YAML stream (one or more YAML documents separated // by "\n---\n") - Build(reader io.Reader) (ResourceList, error) + // + // Validates against OpenAPI schema if validate is true. + Build(reader io.Reader, validate bool) (ResourceList, error) // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase // and returns said phase (PodSucceeded or PodFailed qualify).