Commit cc24e3f5 authored by Ville Aikas's avatar Ville Aikas Committed by GitHub

Merge pull request #1090 from vaikas-google/nodes.txt

First cut of handling NOTES.txt file
parents 27480470 7c4cad5c
...@@ -38,6 +38,10 @@ message Status { ...@@ -38,6 +38,10 @@ message Status {
Code code = 1; Code code = 1;
google.protobuf.Any details = 2; google.protobuf.Any details = 2;
// Cluster resources as kubectl would print them. // Cluster resources as kubectl would print them.
string resources = 3; string resources = 3;
// Contains the rendered templates/NOTES.txt if available
string notes = 4;
} }
...@@ -142,6 +142,9 @@ message GetReleaseStatusResponse { ...@@ -142,6 +142,9 @@ message GetReleaseStatusResponse {
// Info contains information about the release. // Info contains information about the release.
hapi.release.Info info = 2; hapi.release.Info info = 2;
// Namesapce the release was released into
string namespace = 3;
} }
// GetReleaseContentRequest is a request to get the contents of a release. // GetReleaseContentRequest is a request to get the contents of a release.
......
...@@ -67,10 +67,15 @@ func (s *statusCmd) run() error { ...@@ -67,10 +67,15 @@ func (s *statusCmd) run() error {
} }
fmt.Fprintf(s.out, "Last Deployed: %s\n", timeconv.String(res.Info.LastDeployed)) fmt.Fprintf(s.out, "Last Deployed: %s\n", timeconv.String(res.Info.LastDeployed))
fmt.Fprintf(s.out, "Namespace: %s\n", res.Namespace)
fmt.Fprintf(s.out, "Status: %s\n", res.Info.Status.Code) fmt.Fprintf(s.out, "Status: %s\n", res.Info.Status.Code)
fmt.Fprintf(s.out, "Resources:\n%s\n", res.Info.Status.Resources)
if res.Info.Status.Details != nil { if res.Info.Status.Details != nil {
fmt.Fprintf(s.out, "Details: %s\n", res.Info.Status.Details) fmt.Fprintf(s.out, "Details: %s\n", res.Info.Status.Details)
} }
fmt.Fprintf(s.out, "\n")
fmt.Fprintf(s.out, "Resources:\n%s\n", res.Info.Status.Resources)
if len(res.Info.Status.Notes) > 0 {
fmt.Fprintf(s.out, "Notes:\n%s\n", res.Info.Status.Notes)
}
return nil return nil
} }
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"log" "log"
"regexp" "regexp"
"sort" "sort"
"strings"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/technosophos/moniker" "github.com/technosophos/moniker"
...@@ -47,6 +48,12 @@ var srv *releaseServer ...@@ -47,6 +48,12 @@ var srv *releaseServer
// characters in length. See https://github.com/kubernetes/helm/issues/1071 // characters in length. See https://github.com/kubernetes/helm/issues/1071
const releaseNameMaxLen = 14 const releaseNameMaxLen = 14
// NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine
// but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually
// wants to see this file after rendering in the status command. However, it must be a suffix
// since there can be filepath in front of it.
const notesFileSuffix = "NOTES.txt"
func init() { func init() {
srv = &releaseServer{ srv = &releaseServer{
env: env, env: env,
...@@ -179,6 +186,9 @@ func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetRelease ...@@ -179,6 +186,9 @@ func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetRelease
if rel.Info == nil { if rel.Info == nil {
return nil, errors.New("release info is missing") return nil, errors.New("release info is missing")
} }
if rel.Chart == nil {
return nil, errors.New("release chart is missing")
}
// Ok, we got the status of the release as we had jotted down, now we need to match the // Ok, we got the status of the release as we had jotted down, now we need to match the
// manifest we stashed away with reality from the cluster. // manifest we stashed away with reality from the cluster.
...@@ -190,7 +200,7 @@ func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetRelease ...@@ -190,7 +200,7 @@ func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetRelease
} }
rel.Info.Status.Resources = resp rel.Info.Status.Resources = resp
return &services.GetReleaseStatusResponse{Info: rel.Info}, nil return &services.GetReleaseStatusResponse{Info: rel.Info, Namespace: rel.Namespace}, nil
} }
func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) {
...@@ -281,7 +291,7 @@ func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele ...@@ -281,7 +291,7 @@ func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
return nil, nil, err return nil, nil, err
} }
hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender) hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -302,6 +312,9 @@ func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele ...@@ -302,6 +312,9 @@ func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
Hooks: hooks, Hooks: hooks,
} }
if len(notesTxt) > 0 {
updatedRelease.Info.Status.Notes = notesTxt
}
return currentRelease, updatedRelease, nil return currentRelease, updatedRelease, nil
} }
...@@ -389,7 +402,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re ...@@ -389,7 +402,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
return nil, err return nil, err
} }
hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender) hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -409,6 +422,9 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re ...@@ -409,6 +422,9 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
Hooks: hooks, Hooks: hooks,
Version: 1, Version: 1,
} }
if len(notesTxt) > 0 {
rel.Info.Status.Notes = notesTxt
}
return rel, nil return rel, nil
} }
...@@ -437,11 +453,24 @@ func (s *releaseServer) getVersionSet() (versionSet, error) { ...@@ -437,11 +453,24 @@ func (s *releaseServer) getVersionSet() (versionSet, error) {
return newVersionSet(versions...), nil return newVersionSet(versions...), nil
} }
func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values) ([]*release.Hook, *bytes.Buffer, error) { func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values) ([]*release.Hook, *bytes.Buffer, string, error) {
renderer := s.engine(ch) renderer := s.engine(ch)
files, err := renderer.Render(ch, values) files, err := renderer.Render(ch, values)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, "", err
}
// NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource,
// pull it out of here into a separate file so that we can actually use the output of the rendered
// text file. We have to spin through this map because the file contains path information, so we
// look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip
// it in the sortHooks.
notes := ""
for k, v := range files {
if strings.HasSuffix(k, notesFileSuffix) {
notes = v
delete(files, k)
}
} }
// Sort hooks, manifests, and partials. Only hooks and manifests are returned, // Sort hooks, manifests, and partials. Only hooks and manifests are returned,
...@@ -449,13 +478,13 @@ func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values ...@@ -449,13 +478,13 @@ func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values
// removed here. // removed here.
vs, err := s.getVersionSet() vs, err := s.getVersionSet()
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) return nil, nil, "", fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err)
} }
hooks, manifests, err := sortManifests(files, vs) hooks, manifests, err := sortManifests(files, vs)
if err != nil { if err != nil {
// By catching parse errors here, we can prevent bogus releases from going // By catching parse errors here, we can prevent bogus releases from going
// to Kubernetes. // to Kubernetes.
return nil, nil, err return nil, nil, "", err
} }
// Aggregate all valid manifests into one big doc. // Aggregate all valid manifests into one big doc.
...@@ -465,7 +494,7 @@ func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values ...@@ -465,7 +494,7 @@ func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values
b.WriteString(file) b.WriteString(file)
} }
return hooks, b, nil return hooks, b, notes, nil
} }
// validateYAML checks to see if YAML is well-formed. // validateYAML checks to see if YAML is well-formed.
......
...@@ -35,6 +35,8 @@ import ( ...@@ -35,6 +35,8 @@ import (
"k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/storage/driver"
) )
const notesText = "my notes here"
var manifestWithHook = `apiVersion: v1 var manifestWithHook = `apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
...@@ -230,6 +232,137 @@ func TestInstallRelease(t *testing.T) { ...@@ -230,6 +232,137 @@ func TestInstallRelease(t *testing.T) {
} }
} }
func TestInstallReleaseWithNotes(t *testing.T) {
c := context.Background()
rs := rsFixture()
// TODO: Refactor this into a mock.
req := &services.InstallReleaseRequest{
Namespace: "spaced",
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithHook)},
{Name: "NOTES.txt", Data: []byte(notesText)},
},
},
}
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 res.Release.Namespace != "spaced" {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace)
}
rel, err := rs.env.Releases.Get(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(rel.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks))
}
if rel.Hooks[0].Manifest != manifestWithHook {
t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest)
}
if rel.Info.Status.Notes != notesText {
t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes)
}
if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL {
t.Errorf("Expected event 0 is post install")
}
if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE {
t.Errorf("Expected event 0 is pre-delete")
}
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/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
}
func TestInstallReleaseWithNotesRendered(t *testing.T) {
c := context.Background()
rs := rsFixture()
// TODO: Refactor this into a mock.
req := &services.InstallReleaseRequest{
Namespace: "spaced",
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithHook)},
{Name: "NOTES.txt", Data: []byte(notesText + " {{.Release.Name}}")},
},
},
}
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 res.Release.Namespace != "spaced" {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace)
}
rel, err := rs.env.Releases.Get(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(rel.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks))
}
if rel.Hooks[0].Manifest != manifestWithHook {
t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest)
}
expectedNotes := fmt.Sprintf("%s %s", notesText, res.Release.Name)
if rel.Info.Status.Notes != expectedNotes {
t.Fatalf("Expected '%s', got '%s'", expectedNotes, rel.Info.Status.Notes)
}
if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL {
t.Errorf("Expected event 0 is post install")
}
if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE {
t.Errorf("Expected event 0 is pre-delete")
}
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/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
}
func TestInstallReleaseDryRun(t *testing.T) { func TestInstallReleaseDryRun(t *testing.T) {
c := context.Background() c := context.Background()
rs := rsFixture() rs := rsFixture()
......
...@@ -54,7 +54,7 @@ func New() *Engine { ...@@ -54,7 +54,7 @@ func New() *Engine {
} }
} }
// Render takes a chart, optional values, and value overrids, and attempts to render the Go templates. // Render takes a chart, optional values, and value overrides, and attempts to render the Go templates.
// //
// Render can be called repeatedly on the same engine. // Render can be called repeatedly on the same engine.
// //
......
...@@ -55,6 +55,8 @@ type Status struct { ...@@ -55,6 +55,8 @@ type Status struct {
Details *google_protobuf1.Any `protobuf:"bytes,2,opt,name=details" json:"details,omitempty"` Details *google_protobuf1.Any `protobuf:"bytes,2,opt,name=details" json:"details,omitempty"`
// Cluster resources as kubectl would print them. // Cluster resources as kubectl would print them.
Resources string `protobuf:"bytes,3,opt,name=resources" json:"resources,omitempty"` Resources string `protobuf:"bytes,3,opt,name=resources" json:"resources,omitempty"`
// Contains the rendered templates/NOTES.txt if available
Notes string `protobuf:"bytes,4,opt,name=notes" json:"notes,omitempty"`
} }
func (m *Status) Reset() { *m = Status{} } func (m *Status) Reset() { *m = Status{} }
...@@ -75,21 +77,22 @@ func init() { ...@@ -75,21 +77,22 @@ func init() {
} }
var fileDescriptor3 = []byte{ var fileDescriptor3 = []byte{
// 247 bytes of a gzipped FileDescriptorProto // 259 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0x48, 0x2c, 0xc8, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0xc1, 0x4e, 0xf2, 0x40,
0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0xd5, 0x2f, 0x2e, 0x49, 0x2c, 0x29, 0x2d, 0xd6, 0x14, 0x85, 0xff, 0x42, 0xff, 0xd6, 0x5e, 0x08, 0x21, 0x37, 0x2c, 0x5a, 0xe3, 0xc2, 0xb0, 0x72,
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x49, 0xe9, 0x41, 0xa5, 0xa4, 0x24, 0xd3, 0xf3, 0xe3, 0x6d, 0x82, 0x4f, 0x80, 0x76, 0x4c, 0xd4, 0xa6, 0x90, 0x56, 0x62, 0x74, 0x37, 0xc0, 0x88,
0xf3, 0xd3, 0x73, 0x52, 0xf5, 0xc1, 0x72, 0x49, 0xa5, 0x69, 0xfa, 0x89, 0x79, 0x95, 0x10, 0x85, 0x24, 0x4d, 0x87, 0x74, 0xa6, 0x0b, 0x9e, 0xd8, 0xd7, 0x70, 0x3a, 0x85, 0xe8, 0xae, 0xa7, 0xdf,
0x4a, 0x17, 0x19, 0xb9, 0xd8, 0x82, 0xc1, 0x3a, 0x85, 0x74, 0xb9, 0x58, 0x92, 0xf3, 0x53, 0x52, 0x77, 0xe6, 0xcc, 0x40, 0xf4, 0xc5, 0x0f, 0xfb, 0xb8, 0x16, 0xa5, 0xe0, 0x4a, 0xc4, 0x4a, 0x73,
0x25, 0x18, 0x15, 0x18, 0x35, 0xf8, 0x8c, 0x24, 0xf5, 0x90, 0x8d, 0xd0, 0x83, 0xa8, 0xd1, 0x73, 0xdd, 0x28, 0x3a, 0xd4, 0x52, 0x4b, 0x1c, 0xb6, 0x88, 0x4e, 0xe8, 0x32, 0xda, 0x49, 0xb9, 0x2b,
0x06, 0x2a, 0x08, 0x02, 0x2b, 0x13, 0xd2, 0xe3, 0x62, 0x4f, 0x49, 0x2d, 0x49, 0xcc, 0xcc, 0x29, 0x45, 0x6c, 0xd9, 0xba, 0xf9, 0x8c, 0x79, 0x75, 0xec, 0xc4, 0xe9, 0xb7, 0x03, 0x5e, 0x61, 0x9b,
0x96, 0x60, 0x02, 0xea, 0xe0, 0x36, 0x12, 0xd1, 0x83, 0x58, 0xa3, 0x07, 0xb3, 0x46, 0xcf, 0x31, 0x78, 0x0b, 0xee, 0x46, 0x6e, 0x45, 0xe8, 0x5c, 0x3b, 0x37, 0xa3, 0x59, 0x44, 0x7f, 0x8f, 0xa0,
0xaf, 0x32, 0x08, 0xa6, 0x48, 0x48, 0x86, 0x8b, 0xb3, 0x28, 0xb5, 0x38, 0xbf, 0xb4, 0x28, 0x39, 0xce, 0xa1, 0x07, 0x23, 0xe4, 0x56, 0x43, 0x02, 0x7f, 0x2b, 0x34, 0xdf, 0x97, 0x2a, 0xec, 0x99,
0xb5, 0x58, 0x82, 0x19, 0xa8, 0x83, 0x33, 0x08, 0x21, 0xa0, 0xe4, 0xc5, 0xc5, 0x02, 0x32, 0x5b, 0xc6, 0x60, 0x36, 0xa1, 0x6e, 0x86, 0xce, 0x33, 0x34, 0xaf, 0x8e, 0xf9, 0x59, 0xc2, 0x2b, 0x08,
0x88, 0x9b, 0x8b, 0x3d, 0xd4, 0xcf, 0xdb, 0xcf, 0x3f, 0xdc, 0x4f, 0x80, 0x41, 0x88, 0x87, 0x8b, 0x6a, 0xa1, 0x64, 0x53, 0x6f, 0x84, 0x0a, 0xfb, 0xa6, 0x11, 0xe4, 0xbf, 0x3f, 0x70, 0x02, 0xff,
0xc3, 0xc5, 0x35, 0xc0, 0xc7, 0x3f, 0xd2, 0xd5, 0x45, 0x80, 0x11, 0x24, 0xe5, 0xe2, 0xea, 0xe3, 0x2b, 0xa9, 0x0d, 0x71, 0x2d, 0xe9, 0xc2, 0xf4, 0x19, 0xdc, 0x76, 0x11, 0x07, 0xe0, 0xaf, 0xb2,
0x1a, 0x02, 0xe4, 0x30, 0x09, 0xf1, 0x71, 0x71, 0x05, 0x87, 0x06, 0xb8, 0x06, 0x05, 0xbb, 0xba, 0x97, 0x6c, 0xf1, 0x96, 0x8d, 0xff, 0xe1, 0x10, 0x2e, 0x12, 0xb6, 0x4c, 0x17, 0xef, 0x2c, 0x19,
0x00, 0xf9, 0xcc, 0x42, 0x5c, 0x5c, 0x6c, 0x6e, 0x8e, 0x9e, 0x3e, 0x40, 0x36, 0x8b, 0x13, 0x67, 0x3b, 0x2d, 0x4a, 0x58, 0xca, 0x5e, 0x4d, 0xe8, 0xe1, 0x08, 0xa0, 0x58, 0x2d, 0x59, 0x5e, 0xb0,
0x14, 0x3b, 0xd4, 0xd9, 0x49, 0x6c, 0x60, 0xb7, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd1, 0xc4, 0xe4, 0x3e, 0x02, 0x78, 0x8f, 0xf3, 0xa7, 0xd4, 0x7c, 0xbb, 0xf7, 0xc1, 0x87, 0x7f, 0x7a,
0xc3, 0xbf, 0x50, 0x2b, 0x01, 0x00, 0x00, 0xcc, 0xda, 0xb3, 0x37, 0xbc, 0xfb, 0x09, 0x00, 0x00, 0xff, 0xff, 0xae, 0x07, 0x47, 0x1f, 0x41,
0x01, 0x00, 0x00,
} }
...@@ -176,6 +176,8 @@ type GetReleaseStatusResponse struct { ...@@ -176,6 +176,8 @@ type GetReleaseStatusResponse struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// Info contains information about the release. // Info contains information about the release.
Info *hapi_release2.Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` Info *hapi_release2.Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"`
// Namesapce the release was released into
Namespace string `protobuf:"bytes,3,opt,name=namespace" json:"namespace,omitempty"`
} }
func (m *GetReleaseStatusResponse) Reset() { *m = GetReleaseStatusResponse{} } func (m *GetReleaseStatusResponse) Reset() { *m = GetReleaseStatusResponse{} }
...@@ -631,57 +633,57 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ ...@@ -631,57 +633,57 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
} }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 821 bytes of a gzipped FileDescriptorProto // 829 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x56, 0xdd, 0x52, 0xd3, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x56, 0xdd, 0x4e, 0xe3, 0x46,
0x14, 0x26, 0x6d, 0xe9, 0xcf, 0x29, 0x30, 0x65, 0x05, 0x5a, 0x32, 0xea, 0x30, 0x71, 0x54, 0x44, 0x14, 0xc6, 0x49, 0x70, 0x92, 0x13, 0x40, 0x61, 0x0a, 0x24, 0x58, 0x6d, 0x85, 0x5c, 0xb5, 0xa5,
0x49, 0xb5, 0xde, 0x3a, 0xce, 0x94, 0xd2, 0x01, 0x86, 0x5a, 0x66, 0x52, 0xd1, 0x19, 0x2f, 0xec, 0xb4, 0x38, 0x6d, 0x7a, 0x5b, 0x55, 0x0a, 0x21, 0x02, 0x44, 0x1a, 0xa4, 0x49, 0x51, 0xa5, 0x5e,
0x84, 0x76, 0x4b, 0xa3, 0x21, 0xa9, 0xd9, 0x2d, 0x23, 0x8f, 0xe0, 0x6b, 0xf8, 0x14, 0xde, 0xf8, 0x34, 0x32, 0xc9, 0x84, 0xb8, 0x6b, 0xec, 0xac, 0x67, 0x82, 0x96, 0x47, 0xd8, 0xd7, 0xd8, 0xa7,
0x64, 0xde, 0xb8, 0x3f, 0x49, 0x6c, 0xda, 0x44, 0x23, 0x37, 0xe9, 0xee, 0x9e, 0x6f, 0xbf, 0x73, 0xd8, 0x9b, 0x7d, 0xb2, 0xbd, 0xd9, 0xf9, 0xb1, 0xbd, 0x71, 0x62, 0xef, 0x7a, 0xb9, 0x71, 0xe6,
0xce, 0x77, 0xf6, 0x1c, 0x00, 0x75, 0x6c, 0x4e, 0xac, 0x3a, 0xc1, 0xde, 0xb5, 0x35, 0xc0, 0xa4, 0xcc, 0xf9, 0xe6, 0xfc, 0x7c, 0xe7, 0x07, 0xc0, 0x98, 0xd9, 0x73, 0xa7, 0x45, 0x49, 0xf0, 0xe8,
0x4e, 0x2d, 0xdb, 0xc6, 0x9e, 0x3e, 0xf1, 0x5c, 0xea, 0xa2, 0x0d, 0x6e, 0xd3, 0x03, 0x9b, 0x2e, 0x8c, 0x09, 0x6d, 0x31, 0xc7, 0x75, 0x49, 0x60, 0xcd, 0x03, 0x9f, 0xf9, 0x68, 0x4f, 0xe8, 0xac,
0x6d, 0xea, 0x96, 0xb8, 0x31, 0x18, 0x9b, 0x1e, 0x95, 0x5f, 0x89, 0x56, 0xab, 0xb3, 0xe7, 0xae, 0x48, 0x67, 0x29, 0x9d, 0x71, 0x20, 0x5f, 0x8c, 0x67, 0x76, 0xc0, 0xd4, 0x57, 0xa1, 0x8d, 0xc6,
0x33, 0xb2, 0x2e, 0x7d, 0x83, 0x74, 0xe1, 0x61, 0x1b, 0x9b, 0x04, 0x07, 0xbf, 0x91, 0x4b, 0x81, 0xf2, 0xbd, 0xef, 0x4d, 0x9d, 0xfb, 0x50, 0xa1, 0x5c, 0x04, 0xc4, 0x25, 0x36, 0x25, 0xd1, 0x6f,
0xcd, 0x72, 0x46, 0xae, 0x6f, 0xd8, 0x8e, 0x18, 0x08, 0x35, 0xe9, 0x94, 0x48, 0x93, 0xf6, 0x3d, 0xe2, 0x51, 0xa4, 0x73, 0xbc, 0xa9, 0x1f, 0x2a, 0x0e, 0x13, 0x0a, 0xca, 0x6c, 0xb6, 0xa0, 0x4a,
0x03, 0x77, 0x3a, 0x16, 0xa1, 0x86, 0x34, 0x12, 0x03, 0x7f, 0x99, 0x62, 0x42, 0xd1, 0x06, 0x2c, 0x65, 0xbe, 0x29, 0xc0, 0x57, 0x7d, 0x87, 0x32, 0xac, 0x94, 0x14, 0x93, 0x97, 0x0b, 0x42, 0x19,
0xdb, 0xd6, 0x95, 0x45, 0x6b, 0xca, 0x8e, 0xb2, 0x9b, 0x35, 0xe4, 0x06, 0x6d, 0x41, 0xde, 0x1d, 0xda, 0x83, 0x4d, 0xd7, 0x79, 0x70, 0x58, 0x53, 0x3b, 0xd2, 0x8e, 0x8b, 0x58, 0x09, 0xe8, 0x00,
0x8d, 0x08, 0xa6, 0xb5, 0x0c, 0x3b, 0x2e, 0x19, 0xfe, 0x0e, 0xbd, 0x86, 0x02, 0x71, 0x3d, 0xda, 0x74, 0x7f, 0x3a, 0xa5, 0x84, 0x35, 0x0b, 0xfc, 0xba, 0x8a, 0x43, 0x09, 0xfd, 0x09, 0x65, 0xea,
0xbf, 0xb8, 0xa9, 0x65, 0x99, 0x61, 0xad, 0xf1, 0x50, 0x8f, 0x4b, 0x57, 0xe7, 0x9e, 0x7a, 0x0c, 0x07, 0x6c, 0x74, 0xf7, 0xd4, 0x2c, 0x72, 0xc5, 0x4e, 0xfb, 0x7b, 0x2b, 0x2d, 0x5d, 0x4b, 0x78,
0xa8, 0xf3, 0xcf, 0xc1, 0x8d, 0x91, 0x27, 0xe2, 0x97, 0xf3, 0x8e, 0x2c, 0x9b, 0x62, 0xaf, 0x96, 0x1a, 0x72, 0xa0, 0x25, 0x3e, 0x67, 0x4f, 0x58, 0xa7, 0xf2, 0x57, 0xd8, 0x9d, 0x3a, 0x2e, 0x23,
0x93, 0xbc, 0x72, 0x87, 0x8e, 0x00, 0x04, 0xaf, 0xeb, 0x0d, 0x99, 0x6d, 0x59, 0x50, 0xef, 0xa6, 0x41, 0xb3, 0xa4, 0xec, 0x2a, 0x09, 0x5d, 0x00, 0x48, 0xbb, 0x7e, 0x30, 0xe1, 0xba, 0x4d, 0x69,
0xa0, 0x3e, 0xe3, 0x78, 0xa3, 0x44, 0x82, 0x25, 0x7a, 0x05, 0x2b, 0x32, 0xed, 0xfe, 0xc0, 0x1d, 0xfa, 0x38, 0x87, 0xe9, 0x1b, 0x81, 0xc7, 0x55, 0x1a, 0x1d, 0xd1, 0x1f, 0xb0, 0xa5, 0xd2, 0x1e,
0x62, 0x52, 0xcb, 0xef, 0x64, 0x19, 0xd5, 0xb6, 0xa4, 0x0a, 0x54, 0xec, 0x49, 0x61, 0x5a, 0x0c, 0x8d, 0xfd, 0x09, 0xa1, 0x4d, 0xfd, 0xa8, 0xc8, 0x4d, 0x1d, 0x2a, 0x53, 0x11, 0x8b, 0x43, 0x45,
0x61, 0x94, 0x25, 0x9c, 0xaf, 0x89, 0xf6, 0x11, 0x8a, 0x01, 0xbd, 0xd6, 0x80, 0xbc, 0x0c, 0x1e, 0x4c, 0x97, 0x23, 0x70, 0x4d, 0xc1, 0xc5, 0x99, 0x9a, 0xff, 0x41, 0x25, 0x32, 0x6f, 0xb6, 0x41,
0x95, 0xa1, 0x70, 0xde, 0x3d, 0xed, 0x9e, 0xbd, 0xef, 0x56, 0x96, 0x50, 0x11, 0x72, 0xdd, 0xe6, 0x57, 0xc1, 0xa3, 0x1a, 0x94, 0x6f, 0x07, 0xd7, 0x83, 0x9b, 0x7f, 0x06, 0xf5, 0x0d, 0x54, 0x81,
0x9b, 0x76, 0x45, 0x41, 0xeb, 0xb0, 0xda, 0x69, 0xf6, 0xde, 0xf6, 0x8d, 0x76, 0xa7, 0xdd, 0xec, 0xd2, 0xa0, 0xf3, 0x57, 0xaf, 0xae, 0xa1, 0x5d, 0xd8, 0xee, 0x77, 0x86, 0x7f, 0x8f, 0x70, 0xaf,
0xb5, 0x0f, 0x2b, 0x19, 0xed, 0x3e, 0x94, 0xc2, 0xa8, 0x50, 0x01, 0xb2, 0xcd, 0x5e, 0x4b, 0x5e, 0xdf, 0xeb, 0x0c, 0x7b, 0xe7, 0xf5, 0x82, 0xf9, 0x2d, 0x54, 0xe3, 0xa8, 0x50, 0x19, 0x8a, 0x9d,
0x39, 0x6c, 0xb3, 0x95, 0xa2, 0x7d, 0x53, 0x60, 0x23, 0x5a, 0x04, 0x32, 0x71, 0x1d, 0x82, 0x79, 0x61, 0x57, 0x3d, 0x39, 0xef, 0xf1, 0x93, 0x66, 0xbe, 0xd6, 0x60, 0x2f, 0x59, 0x04, 0x3a, 0xf7,
0x15, 0x06, 0xee, 0xd4, 0x09, 0xab, 0x20, 0x36, 0x08, 0x41, 0xce, 0xc1, 0x5f, 0x83, 0x1a, 0x88, 0x3d, 0x4a, 0x44, 0x15, 0xc6, 0xfe, 0xc2, 0x8b, 0xab, 0x20, 0x05, 0x84, 0xa0, 0xe4, 0x91, 0x57,
0x35, 0x47, 0x52, 0x97, 0x9a, 0xb6, 0xd0, 0x9f, 0x21, 0xc5, 0x06, 0xbd, 0x80, 0xa2, 0x9f, 0x1c, 0x51, 0x0d, 0xe4, 0x59, 0x20, 0x99, 0xcf, 0x6c, 0x57, 0xf2, 0xcf, 0x91, 0x52, 0x40, 0xbf, 0x41,
0x61, 0xca, 0x66, 0x77, 0xcb, 0x8d, 0xcd, 0x68, 0xca, 0xbe, 0x47, 0x23, 0x84, 0x69, 0xfb, 0x50, 0x25, 0x4c, 0x8e, 0x72, 0x66, 0x8b, 0xc7, 0xb5, 0xf6, 0x7e, 0x32, 0xe5, 0xd0, 0x23, 0x8e, 0x61,
0x3d, 0xc2, 0x41, 0x24, 0x52, 0x91, 0xe0, 0x4d, 0x70, 0xbf, 0xe6, 0x15, 0x16, 0xc1, 0x70, 0xbf, 0xe6, 0x29, 0x34, 0x2e, 0x48, 0x14, 0x89, 0x62, 0x24, 0xea, 0x09, 0xe1, 0xd7, 0x7e, 0x20, 0x32,
0x6c, 0xad, 0xbd, 0x83, 0xda, 0x22, 0xdc, 0x8f, 0x3e, 0x06, 0x8f, 0x1e, 0x41, 0x8e, 0x3f, 0x4c, 0x18, 0xe1, 0x97, 0x9f, 0x4d, 0x06, 0xcd, 0x75, 0x78, 0x18, 0x7d, 0x0a, 0x1e, 0xfd, 0x00, 0x25,
0x11, 0x7b, 0xb9, 0x81, 0xa2, 0xd1, 0x9c, 0x30, 0x8b, 0x21, 0xec, 0x9a, 0x3e, 0xcb, 0xdb, 0x72, 0xd1, 0x98, 0x32, 0xf6, 0x5a, 0x1b, 0x25, 0xa3, 0xb9, 0xe2, 0x1a, 0x2c, 0xf5, 0xe8, 0x6b, 0xa8,
0x1d, 0x8a, 0x1d, 0xfa, 0xb7, 0x38, 0x3a, 0xb0, 0x1d, 0x83, 0xf7, 0x03, 0xa9, 0x43, 0xc1, 0x77, 0x0a, 0x3c, 0x9d, 0xdb, 0x63, 0x22, 0x73, 0xaa, 0xe2, 0x8f, 0x17, 0xa6, 0xb5, 0xec, 0xb5, 0xeb,
0x21, 0xee, 0x24, 0xaa, 0x10, 0xa0, 0xb4, 0x9f, 0xac, 0x20, 0xe7, 0x93, 0xa1, 0x49, 0x71, 0x60, 0x7b, 0x8c, 0x78, 0xec, 0x53, 0x51, 0xf6, 0xe1, 0x30, 0x05, 0x1f, 0x86, 0xd9, 0x82, 0x72, 0x18,
0x4a, 0x76, 0x8d, 0x1e, 0xb3, 0x22, 0xf1, 0x46, 0xf5, 0x73, 0x5a, 0x97, 0xdc, 0xb2, 0x9b, 0x5b, 0x80, 0x7c, 0x93, 0xc9, 0x51, 0x84, 0x32, 0xdf, 0xf1, 0x72, 0xdd, 0xce, 0x27, 0x36, 0x23, 0x91,
0xfc, 0x6b, 0x48, 0x3b, 0xda, 0x83, 0xfc, 0xb5, 0x69, 0x33, 0x1e, 0x51, 0xa4, 0x30, 0x7b, 0x1f, 0x2a, 0xdb, 0x35, 0xfa, 0x91, 0x97, 0x50, 0x8c, 0x71, 0x98, 0xf1, 0xae, 0xb2, 0xad, 0x66, 0xbd,
0x29, 0xba, 0xdc, 0xf0, 0x11, 0xa8, 0x0a, 0x85, 0xa1, 0x77, 0xd3, 0xf7, 0xa6, 0x8e, 0x68, 0x89, 0x2b, 0xbe, 0x58, 0xe9, 0xd1, 0x09, 0xe8, 0x8f, 0xb6, 0xcb, 0xed, 0xc8, 0x74, 0x63, 0x6e, 0x42,
0xa2, 0x91, 0x67, 0x5b, 0x63, 0xea, 0xa0, 0x07, 0xb0, 0x3a, 0xb4, 0x88, 0x79, 0x61, 0xe3, 0xfe, 0xa4, 0xdc, 0x01, 0x38, 0x44, 0xa0, 0x06, 0x94, 0x27, 0xc1, 0xd3, 0x28, 0x58, 0x78, 0x72, 0x60,
0xd8, 0x75, 0x3f, 0x13, 0xd1, 0x15, 0x45, 0x63, 0xc5, 0x3f, 0x3c, 0xe6, 0x67, 0xda, 0x31, 0x6c, 0x2a, 0x58, 0xe7, 0x22, 0x5e, 0x78, 0xe8, 0x3b, 0xd8, 0x9e, 0x38, 0xd4, 0xbe, 0x73, 0xc9, 0x68,
0xce, 0x85, 0x7f, 0x5b, 0x25, 0x7e, 0x29, 0xb0, 0x79, 0xe2, 0xb0, 0x66, 0xb0, 0xed, 0x39, 0x29, 0xe6, 0xfb, 0x2f, 0xa8, 0x9c, 0x99, 0x0a, 0xde, 0x0a, 0x2f, 0x2f, 0xc5, 0x9d, 0x79, 0x09, 0xfb,
0xc2, 0xb4, 0x95, 0xd4, 0x69, 0x67, 0xfe, 0x27, 0xed, 0x6c, 0x24, 0xed, 0x40, 0xf8, 0xdc, 0x8c, 0x2b, 0xe1, 0x3f, 0x97, 0x89, 0xf7, 0x1a, 0xec, 0x5f, 0x79, 0x7c, 0x54, 0x5c, 0x77, 0x85, 0x8a,
0xf0, 0x69, 0xa4, 0x40, 0x77, 0xa1, 0xc4, 0xc1, 0x64, 0x62, 0x0e, 0x30, 0x6b, 0x7b, 0x7e, 0xfb, 0x38, 0x6d, 0x2d, 0x77, 0xda, 0x85, 0x2f, 0x49, 0xbb, 0x98, 0x48, 0x3b, 0x22, 0xbe, 0xb4, 0x44,
0xcf, 0x01, 0xba, 0x07, 0xe0, 0xe1, 0x29, 0xc1, 0x7d, 0x41, 0x5e, 0x10, 0xf7, 0x4b, 0xe2, 0xa4, 0x7c, 0x1e, 0x2a, 0x92, 0x6d, 0xa6, 0xaf, 0xb4, 0x19, 0xfa, 0x06, 0x20, 0x20, 0x0b, 0x4a, 0x46,
0xcb, 0x5f, 0xd5, 0x09, 0x6c, 0xcd, 0x27, 0x7f, 0x5b, 0x21, 0xc7, 0x50, 0x3d, 0x77, 0xac, 0x58, 0xd2, 0x78, 0x59, 0xbe, 0xaf, 0xca, 0x9b, 0x81, 0xe8, 0xaa, 0x2b, 0x38, 0x58, 0x4d, 0xfe, 0xb9,
0x25, 0xe3, 0x1e, 0xd5, 0x42, 0x6e, 0x99, 0x98, 0xdc, 0x58, 0xd3, 0x4f, 0xa6, 0xde, 0x25, 0xf6, 0x44, 0xce, 0xa0, 0x71, 0xeb, 0x39, 0xa9, 0x4c, 0xa6, 0x35, 0xd5, 0x5a, 0x6e, 0x85, 0x94, 0xdc,
0xb5, 0x92, 0x1b, 0xed, 0x14, 0x6a, 0x8b, 0x9e, 0x6e, 0x19, 0x76, 0xe3, 0xc7, 0x32, 0xac, 0x05, 0xf8, 0x4a, 0x98, 0x2f, 0x82, 0x7b, 0x12, 0x72, 0xa5, 0x04, 0xf3, 0x1a, 0x9a, 0xeb, 0x9e, 0x9e,
0xdd, 0x2d, 0x27, 0x2e, 0xb2, 0x60, 0x65, 0x76, 0x58, 0xa1, 0x27, 0xc9, 0x03, 0x79, 0xee, 0xaf, 0x19, 0x76, 0xfb, 0xed, 0x26, 0xec, 0x44, 0xb3, 0xaf, 0xf6, 0x31, 0x72, 0x60, 0x6b, 0x79, 0x95,
0x8a, 0xba, 0x97, 0x06, 0x2a, 0x43, 0xd5, 0x96, 0x9e, 0x2b, 0x88, 0x40, 0x65, 0x7e, 0xba, 0xa0, 0xa1, 0x9f, 0xb2, 0xd7, 0xf5, 0xca, 0xdf, 0x1c, 0xe3, 0x24, 0x0f, 0x54, 0x85, 0x6a, 0x6e, 0xfc,
0xfd, 0x78, 0x8e, 0x84, 0xa1, 0xa5, 0xea, 0x69, 0xe1, 0x81, 0x5b, 0x74, 0x0d, 0xeb, 0x0b, 0xa3, 0xaa, 0x21, 0x0a, 0xf5, 0xd5, 0xdd, 0x83, 0x4e, 0xd3, 0x6d, 0x64, 0xac, 0x34, 0xc3, 0xca, 0x0b,
0x04, 0xfd, 0x93, 0x26, 0x3a, 0xa3, 0xd4, 0x7a, 0x6a, 0x7c, 0xe8, 0xf7, 0x13, 0xac, 0x46, 0x9a, 0x8f, 0xdc, 0xa2, 0x47, 0xd8, 0x5d, 0x5b, 0x25, 0xe8, 0xb3, 0x66, 0x92, 0x3b, 0xca, 0x68, 0xe5,
0x16, 0x25, 0xa8, 0x15, 0x37, 0x98, 0xd4, 0xa7, 0xa9, 0xb0, 0xa1, 0xaf, 0x2b, 0x58, 0x8b, 0x3e, 0xc6, 0xc7, 0x7e, 0xff, 0x87, 0xed, 0xc4, 0xd0, 0xa2, 0x0c, 0xb6, 0xd2, 0x16, 0x93, 0xf1, 0x73,
0x6c, 0x94, 0x40, 0x10, 0xdb, 0xfb, 0xea, 0xb3, 0x74, 0xe0, 0xd0, 0x1d, 0xab, 0xe3, 0xfc, 0x93, 0x2e, 0x6c, 0xec, 0xeb, 0x01, 0x76, 0x92, 0x8d, 0x8d, 0x32, 0x0c, 0xa4, 0xce, 0xbe, 0xf1, 0x4b,
0x4c, 0xaa, 0x63, 0x42, 0x93, 0x24, 0xd5, 0x31, 0xe9, 0xa5, 0x6b, 0x4b, 0x07, 0xf0, 0xa1, 0x18, 0x3e, 0x70, 0xec, 0x8e, 0xd7, 0x71, 0xb5, 0x25, 0xb3, 0xea, 0x98, 0x31, 0x24, 0x59, 0x75, 0xcc,
0xa0, 0x2f, 0xf2, 0xe2, 0xbf, 0x9d, 0x97, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x44, 0xdd, 0xaf, 0xea, 0x74, 0x73, 0xe3, 0x0c, 0xfe, 0xad, 0x44, 0xe8, 0x3b, 0x5d, 0xfe, 0x2f, 0xf4, 0xfb, 0x87,
0x38, 0xa2, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xde, 0x47, 0x5d, 0x04, 0xc0, 0x09, 0x00, 0x00,
} }
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