mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
Merge pull request #643 from technosophos/feat/install-k8s
feat(helm, tiller): implement k8s portion of install
This commit is contained in:
commit
d644c22059
11 changed files with 199 additions and 46 deletions
|
|
@ -12,13 +12,16 @@ option go_package = "release";
|
|||
//
|
||||
message Status {
|
||||
enum Code {
|
||||
// Status_UNKNOWN indicates that a release is in an uncertain state.
|
||||
UNKNOWN = 0;
|
||||
|
||||
// Status_DEPLOYED indicates that the release has been pushed to Kubernetes.
|
||||
DEPLOYED = 1;
|
||||
|
||||
// Status_DELETED indicates that a release has been deleted from Kubermetes.
|
||||
DELETED = 2;
|
||||
|
||||
// Status_SUPERSEDED indicates that this release object is outdated and a newer one exists.
|
||||
SUPERSEDED = 3;
|
||||
// Status_FAILED indicates that the release was not successfully deployed.
|
||||
FAILED = 4;
|
||||
}
|
||||
|
||||
Code code = 1;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/kubernetes/helm/pkg/helm"
|
||||
"github.com/kubernetes/helm/pkg/proto/hapi/release"
|
||||
"github.com/kubernetes/helm/pkg/timeconv"
|
||||
)
|
||||
|
||||
const installDesc = `
|
||||
|
|
@ -25,9 +27,14 @@ const (
|
|||
|
||||
// install flags & args
|
||||
var (
|
||||
installArg string // name or relative path of the chart to install
|
||||
tillerHost string // override TILLER_HOST envVar
|
||||
verbose bool // enable verbose install
|
||||
// installArg is the name or relative path of the chart to install
|
||||
installArg string
|
||||
// tillerHost overrides TILLER_HOST envVar
|
||||
tillerHost string
|
||||
// verbose enables verbose output
|
||||
verbose bool
|
||||
// installDryRun performs a dry-run install
|
||||
installDryRun bool
|
||||
)
|
||||
|
||||
var installCmd = &cobra.Command{
|
||||
|
|
@ -40,7 +47,7 @@ var installCmd = &cobra.Command{
|
|||
func runInstall(cmd *cobra.Command, args []string) error {
|
||||
setupInstallEnv(args)
|
||||
|
||||
res, err := helm.InstallRelease(installArg)
|
||||
res, err := helm.InstallRelease(installArg, installDryRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -59,8 +66,9 @@ func printRelease(rel *release.Release) {
|
|||
}
|
||||
fmt.Printf("release.name: %s\n", rel.Name)
|
||||
if verbose {
|
||||
fmt.Printf("release.info: %s\n", rel.GetInfo())
|
||||
fmt.Printf("release.chart: %s\n", rel.GetChart())
|
||||
fmt.Printf("release.info: %s %s\n", timeconv.Format(rel.Info.LastDeployed, time.ANSIC), rel.Info.Status)
|
||||
fmt.Printf("release.chart: %s %s\n", rel.Chart.Metadata.Name, rel.Chart.Metadata.Version)
|
||||
fmt.Printf("release.manifest: %s\n", rel.Manifest)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +100,7 @@ func fatalf(format string, args ...interface{}) {
|
|||
func init() {
|
||||
installCmd.Flags().StringVar(&tillerHost, "host", defaultHost, "address of tiller server")
|
||||
installCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose install")
|
||||
installCmd.Flags().BoolVar(&installDryRun, "dry-run", false, "simulate an install")
|
||||
|
||||
RootCommand.AddCommand(installCmd)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
/*Package environment describes the operating environment for Tiller.
|
||||
|
||||
Tiller's environment encapsulates all of the service dependencies Tiller has.
|
||||
These dependencies are expressed as interfaces so that alternate implementations
|
||||
(mocks, etc.) can be easily generated.
|
||||
*/
|
||||
package environment
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/kubernetes/helm/pkg/engine"
|
||||
"github.com/kubernetes/helm/pkg/kube"
|
||||
"github.com/kubernetes/helm/pkg/proto/hapi/chart"
|
||||
"github.com/kubernetes/helm/pkg/proto/hapi/release"
|
||||
"github.com/kubernetes/helm/pkg/storage"
|
||||
|
|
@ -10,6 +19,9 @@ import (
|
|||
// GoTplEngine is the name of the Go template engine, as registered in the EngineYard.
|
||||
const GoTplEngine = "gotpl"
|
||||
|
||||
// DefaultNamespace is the default namespace for Tiller.
|
||||
const DefaultNamespace = "helm"
|
||||
|
||||
// DefaultEngine points to the engine that the EngineYard should treat as the
|
||||
// default. A chart that does not specify an engine may be run through the
|
||||
// default engine.
|
||||
|
|
@ -52,7 +64,11 @@ func (y EngineYard) Default() Engine {
|
|||
// An Engine must be capable of executing multiple concurrent requests, but
|
||||
// without tainting one request's environment with data from another request.
|
||||
type Engine interface {
|
||||
Render(*chart.Chart, *chart.Config) (map[string]string, error)
|
||||
// Render renders a chart.
|
||||
//
|
||||
// It receives a chart, a config, and a map of overrides to the config.
|
||||
// Overrides are assumed to be passed from the system, not the user.
|
||||
Render(*chart.Chart, *chart.Config, map[string]interface{}) (map[string]string, error)
|
||||
}
|
||||
|
||||
// ReleaseStorage represents a storage engine for a Release.
|
||||
|
|
@ -106,19 +122,37 @@ type ReleaseStorage interface {
|
|||
//
|
||||
// A KubeClient must be concurrency safe.
|
||||
type KubeClient interface {
|
||||
// Install takes a map where the key is a "file name" (read: unique relational
|
||||
// id) and the value is a Kubernetes manifest containing one or more resource
|
||||
// definitions.
|
||||
// Create creates one or more resources.
|
||||
//
|
||||
// TODO: Can these be in YAML or JSON, or must they be in one particular
|
||||
// format?
|
||||
Install(manifests map[string]string) error
|
||||
// namespace must contain a valid existing namespace.
|
||||
//
|
||||
// reader must contain a YAML stream (one or more YAML documents separated
|
||||
// by "\n---\n").
|
||||
//
|
||||
// config is optional. If nil, the client will use its existing configuration.
|
||||
// If set, the client will override its default configuration with the
|
||||
// passed in one.
|
||||
Create(namespace string, reader io.Reader) error
|
||||
}
|
||||
|
||||
// PrintingKubeClient implements KubeClient, but simply prints the reader to
|
||||
// the given output.
|
||||
type PrintingKubeClient struct {
|
||||
Out io.Writer
|
||||
}
|
||||
|
||||
// Create prints the values of what would be created with a real KubeClient.
|
||||
func (p *PrintingKubeClient) Create(ns string, r io.Reader) error {
|
||||
_, err := io.Copy(p.Out, r)
|
||||
return err
|
||||
}
|
||||
|
||||
// Environment provides the context for executing a client request.
|
||||
//
|
||||
// All services in a context are concurrency safe.
|
||||
type Environment struct {
|
||||
// The default namespace
|
||||
Namespace string
|
||||
// EngineYard provides access to the known template engines.
|
||||
EngineYard EngineYard
|
||||
// Releases stores records of releases.
|
||||
|
|
@ -136,7 +170,9 @@ func New() *Environment {
|
|||
GoTplEngine: e,
|
||||
}
|
||||
return &Environment{
|
||||
Namespace: DefaultNamespace,
|
||||
EngineYard: ey,
|
||||
Releases: storage.NewMemory(),
|
||||
KubeClient: kube.New(nil), //&PrintingKubeClient{Out: os.Stdout},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package environment
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes/helm/pkg/proto/hapi/chart"
|
||||
|
|
@ -11,7 +13,7 @@ type mockEngine struct {
|
|||
out map[string]string
|
||||
}
|
||||
|
||||
func (e *mockEngine) Render(chrt *chart.Chart, v *chart.Config) (map[string]string, error) {
|
||||
func (e *mockEngine) Render(chrt *chart.Chart, v *chart.Config, o map[string]interface{}) (map[string]string, error) {
|
||||
return e.out, nil
|
||||
}
|
||||
|
||||
|
|
@ -48,13 +50,14 @@ func (r *mockReleaseStorage) Query(labels map[string]string) ([]*release.Release
|
|||
type mockKubeClient struct {
|
||||
}
|
||||
|
||||
func (k *mockKubeClient) Install(manifests map[string]string) error {
|
||||
func (k *mockKubeClient) Create(ns string, r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Engine = &mockEngine{}
|
||||
var _ ReleaseStorage = &mockReleaseStorage{}
|
||||
var _ KubeClient = &mockKubeClient{}
|
||||
var _ KubeClient = &PrintingKubeClient{}
|
||||
|
||||
func TestEngine(t *testing.T) {
|
||||
eng := &mockEngine{out: map[string]string{"albatross": "test"}}
|
||||
|
|
@ -64,7 +67,7 @@ func TestEngine(t *testing.T) {
|
|||
|
||||
if engine, ok := env.EngineYard.Get("test"); !ok {
|
||||
t.Errorf("failed to get engine from EngineYard")
|
||||
} else if out, err := engine.Render(&chart.Chart{}, &chart.Config{}); err != nil {
|
||||
} else if out, err := engine.Render(&chart.Chart{}, &chart.Config{}, map[string]interface{}{}); err != nil {
|
||||
t.Errorf("unexpected template error: %s", err)
|
||||
} else if out["albatross"] != "test" {
|
||||
t.Errorf("expected 'test', got %q", out["albatross"])
|
||||
|
|
@ -102,9 +105,18 @@ func TestKubeClient(t *testing.T) {
|
|||
env := New()
|
||||
env.KubeClient = kc
|
||||
|
||||
manifests := map[string]string{}
|
||||
manifests := map[string]string{
|
||||
"foo": "name: value\n",
|
||||
"bar": "name: value\n",
|
||||
}
|
||||
|
||||
if err := env.KubeClient.Install(manifests); err != nil {
|
||||
b := bytes.NewBuffer(nil)
|
||||
for _, content := range manifests {
|
||||
b.WriteString("\n---\n")
|
||||
b.WriteString(content)
|
||||
}
|
||||
|
||||
if err := env.KubeClient.Create("sharry-bobbins", b); err != nil {
|
||||
t.Errorf("Kubeclient failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/kubernetes/helm/cmd/tiller/environment"
|
||||
"github.com/kubernetes/helm/pkg/proto/hapi/release"
|
||||
"github.com/kubernetes/helm/pkg/proto/hapi/services"
|
||||
"github.com/kubernetes/helm/pkg/storage"
|
||||
"github.com/kubernetes/helm/pkg/timeconv"
|
||||
"github.com/technosophos/moniker"
|
||||
ctx "golang.org/x/net/context"
|
||||
|
|
@ -99,19 +100,44 @@ func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateRelease
|
|||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
func (s *releaseServer) uniqName() (string, error) {
|
||||
maxTries := 5
|
||||
for i := 0; i < maxTries; i++ {
|
||||
namer := moniker.New()
|
||||
name := namer.NameSep("-")
|
||||
if _, err := s.env.Releases.Read(name); err == storage.ErrNotFound {
|
||||
return name, nil
|
||||
}
|
||||
log.Printf("info: Name %q is taken. Searching again.", name)
|
||||
}
|
||||
log.Printf("warning: No available release names found after %d tries", maxTries)
|
||||
return "ERROR", errors.New("no available release name found")
|
||||
}
|
||||
|
||||
func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
|
||||
if req.Chart == nil {
|
||||
return nil, errMissingChart
|
||||
}
|
||||
|
||||
// We should probably make a name generator part of the Environment.
|
||||
namer := moniker.New()
|
||||
// TODO: Make sure this is unique.
|
||||
name := namer.NameSep("-")
|
||||
ts := timeconv.Now()
|
||||
name, err := s.uniqName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
overrides := map[string]interface{}{
|
||||
"Release": map[string]interface{}{
|
||||
"Name": name,
|
||||
"Time": ts,
|
||||
"Namespace": s.env.Namespace,
|
||||
"Service": "Tiller",
|
||||
},
|
||||
"Chart": req.Chart.Metadata,
|
||||
}
|
||||
|
||||
// Render the templates
|
||||
files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values)
|
||||
// TODO: Fix based on whether chart has `engine: SOMETHING` set.
|
||||
files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values, overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -139,16 +165,30 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea
|
|||
Manifest: b.String(),
|
||||
}
|
||||
|
||||
res := &services.InstallReleaseResponse{Release: r}
|
||||
|
||||
if req.DryRun {
|
||||
log.Printf("Dry run for %s", name)
|
||||
return &services.InstallReleaseResponse{Release: r}, nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if err := s.env.KubeClient.Create(s.env.Namespace, b); err != nil {
|
||||
r.Info.Status.Code = release.Status_FAILED
|
||||
log.Printf("warning: Release %q failed: %s", name, err)
|
||||
return res, fmt.Errorf("release %s failed: %s", name, err)
|
||||
}
|
||||
|
||||
// This is a tricky case. The release has been created, but the result
|
||||
// cannot be recorded. The truest thing to tell the user is that the
|
||||
// release was created. However, the user will not be able to do anything
|
||||
// further with this release.
|
||||
//
|
||||
// One possible strategy would be to do a timed retry to see if we can get
|
||||
// this stored in the future.
|
||||
if err := s.env.Releases.Create(r); err != nil {
|
||||
return nil, err
|
||||
log.Printf("warning: Failed to record release %q: %s", name, err)
|
||||
}
|
||||
|
||||
return &services.InstallReleaseResponse{Release: r}, nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -219,6 +220,7 @@ func TestListReleases(t *testing.T) {
|
|||
func mockEnvironment() *environment.Environment {
|
||||
e := environment.New()
|
||||
e.Releases = storage.NewMemory()
|
||||
e.KubeClient = &environment.PrintingKubeClient{Out: os.Stdout}
|
||||
return e
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: {{default "alpine" .name}}
|
||||
name: {{.Release.Name}}-{{.Chart.Name}}
|
||||
labels:
|
||||
heritage: helm
|
||||
heritage: {{.Release.Service}}
|
||||
chartName: {{.Chart.Name}}
|
||||
chartVersion: {{.Chart.Version}}
|
||||
annotations:
|
||||
"helm.sh/created": {{.Release.Time.Seconds}}
|
||||
spec:
|
||||
restartPolicy: {{default "Never" .restart_policy}}
|
||||
containers:
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func New() *Engine {
|
|||
// - Scalar values and arrays are replaced, maps are merged
|
||||
// - A chart has access to all of the variables for it, as well as all of
|
||||
// the values destined for its dependencies.
|
||||
func (e *Engine) Render(chrt *chart.Chart, vals *chart.Config) (map[string]string, error) {
|
||||
func (e *Engine) Render(chrt *chart.Chart, vals *chart.Config, overrides map[string]interface{}) (map[string]string, error) {
|
||||
var cvals chartutil.Values
|
||||
|
||||
// Parse values if not nil. We merge these at the top level because
|
||||
|
|
@ -69,6 +69,12 @@ func (e *Engine) Render(chrt *chart.Chart, vals *chart.Config) (map[string]strin
|
|||
if err != nil {
|
||||
return map[string]string{}, err
|
||||
}
|
||||
// Override the top-level values. Overrides are NEVER merged deeply.
|
||||
// The assumption is that an override is intended to set an explicit
|
||||
// and exact value.
|
||||
for k, v := range overrides {
|
||||
evals[k] = v
|
||||
}
|
||||
cvals = coalesceValues(chrt, evals)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,38 @@ func TestEngine(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender(t *testing.T) {
|
||||
t.Skip()
|
||||
c := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "moby",
|
||||
Version: "1.2.3",
|
||||
},
|
||||
Templates: []*chart.Template{
|
||||
{Name: "test1", Data: []byte("{{.outer | title }} {{.inner | title}}")},
|
||||
},
|
||||
Values: &chart.Config{
|
||||
Raw: `outer = "DEFAULT"\ninner= "DEFAULT"\n`,
|
||||
},
|
||||
}
|
||||
|
||||
vals := &chart.Config{
|
||||
Raw: `outer = "BAD"
|
||||
inner= "inn"`,
|
||||
}
|
||||
|
||||
overrides := map[string]interface{}{
|
||||
"outer": "spouter",
|
||||
}
|
||||
|
||||
e := New()
|
||||
out, err := e.Render(c, vals, overrides)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to render templates: %s", err)
|
||||
}
|
||||
|
||||
expect := "Spouter Inn"
|
||||
if out["test1"] != expect {
|
||||
t.Errorf("Expected %q, got %q", expect, out["test1"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderInternals(t *testing.T) {
|
||||
|
|
@ -129,7 +160,7 @@ func TestRenderDependency(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
out, err := e.Render(ch, nil)
|
||||
out, err := e.Render(ch, nil, map[string]interface{}{})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to render chart: %s", err)
|
||||
|
|
@ -190,7 +221,7 @@ func TestRenderNestedValues(t *testing.T) {
|
|||
what = "flower"`,
|
||||
}
|
||||
|
||||
out, err := e.Render(outer, &inject)
|
||||
out, err := e.Render(outer, &inject, map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to render templates: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func UninstallRelease(name string) (*services.UninstallReleaseResponse, error) {
|
|||
}
|
||||
|
||||
// InstallRelease installs a new chart and returns the release response.
|
||||
func InstallRelease(chStr string) (*services.InstallReleaseResponse, error) {
|
||||
func InstallRelease(chStr string, dryRun bool) (*services.InstallReleaseResponse, error) {
|
||||
chfi, err := chartutil.LoadChart(chStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -90,5 +90,6 @@ func InstallRelease(chStr string) (*services.InstallReleaseResponse, error) {
|
|||
return Config.client().install(&services.InstallReleaseRequest{
|
||||
Chart: chpb,
|
||||
Values: vals,
|
||||
DryRun: dryRun,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,16 @@ var _ = math.Inf
|
|||
type Status_Code int32
|
||||
|
||||
const (
|
||||
Status_UNKNOWN Status_Code = 0
|
||||
Status_DEPLOYED Status_Code = 1
|
||||
Status_DELETED Status_Code = 2
|
||||
// Status_UNKNOWN indicates that a release is in an uncertain state.
|
||||
Status_UNKNOWN Status_Code = 0
|
||||
// Status_DEPLOYED indicates that the release has been pushed to Kubernetes.
|
||||
Status_DEPLOYED Status_Code = 1
|
||||
// Status_DELETED indicates that a release has been deleted from Kubermetes.
|
||||
Status_DELETED Status_Code = 2
|
||||
// Status_SUPERSEDED indicates that this release object is outdated and a newer one exists.
|
||||
Status_SUPERSEDED Status_Code = 3
|
||||
// Status_FAILED indicates that the release was not successfully deployed.
|
||||
Status_FAILED Status_Code = 4
|
||||
)
|
||||
|
||||
var Status_Code_name = map[int32]string{
|
||||
|
|
@ -28,12 +34,14 @@ var Status_Code_name = map[int32]string{
|
|||
1: "DEPLOYED",
|
||||
2: "DELETED",
|
||||
3: "SUPERSEDED",
|
||||
4: "FAILED",
|
||||
}
|
||||
var Status_Code_value = map[string]int32{
|
||||
"UNKNOWN": 0,
|
||||
"DEPLOYED": 1,
|
||||
"DELETED": 2,
|
||||
"SUPERSEDED": 3,
|
||||
"FAILED": 4,
|
||||
}
|
||||
|
||||
func (x Status_Code) String() string {
|
||||
|
|
@ -68,19 +76,20 @@ func init() {
|
|||
}
|
||||
|
||||
var fileDescriptor2 = []byte{
|
||||
// 215 bytes of a gzipped FileDescriptorProto
|
||||
// 226 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0x48, 0x2c, 0xc8,
|
||||
0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0xd5, 0x2f, 0x2e, 0x49, 0x2c, 0x29, 0x2d, 0xd6,
|
||||
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x49, 0xe9, 0x41, 0xa5, 0xa4, 0x24, 0xd3, 0xf3,
|
||||
0xf3, 0xd3, 0x73, 0x52, 0xf5, 0xc1, 0x72, 0x49, 0xa5, 0x69, 0xfa, 0x89, 0x79, 0x95, 0x10, 0x85,
|
||||
0x4a, 0xcb, 0x19, 0xb9, 0xd8, 0x82, 0xc1, 0x3a, 0x85, 0x74, 0xb9, 0x58, 0x92, 0xf3, 0x53, 0x52,
|
||||
0x4a, 0x9b, 0x19, 0xb9, 0xd8, 0x82, 0xc1, 0x3a, 0x85, 0x74, 0xb9, 0x58, 0x92, 0xf3, 0x53, 0x52,
|
||||
0x25, 0x18, 0x15, 0x18, 0x35, 0xf8, 0x8c, 0x24, 0xf5, 0x90, 0x8d, 0xd0, 0x83, 0xa8, 0xd1, 0x73,
|
||||
0x06, 0x2a, 0x08, 0x02, 0x2b, 0x13, 0xd2, 0xe3, 0x62, 0x4f, 0x49, 0x2d, 0x49, 0xcc, 0xcc, 0x29,
|
||||
0x96, 0x60, 0x02, 0xea, 0xe0, 0x36, 0x12, 0xd1, 0x83, 0x58, 0xa3, 0x07, 0xb3, 0x46, 0xcf, 0x31,
|
||||
0xaf, 0x32, 0x08, 0xa6, 0x48, 0xc9, 0x8e, 0x8b, 0x05, 0xa4, 0x5b, 0x88, 0x9b, 0x8b, 0x3d, 0xd4,
|
||||
0xaf, 0x32, 0x08, 0xa6, 0x48, 0xc9, 0x8b, 0x8b, 0x05, 0xa4, 0x5b, 0x88, 0x9b, 0x8b, 0x3d, 0xd4,
|
||||
0xcf, 0xdb, 0xcf, 0x3f, 0xdc, 0x4f, 0x80, 0x41, 0x88, 0x87, 0x8b, 0xc3, 0xc5, 0x35, 0xc0, 0xc7,
|
||||
0x3f, 0xd2, 0xd5, 0x45, 0x80, 0x11, 0x24, 0xe5, 0xe2, 0xea, 0xe3, 0x1a, 0x02, 0xe4, 0x30, 0x09,
|
||||
0xf1, 0x71, 0x71, 0x05, 0x87, 0x06, 0xb8, 0x06, 0x05, 0xbb, 0xba, 0x00, 0xf9, 0xcc, 0x4e, 0x9c,
|
||||
0x51, 0xec, 0x50, 0xc7, 0x24, 0xb1, 0x81, 0x6d, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x0d,
|
||||
0xcd, 0xe7, 0x6f, 0x01, 0x01, 0x00, 0x00,
|
||||
0xf1, 0x71, 0x71, 0x05, 0x87, 0x06, 0xb8, 0x06, 0x05, 0xbb, 0xba, 0x00, 0xf9, 0xcc, 0x42, 0x5c,
|
||||
0x5c, 0x6c, 0x6e, 0x8e, 0x9e, 0x3e, 0x40, 0x36, 0x8b, 0x13, 0x67, 0x14, 0x3b, 0xd4, 0x61, 0x49,
|
||||
0x6c, 0x60, 0xdb, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x99, 0x9a, 0x3b, 0x0d, 0x01,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue