Commit d10e9186 authored by Adam Reese's avatar Adam Reese

feat(tiller): validate objects against kube schema on dry-run

parent a95bd105
...@@ -50,8 +50,6 @@ var ErrNoObjectsVisited = goerrors.New("no objects visited") ...@@ -50,8 +50,6 @@ var ErrNoObjectsVisited = goerrors.New("no objects visited")
// Client represents a client capable of communicating with the Kubernetes API. // Client represents a client capable of communicating with the Kubernetes API.
type Client struct { type Client struct {
cmdutil.Factory cmdutil.Factory
// Validate idicates whether to load a schema for validation.
Validate bool
// SchemaCacheDir is the path for loading cached schema. // SchemaCacheDir is the path for loading cached schema.
SchemaCacheDir string SchemaCacheDir string
} }
...@@ -60,7 +58,6 @@ type Client struct { ...@@ -60,7 +58,6 @@ type Client struct {
func New(config clientcmd.ClientConfig) *Client { func New(config clientcmd.ClientConfig) *Client {
return &Client{ return &Client{
Factory: cmdutil.NewFactory(config), Factory: cmdutil.NewFactory(config),
Validate: true,
SchemaCacheDir: clientcmd.RecommendedSchemaFile, SchemaCacheDir: clientcmd.RecommendedSchemaFile,
} }
} }
...@@ -91,8 +88,8 @@ func (c *Client) Create(namespace string, reader io.Reader) error { ...@@ -91,8 +88,8 @@ func (c *Client) Create(namespace string, reader io.Reader) error {
return perform(c, namespace, reader, createResource) return perform(c, namespace, reader, createResource)
} }
func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Builder { func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result {
schema, err := c.Validator(c.Validate, c.SchemaCacheDir) schema, err := c.Validator(true, c.SchemaCacheDir)
if err != nil { if err != nil {
log.Printf("warning: failed to load schema: %s", err) log.Printf("warning: failed to load schema: %s", err)
} }
...@@ -102,7 +99,13 @@ func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Builde ...@@ -102,7 +99,13 @@ func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Builde
NamespaceParam(namespace). NamespaceParam(namespace).
DefaultNamespace(). DefaultNamespace().
Stream(reader, ""). 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 // Get gets kubernetes resources as pretty printed string
...@@ -165,12 +168,12 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { ...@@ -165,12 +168,12 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
// //
// Namespace will set the namespaces // Namespace will set the namespaces
func (c *Client) Update(namespace string, currentReader, targetReader io.Reader, recreate bool) error { 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 { if err != nil {
return fmt.Errorf("failed decoding reader into objects: %s", err) 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 { if target.Err() != nil {
return fmt.Errorf("failed decoding reader into objects: %s", target.Err()) 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 ...@@ -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 { 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 { switch {
case err != nil: case err != nil:
return scrubValidationError(err) return scrubValidationError(err)
......
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/storage/driver"
"k8s.io/kubernetes/pkg/kubectl/resource"
) )
// DefaultTillerNamespace is the default namespace for tiller. // DefaultTillerNamespace is the default namespace for tiller.
...@@ -132,6 +133,8 @@ type KubeClient interface { ...@@ -132,6 +133,8 @@ type KubeClient interface {
// reader must contain a YAML stream (one or more YAML documents separated // reader must contain a YAML stream (one or more YAML documents separated
// by "\n---\n"). // by "\n---\n").
Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool) error 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 // PrintingKubeClient implements KubeClient, but simply prints the reader to
...@@ -172,6 +175,11 @@ func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io. ...@@ -172,6 +175,11 @@ func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.
return err 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. // Environment provides the context for executing a client request.
// //
// All services in a context are concurrency safe. // All services in a context are concurrency safe.
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/kubernetes/pkg/kubectl/resource"
) )
type mockEngine struct { type mockEngine struct {
...@@ -50,6 +51,9 @@ func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Read ...@@ -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 { func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, t int64) error {
return nil return nil
} }
func (k *mockKubeClient) Build(ns string, reader io.Reader) ([]*resource.Info, error) {
return []*resource.Info{}, nil
}
var _ Engine = &mockEngine{} var _ Engine = &mockEngine{}
var _ KubeClient = &mockKubeClient{} var _ KubeClient = &mockKubeClient{}
......
...@@ -435,7 +435,8 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele ...@@ -435,7 +435,8 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
if len(notesTxt) > 0 { if len(notesTxt) > 0 {
updatedRelease.Info.Status.Notes = notesTxt 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. // RollbackRelease rolls back to a previous version of the given release.
...@@ -706,7 +707,9 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re ...@@ -706,7 +707,9 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
if len(notesTxt) > 0 { if len(notesTxt) > 0 {
rel.Info.Status.Notes = notesTxt 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) { func getVersionSet(client discovery.ServerGroupsInterface) (versionSet, error) {
...@@ -1048,3 +1051,9 @@ func splitManifests(bigfile string) map[string]string { ...@@ -1048,3 +1051,9 @@ func splitManifests(bigfile string) map[string]string {
} }
return res return res
} }
func validateManifest(c environment.KubeClient, ns string, manifest []byte) error {
r := bytes.NewReader(manifest)
_, err := c.Build(ns, r)
return err
}
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