feat(helm): implement 'helm remove'

pull/613/head
Matt Butcher 9 years ago
parent a0aac1ad73
commit c9b3c36243

@ -12,9 +12,12 @@ option go_package = "release";
// //
// //
message Info { message Info {
Status status = 1; Status status = 1;
google.protobuf.Timestamp first_deployed = 2; google.protobuf.Timestamp first_deployed = 2;
google.protobuf.Timestamp last_deployed = 3; google.protobuf.Timestamp last_deployed = 3;
// Deleted tracks when this object was deleted.
google.protobuf.Timestamp deleted = 4;
} }

@ -184,18 +184,14 @@ message InstallReleaseResponse {
hapi.release.Release release = 1; hapi.release.Release release = 1;
} }
// // UninstallReleaseRequest represents a request to uninstall a named release.
// UninstallReleaseRequest:
//
// TODO
//
message UninstallReleaseRequest { message UninstallReleaseRequest {
// Name is the name of the release to delete.
string name = 1;
} }
// // UninstallReleaseResponse represents a successful response to an uninstall request.
// UninstallReleaseResponse:
//
// TODO
//
message UninstallReleaseResponse { message UninstallReleaseResponse {
// Release is the release that was marked deleted.
hapi.release.Release release = 1;
} }

@ -0,0 +1,52 @@
package main
import (
"errors"
"fmt"
"github.com/deis/tiller/pkg/helm"
"github.com/spf13/cobra"
)
const removeDesc = `
This command takes a release name, and then deletes the release from Kubernetes.
It removes all of the resources associated with the last release of the chart.
Use the '--dry-run' flag to see which releases will be deleted without actually
deleting them.
`
var removeDryRun bool
var removeCommand = &cobra.Command{
Use: "remove [flags] RELEASE_NAME",
Aliases: []string{"rm"},
SuggestFor: []string{"delete", "del"},
Short: "Given a release name, remove the release from Kubernetes",
Long: removeDesc,
RunE: rmRelease,
}
func init() {
RootCommand.AddCommand(removeCommand)
removeCommand.Flags().BoolVar(&removeDryRun, "dry-run", false, "Simulate action, but don't actually do it.")
}
func rmRelease(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("Command 'remove' requires a release name.")
}
// TODO: Handle dry run use case.
if removeDryRun {
fmt.Printf("Deleting %s\n", args[0])
return nil
}
_, err := helm.UninstallRelease(args[0])
if err != nil {
return err
}
return nil
}

@ -4,10 +4,12 @@ import (
"bytes" "bytes"
"errors" "errors"
"log" "log"
"time"
"github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/cmd/tiller/environment"
"github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/proto/hapi/release"
"github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/proto/hapi/services"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/technosophos/moniker" "github.com/technosophos/moniker"
ctx "golang.org/x/net/context" ctx "golang.org/x/net/context"
) )
@ -26,7 +28,10 @@ type releaseServer struct {
var ( var (
// errNotImplemented is a temporary error for uninmplemented callbacks. // errNotImplemented is a temporary error for uninmplemented callbacks.
errNotImplemented = errors.New("not implemented") errNotImplemented = errors.New("not implemented")
errMissingChart = errors.New("no chart provided") // errMissingChart indicates that a chart was not provided.
errMissingChart = errors.New("no chart provided")
// errMissingRelease indicates that a release (name) was not provided.
errMissingRelease = errors.New("no release provided")
) )
func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error {
@ -53,7 +58,8 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea
// We should probably make a name generator part of the Environment. // We should probably make a name generator part of the Environment.
namer := moniker.New() namer := moniker.New()
// TODO: Make sure this is unique. // TODO: Make sure this is unique.
name := namer.Name() name := namer.NameSep("-")
ts := now()
// Render the templates // Render the templates
files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values)
@ -77,7 +83,9 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea
Chart: req.Chart, Chart: req.Chart,
Config: req.Values, Config: req.Values,
Info: &release.Info{ Info: &release.Info{
Status: &release.Status{Code: release.Status_UNKNOWN}, FirstDeployed: ts,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
}, },
Manifest: b.String(), Manifest: b.String(),
} }
@ -94,6 +102,38 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea
return &services.InstallReleaseResponse{Release: r}, nil return &services.InstallReleaseResponse{Release: r}, nil
} }
func now() *timestamp.Timestamp {
t := time.Now()
ts := &timestamp.Timestamp{
Seconds: t.Unix(),
Nanos: int32(t.Nanosecond()),
}
return ts
}
func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
return nil, errNotImplemented if req.Name == "" {
log.Printf("uninstall: Release not found: %s", req.Name)
return nil, errMissingRelease
}
rel, err := s.env.Releases.Read(req.Name)
if err != nil {
log.Printf("uninstall: Release not loaded: %s", req.Name)
return nil, err
}
log.Printf("uninstall: Deleting %s", req.Name)
rel.Info.Status.Code = release.Status_DELETED
rel.Info.Deleted = now()
// TODO: Once KubeClient is ready, delete the resources.
log.Println("WARNING: Currently not deleting resources from k8s")
if err := s.env.Releases.Update(rel); err != nil {
log.Printf("uninstall: Failed to store updated release: %s", err)
}
res := services.UninstallReleaseResponse{Release: rel}
return &res, nil
} }

