From 58fdaf1e529536b66c326e3a208b7eb51e32ae9b Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 22 Apr 2016 16:48:33 -0600 Subject: [PATCH] feat(tiller): add support for dry run install This adds support for dry run on install, as well as providing enough info for an install to display (verbose) manifests. While doing this, I ended up just storing the rendered manifests for simplicity. --- _proto/hapi/release/info.proto | 4 +- _proto/hapi/release/release.proto | 19 ++++--- _proto/hapi/services/tiller.proto | 4 ++ cmd/tiller/release_server.go | 20 +++++++- cmd/tiller/release_server_test.go | 70 +++++++++++++++++++++++++- pkg/proto/hapi/release/release.pb.go | 41 ++++++++------- pkg/proto/hapi/services/tiller.pb.go | 75 +++++++++++++++------------- 7 files changed, 168 insertions(+), 65 deletions(-) diff --git a/_proto/hapi/release/info.proto b/_proto/hapi/release/info.proto index 09e022ecd..fa8623ee8 100644 --- a/_proto/hapi/release/info.proto +++ b/_proto/hapi/release/info.proto @@ -13,8 +13,8 @@ option go_package = "release"; // message Info { Status status = 1; - + google.protobuf.Timestamp first_deployed = 2; - + google.protobuf.Timestamp last_deployed = 3; } diff --git a/_proto/hapi/release/release.proto b/_proto/hapi/release/release.proto index 0caa3cf28..52ba2cd44 100644 --- a/_proto/hapi/release/release.proto +++ b/_proto/hapi/release/release.proto @@ -11,18 +11,23 @@ option go_package = "release"; // // Release: // -// TODO +// A release describes a deployment of a chart, together with the chart +// and the variables used to deploy that chart. // message Release { - // TODO + // Name is the name of the release string name = 1; - // TODO + // Info provides information about a release hapi.release.Info info = 2; - - // TODO + + // Chart is the chart that was released. hapi.chart.Chart chart = 3; - - // TODO + + // Config is the set of extra Values added to the chart. + // These values override the default values inside of the chart. hapi.chart.Config config = 4; + + // Manifest is the string representation of the rendered template. + string manifest = 5; } diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index 34fd9687a..581fe659f 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -169,6 +169,10 @@ message InstallReleaseRequest { hapi.chart.Chart chart = 1; // Values is a string containing (unparsed) TOML values. hapi.chart.Config values = 2; + // DryRun, if true, will run through the release logic, but neither create + // a release object nor deploy to Kubernetes. The release object returned + // in the response will be fake. + bool dry_run = 3; } // diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index 212b63d3c..dba45f1c9 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -1,7 +1,9 @@ package main import ( + "bytes" "errors" + "log" "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/proto/hapi/release" @@ -54,11 +56,21 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea name := namer.Name() // Render the templates - _, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) + files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) if err != nil { return nil, err } + b := bytes.NewBuffer(nil) + for name, file := range files { + // Ignore empty documents because the Kubernetes library can't handle + // them. + if len(file) > 0 { + b.WriteString("\n---\n# Source: " + name + "\n") + b.WriteString(file) + } + } + // Store a release. r := &release.Release{ Name: name, @@ -67,6 +79,12 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea Info: &release.Info{ Status: &release.Status{Code: release.Status_UNKNOWN}, }, + Manifest: b.String(), + } + + if req.DryRun { + log.Printf("Dry run for %s", name) + return &services.InstallReleaseResponse{Release: r}, nil } if err := s.env.Releases.Create(r); err != nil { diff --git a/cmd/tiller/release_server_test.go b/cmd/tiller/release_server_test.go index a25bd9378..c09736341 100644 --- a/cmd/tiller/release_server_test.go +++ b/cmd/tiller/release_server_test.go @@ -1,12 +1,14 @@ package main import ( + "strings" + "testing" + "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/proto/hapi/chart" "github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/storage" "golang.org/x/net/context" - "testing" ) func rsFixture() *releaseServer { @@ -20,7 +22,12 @@ func TestInstallRelease(t *testing.T) { rs := rsFixture() req := &services.InstallReleaseRequest{ - Chart: &chart.Chart{}, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "hello", Data: []byte("hello: world")}, + }, + }, } res, err := rs.InstallRelease(c, req) if err != nil { @@ -29,6 +36,65 @@ func TestInstallRelease(t *testing.T) { if res.Release.Name == "" { t.Errorf("Expected release name.") } + + rel, err := rs.env.Releases.Read(res.Release.Name) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(rel.Manifest, "---\n# Source: hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } +} + +func TestInstallReleaseDryRun(t *testing.T) { + c := context.Background() + rs := rsFixture() + + req := &services.InstallReleaseRequest{ + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "hello", Data: []byte("hello: world")}, + {Name: "goodbye", Data: []byte("goodbye: world")}, + {Name: "empty", Data: []byte("")}, + }, + }, + DryRun: true, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Errorf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if !strings.Contains(res.Release.Manifest, "---\n# Source: hello\nhello: world") { + t.Errorf("unexpected output: %s", res.Release.Manifest) + } + + if !strings.Contains(res.Release.Manifest, "---\n# Source: goodbye\ngoodbye: world") { + t.Errorf("unexpected output: %s", res.Release.Manifest) + } + + if strings.Contains(res.Release.Manifest, "empty") { + t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest) + } + + if _, err := rs.env.Releases.Read(res.Release.Name); err == nil { + t.Errorf("Expected no stored release.") + } } func mockEnvironment() *environment.Environment { diff --git a/pkg/proto/hapi/release/release.pb.go b/pkg/proto/hapi/release/release.pb.go index 5f14340d9..b97f38f15 100644 --- a/pkg/proto/hapi/release/release.pb.go +++ b/pkg/proto/hapi/release/release.pb.go @@ -18,17 +18,21 @@ var _ = math.Inf // // Release: // -// TODO +// A release describes a deployment of a chart, together with the chart +// and the variables used to deploy that chart. // type Release struct { - // TODO + // Name is the name of the release Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - // TODO + // Info provides information about a release Info *Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` - // TODO + // Chart is the chart that was released. Chart *hapi_chart3.Chart `protobuf:"bytes,3,opt,name=chart" json:"chart,omitempty"` - // TODO + // Config is the set of extra Values added to the chart. + // These values override the default values inside of the chart. Config *hapi_chart.Config `protobuf:"bytes,4,opt,name=config" json:"config,omitempty"` + // Manifest is the string representation of the rendered template. + Manifest string `protobuf:"bytes,5,opt,name=manifest" json:"manifest,omitempty"` } func (m *Release) Reset() { *m = Release{} } @@ -62,17 +66,18 @@ func init() { } var fileDescriptor1 = []byte{ - // 182 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xca, 0x48, 0x2c, 0xc8, - 0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0x85, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, - 0x42, 0x3c, 0x20, 0x39, 0x3d, 0xa8, 0x98, 0x94, 0x38, 0x8a, 0xca, 0xcc, 0xbc, 0xb4, 0x7c, 0x88, - 0x32, 0xa8, 0x44, 0x72, 0x46, 0x62, 0x51, 0x89, 0x7e, 0x72, 0x7e, 0x5e, 0x5a, 0x66, 0x3a, 0x54, - 0x42, 0x0c, 0x59, 0x02, 0x44, 0x42, 0xc4, 0x95, 0x66, 0x31, 0x72, 0xb1, 0x07, 0x41, 0xcc, 0x11, - 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, - 0x85, 0xd4, 0xb8, 0x58, 0x40, 0xc6, 0x4b, 0x30, 0x01, 0xc5, 0xb8, 0x8d, 0x84, 0xf4, 0x90, 0x9d, - 0xa1, 0xe7, 0x09, 0x94, 0x09, 0x02, 0xcb, 0x0b, 0xa9, 0x73, 0xb1, 0x82, 0x8d, 0x95, 0x60, 0x06, - 0x2b, 0x14, 0x84, 0x28, 0x84, 0xd8, 0xe4, 0x0c, 0x22, 0x83, 0x20, 0xf2, 0x42, 0x5a, 0x5c, 0x6c, - 0x10, 0x87, 0x49, 0xb0, 0x20, 0x1b, 0x09, 0x55, 0x09, 0x96, 0x09, 0x82, 0xaa, 0x70, 0xe2, 0x8c, - 0x62, 0x87, 0x5a, 0x95, 0xc4, 0x06, 0x76, 0xae, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xb4, 0xa3, - 0x61, 0x9d, 0x24, 0x01, 0x00, 0x00, + // 200 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x54, 0x8f, 0xbd, 0x0e, 0x82, 0x30, + 0x14, 0x85, 0x83, 0xf2, 0x23, 0xd5, 0xc5, 0x3b, 0x68, 0xc3, 0x64, 0x1c, 0xd4, 0x38, 0x94, 0x44, + 0xdf, 0x40, 0x27, 0xd7, 0x8e, 0x6e, 0x95, 0x14, 0x69, 0x22, 0x2d, 0x01, 0x9e, 0xcd, 0xe7, 0x93, + 0xf6, 0x56, 0x83, 0xcb, 0x85, 0xde, 0xef, 0xcb, 0xe9, 0x29, 0xc9, 0x2a, 0xd1, 0xa8, 0xbc, 0x95, + 0x2f, 0x29, 0x3a, 0xf9, 0xfd, 0xb2, 0xa6, 0x35, 0xbd, 0x81, 0x85, 0x65, 0xcc, 0xef, 0xb2, 0xf5, + 0x9f, 0xa9, 0x74, 0x69, 0x50, 0xf3, 0xa0, 0xa8, 0x44, 0xdb, 0xe7, 0x85, 0xd1, 0xa5, 0x7a, 0x7a, + 0xb0, 0x1a, 0x03, 0x3b, 0x71, 0xbf, 0x7d, 0x07, 0x24, 0xe1, 0x98, 0x03, 0x40, 0x42, 0x2d, 0x6a, + 0x49, 0x83, 0x4d, 0x70, 0x48, 0xb9, 0xfb, 0x87, 0x1d, 0x09, 0x6d, 0x3c, 0x9d, 0x0c, 0xbb, 0xf9, + 0x09, 0xd8, 0xb8, 0x06, 0xbb, 0x0d, 0x84, 0x3b, 0x0e, 0x7b, 0x12, 0xb9, 0x58, 0x3a, 0x75, 0xe2, + 0x12, 0x45, 0xbc, 0xe9, 0x6a, 0x27, 0x47, 0x0e, 0x47, 0x12, 0x63, 0x31, 0x1a, 0x8e, 0x23, 0xbd, + 0xe9, 0x08, 0xf7, 0x06, 0x64, 0x64, 0x56, 0x0b, 0xad, 0x4a, 0xd9, 0xf5, 0x34, 0x72, 0xa5, 0x7e, + 0xe7, 0x4b, 0x7a, 0x4f, 0x7c, 0x8d, 0x47, 0xec, 0x9e, 0x72, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, + 0xd4, 0xf3, 0x60, 0x0b, 0x40, 0x01, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index b12567750..e8922e10a 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -179,6 +179,10 @@ type InstallReleaseRequest struct { Chart *hapi_chart3.Chart `protobuf:"bytes,1,opt,name=chart" json:"chart,omitempty"` // Values is a string containing (unparsed) TOML values. Values *hapi_chart.Config `protobuf:"bytes,2,opt,name=values" json:"values,omitempty"` + // DryRun, if true, will run through the release logic, but neither create + // a release object nor deploy to Kubernetes. The release object returned + // in the response will be fake. + DryRun bool `protobuf:"varint,3,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` } func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} } @@ -537,39 +541,40 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ } var fileDescriptor1 = []byte{ - // 529 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x4d, 0x73, 0xd3, 0x30, - 0x10, 0x6d, 0x08, 0x0d, 0x65, 0xd3, 0x66, 0xa8, 0x70, 0x12, 0x47, 0xa7, 0xa2, 0x0b, 0xa5, 0x80, - 0x03, 0xe1, 0x08, 0x5c, 0xc8, 0x81, 0xe9, 0x4c, 0x87, 0x83, 0x99, 0x5e, 0xb8, 0x30, 0x26, 0x28, - 0x54, 0x8c, 0x62, 0x07, 0x4b, 0xc9, 0x81, 0xff, 0xc0, 0xff, 0xe1, 0xe7, 0x61, 0xeb, 0xc3, 0x13, - 0x39, 0xd6, 0xd4, 0xf4, 0xe2, 0x8c, 0xf4, 0xde, 0xee, 0xdb, 0x5d, 0xbd, 0x9d, 0x00, 0xbe, 0x49, - 0xd6, 0x6c, 0x2a, 0x68, 0xbe, 0x65, 0x0b, 0x2a, 0xa6, 0x92, 0x71, 0x4e, 0xf3, 0x68, 0x9d, 0x67, - 0x32, 0x43, 0x41, 0x89, 0x45, 0x16, 0x8b, 0x34, 0x86, 0x47, 0x2a, 0x62, 0x71, 0x93, 0xe4, 0x52, - 0x7f, 0x35, 0x1b, 0x8f, 0x77, 0xef, 0xb3, 0x74, 0xc9, 0x7e, 0x18, 0x40, 0x4b, 0xe4, 0x94, 0xd3, - 0x44, 0x50, 0xfb, 0x6b, 0xb0, 0x89, 0x83, 0x09, 0x99, 0xc8, 0x8d, 0xd0, 0x10, 0x99, 0xc3, 0xe3, - 0x2b, 0x26, 0x64, 0xac, 0x31, 0x11, 0xd3, 0x5f, 0x1b, 0x2a, 0x24, 0x0a, 0xe0, 0x90, 0xb3, 0x15, - 0x93, 0x61, 0xe7, 0xac, 0x73, 0xde, 0x8d, 0xf5, 0x01, 0x8d, 0xa0, 0x97, 0x2d, 0x97, 0x82, 0xca, - 0xf0, 0x9e, 0xba, 0x36, 0x27, 0xf2, 0xa7, 0x03, 0x81, 0x9b, 0x45, 0xac, 0xb3, 0x54, 0xd0, 0x32, - 0xcd, 0x22, 0xdb, 0xa4, 0x55, 0x1a, 0x75, 0xf0, 0xa5, 0x29, 0xd9, 0x32, 0x93, 0x09, 0x0f, 0xbb, - 0x9a, 0xad, 0x0e, 0xe8, 0x35, 0x1c, 0x99, 0xca, 0x45, 0x78, 0xff, 0xac, 0x7b, 0xde, 0x9f, 0x0d, - 0x23, 0x35, 0x32, 0xdb, 0xa3, 0x51, 0x8d, 0x2b, 0x1a, 0x79, 0x07, 0xe3, 0x8f, 0xd4, 0x56, 0xf3, - 0x59, 0xb5, 0x6b, 0x1b, 0x7b, 0x02, 0xc7, 0x86, 0xf6, 0x35, 0x4d, 0x56, 0x54, 0x15, 0xf6, 0x30, - 0xee, 0x9b, 0xbb, 0x4f, 0xc5, 0x15, 0xf9, 0x0d, 0xe1, 0x7e, 0xb4, 0x69, 0xe8, 0xf6, 0x70, 0xf4, - 0x16, 0x06, 0x96, 0xa2, 0x27, 0xad, 0xba, 0xec, 0xcf, 0x02, 0xb7, 0x6a, 0x93, 0xf8, 0x24, 0xdf, - 0xd5, 0x21, 0xef, 0x77, 0xb5, 0xe7, 0x59, 0x2a, 0x69, 0x2a, 0xff, 0xa3, 0xf4, 0x2b, 0x98, 0x34, - 0x84, 0x9b, 0xda, 0xa7, 0xf0, 0xc0, 0x70, 0x55, 0xa8, 0x77, 0x8e, 0x96, 0x45, 0x46, 0x10, 0x5c, - 0xaf, 0xbf, 0x27, 0x92, 0x5a, 0x44, 0x17, 0x42, 0xc6, 0x30, 0xac, 0xdd, 0x6b, 0x05, 0xc2, 0x61, - 0x78, 0x99, 0x16, 0x4d, 0x73, 0xee, 0x46, 0xa0, 0xa7, 0x85, 0x0f, 0x4a, 0xcb, 0x1a, 0xe1, 0x53, - 0x2d, 0xac, 0x7d, 0x3d, 0x2f, 0xbf, 0xb1, 0xc6, 0xd1, 0x05, 0xf4, 0xb6, 0x09, 0x2f, 0x62, 0xcc, - 0xd0, 0x90, 0xc3, 0x54, 0x7e, 0x8f, 0x0d, 0x83, 0x5c, 0xc2, 0xa8, 0xae, 0x76, 0xd7, 0x4e, 0x27, - 0x30, 0xbe, 0x4e, 0x59, 0x53, 0xe9, 0x04, 0x43, 0xb8, 0x0f, 0x69, 0x9d, 0xd9, 0xdf, 0x43, 0x18, - 0x58, 0x9f, 0xe8, 0xfd, 0x45, 0x0c, 0x8e, 0x77, 0x37, 0x01, 0x3d, 0x8b, 0x9a, 0xd6, 0x3b, 0x6a, - 0xd8, 0x39, 0x7c, 0xd1, 0x86, 0x6a, 0x26, 0x7d, 0xf0, 0xaa, 0x83, 0x04, 0x3c, 0xaa, 0xfb, 0x14, - 0xbd, 0x6c, 0xce, 0xe1, 0xd9, 0x06, 0x1c, 0xb5, 0xa5, 0x5b, 0x59, 0xb4, 0x85, 0xd3, 0x3d, 0x87, - 0xa1, 0x5b, 0xd3, 0xb8, 0x4e, 0xc6, 0xd3, 0xd6, 0xfc, 0x4a, 0xf7, 0x27, 0x9c, 0x38, 0x9e, 0x43, - 0x9e, 0x69, 0x35, 0x19, 0x16, 0x3f, 0x6f, 0xc5, 0xad, 0xb4, 0x56, 0x30, 0x70, 0x8d, 0x85, 0x3c, - 0x09, 0x1a, 0xcd, 0x8e, 0x5f, 0xb4, 0x23, 0x57, 0x72, 0xc5, 0x3b, 0xd6, 0x1d, 0xe6, 0x7b, 0x47, - 0x8f, 0x49, 0x7d, 0xef, 0xe8, 0x33, 0x2e, 0x39, 0xf8, 0x00, 0x5f, 0x8e, 0x2c, 0xfb, 0x5b, 0x4f, - 0xfd, 0x15, 0xbc, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0x75, 0xe1, 0x2e, 0x4f, 0xa6, 0x06, 0x00, - 0x00, + // 550 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0xad, 0x09, 0x4d, 0xc3, 0xa4, 0x8d, 0xe8, 0xe2, 0xc4, 0x8e, 0x4f, 0x65, 0x2f, 0x94, 0x02, + 0x0e, 0x84, 0x23, 0x70, 0x21, 0x07, 0x54, 0xa9, 0xe2, 0x60, 0xd4, 0x0b, 0x97, 0xca, 0xa4, 0x1b, + 0xba, 0xc8, 0x59, 0x07, 0xef, 0x3a, 0x12, 0xdc, 0x39, 0xf2, 0x7f, 0xf8, 0x79, 0xd8, 0xfb, 0x61, + 0xc5, 0x89, 0x57, 0x31, 0x5c, 0x1c, 0xed, 0xbe, 0x37, 0xf3, 0x66, 0x66, 0xdf, 0x28, 0x10, 0xdc, + 0xc5, 0x2b, 0x3a, 0xe1, 0x24, 0x5b, 0xd3, 0x39, 0xe1, 0x13, 0x41, 0x93, 0x84, 0x64, 0xe1, 0x2a, + 0x4b, 0x45, 0x8a, 0xdc, 0x12, 0x0b, 0x0d, 0x16, 0x2a, 0x2c, 0x18, 0xc9, 0x88, 0xf9, 0x5d, 0x9c, + 0x09, 0xf5, 0x55, 0xec, 0xc0, 0xdb, 0xbc, 0x4f, 0xd9, 0x82, 0x7e, 0xd5, 0x80, 0x92, 0xc8, 0x48, + 0x42, 0x62, 0x4e, 0xcc, 0xaf, 0xc6, 0xc6, 0x35, 0x8c, 0x8b, 0x58, 0xe4, 0x5c, 0x41, 0x78, 0x06, + 0x8f, 0xae, 0x28, 0x17, 0x91, 0xc2, 0x78, 0x44, 0xbe, 0xe7, 0x84, 0x0b, 0xe4, 0xc2, 0x61, 0x42, + 0x97, 0x54, 0xf8, 0xce, 0x99, 0x73, 0xde, 0x89, 0xd4, 0x01, 0x8d, 0xa0, 0x9b, 0x2e, 0x16, 0x9c, + 0x08, 0xff, 0x9e, 0xbc, 0xd6, 0x27, 0xfc, 0xdb, 0x01, 0xb7, 0x9e, 0x85, 0xaf, 0x52, 0xc6, 0x49, + 0x99, 0x66, 0x9e, 0xe6, 0xac, 0x4a, 0x23, 0x0f, 0xb6, 0x34, 0x25, 0x5b, 0xa4, 0x22, 0x4e, 0xfc, + 0x8e, 0x62, 0xcb, 0x03, 0x7a, 0x05, 0x3d, 0x5d, 0x39, 0xf7, 0xef, 0x9f, 0x75, 0xce, 0xfb, 0xd3, + 0x61, 0x28, 0x47, 0x66, 0x7a, 0xd4, 0xaa, 0x51, 0x45, 0xc3, 0x6f, 0xc1, 0xfb, 0x40, 0x4c, 0x35, + 0x9f, 0x64, 0xbb, 0xa6, 0xb1, 0xc7, 0x70, 0xac, 0x69, 0x37, 0x2c, 0x5e, 0x12, 0x59, 0xd8, 0x83, + 0xa8, 0xaf, 0xef, 0x3e, 0x16, 0x57, 0xf8, 0x27, 0xf8, 0xbb, 0xd1, 0xba, 0xa1, 0xfd, 0xe1, 0xe8, + 0x0d, 0x0c, 0x0c, 0x45, 0x4d, 0x5a, 0x76, 0xd9, 0x9f, 0xba, 0xf5, 0xaa, 0x75, 0xe2, 0x93, 0x6c, + 0x53, 0x07, 0xbf, 0xdb, 0xd4, 0x9e, 0xa5, 0x4c, 0x10, 0x26, 0xfe, 0xa1, 0xf4, 0x2b, 0x18, 0x37, + 0x84, 0xeb, 0xda, 0x27, 0x70, 0xa4, 0xb9, 0x32, 0xd4, 0x3a, 0x47, 0xc3, 0xc2, 0x23, 0x70, 0xaf, + 0x57, 0xb7, 0xb1, 0x20, 0x06, 0x51, 0x85, 0x60, 0x0f, 0x86, 0x5b, 0xf7, 0x4a, 0x01, 0xff, 0x72, + 0x60, 0x78, 0xc9, 0x8a, 0xae, 0x93, 0xa4, 0x1e, 0x82, 0x9e, 0x14, 0x46, 0x28, 0x3d, 0xab, 0x95, + 0x4f, 0x95, 0xb2, 0x32, 0xf6, 0xac, 0xfc, 0x46, 0x0a, 0x47, 0x17, 0xd0, 0x5d, 0xc7, 0x49, 0x11, + 0xa3, 0xa7, 0x86, 0x6a, 0x4c, 0x69, 0xf8, 0x48, 0x33, 0x90, 0x07, 0x47, 0xb7, 0xd9, 0x8f, 0x9b, + 0x2c, 0x67, 0xd2, 0x31, 0xbd, 0xa8, 0x5b, 0x1c, 0xa3, 0x9c, 0xe1, 0x4b, 0x18, 0x6d, 0x97, 0xf1, + 0xbf, 0x33, 0x18, 0x83, 0x77, 0xcd, 0x68, 0x53, 0x4f, 0x38, 0x00, 0x7f, 0x17, 0x52, 0x3a, 0xd3, + 0x3f, 0x87, 0x30, 0x30, 0x0e, 0x52, 0x9b, 0x8d, 0x28, 0x1c, 0x6f, 0xee, 0x08, 0x7a, 0x1a, 0x36, + 0x2d, 0x7e, 0xd8, 0xb0, 0x8d, 0xc1, 0x45, 0x1b, 0xaa, 0x7e, 0x83, 0x83, 0x97, 0x0e, 0xe2, 0xf0, + 0x70, 0xdb, 0xc1, 0xe8, 0x45, 0x73, 0x0e, 0xcb, 0x9e, 0x04, 0x61, 0x5b, 0xba, 0x91, 0x45, 0x6b, + 0x38, 0xdd, 0xf1, 0x1e, 0xda, 0x9b, 0xa6, 0xee, 0xf1, 0x60, 0xd2, 0x9a, 0x5f, 0xe9, 0x7e, 0x83, + 0x93, 0x9a, 0x1b, 0x91, 0x65, 0x5a, 0x4d, 0x56, 0x0e, 0x9e, 0xb5, 0xe2, 0x56, 0x5a, 0x4b, 0x18, + 0xd4, 0x8d, 0x85, 0x2c, 0x09, 0x1a, 0xb7, 0x20, 0x78, 0xde, 0x8e, 0x5c, 0xc9, 0x15, 0xef, 0xb8, + 0xed, 0x30, 0xdb, 0x3b, 0x5a, 0x4c, 0x6a, 0x7b, 0x47, 0x9b, 0x71, 0xf1, 0xc1, 0x7b, 0xf8, 0xdc, + 0x33, 0xec, 0x2f, 0x5d, 0xf9, 0x27, 0xf1, 0xfa, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x65, + 0x97, 0x54, 0xc0, 0x06, 0x00, 0x00, }