feat(tiller): add initial tiller client for basic helm installs.

1. install command loads chart archive.
  2. invokes helm api to transform the pkg/chart.Chart type
     to it's proto model.
  3. the client then establishes a connection to tiller.
  4. sends InstallReleaseRequest, receives InstallReleaseResponse.

todo (for complete install):
  - walk pkg/chart.{Values,Deps,Templates} types and populate proto
    definitions for various apis/messages.
pull/613/head
Brian 9 years ago
parent 50b56efc02
commit 7bc50a5d79

@ -23,7 +23,7 @@ message Chart {
// Charts that this chart depends on.
repeated Chart dependencies = 3;
// Default config for this template.
hapi.chart.Config values = 4;
// Default config for this template.
hapi.chart.Config values = 4;
}

@ -11,4 +11,15 @@ option go_package = "chart";
//
message Config {
string raw = 1;
map<string,Value> values = 2;
}
//
// Value:
//
// TODO
//
message Value {
string value = 1;
}

@ -33,7 +33,7 @@ message Metadata {
string home = 2;
// Source is the URL to the source code of this chart
string source = 3;
repeated string sources = 3;
// A SemVer 2 conformant version string of the chart
string version = 4;

@ -12,7 +12,7 @@ import (
"github.com/spf13/cobra"
)
const installDesc = `
const initDesc = `
This command installs Tiller (the helm server side component) onto your
Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/)
`

@ -0,0 +1,65 @@
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/deis/tiller/pkg/chart"
"github.com/deis/tiller/pkg/helm"
)
const installDesc = `
This command installs a chart archive.
`
func init() {
RootCommand.Flags()
RootCommand.AddCommand(installCmd)
}
var installCmd = &cobra.Command{
Use: "install [CHART]",
Short: "install a chart archive.",
Long: installDesc,
RunE: runInstall,
}
func runInstall(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("This command needs at least one argument, the name of the chart.")
}
ch, err := loadChart(args[0])
if err != nil {
return err
}
res, err := helm.InstallRelease(ch)
if err != nil {
return err
}
fmt.Printf("release.name: %s\n", res.Release.Name)
fmt.Printf("release.chart: %s\n", res.Release.Chart.Metadata.Name)
fmt.Printf("release.status: %s\n", res.Release.Info.Status.Code)
return nil
}
func loadChart(path string) (*chart.Chart, error) {
path, err := filepath.Abs(path)
if err != nil {
return nil, err
}
if fi, err := os.Stat(path); err != nil {
return nil, err
} else if fi.IsDir() {
return chart.LoadDir(path)
}
return chart.Load(path)
}

@ -93,6 +93,11 @@ func (c *Chart) ChartsDir() string {
return filepath.Join(c.loader.dir(), preCharts)
}
// LoadValues loads the contents of values.toml into a map
func (c *Chart) LoadValues() (Values, error) {
return ReadValuesFile(filepath.Join(c.loader.dir(), preValues))
}
// chartLoader provides load, close, and save implementations for a chart.
type chartLoader interface {
// Chartfile resturns a *Chartfile for this chart.

@ -1,10 +0,0 @@
//go:generate protoc -I ../../_proto ../../_proto/helm.proto --go_out=plugins=grpc:.
/*Package hapi contains the Helm API (HAPI).
Helm uses gRPC to handle communication between client and server. This package
contains the definitions of the API objeccts.
The files in this package that end with the extension *.pb.go are automatically
generated by Protobuf for use with gRPC.
*/
package hapi

@ -1,176 +0,0 @@
// Code generated by protoc-gen-go.
// source: helm.proto
// DO NOT EDIT!
/*
Package hapi is a generated protocol buffer package.
hapi: The Helm API
It is generated from these files:
helm.proto
It has these top-level messages:
PingRequest
PingResponse
Chart
Values
Release
*/
package hapi
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
const _ = proto.ProtoPackageIsVersion1
// The readiness test request.
type PingRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *PingRequest) Reset() { *m = PingRequest{} }
func (m *PingRequest) String() string { return proto.CompactTextString(m) }
func (*PingRequest) ProtoMessage() {}
func (*PingRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
// The readiness test response.
type PingResponse struct {
Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"`
}
func (m *PingResponse) Reset() { *m = PingResponse{} }
func (m *PingResponse) String() string { return proto.CompactTextString(m) }
func (*PingResponse) ProtoMessage() {}
func (*PingResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
type Chart struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *Chart) Reset() { *m = Chart{} }
func (m *Chart) String() string { return proto.CompactTextString(m) }
func (*Chart) ProtoMessage() {}
func (*Chart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
type Values struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *Values) Reset() { *m = Values{} }
func (m *Values) String() string { return proto.CompactTextString(m) }
func (*Values) ProtoMessage() {}
func (*Values) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
type Release struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *Release) Reset() { *m = Release{} }
func (m *Release) String() string { return proto.CompactTextString(m) }
func (*Release) ProtoMessage() {}
func (*Release) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func init() {
proto.RegisterType((*PingRequest)(nil), "hapi.PingRequest")
proto.RegisterType((*PingResponse)(nil), "hapi.PingResponse")
proto.RegisterType((*Chart)(nil), "hapi.Chart")
proto.RegisterType((*Values)(nil), "hapi.Values")
proto.RegisterType((*Release)(nil), "hapi.Release")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion1
// Client API for Probe service
type ProbeClient interface {
// Run a readiness test.
Ready(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
}
type probeClient struct {
cc *grpc.ClientConn
}
func NewProbeClient(cc *grpc.ClientConn) ProbeClient {
return &probeClient{cc}
}
func (c *probeClient) Ready(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) {
out := new(PingResponse)
err := grpc.Invoke(ctx, "/hapi.Probe/Ready", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Probe service
type ProbeServer interface {
// Run a readiness test.
Ready(context.Context, *PingRequest) (*PingResponse, error)
}
func RegisterProbeServer(s *grpc.Server, srv ProbeServer) {
s.RegisterService(&_Probe_serviceDesc, srv)
}
func _Probe_Ready_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(PingRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(ProbeServer).Ready(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
var _Probe_serviceDesc = grpc.ServiceDesc{
ServiceName: "hapi.Probe",
HandlerType: (*ProbeServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Ready",
Handler: _Probe_Ready_Handler,
},
},
Streams: []grpc.StreamDesc{},
}
var fileDescriptor0 = []byte{
// 182 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xca, 0x48, 0xcd, 0xc9,
0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x48, 0x2c, 0xc8, 0x54, 0x52, 0xe4, 0xe2,
0x0e, 0xc8, 0xcc, 0x4b, 0x0f, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9,
0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0xd4, 0xb8, 0x78,
0x20, 0x4a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x4b, 0x12, 0x4b,
0x4a, 0x8b, 0xa1, 0xaa, 0xa0, 0x3c, 0x25, 0x69, 0x2e, 0x56, 0xe7, 0x8c, 0xc4, 0x22, 0xec, 0x86,
0xc8, 0x70, 0xb1, 0x85, 0x25, 0xe6, 0x00, 0xed, 0xc0, 0x2a, 0x2b, 0xcb, 0xc5, 0x1e, 0x94, 0x9a,
0x93, 0x9a, 0x08, 0x34, 0x1d, 0x8b, 0xb4, 0x91, 0x25, 0x17, 0x6b, 0x40, 0x51, 0x7e, 0x52, 0xaa,
0x90, 0x01, 0x17, 0x6b, 0x50, 0x6a, 0x62, 0x4a, 0xa5, 0x90, 0xa0, 0x1e, 0xc8, 0xf5, 0x7a, 0x48,
0x4e, 0x97, 0x12, 0x42, 0x16, 0x82, 0x38, 0x55, 0x89, 0xc1, 0x89, 0x93, 0x8b, 0xbd, 0x38, 0x43,
0x0f, 0xe4, 0xed, 0x24, 0x36, 0xb0, 0xbf, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1e, 0x2f,
0xf3, 0xed, 0x05, 0x01, 0x00, 0x00,
}

@ -0,0 +1,34 @@
package helm
import (
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/deis/tiller/pkg/proto/hapi/services"
)
type client struct {
cfg *config
conn *grpc.ClientConn
impl services.ReleaseServiceClient
}
func (c *client) dial() (err error) {
c.conn, err = grpc.Dial(c.cfg.ServAddr, c.cfg.DialOpts()...)
c.impl = services.NewReleaseServiceClient(c.conn)
return
}
func (c *client) install(req *services.InstallReleaseRequest) (res *services.InstallReleaseResponse, err error) {
if err = c.dial(); err != nil {
return
}
defer c.Close()
return c.impl.InstallRelease(context.TODO(), req, c.cfg.CallOpts()...)
}
func (c *client) Close() error {
return c.conn.Close()
}

@ -0,0 +1,28 @@
package helm
import (
"google.golang.org/grpc"
)
type config struct {
ServAddr string
Insecure bool
}
func (cfg *config) DialOpts() (opts []grpc.DialOption) {
if cfg.Insecure {
opts = append(opts, grpc.WithInsecure())
} else {
// TODO: handle transport credentials
}
return
}
func (cfg *config) CallOpts() (opts []grpc.CallOption) {
return
}
func (cfg *config) client() *client {
return &client{cfg: cfg}
}

@ -0,0 +1,15 @@
package helm
const (
errNotImplemented = Error("helm api not implemented")
errMissingSrvAddr = Error("missing tiller address")
errMissingTpls = Error("missing chart templates")
errMissingChart = Error("missing chart metadata")
errMissingValues = Error("missing chart values")
)
type Error string
func (e Error) Error() string {
return string(e)
}

@ -0,0 +1,120 @@
package helm
import (
"github.com/deis/tiller/pkg/chart"
chartpb "github.com/deis/tiller/pkg/proto/hapi/chart"
"github.com/deis/tiller/pkg/proto/hapi/services"
)
var Config = &config{
ServAddr: ":44134",
Insecure: true,
}
func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) {
return nil, errNotImplemented
}
func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) {
return nil, errNotImplemented
}
func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) {
return nil, errNotImplemented
}
func UpdateRelease(name string) (*services.UpdateReleaseResponse, error) {
return nil, errNotImplemented
}
func UninstallRelease(name string) (*services.UninstallReleaseResponse, error) {
return nil, errNotImplemented
}
func InstallRelease(ch *chart.Chart) (res *services.InstallReleaseResponse, err error) {
chpb := new(chartpb.Chart)
chpb.Metadata, err = mkProtoMetadata(ch.Chartfile())
if err != nil {
return
}
chpb.Templates, err = mkProtoTemplates(ch)
if err != nil {
return
}
chpb.Dependencies, err = mkProtoChartDeps(ch)
if err != nil {
return
}
var vals *chartpb.Config
vals, err = mkProtoConfigValues(ch)
if err != nil {
return
}
res, err = Config.client().install(&services.InstallReleaseRequest{
Chart: chpb,
Values: vals,
})
return
}
// pkg/chart to proto/hapi/chart helpers. temporary.
func mkProtoMetadata(ch *chart.Chartfile) (*chartpb.Metadata, error) {
if ch == nil {
return nil, errMissingChart
}
md := &chartpb.Metadata{
Name: ch.Name,
Home: ch.Home,
Version: ch.Version,
Description: ch.Description,
}
md.Sources = make([]string, len(ch.Source))
copy(md.Sources, ch.Source)
md.Keywords = make([]string, len(ch.Keywords))
copy(md.Keywords, ch.Keywords)
for _, maintainer := range ch.Maintainers {
md.Maintainers = append(md.Maintainers, &chartpb.Maintainer{
Name: maintainer.Name,
Email: maintainer.Email,
})
}
return md, nil
}
func mkProtoTemplates(ch *chart.Chart) ([]*chartpb.Template, error) {
tpls, err := ch.LoadTemplates()
if err != nil {
return nil, err
}
_ = tpls
return nil, nil
}
func mkProtoChartDeps(ch *chart.Chart) ([]*chartpb.Chart, error) {
return nil, nil
}
func mkProtoConfigValues(ch *chart.Chart) (*chartpb.Config, error) {
vals, err := ch.LoadValues()
if err != nil {
return nil, errMissingValues
}
_ = vals
return nil, nil
}

@ -14,6 +14,7 @@ It is generated from these files:
It has these top-level messages:
Chart
Config
Value
Maintainer
Metadata
Template

@ -19,7 +19,8 @@ var _ = math.Inf
// A config supplies values to the parametrizable templates of a chart.
//
type Config struct {
Raw string `protobuf:"bytes,1,opt,name=raw" json:"raw,omitempty"`
Raw string `protobuf:"bytes,1,opt,name=raw" json:"raw,omitempty"`
Values map[string]*Value `protobuf:"bytes,2,rep,name=values" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
}
func (m *Config) Reset() { *m = Config{} }
@ -27,16 +28,44 @@ func (m *Config) String() string { return proto.CompactTextString(m)
func (*Config) ProtoMessage() {}
func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
func (m *Config) GetValues() map[string]*Value {
if m != nil {
return m.Values
}
return nil
}
//
// Value:
//
// TODO
//
type Value struct {
Value string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
}
func (m *Value) Reset() { *m = Value{} }
func (m *Value) String() string { return proto.CompactTextString(m) }
func (*Value) ProtoMessage() {}
func (*Value) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} }
func init() {
proto.RegisterType((*Config)(nil), "hapi.chart.Config")
proto.RegisterType((*Value)(nil), "hapi.chart.Value")
}
var fileDescriptor1 = []byte{
// 89 bytes of a gzipped FileDescriptorProto
// 179 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8,
0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0xd1, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28,
0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe8, 0x81, 0x25, 0x94, 0xa4, 0xb8, 0xd8, 0x9c, 0xc1,
0x72, 0x42, 0x02, 0x5c, 0xcc, 0x45, 0x89, 0xe5, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x20,
0xa6, 0x13, 0x7b, 0x14, 0x2b, 0x58, 0x51, 0x12, 0x1b, 0x58, 0x9f, 0x31, 0x20, 0x00, 0x00, 0xff,
0xff, 0xfe, 0xa0, 0x78, 0x2a, 0x52, 0x00, 0x00, 0x00,
0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe8, 0x81, 0x25, 0x94, 0x16, 0x30, 0x72, 0xb1, 0x39,
0x83, 0x25, 0x85, 0x04, 0xb8, 0x98, 0x8b, 0x12, 0xcb, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83,
0x40, 0x4c, 0x21, 0x33, 0x2e, 0xb6, 0xb2, 0xc4, 0x9c, 0xd2, 0xd4, 0x62, 0x09, 0x26, 0x05, 0x66,
0x0d, 0x6e, 0x23, 0x39, 0x3d, 0x84, 0x4e, 0x3d, 0x88, 0x2e, 0xbd, 0x30, 0xb0, 0x02, 0xd7, 0xbc,
0x92, 0xa2, 0xca, 0x20, 0xa8, 0x6a, 0x29, 0x1f, 0x2e, 0x6e, 0x24, 0x61, 0x90, 0xc1, 0xd9, 0xa9,
0x95, 0x30, 0x83, 0x81, 0x4c, 0x21, 0x75, 0x2e, 0x56, 0xb0, 0x52, 0xa0, 0xb9, 0x8c, 0x40, 0x73,
0x05, 0x91, 0xcd, 0x05, 0xeb, 0x0c, 0x82, 0xc8, 0x5b, 0x31, 0x59, 0x30, 0x2a, 0xc9, 0x72, 0xb1,
0x82, 0xc5, 0x84, 0x44, 0x60, 0xba, 0x20, 0x26, 0x41, 0x38, 0x4e, 0xec, 0x51, 0xac, 0x60, 0x8d,
0x49, 0x6c, 0x60, 0xdf, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x12, 0x60, 0xda, 0xf8,
0x00, 0x00, 0x00,
}

@ -44,7 +44,7 @@ type Metadata struct {
// The URL to a relecant project page, git repo, or contact person
Home string `protobuf:"bytes,2,opt,name=home" json:"home,omitempty"`
// Source is the URL to the source code of this chart
Source string `protobuf:"bytes,3,opt,name=source" json:"source,omitempty"`
Sources []string `protobuf:"bytes,3,rep,name=sources" json:"sources,omitempty"`
// A SemVer 2 conformant version string of the chart
Version string `protobuf:"bytes,4,opt,name=version" json:"version,omitempty"`
// A one-sentence description of the chart
@ -74,18 +74,18 @@ func init() {
var fileDescriptor2 = []byte{
// 224 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4e, 0xc3, 0x30,
0x10, 0x86, 0x15, 0xda, 0x24, 0xe5, 0xb2, 0x9d, 0x50, 0x65, 0x98, 0xa2, 0x4e, 0x4c, 0xae, 0x04,
0x12, 0x62, 0x66, 0xef, 0xd2, 0x91, 0xed, 0x48, 0x4e, 0x8a, 0x05, 0x8e, 0x23, 0xdb, 0x80, 0x78,
0x57, 0x1e, 0x06, 0xf7, 0x1a, 0x92, 0x0c, 0x1d, 0x22, 0xdd, 0xff, 0x7d, 0x77, 0x91, 0x7e, 0xc3,
0x6d, 0x47, 0x83, 0xd9, 0x37, 0x1d, 0xf9, 0xb8, 0xb7, 0x1c, 0xa9, 0xa5, 0x48, 0x7a, 0xf0, 0x2e,
0x3a, 0x84, 0x93, 0xd2, 0xa2, 0x76, 0x4f, 0x00, 0x07, 0x32, 0x7d, 0x4c, 0x1f, 0x7b, 0x44, 0x58,
0xf7, 0x64, 0x59, 0x65, 0x75, 0x76, 0x7f, 0x7d, 0x94, 0x19, 0x6f, 0x20, 0x67, 0x4b, 0xe6, 0x43,
0x5d, 0x09, 0x3c, 0x87, 0xdd, 0x6f, 0x06, 0x9b, 0xc3, 0xf8, 0xdb, 0x8b, 0x67, 0x89, 0x75, 0x2e,
0xb1, 0xf3, 0x95, 0xcc, 0xb8, 0x85, 0x22, 0xb8, 0x4f, 0xdf, 0xb0, 0x5a, 0x09, 0x1d, 0x13, 0x2a,
0x28, 0xbf, 0xd8, 0x07, 0xe3, 0x7a, 0xb5, 0x16, 0xf1, 0x1f, 0xb1, 0x86, 0xaa, 0xe5, 0xd0, 0x78,
0x33, 0xc4, 0x93, 0xcd, 0xc5, 0x2e, 0x11, 0xde, 0xc1, 0xe6, 0x9d, 0x7f, 0xbe, 0x9d, 0x6f, 0x83,
0x2a, 0xea, 0x55, 0xd2, 0x53, 0xc6, 0x67, 0xa8, 0xec, 0x54, 0x2e, 0xa8, 0x32, 0xe9, 0xea, 0x61,
0xab, 0xe7, 0xfa, 0x7a, 0xee, 0x7e, 0x5c, 0xae, 0xbe, 0x94, 0xaf, 0xb9, 0x2c, 0xbc, 0x15, 0xf2,
0x64, 0x8f, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x23, 0x79, 0xfc, 0xf8, 0x4f, 0x01, 0x00, 0x00,
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0x3f, 0x4f, 0xc4, 0x30,
0x0c, 0xc5, 0x55, 0xee, 0x7a, 0x3d, 0xdc, 0xcd, 0x42, 0x28, 0x30, 0x55, 0x37, 0x31, 0xe5, 0x24,
0x90, 0x10, 0x33, 0xfb, 0x2d, 0x37, 0xb2, 0x99, 0xd6, 0x52, 0x23, 0x48, 0x53, 0x25, 0x01, 0xc4,
0x97, 0xe5, 0xb3, 0x90, 0xba, 0xf4, 0xcf, 0xc0, 0x60, 0xc9, 0xef, 0xfd, 0xfc, 0x2c, 0xd9, 0x70,
0xd3, 0x52, 0x6f, 0x8e, 0x75, 0x4b, 0x3e, 0x1e, 0x2d, 0x47, 0x6a, 0x28, 0x92, 0xee, 0xbd, 0x8b,
0x0e, 0x61, 0x40, 0x5a, 0xd0, 0xe1, 0x11, 0xe0, 0x44, 0xa6, 0x8b, 0xa9, 0xd8, 0x23, 0xc2, 0xb6,
0x23, 0xcb, 0x2a, 0xab, 0xb2, 0xbb, 0xcb, 0xb3, 0xf4, 0x78, 0x05, 0x39, 0x5b, 0x32, 0xef, 0xea,
0x42, 0xcc, 0x51, 0x1c, 0x7e, 0x32, 0xd8, 0x9f, 0xfe, 0xd6, 0xfe, 0x1b, 0x4b, 0x5e, 0xeb, 0x92,
0x37, 0xa6, 0xa4, 0x47, 0x05, 0x45, 0x70, 0x1f, 0xbe, 0xe6, 0xa0, 0x36, 0xd5, 0x26, 0xd9, 0x93,
0x1c, 0xc8, 0x27, 0xfb, 0x60, 0x5c, 0xa7, 0xb6, 0x12, 0x98, 0x24, 0x56, 0x50, 0x36, 0x1c, 0x6a,
0x6f, 0xfa, 0x38, 0xd0, 0x5c, 0xe8, 0xda, 0xc2, 0x5b, 0xd8, 0xbf, 0xf1, 0xf7, 0x97, 0xf3, 0x4d,
0x50, 0x3b, 0x59, 0x3b, 0x6b, 0x7c, 0x82, 0xd2, 0xce, 0xe7, 0x05, 0x55, 0x24, 0x5c, 0xde, 0x5f,
0xeb, 0xe5, 0x01, 0x7a, 0xb9, 0xfe, 0xbc, 0x1e, 0x7d, 0x2e, 0x5e, 0x72, 0x19, 0x78, 0xdd, 0xc9,
0xd3, 0x1e, 0x7e, 0x03, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xaf, 0x44, 0xa7, 0x51, 0x01, 0x00, 0x00,
}

Loading…
Cancel
Save