@ -6,6 +6,7 @@ import (
"github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/cmd/tiller/environment"
"github.com/deis/tiller/pkg/proto/hapi/chart" "github.com/deis/tiller/pkg/proto/hapi/chart"
"github.com/deis/tiller/pkg/proto/hapi/release"
"github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/proto/hapi/services"
"github.com/deis/tiller/pkg/storage" "github.com/deis/tiller/pkg/storage"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -97,6 +98,41 @@ func TestInstallReleaseDryRun(t *testing.T) {
} }
} }
func TestUninstallRelease(t *testing.T) {
c := context.Background()
rs := rsFixture()
rs.env.Releases.Create(&release.Release{
Name: "angry-panda",
Info: &release.Info{
FirstDeployed: now(),
Status: &release.Status{
Code: release.Status_DEPLOYED,
},
},
})
req := &services.UninstallReleaseRequest{
Name: "angry-panda",
}
res, err := rs.UninstallRelease(c, req)
if err != nil {
t.Errorf("Failed uninstall: %s", err)
}
if res.Release.Name != "angry-panda" {
t.Errorf("Expected angry-panda, got %q", res.Release.Name)
}
if res.Release.Info.Status.Code != release.Status_DELETED {
t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code)
}
if res.Release.Info.Deleted.Seconds <= 0 {
t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds)
}
}
func mockEnvironment() *environment.Environment { func mockEnvironment() *environment.Environment {
e := environment.New() e := environment.New()
e.Releases = storage.NewMemory() e.Releases = storage.NewMemory()

@ -29,6 +29,15 @@ func (c *client) install(req *services.InstallReleaseRequest) (res *services.Ins
return c.impl.InstallRelease(context.TODO(), req, c.cfg.CallOpts()...) return c.impl.InstallRelease(context.TODO(), req, c.cfg.CallOpts()...)
} }
func (c *client) uninstall(req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
if err := c.dial(); err != nil {
return nil, err
}
defer c.Close()
return c.impl.UninstallRelease(context.TODO(), req, c.cfg.CallOpts()...)
}
func (c *client) Close() error { func (c *client) Close() error {
return c.conn.Close() return c.conn.Close()
} }

@ -8,6 +8,7 @@ const (
errMissingValues = Error("missing chart values") errMissingValues = Error("missing chart values")
) )
// Error represents a Helm client error.
type Error string type Error string
func (e Error) Error() string { func (e Error) Error() string {

@ -6,31 +6,42 @@ import (
"github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/proto/hapi/services"
) )
// Config defines a gRPC client's configuration.
var Config = &config{ var Config = &config{
ServAddr: ":44134", ServAddr: ":44134",
Insecure: true, Insecure: true,
} }
// ListReleases lists the current releases.
func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) { func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) {
return nil, errNotImplemented return nil, errNotImplemented
} }
// GetReleaseStatus returns the given release's status.
func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) { func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) {
return nil, errNotImplemented return nil, errNotImplemented
} }
// GetReleaseContent returns the configuration for a given release.
func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) { func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) {
return nil, errNotImplemented return nil, errNotImplemented
} }
// UpdateRelease updates a release to a new/different chart.
// TODO: This must take more than just name for an arg.
func UpdateRelease(name string) (*services.UpdateReleaseResponse, error) { func UpdateRelease(name string) (*services.UpdateReleaseResponse, error) {
return nil, errNotImplemented return nil, errNotImplemented
} }
// UninstallRelease uninstalls a named release and returns the response.
func UninstallRelease(name string) (*services.UninstallReleaseResponse, error) { func UninstallRelease(name string) (*services.UninstallReleaseResponse, error) {
return nil, errNotImplemented u := &services.UninstallReleaseRequest{
Name: name,
}
return Config.client().uninstall(u)
} }
// InstallRelease installs a new chart and returns the release response.
func InstallRelease(ch *chart.Chart) (res *services.InstallReleaseResponse, err error) { func InstallRelease(ch *chart.Chart) (res *services.InstallReleaseResponse, err error) {
chpb := new(chartpb.Chart) chpb := new(chartpb.Chart)

@ -39,6 +39,8 @@ type Info struct {
Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"`
FirstDeployed *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=first_deployed,json=firstDeployed" json:"first_deployed,omitempty"` FirstDeployed *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=first_deployed,json=firstDeployed" json:"first_deployed,omitempty"`
LastDeployed *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=last_deployed,json=lastDeployed" json:"last_deployed,omitempty"` LastDeployed *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=last_deployed,json=lastDeployed" json:"last_deployed,omitempty"`
// Deleted tracks when this object was deleted.
Deleted *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"`
} }
func (m *Info) Reset() { *m = Info{} } func (m *Info) Reset() { *m = Info{} }
@ -67,23 +69,30 @@ func (m *Info) GetLastDeployed() *google_protobuf.Timestamp {
return nil return nil
} }
func (m *Info) GetDeleted() *google_protobuf.Timestamp {
if m != nil {
return m.Deleted
}
return nil
}
func init() { func init() {
proto.RegisterType((*Info)(nil), "hapi.release.Info") proto.RegisterType((*Info)(nil), "hapi.release.Info")
} }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 194 bytes of a gzipped FileDescriptorProto // 208 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8,
0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0xd5, 0xcf, 0xcc, 0x4b, 0xcb, 0xd7, 0x2b, 0x28, 0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0xd5, 0xcf, 0xcc, 0x4b, 0xcb, 0xd7, 0x2b, 0x28,
0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x49, 0xe8, 0x41, 0x25, 0xa4, 0xe4, 0xd3, 0xf3, 0xf3, 0xd3, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x49, 0xe8, 0x41, 0x25, 0xa4, 0xe4, 0xd3, 0xf3, 0xf3, 0xd3,
0x73, 0x52, 0xf5, 0xc1, 0x72, 0x49, 0xa5, 0x69, 0xfa, 0x25, 0x99, 0xb9, 0xa9, 0xc5, 0x25, 0x89, 0x73, 0x52, 0xf5, 0xc1, 0x72, 0x49, 0xa5, 0x69, 0xfa, 0x25, 0x99, 0xb9, 0xa9, 0xc5, 0x25, 0x89,
0xb9, 0x05, 0x10, 0xe5, 0x52, 0x92, 0x28, 0xe6, 0x00, 0x65, 0x4a, 0x4a, 0x8b, 0x21, 0x52, 0x4a, 0xb9, 0x05, 0x10, 0xe5, 0x52, 0x92, 0x28, 0xe6, 0x00, 0x65, 0x4a, 0x4a, 0x8b, 0x21, 0x52, 0x4a,
0x3b, 0x18, 0xb9, 0x58, 0x3c, 0x81, 0x06, 0x0b, 0xe9, 0x70, 0xb1, 0x41, 0x24, 0x24, 0x18, 0x15, 0xef, 0x18, 0xb9, 0x58, 0x3c, 0x81, 0x06, 0x0b, 0xe9, 0x70, 0xb1, 0x41, 0x24, 0x24, 0x18, 0x15,
0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x90, 0xed, 0xd0, 0x0b, 0x06, 0xcb, 0x05, 0x41, 0xd5, 0x08, 0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x90, 0xed, 0xd0, 0x0b, 0x06, 0xcb, 0x05, 0x41, 0xd5, 0x08,
0x39, 0x72, 0xf1, 0xa5, 0x65, 0x16, 0x15, 0x97, 0xc4, 0xa7, 0xa4, 0x16, 0xe4, 0xe4, 0x57, 0xa6, 0x39, 0x72, 0xf1, 0xa5, 0x65, 0x16, 0x15, 0x97, 0xc4, 0xa7, 0xa4, 0x16, 0xe4, 0xe4, 0x57, 0xa6,
0xa6, 0x48, 0x30, 0x81, 0x75, 0x49, 0xe9, 0x41, 0xdc, 0xa2, 0x07, 0x73, 0x8b, 0x5e, 0x08, 0xcc, 0xa6, 0x48, 0x30, 0x81, 0x75, 0x49, 0xe9, 0x41, 0xdc, 0xa2, 0x07, 0x73, 0x8b, 0x5e, 0x08, 0xcc,
0x2d, 0x41, 0xbc, 0x60, 0x1d, 0x2e, 0x50, 0x0d, 0x42, 0xf6, 0x5c, 0xbc, 0x39, 0x89, 0xc8, 0x26, 0x2d, 0x41, 0xbc, 0x60, 0x1d, 0x2e, 0x50, 0x0d, 0x42, 0xf6, 0x5c, 0xbc, 0x39, 0x89, 0xc8, 0x26,
0x30, 0x13, 0x34, 0x81, 0x07, 0xa4, 0x01, 0x66, 0x80, 0x13, 0x67, 0x14, 0x3b, 0xd4, 0x75, 0x49, 0x30, 0x13, 0x34, 0x81, 0x07, 0xa4, 0x01, 0x6e, 0x80, 0x09, 0x17, 0x7b, 0x0a, 0xd0, 0x75, 0x25,
0x6c, 0x60, 0xc5, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0xae, 0xa9, 0x99, 0x31, 0x01, 0x40, 0xad, 0x2c, 0x04, 0xb5, 0xc2, 0x94, 0x3a, 0x71, 0x46, 0xb1, 0x43, 0xfd, 0x94, 0xc4, 0x06,
0x00, 0x00, 0x56, 0x67, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x9d, 0xa1, 0xf8, 0x67, 0x01, 0x00, 0x00,
} }

