Commit ae4ff5cd authored by Michelle Noorali's avatar Michelle Noorali Committed by GitHub

Merge pull request #1030 from michelleN/feat/690-helm-upgrade

feat(*): implement helm upgrade + upgrade hooks
parents 37e33e7d f9922877
...@@ -162,6 +162,9 @@ message UpdateReleaseRequest { ...@@ -162,6 +162,9 @@ message UpdateReleaseRequest {
hapi.chart.Config values = 3; hapi.chart.Config values = 3;
// dry_run, if true, will run through the release logic, but neither create // dry_run, if true, will run through the release logic, but neither create
bool dry_run = 4; bool dry_run = 4;
// DisableHooks causes the server to skip running any hooks for the upgrade.
bool disable_hooks = 5;
} }
// UpdateReleaseResponse is the response to an update request. // UpdateReleaseResponse is the response to an update request.
......
...@@ -39,6 +39,7 @@ type upgradeCmd struct { ...@@ -39,6 +39,7 @@ type upgradeCmd struct {
out io.Writer out io.Writer
client helm.Interface client helm.Interface
dryRun bool dryRun bool
disableHooks bool
valuesFile string valuesFile string
} }
...@@ -70,6 +71,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { ...@@ -70,6 +71,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.StringVarP(&upgrade.valuesFile, "values", "f", "", "path to a values YAML file") f.StringVarP(&upgrade.valuesFile, "values", "f", "", "path to a values YAML file")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade")
f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks")
return cmd return cmd
} }
...@@ -88,12 +90,13 @@ func (u *upgradeCmd) run() error { ...@@ -88,12 +90,13 @@ func (u *upgradeCmd) run() error {
} }
} }
_, err = u.client.UpdateRelease(u.release, chartPath, helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun)) _, err = u.client.UpdateRelease(u.release, chartPath, helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun), helm.UpgradeDisableHooks(u.disableHooks))
if err != nil { if err != nil {
return prettyError(err) return prettyError(err)
} }
fmt.Fprintf(u.out, "It's not you. It's me\nYour upgrade looks valid but this command is still under active development.\nHang tight.\n") success := u.release + " has been upgraded. Happy Helming!\n"
fmt.Fprintf(u.out, success)
return nil return nil
......
...@@ -52,18 +52,22 @@ func TestUpgradeCmd(t *testing.T) { ...@@ -52,18 +52,22 @@ func TestUpgradeCmd(t *testing.T) {
Description: "A Helm chart for Kubernetes", Description: "A Helm chart for Kubernetes",
Version: "0.1.2", Version: "0.1.2",
} }
chartPath, err = chartutil.Create(cfile, tmpChart) chartPath, err = chartutil.Create(cfile, tmpChart)
if err != nil { if err != nil {
t.Errorf("Error creating chart: %v", err) t.Errorf("Error creating chart: %v", err)
} }
ch, _ = chartutil.Load(chartPath) ch, err = chartutil.Load(chartPath)
if err != nil {
t.Errorf("Error loading updated chart: %v", err)
}
tests := []releaseCase{ tests := []releaseCase{
{ {
name: "upgrade a release", name: "upgrade a release",
args: []string{"funny-bunny", chartPath}, args: []string{"funny-bunny", chartPath},
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}), resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}),
expected: "It's not you. It's me\nYour upgrade looks valid but this command is still under active development.\nHang tight.\n", expected: "funny-bunny has been upgraded. Happy Helming!\n",
}, },
} }
......
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
"regexp" "regexp"
"sort" "sort"
"github.com/Masterminds/semver"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/technosophos/moniker" "github.com/technosophos/moniker"
ctx "golang.org/x/net/context" ctx "golang.org/x/net/context"
...@@ -171,65 +170,107 @@ func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleas ...@@ -171,65 +170,107 @@ func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleas
} }
func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
rel, err := s.prepareUpdate(req) currentRelease, updatedRelease, err := s.prepareUpdate(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: perform update res, err := s.performUpdate(currentRelease, updatedRelease, req)
if err != nil {
return nil, err
}
if err := s.env.Releases.Update(updatedRelease); err != nil {
return nil, err
}
return res, nil
}
func (s *releaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
res := &services.UpdateReleaseResponse{Release: updatedRelease}
if req.DryRun {
log.Printf("Dry run for %s", updatedRelease.Name)
return res, nil
}
// pre-ugrade hooks
if !req.DisableHooks {
if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, preUpgrade); err != nil {
return res, err
}
}
kubeCli := s.env.KubeClient
original := bytes.NewBufferString(originalRelease.Manifest)
modified := bytes.NewBufferString(updatedRelease.Manifest)
if err := kubeCli.Update(updatedRelease.Namespace, original, modified); err != nil {
return nil, fmt.Errorf("Update of %s failed: %s", updatedRelease.Name, err)
}
// post-upgrade hooks
if !req.DisableHooks {
if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, postUpgrade); err != nil {
return res, err
}
}
return &services.UpdateReleaseResponse{Release: rel}, nil updatedRelease.Info.Status.Code = release.Status_DEPLOYED
return res, nil
} }
// prepareUpdate builds a release for an update operation. // prepareUpdate builds an updated release for an update operation.
func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, error) { func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) {
if req.Name == "" { if req.Name == "" {
return nil, errMissingRelease return nil, nil, errMissingRelease
} }
if req.Chart == nil { if req.Chart == nil {
return nil, errMissingChart return nil, nil, errMissingChart
} }
// finds the non-deleted release with the given name // finds the non-deleted release with the given name
rel, err := s.env.Releases.Read(req.Name) currentRelease, err := s.env.Releases.Read(req.Name)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
//validate chart name is same as previous release ts := timeconv.Now()
givenChart := req.Chart.Metadata.Name options := chartutil.ReleaseOptions{
releasedChart := rel.Chart.Metadata.Name Name: req.Name,
if givenChart != releasedChart { Time: ts,
return nil, fmt.Errorf("Given chart, %s, does not match chart originally released, %s", givenChart, releasedChart) Namespace: currentRelease.Namespace,
} }
// validate new chart version is higher than old valuesToRender, err := chartutil.ToRenderValues(req.Chart, req.Values, options)
givenChartVersion := req.Chart.Metadata.Version
releasedChartVersion := rel.Chart.Metadata.Version
c, err := semver.NewConstraint("> " + releasedChartVersion)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
v, err := semver.NewVersion(givenChartVersion) hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender)
if err != nil { if err != nil {
return nil, err return nil, nil, err
}
if a := c.Check(v); !a {
return nil, fmt.Errorf("Given chart (%s-%v) must be a higher version than released chart (%s-%v)", givenChart, givenChartVersion, releasedChart, releasedChartVersion)
} }
// Store an updated release. // Store an updated release.
updatedRelease := &release.Release{ updatedRelease := &release.Release{
Name: req.Name, Name: req.Name,
Namespace: currentRelease.Namespace,
Chart: req.Chart, Chart: req.Chart,
Config: req.Values, Config: req.Values,
Version: rel.Version + 1, Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
},
Version: currentRelease.Version + 1,
Manifest: manifestDoc.String(),
Hooks: hooks,
} }
return updatedRelease, nil
return currentRelease, updatedRelease, nil
} }
func (s *releaseServer) uniqName(start string, reuse bool) (string, error) { func (s *releaseServer) uniqName(start string, reuse bool) (string, error) {
...@@ -308,12 +349,36 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re ...@@ -308,12 +349,36 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
return nil, err return nil, err
} }
renderer := s.engine(req.Chart) hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender)
files, err := renderer.Render(req.Chart, valuesToRender)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Store a release.
rel := &release.Release{
Name: name,
Namespace: req.Namespace,
Chart: req.Chart,
Config: req.Values,
Info: &release.Info{
FirstDeployed: ts,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
},
Manifest: manifestDoc.String(),
Hooks: hooks,
Version: 1,
}
return rel, nil
}
func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values) ([]*release.Hook, *bytes.Buffer, error) {
renderer := s.engine(ch)
files, err := renderer.Render(ch, values)
if err != nil {
return nil, nil, err
}
// Sort hooks, manifests, and partials. Only hooks and manifests are returned, // Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also // as partials are not used after renderer.Render. Empty manifests are also
// removed here. // removed here.
...@@ -321,7 +386,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re ...@@ -321,7 +386,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
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, err return nil, nil, err
} }
// Aggregate all valid manifests into one big doc. // Aggregate all valid manifests into one big doc.
...@@ -331,22 +396,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re ...@@ -331,22 +396,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
b.WriteString(file) b.WriteString(file)
} }
// Store a release. return hooks, b, nil
rel := &release.Release{
Name: name,
Namespace: req.Namespace,
Chart: req.Chart,
Config: req.Values,
Info: &release.Info{
FirstDeployed: ts,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
},
Manifest: b.String(),
Hooks: hooks,
Version: 1,
}
return rel, nil
} }
// validateYAML checks to see if YAML is well-formed. // validateYAML checks to see if YAML is well-formed.
......
...@@ -44,6 +44,16 @@ data: ...@@ -44,6 +44,16 @@ data:
name: value name: value
` `
var manifestWithUpgradeHooks = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-upgrade,pre-upgrade
data:
name: value
`
func rsFixture() *releaseServer { func rsFixture() *releaseServer {
return &releaseServer{ return &releaseServer{
env: mockEnvironment(), env: mockEnvironment(),
...@@ -81,6 +91,7 @@ func releaseStub() *release.Release { ...@@ -81,6 +91,7 @@ func releaseStub() *release.Release {
}, },
Chart: chartStub(), Chart: chartStub(),
Config: &chart.Config{Raw: `name = "value"`}, Config: &chart.Config{Raw: `name = "value"`},
Version: 1,
Hooks: []*release.Hook{ Hooks: []*release.Hook{
{ {
Name: "test-cm", Name: "test-cm",
...@@ -289,6 +300,105 @@ func TestInstallReleaseReuseName(t *testing.T) { ...@@ -289,6 +300,105 @@ func TestInstallReleaseReuseName(t *testing.T) {
} }
} }
func TestUpdateRelease(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
req := &services.UpdateReleaseRequest{
Name: rel.Name,
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithUpgradeHooks)},
},
},
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Errorf("Failed updated: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
}
if res.Release.Name != rel.Name {
t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name)
}
if res.Release.Namespace != rel.Namespace {
t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace)
}
updated, err := rs.env.Releases.Read(res.Release.Name)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
}
if updated.Hooks[0].Manifest != manifestWithUpgradeHooks {
t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest)
}
if updated.Hooks[0].Events[0] != release.Hook_POST_UPGRADE {
t.Errorf("Expected event 0 to be post upgrade")
}
if updated.Hooks[0].Events[1] != release.Hook_PRE_UPGRADE {
t.Errorf("Expected event 0 to be pre upgrade")
}
if len(res.Release.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release)
}
if len(updated.Manifest) == 0 {
t.Errorf("Expected manifest in %v", res)
}
if !strings.Contains(updated.Manifest, "---\n# Source: hello/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
if res.Release.Version != 2 {
t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version)
}
}
func TestUpdateReleaseNoHooks(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
req := &services.UpdateReleaseRequest{
Name: rel.Name,
DisableHooks: true,
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithUpgradeHooks)},
},
},
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Errorf("Failed updated: %s", err)
}
if hl := res.Release.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl)
}
}
func TestUninstallRelease(t *testing.T) { func TestUninstallRelease(t *testing.T) {
c := context.Background() c := context.Background()
rs := rsFixture() rs := rsFixture()
......
...@@ -143,6 +143,13 @@ func DeleteDryRun(dry bool) DeleteOption { ...@@ -143,6 +143,13 @@ func DeleteDryRun(dry bool) DeleteOption {
} }
} }
// UpgradeDisableHooks will disable hooks for an upgrade operation.
func UpgradeDisableHooks(disable bool) UpdateOption {
return func(opts *options) {
opts.disableHooks = disable
}
}
// UpgradeDryRun will (if true) execute an upgrade as a dry run. // UpgradeDryRun will (if true) execute an upgrade as a dry run.
func UpgradeDryRun(dry bool) UpdateOption { func UpgradeDryRun(dry bool) UpdateOption {
return func(opts *options) { return func(opts *options) {
...@@ -237,9 +244,15 @@ func (o *options) rpcDeleteRelease(rlsName string, rlc rls.ReleaseServiceClient, ...@@ -237,9 +244,15 @@ func (o *options) rpcDeleteRelease(rlsName string, rlc rls.ReleaseServiceClient,
// Executes tiller.UpdateRelease RPC. // Executes tiller.UpdateRelease RPC.
func (o *options) rpcUpdateRelease(rlsName string, chr *cpb.Chart, rlc rls.ReleaseServiceClient, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { func (o *options) rpcUpdateRelease(rlsName string, chr *cpb.Chart, rlc rls.ReleaseServiceClient, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
//TODO: handle dryRun for _, opt := range opts {
opt(o)
}
o.updateReq.Chart = chr
o.updateReq.DryRun = o.dryRun
o.updateReq.Name = rlsName
return rlc.UpdateRelease(context.TODO(), &rls.UpdateReleaseRequest{Name: rlsName, Chart: chr}) return rlc.UpdateRelease(context.TODO(), &o.updateReq)
} }
// Executes tiller.GetReleaseStatus RPC. // Executes tiller.GetReleaseStatus RPC.
......
...@@ -226,6 +226,8 @@ type UpdateReleaseRequest struct { ...@@ -226,6 +226,8 @@ type UpdateReleaseRequest struct {
Values *hapi_chart.Config `protobuf:"bytes,3,opt,name=values" json:"values,omitempty"` Values *hapi_chart.Config `protobuf:"bytes,3,opt,name=values" json:"values,omitempty"`
// dry_run, if true, will run through the release logic, but neither create // dry_run, if true, will run through the release logic, but neither create
DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` 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"`
} }
func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} }
...@@ -624,54 +626,54 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ ...@@ -624,54 +626,54 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
} }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 769 bytes of a gzipped FileDescriptorProto // 774 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x56, 0xdb, 0x6e, 0xd3, 0x4a, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x56, 0xdb, 0x6e, 0xd3, 0x4c,
0x14, 0xad, 0x93, 0xd4, 0x49, 0x76, 0x2f, 0x4a, 0xe7, 0xb4, 0x8d, 0x8f, 0x75, 0x0e, 0x42, 0x46, 0x10, 0xae, 0x93, 0x34, 0x87, 0xe9, 0x41, 0xe9, 0xfe, 0x6d, 0x93, 0xdf, 0xfa, 0x7f, 0x84, 0x8c,
0x40, 0x29, 0xd4, 0x81, 0xf0, 0x8e, 0x94, 0xb6, 0x51, 0x5b, 0x35, 0xa4, 0xd2, 0x84, 0x82, 0xc4, 0x80, 0x52, 0xa8, 0x03, 0xe1, 0x1e, 0x29, 0x6d, 0xa3, 0xb6, 0x6a, 0x48, 0xa5, 0x0d, 0x05, 0x89,
0x03, 0x91, 0x9b, 0x4c, 0xa8, 0xc1, 0xb5, 0x83, 0x67, 0x52, 0xd1, 0x4f, 0xe0, 0x0f, 0xf8, 0x14, 0x0b, 0x22, 0x37, 0xd9, 0x50, 0x83, 0x6b, 0x07, 0xef, 0xa6, 0xa2, 0x8f, 0xc0, 0x1b, 0x71, 0xc3,
0x3e, 0x88, 0xbf, 0xe0, 0x85, 0xb9, 0xd8, 0x26, 0x17, 0x1b, 0x4c, 0x5f, 0x9c, 0x99, 0xd9, 0x6b, 0xdb, 0xf0, 0x16, 0xdc, 0xb0, 0x07, 0xaf, 0xc9, 0xc1, 0x06, 0xd3, 0x1b, 0x67, 0x77, 0xe7, 0xdb,
0xaf, 0x7d, 0xdf, 0x0a, 0x98, 0x97, 0xce, 0xd8, 0x6d, 0x50, 0x12, 0x5e, 0xbb, 0x03, 0x42, 0x1b, 0x6f, 0x66, 0xbe, 0x99, 0x9d, 0x16, 0xcc, 0x4b, 0x67, 0xec, 0x36, 0x28, 0x09, 0xaf, 0xdd, 0x01,
0xcc, 0xf5, 0x3c, 0x12, 0xda, 0xe3, 0x30, 0x60, 0x01, 0xda, 0x14, 0x32, 0x3b, 0x96, 0xd9, 0x4a, 0xa1, 0x0d, 0xe6, 0x7a, 0x1e, 0x09, 0xed, 0x71, 0x18, 0xb0, 0x00, 0x6d, 0x0a, 0x9b, 0xad, 0x6d,
0x66, 0x6e, 0x4b, 0x8d, 0xc1, 0xa5, 0x13, 0x32, 0xf5, 0x55, 0x68, 0xb3, 0x3e, 0xfd, 0x1e, 0xf8, 0xb6, 0xb2, 0x99, 0xdb, 0xf2, 0xc6, 0xe0, 0xd2, 0x09, 0x99, 0xfa, 0x2a, 0xb4, 0x59, 0x9b, 0x3e,
0x23, 0xf7, 0x7d, 0x24, 0x50, 0x26, 0x42, 0xe2, 0x11, 0x87, 0x92, 0xf8, 0x77, 0x46, 0x29, 0x96, 0x0f, 0xfc, 0x91, 0xfb, 0x3e, 0x32, 0x28, 0x17, 0x21, 0xf1, 0x88, 0x43, 0x89, 0xfe, 0x9d, 0xb9,
0xb9, 0xfe, 0x28, 0x50, 0x02, 0xeb, 0xbb, 0x06, 0xff, 0x74, 0x5c, 0xca, 0xb0, 0x12, 0x51, 0x4c, 0xa4, 0x6d, 0xae, 0x3f, 0x0a, 0x94, 0xc1, 0xfa, 0x6e, 0xc0, 0x3f, 0x1d, 0x97, 0x32, 0xac, 0x4c,
0x3e, 0x4d, 0x08, 0x65, 0x68, 0x13, 0x96, 0x3d, 0xf7, 0xca, 0x65, 0x86, 0x76, 0x57, 0xdb, 0x29, 0x14, 0x93, 0x4f, 0x13, 0x42, 0x19, 0xda, 0x84, 0x65, 0xcf, 0xbd, 0x72, 0x59, 0xdd, 0xb8, 0x6b,
0x62, 0x75, 0x41, 0xdb, 0xa0, 0x07, 0xa3, 0x11, 0x25, 0xcc, 0x28, 0xf0, 0xe7, 0x2a, 0x8e, 0x6e, 0xec, 0xe4, 0xb1, 0xda, 0xa0, 0x6d, 0x28, 0x06, 0xa3, 0x11, 0x25, 0xac, 0x9e, 0xe3, 0xc7, 0x15,
0xe8, 0x05, 0x94, 0x69, 0x10, 0xb2, 0xfe, 0xc5, 0x8d, 0x51, 0xe4, 0x82, 0xf5, 0xe6, 0x7d, 0x3b, 0x1c, 0xed, 0xd0, 0x0b, 0x28, 0xd1, 0x20, 0x64, 0xfd, 0x8b, 0x9b, 0x7a, 0x9e, 0x1b, 0xd6, 0x9b,
0x2d, 0x26, 0x5b, 0x58, 0xea, 0x71, 0xa0, 0x2d, 0x3e, 0xfb, 0x37, 0x58, 0xa7, 0xf2, 0x57, 0xf0, 0xf7, 0xed, 0xa4, 0x9c, 0x6c, 0xe1, 0xa9, 0xc7, 0x81, 0xb6, 0xf8, 0xec, 0xdf, 0xe0, 0x22, 0x95,
0x8e, 0x5c, 0x8f, 0x91, 0xd0, 0x28, 0x29, 0x5e, 0x75, 0x43, 0x47, 0x00, 0x92, 0x37, 0x08, 0x87, 0xbf, 0x82, 0x77, 0xe4, 0x7a, 0x8c, 0x84, 0xf5, 0x82, 0xe2, 0x55, 0x3b, 0x74, 0x04, 0x20, 0x79,
0x5c, 0xb6, 0x2c, 0xa9, 0x77, 0x72, 0x50, 0x9f, 0x09, 0x3c, 0xae, 0xd2, 0xf8, 0x68, 0xbd, 0x83, 0x83, 0x70, 0xc8, 0x6d, 0xcb, 0x92, 0x7a, 0x27, 0x03, 0xf5, 0x99, 0xc0, 0xe3, 0x0a, 0xd5, 0x4b,
0x4a, 0x0c, 0xb0, 0x9a, 0xa0, 0x2b, 0xf3, 0x68, 0x05, 0xca, 0xe7, 0xdd, 0xd3, 0xee, 0xd9, 0x9b, 0xeb, 0x1d, 0x94, 0x35, 0xc0, 0x6a, 0x42, 0x51, 0xb9, 0x47, 0x2b, 0x50, 0x3a, 0xef, 0x9e, 0x76,
0x6e, 0x6d, 0x09, 0x55, 0xa0, 0xd4, 0x6d, 0xbd, 0x6c, 0xd7, 0x34, 0xb4, 0x01, 0x6b, 0x9d, 0x56, 0xcf, 0xde, 0x74, 0xab, 0x4b, 0xa8, 0x0c, 0x85, 0x6e, 0xeb, 0x65, 0xbb, 0x6a, 0xa0, 0x0d, 0x58,
0xef, 0x55, 0x1f, 0xb7, 0x3b, 0xed, 0x56, 0xaf, 0x7d, 0x58, 0x2b, 0x58, 0x77, 0xa0, 0x9a, 0xf0, 0xeb, 0xb4, 0x7a, 0xaf, 0xfa, 0xb8, 0xdd, 0x69, 0xb7, 0x7a, 0xed, 0xc3, 0x6a, 0xce, 0xba, 0x03,
0xa2, 0x32, 0x14, 0x5b, 0xbd, 0x03, 0xa5, 0x72, 0xd8, 0xe6, 0x27, 0xcd, 0xfa, 0xa2, 0xc1, 0xe6, 0x95, 0x98, 0x17, 0x95, 0x20, 0xdf, 0xea, 0x1d, 0xa8, 0x2b, 0x87, 0x6d, 0xbe, 0x32, 0xac, 0x2f,
0x6c, 0x1a, 0xe9, 0x38, 0xf0, 0x29, 0x11, 0x79, 0x1c, 0x04, 0x13, 0x3f, 0xc9, 0xa3, 0xbc, 0x20, 0x06, 0x6c, 0xce, 0xca, 0x48, 0xc7, 0x81, 0x4f, 0x89, 0xd0, 0x71, 0x10, 0x4c, 0xfc, 0x58, 0x47,
0x04, 0x25, 0x9f, 0x7c, 0x8e, 0xb3, 0x28, 0xcf, 0x02, 0xc9, 0x02, 0xe6, 0x78, 0x32, 0x83, 0x1c, 0xb9, 0x41, 0x08, 0x0a, 0x3e, 0xf9, 0xac, 0x55, 0x94, 0x6b, 0x81, 0x64, 0x01, 0x73, 0x3c, 0xa9,
0x29, 0x2f, 0xe8, 0x19, 0x54, 0xa2, 0xaa, 0x51, 0x9e, 0x9b, 0xe2, 0xce, 0x4a, 0x73, 0x4b, 0xc5, 0x20, 0x47, 0xca, 0x0d, 0x7a, 0x06, 0xe5, 0xa8, 0x6a, 0x94, 0x6b, 0x93, 0xdf, 0x59, 0x69, 0x6e,
0x1f, 0xd7, 0x37, 0xb2, 0x88, 0x13, 0x98, 0xb5, 0x07, 0xf5, 0x23, 0x12, 0x7b, 0xd2, 0x63, 0x0e, 0xa9, 0xfc, 0x75, 0x7d, 0x23, 0x8f, 0x38, 0x86, 0x59, 0x7b, 0x50, 0x3b, 0x22, 0x3a, 0x92, 0x1e,
0x9b, 0x24, 0x55, 0x15, 0x76, 0x9d, 0x2b, 0x22, 0x9d, 0x11, 0x76, 0xf9, 0xd9, 0x7a, 0x0d, 0xc6, 0x73, 0xd8, 0x24, 0xae, 0xaa, 0xf0, 0xeb, 0x5c, 0x11, 0x19, 0x8c, 0xf0, 0xcb, 0xd7, 0xd6, 0x6b,
0x22, 0x3c, 0xf2, 0x3e, 0x05, 0x8f, 0x1e, 0x40, 0x49, 0xf4, 0x8f, 0xf4, 0x7d, 0xa5, 0x89, 0x66, 0xa8, 0x2f, 0xc2, 0xa3, 0xe8, 0x13, 0xf0, 0xe8, 0x01, 0x14, 0x44, 0xff, 0xc8, 0xd8, 0x57, 0x9a,
0xbd, 0x39, 0xe1, 0x12, 0x2c, 0xe5, 0x96, 0x3d, 0xcd, 0x7b, 0x10, 0xf8, 0x8c, 0xf8, 0xec, 0x77, 0x68, 0x36, 0x9a, 0x13, 0x6e, 0xc1, 0xd2, 0x6e, 0xd9, 0xd3, 0xbc, 0x07, 0x81, 0xcf, 0x88, 0xcf,
0x7e, 0x74, 0xe0, 0xdf, 0x14, 0x7c, 0xe4, 0x48, 0x03, 0xca, 0x91, 0x09, 0xa9, 0x93, 0x99, 0x85, 0x7e, 0x17, 0x47, 0x07, 0xfe, 0x4d, 0xc0, 0x47, 0x81, 0x34, 0xa0, 0x14, 0xb9, 0x90, 0x77, 0x52,
0x18, 0x65, 0x7d, 0xe5, 0x05, 0x39, 0x1f, 0x0f, 0x1d, 0x46, 0x62, 0x51, 0xb6, 0x69, 0xf4, 0x90, 0x55, 0xd0, 0x28, 0xeb, 0x1b, 0x2f, 0xc8, 0xf9, 0x78, 0xe8, 0x30, 0xa2, 0x4d, 0xe9, 0xae, 0xd1,
0x17, 0x49, 0xcc, 0x53, 0x14, 0xd3, 0x86, 0xe2, 0x56, 0x43, 0x77, 0x20, 0xbe, 0x58, 0xc9, 0xd1, 0x43, 0x5e, 0x24, 0xf1, 0x9e, 0xa2, 0x9c, 0x36, 0x14, 0xb7, 0x7a, 0x74, 0x07, 0xe2, 0x8b, 0x95,
0x2e, 0xe8, 0xd7, 0x8e, 0xc7, 0x79, 0x64, 0x91, 0x92, 0xe8, 0x23, 0xa4, 0x1c, 0x46, 0x1c, 0x21, 0x1d, 0xed, 0x42, 0xf1, 0xda, 0xf1, 0x38, 0x8f, 0x2c, 0x52, 0x9c, 0x7d, 0x84, 0x94, 0x8f, 0x11,
0x50, 0x1d, 0xca, 0xc3, 0xf0, 0xa6, 0x1f, 0x4e, 0x7c, 0xd9, 0xd4, 0x15, 0xac, 0xf3, 0x2b, 0x9e, 0x47, 0x08, 0x54, 0x83, 0xd2, 0x30, 0xbc, 0xe9, 0x87, 0x13, 0x5f, 0x36, 0x75, 0x19, 0x17, 0xf9,
0xf8, 0xd6, 0x31, 0x6c, 0xcd, 0x79, 0x76, 0xdb, 0x20, 0x7f, 0x68, 0xb0, 0x75, 0xe2, 0x53, 0xde, 0x16, 0x4f, 0x7c, 0x74, 0x0f, 0xd6, 0x86, 0x2e, 0x75, 0x2e, 0x3c, 0xd2, 0xbf, 0x0c, 0x82, 0x8f,
0x27, 0xde, 0x5c, 0x94, 0x49, 0x44, 0x5a, 0xee, 0x88, 0x0a, 0x7f, 0x13, 0x51, 0x71, 0x3a, 0xa2, 0x54, 0xf6, 0x75, 0x19, 0xaf, 0x46, 0x87, 0xc7, 0xe2, 0xcc, 0x3a, 0x86, 0xad, 0xb9, 0xf0, 0x6f,
0x24, 0xa7, 0xa5, 0xa9, 0x9c, 0xde, 0x83, 0xb5, 0xa1, 0x4b, 0x9d, 0x0b, 0x8f, 0xf4, 0x2f, 0x83, 0xab, 0xc4, 0x0f, 0x03, 0xb6, 0x4e, 0x7c, 0xca, 0x9b, 0xc9, 0x9b, 0x93, 0x22, 0x4e, 0xdb, 0xc8,
0xe0, 0x23, 0x95, 0xd3, 0x5b, 0xc1, 0xab, 0xd1, 0xe3, 0xb1, 0x78, 0x43, 0xff, 0x41, 0x55, 0x80, 0x9c, 0x76, 0xee, 0x6f, 0xd2, 0xce, 0xcf, 0xa4, 0xad, 0x85, 0x2f, 0x4c, 0x09, 0x9f, 0x45, 0x0a,
0xe9, 0xd8, 0x19, 0x10, 0x43, 0x97, 0xda, 0xbf, 0x1e, 0xd0, 0xff, 0x00, 0x21, 0x99, 0x50, 0xd2, 0xf4, 0x1f, 0x54, 0x04, 0x98, 0x8e, 0x9d, 0x01, 0xa9, 0x17, 0xe5, 0xed, 0x5f, 0x07, 0xe8, 0x7f,
0x97, 0xe4, 0x65, 0xa9, 0x5f, 0x95, 0x2f, 0x5d, 0xd1, 0x30, 0x27, 0xb0, 0x3d, 0x1f, 0xfc, 0x6d, 0x80, 0x90, 0x4c, 0x28, 0xe9, 0x4b, 0xf2, 0x92, 0xbc, 0x5f, 0x91, 0x27, 0x5d, 0xd1, 0x55, 0x27,
0x13, 0x89, 0xa1, 0x7e, 0xee, 0xbb, 0xa9, 0x99, 0x4c, 0xeb, 0x97, 0x85, 0xd8, 0x0a, 0x8b, 0xb1, 0xb0, 0x3d, 0x9f, 0xfc, 0x6d, 0x85, 0xc4, 0x50, 0x3b, 0xf7, 0xdd, 0x44, 0x25, 0x93, 0x9a, 0x6a,
0x59, 0xa7, 0x60, 0x2c, 0x72, 0xde, 0xd2, 0xc1, 0xe6, 0xb7, 0x65, 0x58, 0x8f, 0x47, 0x54, 0x2d, 0x21, 0xb7, 0x5c, 0x42, 0x99, 0x4f, 0xa1, 0xbe, 0xc8, 0x79, 0xcb, 0x00, 0x9b, 0x5f, 0x97, 0x61,
0x3e, 0xe4, 0xc2, 0xea, 0xf4, 0xc6, 0x41, 0x8f, 0xb2, 0xf7, 0xe2, 0xdc, 0x72, 0x37, 0x77, 0xf3, 0x5d, 0xbf, 0x63, 0x35, 0x1d, 0x91, 0x0b, 0xab, 0xd3, 0x63, 0x09, 0x3d, 0x4a, 0x1f, 0x9e, 0x73,
0x40, 0x95, 0xab, 0xd6, 0xd2, 0x53, 0x0d, 0x51, 0xa8, 0xcd, 0xaf, 0x08, 0xb4, 0x97, 0xce, 0x91, 0x7f, 0x01, 0xcc, 0xdd, 0x2c, 0x50, 0x15, 0xaa, 0xb5, 0xf4, 0xd4, 0x40, 0x14, 0xaa, 0xf3, 0x73,
0xb1, 0x79, 0x4c, 0x3b, 0x2f, 0x3c, 0x36, 0x8b, 0xae, 0x61, 0x63, 0x61, 0x1f, 0xa0, 0x3f, 0xd2, 0x04, 0xed, 0x25, 0x73, 0xa4, 0x8c, 0x27, 0xd3, 0xce, 0x0a, 0xd7, 0x6e, 0xd1, 0x35, 0x6c, 0x2c,
0xcc, 0x2e, 0x1a, 0xb3, 0x91, 0x1b, 0x9f, 0xd8, 0xfd, 0x00, 0x6b, 0x33, 0xe3, 0x89, 0x32, 0xb2, 0x0c, 0x0d, 0xf4, 0x47, 0x9a, 0xd9, 0x69, 0x64, 0x36, 0x32, 0xe3, 0x63, 0xbf, 0x1f, 0x60, 0x6d,
0x95, 0xb6, 0x5d, 0xcc, 0xc7, 0xb9, 0xb0, 0x89, 0xad, 0x2b, 0x58, 0x9f, 0x6d, 0x61, 0x94, 0x41, 0xe6, 0x79, 0xa2, 0x14, 0xb5, 0x92, 0x46, 0x90, 0xf9, 0x38, 0x13, 0x36, 0xf6, 0x75, 0x05, 0xeb,
0x90, 0x3a, 0xe5, 0xe6, 0x93, 0x7c, 0xe0, 0xc4, 0x1c, 0xaf, 0xe3, 0x7c, 0x4b, 0x66, 0xd5, 0x31, 0xb3, 0x2d, 0x8c, 0x52, 0x08, 0x12, 0x5f, 0xb9, 0xf9, 0x24, 0x1b, 0x38, 0x76, 0xc7, 0xeb, 0x38,
0x63, 0x1c, 0xb2, 0xea, 0x98, 0xd5, 0xe9, 0xd6, 0xd2, 0x3e, 0xbc, 0xad, 0xc4, 0xe8, 0x0b, 0x5d, 0xdf, 0x92, 0x69, 0x75, 0x4c, 0x79, 0x0e, 0x69, 0x75, 0x4c, 0xeb, 0x74, 0x6b, 0x69, 0x1f, 0xde,
0xfe, 0xe9, 0x78, 0xfe, 0x33, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x56, 0x6f, 0xa5, 0x0e, 0x09, 0x00, 0x96, 0x35, 0xfa, 0xa2, 0x28, 0xff, 0x33, 0x79, 0xfe, 0x33, 0x00, 0x00, 0xff, 0xff, 0x97, 0xd7,
0x00, 0x36, 0xd6, 0x33, 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