diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index d3fd05337..e15f1755e 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -188,6 +188,10 @@ message UpdateReleaseRequest { bool dry_run = 4; // DisableHooks causes the server to skip running any hooks for the upgrade. bool disable_hooks = 5; + + // Validate requests that Tiller uses kubernetes validation logic on the + // manifest before handing it off for reification + bool validate = 6; } // UpdateReleaseResponse is the response to an update request. @@ -235,6 +239,10 @@ message InstallReleaseRequest { // ReuseName requests that Tiller re-uses a name, instead of erroring out. bool reuse_name = 7; + + // Validate requests that Tiller uses kubernetes validation logic on the + // manifest before handing it off for reification + bool validate = 8; } // InstallReleaseResponse is the response from a release installation. diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 58d590bc4..2546cddfe 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -90,6 +90,7 @@ type installCmd struct { disableHooks bool replace bool verify bool + validate bool keyring string out io.Writer client helm.Interface @@ -135,6 +136,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { f.Var(inst.values, "set", "set values on the command line. Separate values with commas: key1=val1,key2=val2") f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release") f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it") + f.BoolVar(&inst.validate, "validate", false, "validate manifest before reifying it") f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed.") return cmd @@ -167,6 +169,7 @@ func (i *installCmd) run() error { helm.ReleaseName(i.name), helm.InstallDryRun(i.dryRun), helm.InstallReuseName(i.replace), + helm.InstallValidate(i.validate), helm.InstallDisableHooks(i.disableHooks)) if err != nil { return prettyError(err) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index d85bf8cfe..ed1eaa969 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -51,6 +51,7 @@ type upgradeCmd struct { valuesFile string values *values verify bool + validate bool keyring string install bool namespace string @@ -89,6 +90,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { f.Var(upgrade.values, "set", "set values on the command line. Separate values with commas: key1=val1,key2=val2") f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks") f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading") + f.BoolVar(&upgrade.validate, "validate", false, "validate manifest before reifying it") f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "the path to the keyring that contains public singing keys") f.BoolVarP(&upgrade.install, "install", "i", false, "if a release by this name doesn't already exist, run an install") f.StringVar(&upgrade.namespace, "namespace", "default", "the namespace to install the release into (only used if --install is set)") @@ -135,7 +137,7 @@ func (u *upgradeCmd) run() error { return err } - _, err = u.client.UpdateRelease(u.release, chartPath, helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun), helm.UpgradeDisableHooks(u.disableHooks)) + _, err = u.client.UpdateRelease(u.release, chartPath, helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun), helm.UpgradeDisableHooks(u.disableHooks), helm.UpgradeValidate(u.validate)) if err != nil { return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err)) } diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index a9e6cafb5..e8b32f064 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "log" + "os" "regexp" "strings" @@ -39,6 +40,9 @@ import ( "k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/timeconv" "k8s.io/helm/pkg/version" + "k8s.io/kubernetes/pkg/api/unversioned" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" ) var srv *releaseServer @@ -305,6 +309,19 @@ func (s *releaseServer) performUpdate(originalRelease, updatedRelease *release.R } } + kubeCli := s.env.KubeClient + original := bytes.NewBufferString(originalRelease.Manifest) + modified := bytes.NewBufferString(updatedRelease.Manifest) + + // Validate the manifest + if req.Validate { + log.Printf("Validating manifest: %s\n", modified) + err := validateResources(updatedRelease.Namespace, modified) + if err != nil { + return nil, err + } + } + if err := s.performKubeUpdate(originalRelease, updatedRelease); err != nil { log.Printf("warning: Release Upgrade %q failed: %s", updatedRelease.Name, err) originalRelease.Info.Status.Code = release.Status_SUPERSEDED @@ -736,6 +753,16 @@ func (s *releaseServer) performRelease(r *release.Release, req *services.Install // regular manifests kubeCli := s.env.KubeClient b := bytes.NewBufferString(r.Manifest) + + // Validate the manifest + if req.Validate { + log.Printf("Validating manifest: %s\n", b) + err := validateResources(r.Namespace, b) + if err != nil { + return res, err + } + } + if err := kubeCli.Create(r.Namespace, b); err != nil { log.Printf("warning: Release %q failed: %s", r.Name, err) r.Info.Status.Code = release.Status_FAILED @@ -885,8 +912,40 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR if len(es) > 0 { errs = fmt.Errorf("deletion error count %d: %s", len(es), strings.Join(es, "; ")) } + return res, nil +} - return res, errs +// validateResources takes a namespace and a manifest (fully expanded set of templates) and +// validates resources. +func validateResources(namespace string, manifest *bytes.Buffer) error { + f := cmdutil.NewFactory(nil) + schema, err := f.Validator(true, os.TempDir()) + if err != nil { + return err + } + + mapper, typer := f.Object(true) + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + Schema(schema). + NamespaceParam(namespace).DefaultNamespace(). + Stream(manifest, "release"). + Flatten(). + Do() + err = r.Err() + if err != nil { + return err + } + + err = r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + return nil } func splitManifests(bigfile string) map[string]string { diff --git a/cmd/tiller/release_server_test.go b/cmd/tiller/release_server_test.go index e3073c189..67238db31 100644 --- a/cmd/tiller/release_server_test.go +++ b/cmd/tiller/release_server_test.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "bytes" "errors" "fmt" "io" @@ -70,6 +71,23 @@ data: name: value ` +var invalidResource = `apiVersion: v1 +kind: ConfigMap +metadata: + naaame: test-cm +data: + name: value +` + +var validResource = `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm + +data: + name: value +` + func rsFixture() *releaseServer { return &releaseServer{ env: mockEnvironment(), @@ -511,6 +529,148 @@ func TestInstallReleaseReuseName(t *testing.T) { } } +func TestInstallReleaseWithValidationFailsForInvalid(t *testing.T) { + c := context.Background() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Validate: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "invalid", Data: []byte(invalidResource)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + if err == nil { + t.Fatalf("Install succeeded but was expected to fail") + } + if !strings.Contains(err.Error(), "naaame") { + t.Errorf("didn't fail with expected error") + } + +} + +func TestInstallReleaseWithValidationWorksForValid(t *testing.T) { + c := context.Background() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Validate: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "invalid", Data: []byte(validResource)}, + }, + }, + } + res, err := rs.InstallRelease(c, req) + + if err != nil { + t.Fatalf("Failed install: %s %s", err, req.Chart) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + if res.Release.Namespace != "spaced" { + t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + +} + +func TestUpdateReleaseWithValidationFailsForInvalid(t *testing.T) { + c := context.Background() + rs := rsFixture() + + req := &services.InstallReleaseRequest{ + Name: "myspecialone", + Namespace: "spaced", + Validate: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "invalid", Data: []byte(validResource)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Install failed: %v", err) + } + + uReq := &services.UpdateReleaseRequest{ + Name: "myspecialone", + Validate: true, + DisableHooks: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "invalid", Data: []byte(invalidResource)}, + }, + }, + } + + _, err = rs.UpdateRelease(c, uReq) + if err == nil { + t.Fatalf("Install succeeded but was expected to fail") + } + + if !strings.Contains(err.Error(), "naaame") { + t.Errorf("didn't fail with expected error") + } + +} + +func TestUpdateReleaseWithValidationWorksForValid(t *testing.T) { + c := context.Background() + rs := rsFixture() + + req := &services.InstallReleaseRequest{ + Name: "myspecialone", + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "invalid", Data: []byte(invalidResource)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Install failed: %v", err) + } + + uReq := &services.UpdateReleaseRequest{ + Name: "myspecialone", + Validate: true, + DisableHooks: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "invalid", Data: []byte(validResource)}, + }, + }, + } + + _, err = rs.UpdateRelease(c, uReq) + if err != nil { + t.Fatalf("Install failed: %v", err) + } +} + func TestUpdateRelease(t *testing.T) { c := helm.NewContext() rs := rsFixture() @@ -1198,6 +1358,11 @@ func TestListReleasesFilter(t *testing.T) { } } +func testValidateResources(t *testing.T) { + b := bytes.NewBufferString("") + validateResources("default", b) +} + func mockEnvironment() *environment.Environment { e := environment.New() e.Releases = storage.Init(driver.NewMemory()) diff --git a/pkg/helm/option.go b/pkg/helm/option.go index a445e8643..b764e32f7 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -38,6 +38,8 @@ type options struct { host string // if set dry-run helm client calls dryRun bool + // if set validate manifest + validate bool // if set, re-use an existing name reuseName bool // if set, skip running hooks @@ -219,6 +221,22 @@ func RollbackVersion(ver int32) RollbackOption { } } +// UpgradeValidate will (if true) instruct Tiller to validate manifest before +// reifying +func UpgradeValidate(validate bool) UpdateOption { + return func(opts *options) { + opts.validate = validate + } +} + +// InstallValidate will (if true) instruct Tiller to validate manifest before +// reifying +func InstallValidate(validate bool) InstallOption { + return func(opts *options) { + opts.validate = validate + } +} + // UpgradeDisableHooks will disable hooks for an upgrade operation. func UpgradeDisableHooks(disable bool) UpdateOption { return func(opts *options) { @@ -278,6 +296,75 @@ type RollbackOption func(*options) // issuing a GetHistory rpc. type HistoryOption func(*options) +// RPC helpers defined on `options` type. Note: These actually execute the +// the corresponding tiller RPC. There is no particular reason why these +// are APIs are hung off `options`, they are internal to pkg/helm to remain +// malleable. + +// Executes tiller.ListReleases RPC. +func (o *options) rpcListReleases(rlc rls.ReleaseServiceClient, opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) { + // apply release list options + for _, opt := range opts { + opt(o) + } + s, err := rlc.ListReleases(context.TODO(), &o.listReq) + if err != nil { + return nil, err + } + + return s.Recv() +} + +// Executes tiller.InstallRelease RPC. +func (o *options) rpcInstallRelease(chr *cpb.Chart, rlc rls.ReleaseServiceClient, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { + // apply the install options + for _, opt := range opts { + opt(o) + } + o.instReq.Chart = chr + o.instReq.Namespace = ns + o.instReq.DryRun = o.dryRun + o.instReq.DisableHooks = o.disableHooks + o.instReq.ReuseName = o.reuseName + o.instReq.Validate = o.validate + + return rlc.InstallRelease(context.TODO(), &o.instReq) +} + +// Executes tiller.UninstallRelease RPC. +func (o *options) rpcDeleteRelease(rlsName string, rlc rls.ReleaseServiceClient, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) { + for _, opt := range opts { + opt(o) + } + if o.dryRun { + // In the dry run case, just see if the release exists + r, err := o.rpcGetReleaseContent(rlsName, rlc) + if err != nil { + return &rls.UninstallReleaseResponse{}, err + } + return &rls.UninstallReleaseResponse{Release: r.Release}, nil + } + + o.uninstallReq.Name = rlsName + o.uninstallReq.DisableHooks = o.disableHooks + + return rlc.UninstallRelease(context.TODO(), &o.uninstallReq) +} + +// Executes tiller.UpdateRelease RPC. +func (o *options) rpcUpdateRelease(rlsName string, chr *cpb.Chart, rlc rls.ReleaseServiceClient, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { + for _, opt := range opts { + opt(o) + } + + o.updateReq.Chart = chr + o.updateReq.DryRun = o.dryRun + o.updateReq.Name = rlsName + o.updateReq.Validate = o.validate + + return rlc.UpdateRelease(context.TODO(), &o.updateReq) +} + // WithMaxHistory sets the max number of releases to return // in a release history query. func WithMaxHistory(max int32) HistoryOption { diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index 074418600..620f619a6 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -246,6 +246,9 @@ type UpdateReleaseRequest struct { DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` // DisableHooks causes the server to skip running any hooks for the upgrade. DisableHooks bool `protobuf:"varint,5,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"` + // Validate requests that Tiller uses kubernetes validation logic on the + // manifest before handing it off for reification + Validate bool `protobuf:"varint,6,opt,name=validate" json:"validate,omitempty"` } func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } @@ -337,6 +340,9 @@ type InstallReleaseRequest struct { Namespace string `protobuf:"bytes,6,opt,name=namespace" json:"namespace,omitempty"` // ReuseName requests that Tiller re-uses a name, instead of erroring out. ReuseName bool `protobuf:"varint,7,opt,name=reuse_name,json=reuseName" json:"reuse_name,omitempty"` + // Validate requests that Tiller uses kubernetes validation logic on the + // manifest before handing it off for reification + Validate bool `protobuf:"varint,8,opt,name=validate" json:"validate,omitempty"` } func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} } @@ -878,6 +884,7 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ +<<<<<<< 70b29a47d08628a6909964bcfc35f4a60507ea30 // 1004 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdf, 0x6f, 0xe3, 0xc4, 0x13, 0xaf, 0x93, 0x34, 0x3f, 0xa6, 0x3f, 0xbe, 0xe9, 0x5e, 0xda, 0xb8, 0xd6, 0x17, 0x14, 0x19, @@ -942,4 +949,64 @@ var fileDescriptor0 = []byte{ 0xe7, 0x39, 0xb5, 0x13, 0x41, 0xa9, 0xc2, 0xde, 0x12, 0x54, 0xbc, 0x6b, 0x6c, 0x09, 0x2a, 0xd1, 0x23, 0xec, 0xbd, 0x17, 0xf0, 0x73, 0x55, 0xeb, 0xdd, 0x95, 0xc5, 0x5f, 0x85, 0xaf, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x9a, 0xb0, 0xe9, 0x29, 0xfb, 0x0c, 0x00, 0x00, +======= + // 909 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x56, 0xdd, 0x6e, 0xe3, 0x44, + 0x14, 0xde, 0xfc, 0xd4, 0x71, 0x4e, 0x7f, 0xd4, 0xce, 0xf6, 0xc7, 0xb5, 0x00, 0xad, 0x8c, 0x60, + 0xcb, 0x02, 0x29, 0x84, 0x5b, 0x84, 0xd4, 0xcd, 0x46, 0xdd, 0x6a, 0x43, 0x56, 0x9a, 0x50, 0x90, + 0xb8, 0x20, 0x72, 0x93, 0xc9, 0xc6, 0xe0, 0xda, 0xc1, 0x33, 0x89, 0xe8, 0x23, 0xf0, 0x04, 0x5c, + 0x70, 0xc7, 0x5b, 0xf1, 0x36, 0xcc, 0xaf, 0x1b, 0xbb, 0x36, 0xf5, 0xe6, 0x26, 0xf6, 0xcc, 0xf9, + 0xe6, 0x3b, 0xe7, 0x7c, 0x67, 0xce, 0x71, 0xc0, 0x9d, 0xfb, 0x8b, 0xe0, 0x9c, 0x92, 0x64, 0x15, + 0x4c, 0x08, 0x3d, 0x67, 0x41, 0x18, 0x92, 0xa4, 0xb3, 0x48, 0x62, 0x16, 0xa3, 0x43, 0x61, 0xeb, + 0x18, 0x5b, 0x47, 0xd9, 0xdc, 0x63, 0x79, 0x62, 0x32, 0xf7, 0x13, 0xa6, 0x7e, 0x15, 0xda, 0x3d, + 0x59, 0xdf, 0x8f, 0xa3, 0x59, 0xf0, 0x4e, 0x1b, 0x94, 0x8b, 0x84, 0x84, 0xc4, 0xa7, 0xc4, 0x3c, + 0x33, 0x87, 0x8c, 0x2d, 0x88, 0x66, 0xb1, 0x36, 0x9c, 0x66, 0x0c, 0x94, 0xf9, 0x6c, 0x49, 0x33, + 0x7c, 0x2b, 0x92, 0xd0, 0x20, 0x8e, 0xcc, 0x53, 0xd9, 0xbc, 0x7f, 0xea, 0xf0, 0x74, 0x10, 0x50, + 0x86, 0xd5, 0x41, 0x8a, 0xc9, 0xef, 0x4b, 0x42, 0x19, 0x3a, 0x84, 0xad, 0x30, 0xb8, 0x0d, 0x98, + 0x53, 0x7b, 0x56, 0x3b, 0x6b, 0x60, 0xb5, 0x40, 0xc7, 0x60, 0xc5, 0xb3, 0x19, 0x25, 0xcc, 0xa9, + 0xf3, 0xed, 0x36, 0xd6, 0x2b, 0xf4, 0x1d, 0xb4, 0x68, 0x9c, 0xb0, 0xf1, 0xcd, 0x9d, 0xd3, 0xe0, + 0x86, 0xbd, 0xee, 0x27, 0x9d, 0x22, 0x29, 0x3a, 0xc2, 0xd3, 0x88, 0x03, 0x3b, 0xe2, 0xe7, 0xe5, + 0x1d, 0xb6, 0xa8, 0x7c, 0x0a, 0xde, 0x59, 0x10, 0x32, 0x92, 0x38, 0x4d, 0xc5, 0xab, 0x56, 0xe8, + 0x12, 0x40, 0xf2, 0xc6, 0xc9, 0x94, 0xdb, 0xb6, 0x24, 0xf5, 0x59, 0x05, 0xea, 0xb7, 0x02, 0x8f, + 0xdb, 0xd4, 0xbc, 0xa2, 0x6f, 0x61, 0x47, 0x49, 0x32, 0x9e, 0xc4, 0x53, 0x42, 0x1d, 0xeb, 0x59, + 0x83, 0x53, 0x9d, 0x2a, 0x2a, 0xa3, 0xf0, 0x48, 0x89, 0xd6, 0xe3, 0x08, 0xbc, 0xad, 0xe0, 0xe2, + 0x9d, 0x7a, 0xbf, 0x80, 0x6d, 0xe8, 0xbd, 0x2e, 0x58, 0x2a, 0x78, 0xb4, 0x0d, 0xad, 0xeb, 0xe1, + 0x9b, 0xe1, 0xdb, 0x9f, 0x86, 0xfb, 0x4f, 0x90, 0x0d, 0xcd, 0xe1, 0xc5, 0xf7, 0xfd, 0xfd, 0x1a, + 0x3a, 0x80, 0xdd, 0xc1, 0xc5, 0xe8, 0x87, 0x31, 0xee, 0x0f, 0xfa, 0x17, 0xa3, 0xfe, 0xab, 0xfd, + 0xba, 0xf7, 0x11, 0xb4, 0xd3, 0xa8, 0x50, 0x0b, 0x1a, 0x17, 0xa3, 0x9e, 0x3a, 0xf2, 0xaa, 0xcf, + 0xdf, 0x6a, 0xde, 0x9f, 0x35, 0x38, 0xcc, 0x16, 0x81, 0x2e, 0xe2, 0x88, 0x12, 0x51, 0x85, 0x49, + 0xbc, 0x8c, 0xd2, 0x2a, 0xc8, 0x05, 0x42, 0xd0, 0x8c, 0xc8, 0x1f, 0xa6, 0x06, 0xf2, 0x5d, 0x20, + 0x59, 0xcc, 0xfc, 0x50, 0xea, 0xcf, 0x91, 0x72, 0x81, 0xbe, 0x06, 0x5b, 0x27, 0x47, 0xb9, 0xb2, + 0x8d, 0xb3, 0xed, 0xee, 0x51, 0x36, 0x65, 0xed, 0x11, 0xa7, 0x30, 0xef, 0x12, 0x4e, 0x2e, 0x89, + 0x89, 0x44, 0x29, 0x62, 0xee, 0x84, 0xf0, 0xeb, 0xdf, 0x12, 0x19, 0x8c, 0xf0, 0xcb, 0xdf, 0x91, + 0x03, 0x2d, 0x7d, 0xa1, 0x64, 0x38, 0x5b, 0xd8, 0x2c, 0x3d, 0x06, 0xce, 0x43, 0x22, 0x9d, 0x57, + 0x11, 0xd3, 0xa7, 0xd0, 0x14, 0xd7, 0x59, 0xd2, 0x6c, 0x77, 0x51, 0x36, 0xce, 0x2b, 0x6e, 0xc1, + 0xd2, 0x8e, 0x3e, 0x80, 0xb6, 0xc0, 0xd3, 0x85, 0x3f, 0x21, 0x32, 0xdb, 0x36, 0xbe, 0xdf, 0xf0, + 0x5e, 0xaf, 0x7b, 0xed, 0xc5, 0x11, 0x23, 0x11, 0xdb, 0x2c, 0xfe, 0x01, 0x9c, 0x16, 0x30, 0xe9, + 0x04, 0xce, 0xa1, 0xa5, 0x43, 0x93, 0x6c, 0xa5, 0xba, 0x1a, 0x94, 0xf7, 0x2f, 0x2f, 0xf1, 0xf5, + 0x62, 0xea, 0x33, 0x62, 0x4c, 0xff, 0x13, 0xd4, 0x73, 0x5e, 0x76, 0x31, 0x16, 0xb4, 0x16, 0x07, + 0x8a, 0x5b, 0xcd, 0x8e, 0x9e, 0xf8, 0xc5, 0xca, 0x8e, 0x5e, 0x80, 0xb5, 0xf2, 0x43, 0xce, 0x23, + 0x85, 0x48, 0x55, 0xd3, 0x48, 0x39, 0x53, 0xb0, 0x46, 0xa0, 0x13, 0x68, 0x4d, 0x93, 0xbb, 0x71, + 0xb2, 0x8c, 0x64, 0x93, 0xd9, 0xd8, 0xe2, 0x4b, 0xbc, 0x8c, 0xd0, 0xc7, 0xb0, 0x3b, 0x0d, 0xa8, + 0x7f, 0x13, 0x92, 0xf1, 0x3c, 0x8e, 0x7f, 0xa3, 0xb2, 0xcf, 0x6c, 0xbc, 0xa3, 0x37, 0x5f, 0x8b, + 0x3d, 0xe4, 0x82, 0xcd, 0x79, 0x02, 0x91, 0x00, 0x6f, 0x1e, 0x61, 0x4f, 0xd7, 0x5c, 0xf3, 0xa3, + 0x5c, 0x6a, 0x9b, 0xaa, 0xf4, 0x57, 0x1d, 0x8e, 0xae, 0x22, 0xde, 0x7a, 0x61, 0x98, 0x93, 0x29, + 0x95, 0xa4, 0x56, 0x59, 0x92, 0xfa, 0xfb, 0x48, 0xd2, 0xc8, 0x48, 0x62, 0x8a, 0xd2, 0x5c, 0x2b, + 0x4a, 0x25, 0x99, 0x32, 0x97, 0xd3, 0xca, 0x5d, 0x4e, 0xf4, 0x21, 0x40, 0x42, 0x96, 0x94, 0x8c, + 0x25, 0x79, 0x4b, 0x9e, 0x6f, 0xcb, 0x9d, 0xa1, 0xf0, 0xb0, 0xae, 0xb1, 0x9d, 0xd3, 0xf8, 0x0a, + 0x8e, 0xf3, 0xc2, 0x6c, 0x2a, 0xf2, 0x1c, 0x4e, 0xae, 0xa3, 0xa0, 0x50, 0xe5, 0xa2, 0xcb, 0xf8, + 0x20, 0xef, 0x7a, 0x41, 0xde, 0x7c, 0xfc, 0x2c, 0x96, 0xc9, 0x3b, 0xa2, 0x75, 0x54, 0x0b, 0xef, + 0x0d, 0x38, 0x0f, 0x3d, 0x6d, 0x1a, 0xf6, 0x53, 0x38, 0xe0, 0xfd, 0xf8, 0xa3, 0xea, 0x4e, 0x1d, + 0xb0, 0xd7, 0x07, 0xb4, 0xbe, 0x79, 0xcf, 0xad, 0xb7, 0xb2, 0xdc, 0xe6, 0xd3, 0x67, 0xf0, 0xa6, + 0xd7, 0xbb, 0x7f, 0x5b, 0xb0, 0x67, 0x26, 0x95, 0xfa, 0xae, 0xa0, 0x00, 0x76, 0xd6, 0x47, 0x32, + 0xfa, 0xac, 0xfc, 0xb3, 0x93, 0xfb, 0x76, 0xba, 0x2f, 0xaa, 0x40, 0x55, 0xa8, 0xde, 0x93, 0xaf, + 0x6a, 0x88, 0xc2, 0x7e, 0x7e, 0x52, 0xa2, 0x2f, 0x8b, 0x39, 0x4a, 0x46, 0xb3, 0xdb, 0xa9, 0x0a, + 0x37, 0x6e, 0xd1, 0x4a, 0xca, 0x99, 0x1d, 0x6f, 0xe8, 0x51, 0x9a, 0xec, 0x44, 0x75, 0xcf, 0x2b, + 0xe3, 0x53, 0xbf, 0xbf, 0xc2, 0x6e, 0x66, 0x58, 0xa0, 0x12, 0xb5, 0x8a, 0x86, 0xa5, 0xfb, 0x79, + 0x25, 0x6c, 0xea, 0xeb, 0x16, 0xf6, 0xb2, 0x4d, 0x83, 0x4a, 0x08, 0x0a, 0x67, 0x8e, 0xfb, 0x45, + 0x35, 0x70, 0xea, 0x8e, 0xd7, 0x31, 0x7f, 0xdd, 0xcb, 0xea, 0x58, 0xd2, 0x80, 0x65, 0x75, 0x2c, + 0xeb, 0x22, 0xee, 0xd4, 0x07, 0xb8, 0xef, 0x00, 0xf4, 0xbc, 0xb4, 0x20, 0xd9, 0xc6, 0x71, 0xcf, + 0x1e, 0x07, 0x1a, 0x17, 0x2f, 0xe1, 0x67, 0xdb, 0xe0, 0x6e, 0x2c, 0xf9, 0xb7, 0xf1, 0x9b, 0xff, + 0x02, 0x00, 0x00, 0xff, 0xff, 0x7b, 0x5d, 0x13, 0x1a, 0x07, 0x0b, 0x00, 0x00, +>>>>>>> Add validate to Install / Upgrade commands that use kubernets to validate the manifest before reifying }