@ -225,12 +225,10 @@ func (m *InstallReleaseResponse) GetRelease() *hapi_release2.Release {
return nil return nil
} }
// // UninstallReleaseRequest represents a request to uninstall a named release.
// UninstallReleaseRequest:
//
// TODO
//
type UninstallReleaseRequest struct { type UninstallReleaseRequest struct {
// Name is the name of the release to delete.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
} }
func (m *UninstallReleaseRequest) Reset() { *m = UninstallReleaseRequest{} } func (m *UninstallReleaseRequest) Reset() { *m = UninstallReleaseRequest{} }
@ -238,12 +236,10 @@ func (m *UninstallReleaseRequest) String() string { return proto.Comp
func (*UninstallReleaseRequest) ProtoMessage() {} func (*UninstallReleaseRequest) ProtoMessage() {}
func (*UninstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{10} } func (*UninstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{10} }
// // UninstallReleaseResponse represents a successful response to an uninstall request.
// UninstallReleaseResponse:
//
// TODO
//
type UninstallReleaseResponse struct { type UninstallReleaseResponse struct {
// Release is the release that was marked deleted.
Release *hapi_release2.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
} }
func (m *UninstallReleaseResponse) Reset() { *m = UninstallReleaseResponse{} } func (m *UninstallReleaseResponse) Reset() { *m = UninstallReleaseResponse{} }
@ -251,6 +247,13 @@ func (m *UninstallReleaseResponse) String() string { return proto.Com
func (*UninstallReleaseResponse) ProtoMessage() {} func (*UninstallReleaseResponse) ProtoMessage() {}
func (*UninstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{11} } func (*UninstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{11} }
func (m *UninstallReleaseResponse) GetRelease() *hapi_release2.Release {
if m != nil {
return m.Release
}
return nil
}
func init() { func init() {
proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest") proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest")
proto.RegisterType((*ListReleasesResponse)(nil), "hapi.services.tiller.ListReleasesResponse") proto.RegisterType((*ListReleasesResponse)(nil), "hapi.services.tiller.ListReleasesResponse")
@ -541,40 +544,40 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
} }
var fileDescriptor1 = []byte{ var fileDescriptor1 = []byte{
// 550 bytes of a gzipped FileDescriptorProto // 558 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x4d, 0x6f, 0xd3, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x3d, 0x73, 0xd3, 0x40,
0x10, 0xad, 0x09, 0x4d, 0xc3, 0xa4, 0x8d, 0xe8, 0xe2, 0xc4, 0x8e, 0x4f, 0x65, 0x2f, 0x94, 0x02, 0x10, 0x8d, 0x71, 0xe2, 0x98, 0x75, 0xe2, 0x21, 0x8b, 0x6c, 0x29, 0xaa, 0xc2, 0x35, 0x84, 0x40,
0x0e, 0x84, 0x23, 0x70, 0x21, 0x07, 0x54, 0xa9, 0xe2, 0x60, 0xd4, 0x0b, 0x97, 0xca, 0xa4, 0x1b, 0x64, 0x30, 0x25, 0xd0, 0xe0, 0x82, 0xc9, 0x90, 0xa1, 0x10, 0x93, 0x86, 0x26, 0x23, 0x9c, 0x33,
0xba, 0xc8, 0x59, 0x07, 0xef, 0x3a, 0x12, 0xdc, 0x39, 0xf2, 0x7f, 0xf8, 0x79, 0xd8, 0xfb, 0x61, 0x11, 0x23, 0x9f, 0x8c, 0xee, 0xe4, 0x19, 0xe8, 0x29, 0xf9, 0x3f, 0xfc, 0x3c, 0xa4, 0xfb, 0xd0,
0xc5, 0x89, 0x57, 0x31, 0x5c, 0x1c, 0xed, 0xbe, 0x37, 0xf3, 0x66, 0x66, 0xdf, 0x28, 0x10, 0xdc, 0x48, 0xb6, 0x34, 0x11, 0x69, 0x64, 0xdf, 0xed, 0xdb, 0x7d, 0xfb, 0xf1, 0x56, 0x02, 0xf7, 0x36,
0xc5, 0x2b, 0x3a, 0xe1, 0x24, 0x5b, 0xd3, 0x39, 0xe1, 0x13, 0x41, 0x93, 0x84, 0x64, 0xe1, 0x2a, 0x58, 0x85, 0x13, 0x4e, 0x93, 0x75, 0x38, 0xa7, 0x7c, 0x22, 0xc2, 0x28, 0xa2, 0x89, 0xb7, 0x4a,
0x4b, 0x45, 0x8a, 0xdc, 0x12, 0x0b, 0x0d, 0x16, 0x2a, 0x2c, 0x18, 0xc9, 0x88, 0xf9, 0x5d, 0x9c, 0x62, 0x11, 0xa3, 0x95, 0xdb, 0x3c, 0x63, 0xf3, 0x94, 0xcd, 0x1d, 0x4b, 0x8f, 0xf9, 0x6d, 0x90,
0x09, 0xf5, 0x55, 0xec, 0xc0, 0xdb, 0xbc, 0x4f, 0xd9, 0x82, 0x7e, 0xd5, 0x80, 0x92, 0xc8, 0x48, 0x08, 0xf5, 0x54, 0x68, 0xd7, 0x2e, 0xdf, 0xc7, 0x6c, 0x11, 0x7e, 0xd3, 0x06, 0x45, 0x91, 0xd0,
0x42, 0x62, 0x4e, 0xcc, 0xaf, 0xc6, 0xc6, 0x35, 0x8c, 0x8b, 0x58, 0xe4, 0x5c, 0x41, 0x78, 0x06, 0x88, 0x06, 0x9c, 0x9a, 0x5f, 0x6d, 0x3b, 0xae, 0xd8, 0xb8, 0x08, 0x44, 0xca, 0x95, 0x89, 0xcc,
0x8f, 0xae, 0x28, 0x17, 0x91, 0xc2, 0x78, 0x44, 0xbe, 0xe7, 0x84, 0x0b, 0xe4, 0xc2, 0x61, 0x42, 0xe0, 0xf1, 0x65, 0xc8, 0x85, 0xaf, 0x6c, 0xdc, 0xa7, 0x3f, 0x52, 0xca, 0x05, 0x5a, 0xb0, 0x17,
0x97, 0x54, 0xf8, 0xce, 0x99, 0x73, 0xde, 0x89, 0xd4, 0x01, 0x8d, 0xa0, 0x9b, 0x2e, 0x16, 0x9c, 0x85, 0xcb, 0x50, 0x38, 0x9d, 0x93, 0xce, 0x69, 0xd7, 0x57, 0x07, 0x1c, 0x43, 0x2f, 0x5e, 0x2c,
0x08, 0xff, 0x9e, 0xbc, 0xd6, 0x27, 0xfc, 0xdb, 0x01, 0xb7, 0x9e, 0x85, 0xaf, 0x52, 0xc6, 0x49, 0x38, 0x15, 0xce, 0x03, 0x79, 0xad, 0x4f, 0xe4, 0x4f, 0x07, 0xac, 0x6a, 0x14, 0xbe, 0x8a, 0x19,
0x99, 0x66, 0x9e, 0xe6, 0xac, 0x4a, 0x23, 0x0f, 0xb6, 0x34, 0x25, 0x5b, 0xa4, 0x22, 0x4e, 0xfc, 0xa7, 0x79, 0x98, 0x79, 0x9c, 0xb2, 0x22, 0x8c, 0x3c, 0x34, 0x85, 0xc9, 0xd1, 0x22, 0x16, 0x41,
0x8e, 0x62, 0xcb, 0x03, 0x7a, 0x05, 0x3d, 0x5d, 0x39, 0xf7, 0xef, 0x9f, 0x75, 0xce, 0xfb, 0xd3, 0xe4, 0x74, 0x15, 0x5a, 0x1e, 0xf0, 0x15, 0xf4, 0x75, 0xe6, 0xdc, 0xd9, 0x3d, 0xe9, 0x9e, 0x0e,
0x61, 0x28, 0x47, 0x66, 0x7a, 0xd4, 0xaa, 0x51, 0x45, 0xc3, 0x6f, 0xc1, 0xfb, 0x40, 0x4c, 0x35, 0xa6, 0x23, 0x4f, 0xb6, 0xcc, 0xd4, 0xa8, 0x59, 0xfd, 0x02, 0x46, 0xde, 0x82, 0xfd, 0x81, 0x9a,
0x9f, 0x64, 0xbb, 0xa6, 0xb1, 0xc7, 0x70, 0xac, 0x69, 0x37, 0x2c, 0x5e, 0x12, 0x59, 0xd8, 0x83, 0x6c, 0x3e, 0xcb, 0x72, 0x4d, 0x61, 0x4f, 0xe0, 0x40, 0xc3, 0xae, 0x59, 0xb0, 0xa4, 0x32, 0xb1,
0xa8, 0xaf, 0xef, 0x3e, 0x16, 0x57, 0xf8, 0x27, 0xf8, 0xbb, 0xd1, 0xba, 0xa1, 0xfd, 0xe1, 0xe8, 0x87, 0xfe, 0x40, 0xdf, 0x7d, 0xca, 0xae, 0xc8, 0x2f, 0x70, 0xb6, 0xbd, 0x75, 0x41, 0x77, 0xbb,
0x0d, 0x0c, 0x0c, 0x45, 0x4d, 0x5a, 0x76, 0xd9, 0x9f, 0xba, 0xf5, 0xaa, 0x75, 0xe2, 0x93, 0x6c, 0xe3, 0x1b, 0x18, 0x1a, 0x88, 0xea, 0xb4, 0xac, 0x72, 0x30, 0xb5, 0xaa, 0x59, 0xeb, 0xc0, 0x87,
0x53, 0x07, 0xbf, 0xdb, 0xd4, 0x9e, 0xa5, 0x4c, 0x10, 0x26, 0xfe, 0xa1, 0xf4, 0x2b, 0x18, 0x37, 0x49, 0x99, 0x87, 0xbc, 0x2b, 0x73, 0xcf, 0x62, 0x26, 0x28, 0x13, 0xff, 0x91, 0xfa, 0x25, 0x1c,
0x84, 0xeb, 0xda, 0x27, 0x70, 0xa4, 0xb9, 0x32, 0xd4, 0x3a, 0x47, 0xc3, 0xc2, 0x23, 0x70, 0xaf, 0xd7, 0xb8, 0xeb, 0xdc, 0x27, 0xb0, 0xaf, 0xb1, 0xd2, 0xb5, 0xb1, 0x8f, 0x06, 0x45, 0xc6, 0x60,
0x57, 0xb7, 0xb1, 0x20, 0x06, 0x51, 0x85, 0x60, 0x0f, 0x86, 0x5b, 0xf7, 0x4a, 0x01, 0xff, 0x72, 0x5d, 0xad, 0x6e, 0x02, 0x41, 0x8d, 0x45, 0x25, 0x42, 0x6c, 0x18, 0x6d, 0xdc, 0x2b, 0x06, 0xf2,
0x60, 0x78, 0xc9, 0x8a, 0xae, 0x93, 0xa4, 0x1e, 0x82, 0x9e, 0x14, 0x46, 0x28, 0x3d, 0xab, 0x95, 0xbb, 0x03, 0xa3, 0x0b, 0x96, 0x55, 0x1d, 0x45, 0x55, 0x17, 0x7c, 0x9a, 0x09, 0x21, 0xd7, 0xac,
0x4f, 0x95, 0xb2, 0x32, 0xf6, 0xac, 0xfc, 0x46, 0x0a, 0x47, 0x17, 0xd0, 0x5d, 0xc7, 0x49, 0x11, 0x66, 0x3e, 0x52, 0xcc, 0x4a, 0xd8, 0xb3, 0xfc, 0xe9, 0x2b, 0x3b, 0x9e, 0x41, 0x6f, 0x1d, 0x44,
0xa3, 0xa7, 0x86, 0x6a, 0x4c, 0x69, 0xf8, 0x48, 0x33, 0x90, 0x07, 0x47, 0xb7, 0xd9, 0x8f, 0x9b, 0x99, 0x8f, 0xee, 0x1a, 0x56, 0x90, 0x52, 0xf0, 0xbe, 0x46, 0xa0, 0x0d, 0xfb, 0x37, 0xc9, 0xcf,
0x2c, 0x67, 0xd2, 0x31, 0xbd, 0xa8, 0x5b, 0x1c, 0xa3, 0x9c, 0xe1, 0x4b, 0x18, 0x6d, 0x97, 0xf1, 0xeb, 0x24, 0x65, 0x52, 0x31, 0x7d, 0xbf, 0x97, 0x1d, 0xfd, 0x94, 0x91, 0x0b, 0x18, 0x6f, 0xa6,
0xbf, 0x33, 0x18, 0x83, 0x77, 0xcd, 0x68, 0x53, 0x4f, 0x38, 0x00, 0x7f, 0x17, 0x52, 0x3a, 0xd3, 0x71, 0xdf, 0x1e, 0x9c, 0x83, 0x7d, 0xc5, 0xc2, 0xda, 0x9a, 0x10, 0x76, 0x4b, 0x73, 0x90, 0xff,
0x3f, 0x87, 0x30, 0x30, 0x0e, 0x52, 0x9b, 0x8d, 0x28, 0x1c, 0x6f, 0xee, 0x08, 0x7a, 0x1a, 0x36, 0xc9, 0x47, 0x70, 0xb6, 0xe1, 0xf7, 0xe4, 0x9e, 0xfe, 0xdd, 0x83, 0xa1, 0x91, 0xa1, 0x7a, 0x3d,
0x2d, 0x7e, 0xd8, 0xb0, 0x8d, 0xc1, 0x45, 0x1b, 0xaa, 0x7e, 0x83, 0x83, 0x97, 0x0e, 0xe2, 0xf0, 0x60, 0x08, 0x07, 0xe5, 0x45, 0xc3, 0x67, 0x5e, 0xdd, 0xdb, 0xc3, 0xab, 0x59, 0x69, 0xf7, 0xac,
0x70, 0xdb, 0xc1, 0xe8, 0x45, 0x73, 0x0e, 0xcb, 0x9e, 0x04, 0x61, 0x5b, 0xba, 0x91, 0x45, 0x6b, 0x0d, 0x54, 0x0f, 0x72, 0xe7, 0x65, 0x07, 0x39, 0x3c, 0xda, 0x5c, 0x03, 0x3c, 0xaf, 0x8f, 0xd1,
0x38, 0xdd, 0xf1, 0x1e, 0xda, 0x9b, 0xa6, 0xee, 0xf1, 0x60, 0xd2, 0x9a, 0x5f, 0xe9, 0x7e, 0x83, 0xb0, 0x6c, 0xae, 0xd7, 0x16, 0x6e, 0x68, 0x71, 0x0d, 0x47, 0x5b, 0x02, 0xc6, 0x3b, 0xc3, 0x54,
0x93, 0x9a, 0x1b, 0x91, 0x65, 0x5a, 0x4d, 0x56, 0x0e, 0x9e, 0xb5, 0xe2, 0x56, 0x5a, 0x4b, 0x18, 0x17, 0xc5, 0x9d, 0xb4, 0xc6, 0x17, 0xbc, 0xdf, 0xe1, 0xb0, 0x22, 0x69, 0x6c, 0xe8, 0x56, 0xdd,
0xd4, 0x8d, 0x85, 0x2c, 0x09, 0x1a, 0xb7, 0x20, 0x78, 0xde, 0x8e, 0x5c, 0xc9, 0x15, 0xef, 0xb8, 0x3e, 0xb8, 0xcf, 0x5b, 0x61, 0x0b, 0xae, 0x25, 0x0c, 0xab, 0xea, 0xc4, 0x86, 0x00, 0xb5, 0xab,
0xed, 0x30, 0xdb, 0x3b, 0x5a, 0x4c, 0x6a, 0x7b, 0x47, 0x9b, 0x71, 0xf1, 0xc1, 0x7b, 0xf8, 0xdc, 0xe4, 0xbe, 0x68, 0x07, 0x2e, 0xe8, 0xb2, 0x39, 0x6e, 0x4a, 0xb2, 0x69, 0x8e, 0x0d, 0x4a, 0x6f,
0x33, 0xec, 0x2f, 0x5d, 0xf9, 0x27, 0xf1, 0xfa, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x65, 0x9a, 0x63, 0x93, 0xd2, 0xc9, 0xce, 0x7b, 0xf8, 0xd2, 0x37, 0xe8, 0xaf, 0x3d, 0xf9, 0xa5, 0x79,
0x97, 0x54, 0xc0, 0x06, 0x00, 0x00, 0xfd, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x9f, 0x15, 0x68, 0xaf, 0x05, 0x07, 0x00, 0x00,
} }

Loading…
Cancel
Save