From d10e9186dc4a52f073c22b028cf2b33f69571c39 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Wed, 21 Dec 2016 15:06:50 -0800 Subject: [PATCH] feat(tiller): validate objects against kube schema on dry-run --- pkg/kube/client.go | 21 ++++++++++++--------- pkg/tiller/environment/environment.go | 8 ++++++++ pkg/tiller/environment/environment_test.go | 4 ++++ pkg/tiller/release_server.go | 13 +++++++++++-- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 2c8af05b0..53d087ffc 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -50,8 +50,6 @@ var ErrNoObjectsVisited = goerrors.New("no objects visited") // Client represents a client capable of communicating with the Kubernetes API. type Client struct { cmdutil.Factory - // Validate idicates whether to load a schema for validation. - Validate bool // SchemaCacheDir is the path for loading cached schema. SchemaCacheDir string } @@ -60,7 +58,6 @@ type Client struct { func New(config clientcmd.ClientConfig) *Client { return &Client{ Factory: cmdutil.NewFactory(config), - Validate: true, SchemaCacheDir: clientcmd.RecommendedSchemaFile, } } @@ -91,8 +88,8 @@ func (c *Client) Create(namespace string, reader io.Reader) error { return perform(c, namespace, reader, createResource) } -func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Builder { - schema, err := c.Validator(c.Validate, c.SchemaCacheDir) +func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { + schema, err := c.Validator(true, c.SchemaCacheDir) if err != nil { log.Printf("warning: failed to load schema: %s", err) } @@ -102,7 +99,13 @@ func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Builde NamespaceParam(namespace). DefaultNamespace(). Stream(reader, ""). - Flatten() + Flatten(). + Do() +} + +// Build validates for Kubernetes objects and returns resource Infos from a io.Reader. +func (c *Client) Build(namespace string, reader io.Reader) ([]*resource.Info, error) { + return c.newBuilder(namespace, reader).Infos() } // Get gets kubernetes resources as pretty printed string @@ -165,12 +168,12 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { // // Namespace will set the namespaces func (c *Client) Update(namespace string, currentReader, targetReader io.Reader, recreate bool) error { - currentInfos, err := c.newBuilder(namespace, currentReader).Do().Infos() + currentInfos, err := c.Build(namespace, currentReader) if err != nil { return fmt.Errorf("failed decoding reader into objects: %s", err) } - target := c.newBuilder(namespace, targetReader).Do() + target := c.newBuilder(namespace, targetReader) if target.Err() != nil { return fmt.Errorf("failed decoding reader into objects: %s", target.Err()) } @@ -283,7 +286,7 @@ func (c *Client) WatchUntilReady(namespace string, reader io.Reader, timeout int } func perform(c *Client, namespace string, reader io.Reader, fn ResourceActorFunc) error { - infos, err := c.newBuilder(namespace, reader).Do().Infos() + infos, err := c.Build(namespace, reader) switch { case err != nil: return scrubValidationError(err) diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index aefb7b80a..349d82a85 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -31,6 +31,7 @@ import ( "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage/driver" + "k8s.io/kubernetes/pkg/kubectl/resource" ) // DefaultTillerNamespace is the default namespace for tiller. @@ -132,6 +133,8 @@ type KubeClient interface { // reader must contain a YAML stream (one or more YAML documents separated // by "\n---\n"). Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool) error + + Build(namespace string, reader io.Reader) ([]*resource.Info, error) } // PrintingKubeClient implements KubeClient, but simply prints the reader to @@ -172,6 +175,11 @@ func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io. return err } +// Build implements KubeClient Build. +func (p *PrintingKubeClient) Build(ns string, reader io.Reader) ([]*resource.Info, error) { + return []*resource.Info{}, nil +} + // Environment provides the context for executing a client request. // // All services in a context are concurrency safe. diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index edf708e9b..c3b85b778 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -23,6 +23,7 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/kubernetes/pkg/kubectl/resource" ) type mockEngine struct { @@ -50,6 +51,9 @@ func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Read func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, t int64) error { return nil } +func (k *mockKubeClient) Build(ns string, reader io.Reader) ([]*resource.Info, error) { + return []*resource.Info{}, nil +} var _ Engine = &mockEngine{} var _ KubeClient = &mockKubeClient{} diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 275044e57..1c3c65efb 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -435,7 +435,8 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele if len(notesTxt) > 0 { updatedRelease.Info.Status.Notes = notesTxt } - return currentRelease, updatedRelease, nil + err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) + return currentRelease, updatedRelease, err } // RollbackRelease rolls back to a previous version of the given release. @@ -706,7 +707,9 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re if len(notesTxt) > 0 { rel.Info.Status.Notes = notesTxt } - return rel, nil + + err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) + return rel, err } func getVersionSet(client discovery.ServerGroupsInterface) (versionSet, error) { @@ -1048,3 +1051,9 @@ func splitManifests(bigfile string) map[string]string { } return res } + +func validateManifest(c environment.KubeClient, ns string, manifest []byte) error { + r := bytes.NewReader(manifest) + _, err := c.Build(ns, r) + return err +}