feat(helm): add --no-hook to helm install

This includes a substantial bit of unit test improvements. Also, in
order to allow us to tests command line args (which translate to
helm.Option objects), I had to add a new interface to pkg/helm.
pull/955/head
Matt Butcher 8 years ago
parent ed5be30acc
commit a42b43a9fa

@ -175,6 +175,9 @@ message InstallReleaseRequest {
// namespace, otherwise the server will return an error. If it is not
// supplied, the server will autogenerate one.
string name = 4;
// DisableHooks causes the server to skip running any hooks for the install.
bool disable_hooks = 5;
}
// InstallReleaseResponse is the response from a release installation.
@ -186,6 +189,8 @@ message InstallReleaseResponse {
message UninstallReleaseRequest {
// Name is the name of the release to delete.
string name = 1;
// DisableHooks causes the server to skip running any hooks for the uninstall.
bool disable_hooks = 2;
}
// UninstallReleaseResponse represents a successful response to an uninstall request.

@ -126,7 +126,7 @@ func tpl(t string, vals map[string]interface{}, out io.Writer) error {
return tt.Execute(out, vals)
}
func ensureHelmClient(h helm.Interface) helm.Interface {
func ensureHelmClient(h helm.OptionalInterface) helm.OptionalInterface {
if h != nil {
return h
}

@ -34,10 +34,10 @@ Hooks are formatted in YAML and separated by the YAML '---\n' separator.
type getHooksCmd struct {
release string
out io.Writer
client helm.Interface
client helm.OptionalInterface
}
func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
func newGetHooksCmd(client helm.OptionalInterface, out io.Writer) *cobra.Command {
ghc := &getHooksCmd{
out: out,
client: client,

@ -34,10 +34,10 @@ type getValuesCmd struct {
release string
allValues bool
out io.Writer
client helm.Interface
client helm.OptionalInterface
}
func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
func newGetValuesCmd(client helm.OptionalInterface, out io.Writer) *cobra.Command {
get := &getValuesCmd{
out: out,
client: client,

@ -90,6 +90,7 @@ func newRootCmd(out io.Writer) *cobra.Command {
newGetCmd(nil, out),
newListCmd(nil, out),
newStatusCmd(nil, out),
newInstallCmd(nil, out),
)
return cmd
}

@ -92,7 +92,9 @@ func (c *fakeReleaseClient) ListReleases(opts ...helm.ReleaseListOption) (*rls.L
}
func (c *fakeReleaseClient) InstallRelease(chStr string, opts ...helm.InstallOption) (*rls.InstallReleaseResponse, error) {
return nil, nil
return &rls.InstallReleaseResponse{
Release: c.rels[0],
}, nil
}
func (c *fakeReleaseClient) DeleteRelease(rlsName string, opts ...helm.DeleteOption) (*rls.UninstallReleaseResponse, error) {
@ -116,6 +118,10 @@ func (c *fakeReleaseClient) ReleaseContent(rlsName string, opts ...helm.ContentO
return resp, c.err
}
func (c *fakeReleaseClient) Option(opt ...helm.Option) helm.OptionalInterface {
return c
}
// releaseCmd is a command that works with a fakeReleaseClient
type releaseCmd func(c *fakeReleaseClient, out io.Writer) *cobra.Command
@ -127,9 +133,11 @@ func runReleaseCases(t *testing.T, tests []releaseCase, rcmd releaseCmd) {
rels: []*release.Release{tt.resp},
}
cmd := rcmd(c, &buf)
cmd.ParseFlags(tt.flags)
println("parsed flags")
err := cmd.RunE(cmd, tt.args)
if (err != nil) != tt.err {
t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, err)
t.Errorf("%q. expected error: %v, got '%v'", tt.name, tt.err, err)
}
re := regexp.MustCompile(tt.expected)
if !re.Match(buf.Bytes()) {
@ -141,8 +149,9 @@ func runReleaseCases(t *testing.T, tests []releaseCase, rcmd releaseCmd) {
// releaseCase describes a test case that works with releases.
type releaseCase struct {
name string
args []string
name string
args []string
flags []string
// expected is the string to be matched. This supports regular expressions.
expected string
err bool

@ -18,6 +18,7 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
@ -46,70 +47,102 @@ var (
installValues string
// installRelName is the user-supplied release name.
installRelName string
// installNoHooks is the flag for controlling hook execution.
installNoHooks bool
)
var installCmd = &cobra.Command{
Use: "install [CHART]",
Short: "install a chart archive",
Long: installDesc,
RunE: runInstall,
PersistentPreRunE: setupConnection,
}
func init() {
f := installCmd.Flags()
f.StringVarP(&installValues, "values", "f", "", "path to a values YAML file")
f.StringVarP(&installRelName, "name", "n", "", "the release name. If unspecified, it will autogenerate one for you.")
f.BoolVar(&installDryRun, "dry-run", false, "simulate an install")
type installCmd struct {
name string
valuesFile string
chartPath string
dryRun bool
disableHooks bool
RootCommand.AddCommand(installCmd)
out io.Writer
client helm.OptionalInterface
}
func runInstall(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(1, len(args), "chart name"); err != nil {
return err
func newInstallCmd(c helm.OptionalInterface, out io.Writer) *cobra.Command {
inst := &installCmd{
out: out,
client: c,
}
chartpath, err := locateChartPath(args[0])
if err != nil {
return err
cmd := &cobra.Command{
Use: "install [CHART]",
Short: "install a chart archive",
Long: installDesc,
PersistentPreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("%v", args)
if err := checkArgsLength(1, len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(args[0])
if err != nil {
return err
}
inst.chartPath = cp
inst.client = ensureHelmClient(inst.client)
return inst.run()
},
}
f := cmd.Flags()
f.StringVarP(&inst.valuesFile, "values", "f", "", "path to a values YAML file")
f.StringVarP(&inst.name, "name", "n", "", "the release name. If unspecified, it will autogenerate one for you.")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
return cmd
}
func (i *installCmd) run() error {
if flagDebug {
fmt.Printf("Chart path: %s\n", chartpath)
fmt.Printf("Chart path: %s\n", i.chartPath)
}
rawVals, err := vals()
rawVals, err := i.vals()
if err != nil {
return err
}
res, err := helm.InstallRelease(rawVals, installRelName, chartpath, installDryRun)
if i.dryRun {
i.client.Option(helm.DryRun())
}
if i.disableHooks {
i.client.Option(helm.DisableHooks())
}
res, err := i.client.InstallRelease(i.chartPath, helm.ValueOverrides(rawVals), helm.ReleaseName(i.name))
if err != nil {
return prettyError(err)
}
printRelease(res.GetRelease())
i.printRelease(res.GetRelease())
return nil
}
func vals() ([]byte, error) {
if installValues == "" {
func (i *installCmd) vals() ([]byte, error) {
if i.valuesFile == "" {
return []byte{}, nil
}
return ioutil.ReadFile(installValues)
return ioutil.ReadFile(i.valuesFile)
}
func printRelease(rel *release.Release) {
func (i *installCmd) printRelease(rel *release.Release) {
if rel == nil {
return
}
// TODO: Switch to text/template like everything else.
if flagDebug {
fmt.Printf("NAME: %s\n", rel.Name)
fmt.Printf("INFO: %s %s\n", timeconv.String(rel.Info.LastDeployed), rel.Info.Status)
fmt.Printf("CHART: %s %s\n", rel.Chart.Metadata.Name, rel.Chart.Metadata.Version)
fmt.Printf("MANIFEST: %s\n", rel.Manifest)
fmt.Fprintf(i.out, "NAME: %s\n", rel.Name)
fmt.Fprintf(i.out, "INFO: %s %s\n", timeconv.String(rel.Info.LastDeployed), rel.Info.Status)
fmt.Fprintf(i.out, "CHART: %s %s\n", rel.Chart.Metadata.Name, rel.Chart.Metadata.Version)
fmt.Fprintf(i.out, "MANIFEST: %s\n", rel.Manifest)
} else {
fmt.Println(rel.Name)
fmt.Fprintln(i.out, rel.Name)
}
}

@ -0,0 +1,56 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"strings"
"testing"
"github.com/spf13/cobra"
)
func TestInstall(t *testing.T) {
tests := []releaseCase{
// Install, base case
{
name: "basic install",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--name aeneas", " "),
expected: "aeneas",
resp: releaseMock("aeneas"),
},
// Install, no hooks
{
name: "install without hooks",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--name aeneas --no-hooks", " "),
expected: "juno",
resp: releaseMock("juno"),
},
// Install, no charts
{
name: "install with no chart specified",
args: []string{},
err: true,
},
}
runReleaseCases(t, tests, func(c *fakeReleaseClient, out io.Writer) *cobra.Command {
return newInstallCmd(c, out)
})
}

@ -0,0 +1,6 @@
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0
home: https://k8s.io/helm
sources:
- https://github.com/kubernetes/helm

@ -0,0 +1,13 @@
#Alpine: A simple Helm chart
Run a single pod of Alpine Linux.
This example was generated using the command `helm create alpine`.
The `templates/` directory contains a very simple pod resource with a
couple of parameters.
The `values.yaml` file contains the default values for the
`alpine-pod.yaml` template.
You can install this example using `helm install docs/examples/alpine`.

@ -0,0 +1,26 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{.Release.Name}}-{{.Values.Name}}"
labels:
# The "heritage" label is used to track which tool deployed a given chart.
# It is useful for admins who want to see what releases a particular tool
# is responsible for.
heritage: {{.Release.Service | quote }}
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
release: {{.Release.Name | quote }}
# This makes it easy to audit chart usage.
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
annotations:
"helm.sh/created": {{.Release.Time.Seconds | quote }}
spec:
# This shows how to use a simple value. This will look for a passed-in value
# called restartPolicy. If it is not found, it will use the default value.
# {{default "Never" .restartPolicy}} is a slightly optimized version of the
# more conventional syntax: {{.restartPolicy | default "Never"}}
restartPolicy: {{default "Never" .Values.restartPolicy}}
containers:
- name: waiter
image: "alpine:3.3"
command: ["/bin/sleep","9000"]

@ -0,0 +1,2 @@
# The pod name
Name: my-alpine

@ -292,8 +292,10 @@ func (s *releaseServer) performRelease(r *release.Release, req *services.Install
}
// pre-install hooks
if err := s.execHook(r.Hooks, r.Name, preInstall); err != nil {
return res, err
if !req.DisableHooks {
if err := s.execHook(r.Hooks, r.Name, preInstall); err != nil {
return res, err
}
}
// regular manifests
@ -309,8 +311,10 @@ func (s *releaseServer) performRelease(r *release.Release, req *services.Install
}
// post-install hooks
if err := s.execHook(r.Hooks, r.Name, postInstall); err != nil {
return res, err
if !req.DisableHooks {
if err := s.execHook(r.Hooks, r.Name, postInstall); err != nil {
return res, err
}
}
// This is a tricky case. The release has been created, but the result
@ -382,8 +386,10 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
rel.Info.Deleted = timeconv.Now()
res := &services.UninstallReleaseResponse{Release: rel}
if err := s.execHook(rel.Hooks, rel.Name, preDelete); err != nil {
return res, err
if !req.DisableHooks {
if err := s.execHook(rel.Hooks, rel.Name, preDelete); err != nil {
return res, err
}
}
b := bytes.NewBuffer([]byte(rel.Manifest))
@ -392,8 +398,10 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
return nil, err
}
if err := s.execHook(rel.Hooks, rel.Name, postDelete); err != nil {
return res, err
if !req.DisableHooks {
if err := s.execHook(rel.Hooks, rel.Name, postDelete); err != nil {
return res, err
}
}
if err := s.env.Releases.Update(rel); err != nil {

@ -31,7 +31,7 @@ import (
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/timeconv"
//"k8s.io/helm/pkg/timeconv"
)
var manifestWithHook = `apiVersion: v1
@ -50,7 +50,27 @@ func rsFixture() *releaseServer {
}
}
func releaseMock() *release.Release {
// chartStub creates a fully stubbed out chart.
func chartStub() *chart.Chart {
return &chart.Chart{
// TODO: This should be more complete.
Metadata: &chart.Metadata{
Name: "hello",
},
// This adds basic templates, partials, and hooks.
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "goodbye", Data: []byte("goodbye: world")},
{Name: "empty", Data: []byte("")},
{Name: "with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)},
{Name: "partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)},
{Name: "hooks", Data: []byte(manifestWithHook)},
},
}
}
// releaseStub creates a release stub, complete with the chartStub as its chart.
func releaseStub() *release.Release {
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
return &release.Release{
Name: "angry-panda",
@ -59,15 +79,7 @@ func releaseMock() *release.Release {
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "foo",
Version: "0.1.0-beta.1",
},
Templates: []*chart.Template{
{Name: "foo.tpl", Data: []byte("Hello")},
},
},
Chart: chartStub(),
Config: &chart.Config{Raw: `name = "value"`},
Hooks: []*release.Hook{
{
@ -88,14 +100,9 @@ func TestInstallRelease(t *testing.T) {
c := context.Background()
rs := rsFixture()
// TODO: Refactor this into a mock.
req := &services.InstallReleaseRequest{
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithHook)},
},
},
Chart: chartStub(),
}
res, err := rs.InstallRelease(c, req)
if err != nil {
@ -144,17 +151,7 @@ func TestInstallReleaseDryRun(t *testing.T) {
rs := rsFixture()
req := &services.InstallReleaseRequest{
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "goodbye", Data: []byte("goodbye: world")},
{Name: "empty", Data: []byte("")},
{Name: "with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)},
{Name: "partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)},
{Name: "hooks", Data: []byte(manifestWithHook)},
},
},
Chart: chartStub(),
DryRun: true,
}
res, err := rs.InstallRelease(c, req)
@ -198,30 +195,29 @@ func TestInstallReleaseDryRun(t *testing.T) {
}
}
func TestInstallReleaseNoHooks(t *testing.T) {
c := context.Background()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
req := &services.InstallReleaseRequest{
Chart: chartStub(),
DisableHooks: true,
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %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) {
c := context.Background()
rs := rsFixture()
rs.env.Releases.Create(&release.Release{
Name: "angry-panda",
Info: &release.Info{
FirstDeployed: timeconv.Now(),
Status: &release.Status{
Code: release.Status_DEPLOYED,
},
},
Hooks: []*release.Hook{
{
Name: "test-cm",
Kind: "ConfigMap",
Path: "test-cm",
Manifest: manifestWithHook,
Events: []release.Hook_Event{
release.Hook_POST_INSTALL,
release.Hook_PRE_DELETE,
},
},
},
})
rs.env.Releases.Create(releaseStub())
req := &services.UninstallReleaseRequest{
Name: "angry-panda",
@ -249,10 +245,31 @@ func TestUninstallRelease(t *testing.T) {
}
}
func TestUninstallReleaseNoHooks(t *testing.T) {
c := context.Background()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
req := &services.UninstallReleaseRequest{
Name: "angry-panda",
DisableHooks: true,
}
res, err := rs.UninstallRelease(c, req)
if err != nil {
t.Errorf("Failed uninstall: %s", err)
}
// The default value for a protobuf timestamp is nil.
if res.Release.Hooks[0].LastRun != nil {
t.Errorf("Expected LastRun to be zero, got %d.", res.Release.Hooks[0].LastRun.Seconds)
}
}
func TestGetReleaseContent(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseMock()
rel := releaseStub()
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
@ -270,7 +287,7 @@ func TestGetReleaseContent(t *testing.T) {
func TestGetReleaseStatus(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseMock()
rel := releaseStub()
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
@ -289,7 +306,7 @@ func TestListReleases(t *testing.T) {
rs := rsFixture()
num := 7
for i := 0; i < num; i++ {
rel := releaseMock()
rel := releaseStub()
rel.Name = fmt.Sprintf("rel-%d", i)
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
@ -313,7 +330,7 @@ func TestListReleasesSort(t *testing.T) {
// sort.
num := 7
for i := num; i > 0; i-- {
rel := releaseMock()
rel := releaseStub()
rel.Name = fmt.Sprintf("rel-%d", i)
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
@ -356,7 +373,7 @@ func TestListReleasesFilter(t *testing.T) {
}
num := 7
for i := 0; i < num; i++ {
rel := releaseMock()
rel := releaseStub()
rel.Name = names[i]
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)

@ -44,11 +44,11 @@ type Client struct {
// NewClient creates a new client.
func NewClient(opts ...Option) *Client {
return new(Client).Init().Option(opts...)
return new(Client).Init().Option(opts...).(*Client)
}
// Option configures the helm client with the provided options
func (h *Client) Option(opts ...Option) *Client {
func (h *Client) Option(opts ...Option) OptionalInterface {
for _, opt := range opts {
opt(&h.opts)
}
@ -58,7 +58,7 @@ func (h *Client) Option(opts ...Option) *Client {
// Init initializes the helm client with default options
func (h *Client) Init() *Client {
return h.Option(Host(DefaultHelmHost)).
Option(Home(os.ExpandEnv(DefaultHelmHome)))
Option(Home(os.ExpandEnv(DefaultHelmHome))).(*Client)
}
// ListReleases lists the current releases.

@ -63,11 +63,14 @@ func UpdateRelease(rlsName string) (*rls.UpdateReleaseResponse, error) {
// InstallRelease runs an install for a release.
// Soon to be deprecated helm InstallRelease API.
func InstallRelease(vals []byte, rlsName, chStr string, dryRun bool) (*rls.InstallReleaseResponse, error) {
func InstallRelease(vals []byte, rlsName, chStr string, dryRun, skipHooks bool) (*rls.InstallReleaseResponse, error) {
client := NewClient(Host(Config.ServAddr))
if dryRun {
client.Option(DryRun())
}
if skipHooks {
client.Option(DisableHooks())
}
return client.InstallRelease(chStr, ValueOverrides(vals), ReleaseName(rlsName))
}

@ -29,3 +29,9 @@ type Interface interface {
UpdateRelease(rlsName string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error)
ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error)
}
// OptionalInterface is an Interface that also supports Option().
type OptionalInterface interface {
Interface
Option(opts ...Option) OptionalInterface
}

@ -38,6 +38,8 @@ type options struct {
chart string
// if set dry-run helm client calls
dryRun bool
// if set, skip running hooks
disableHooks bool
// release list options are applied directly to the list releases request
listReq rls.ListReleasesRequest
// release install options are applied directly to the install release request
@ -51,6 +53,12 @@ func DryRun() Option {
}
}
func DisableHooks() Option {
return func(opts *options) {
opts.disableHooks = true
}
}
// Home specifies the location of helm home, (default = "$HOME/.helm").
func Home(home string) Option {
return func(opts *options) {
@ -163,6 +171,7 @@ func (o *options) rpcInstallRelease(chr *cpb.Chart, rlc rls.ReleaseServiceClient
}
o.instReq.Chart = chr
o.instReq.DryRun = o.dryRun
o.instReq.DisableHooks = o.disableHooks
return rlc.InstallRelease(context.TODO(), &o.instReq)
}

@ -248,6 +248,8 @@ type InstallReleaseRequest struct {
// namespace, otherwise the server will return an error. If it is not
// supplied, the server will autogenerate one.
Name string `protobuf:"bytes,4,opt,name=name" json:"name,omitempty"`
// DisableHooks causes the server to skip running any hooks for the install.
DisableHooks bool `protobuf:"varint,5,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"`
}
func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} }
@ -290,6 +292,8 @@ func (m *InstallReleaseResponse) GetRelease() *hapi_release3.Release {
type UninstallReleaseRequest struct {
// Name is the name of the release to delete.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// DisableHooks causes the server to skip running any hooks for the uninstall.
DisableHooks bool `protobuf:"varint,2,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"`
}
func (m *UninstallReleaseRequest) Reset() { *m = UninstallReleaseRequest{} }
@ -586,48 +590,50 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
}
var fileDescriptor0 = []byte{
// 688 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0xdd, 0x4e, 0x13, 0x41,
0x14, 0x66, 0x69, 0x69, 0xcb, 0x41, 0x48, 0x39, 0x96, 0xb6, 0xee, 0x85, 0x31, 0x9b, 0xa8, 0x88,
0xb2, 0xd5, 0x7a, 0x6f, 0x52, 0xa0, 0x21, 0x84, 0x5a, 0x92, 0xa9, 0x68, 0xe2, 0x85, 0x64, 0x81,
0xa9, 0xac, 0x59, 0x76, 0xeb, 0xce, 0x94, 0xc8, 0x23, 0xf8, 0x08, 0xbe, 0x89, 0x0f, 0xe4, 0x83,
0x38, 0x3f, 0x3b, 0x9b, 0x6e, 0xd9, 0xd5, 0x86, 0x9b, 0xdd, 0x99, 0xfd, 0xbe, 0x39, 0xdf, 0x99,
0xef, 0x9c, 0xd3, 0x82, 0x7d, 0xe5, 0x4d, 0xfc, 0x0e, 0xa3, 0xf1, 0x8d, 0x7f, 0x41, 0x59, 0x87,
0xfb, 0x41, 0x40, 0x63, 0x77, 0x12, 0x47, 0x3c, 0xc2, 0x86, 0xc4, 0x5c, 0x83, 0xb9, 0x1a, 0xb3,
0x9b, 0xea, 0xc4, 0xc5, 0x95, 0x17, 0x73, 0xfd, 0xd4, 0x6c, 0xbb, 0x35, 0xfb, 0x3d, 0x0a, 0xc7,
0xfe, 0xd7, 0x04, 0xd0, 0x12, 0x31, 0x0d, 0xa8, 0xc7, 0xa8, 0x79, 0x67, 0x0e, 0x19, 0xcc, 0x0f,
0xc7, 0x91, 0x06, 0x9c, 0x3f, 0x16, 0x3c, 0x1c, 0xf8, 0x8c, 0x13, 0x0d, 0x31, 0x42, 0xbf, 0x4f,
0x29, 0xe3, 0xd8, 0x80, 0x95, 0xc0, 0xbf, 0xf6, 0x79, 0xdb, 0x7a, 0x62, 0x6d, 0x97, 0x88, 0xde,
0x60, 0x13, 0x2a, 0xd1, 0x78, 0xcc, 0x28, 0x6f, 0x2f, 0x8b, 0xcf, 0xab, 0x24, 0xd9, 0xe1, 0x3b,
0xa8, 0xb2, 0x28, 0xe6, 0x67, 0xe7, 0xb7, 0xed, 0x92, 0x00, 0x36, 0xba, 0x4f, 0xdd, 0xbc, 0x3b,
0xb9, 0x52, 0x69, 0x24, 0x88, 0xae, 0x7c, 0xec, 0xdd, 0x92, 0x0a, 0x53, 0x6f, 0x19, 0x77, 0xec,
0x07, 0x9c, 0xc6, 0xed, 0xb2, 0x8e, 0xab, 0x77, 0x78, 0x08, 0xa0, 0xe2, 0x46, 0xf1, 0xa5, 0xc0,
0x56, 0x54, 0xe8, 0xed, 0x05, 0x42, 0x9f, 0x48, 0x3e, 0x59, 0x65, 0x66, 0xe9, 0x7c, 0x81, 0x9a,
0x21, 0x38, 0x5d, 0xa8, 0x68, 0x79, 0x5c, 0x83, 0xea, 0xe9, 0xf0, 0x78, 0x78, 0xf2, 0x69, 0x58,
0x5f, 0xc2, 0x1a, 0x94, 0x87, 0xbd, 0xf7, 0xfd, 0xba, 0x85, 0x9b, 0xb0, 0x3e, 0xe8, 0x8d, 0x3e,
0x9c, 0x91, 0xfe, 0xa0, 0xdf, 0x1b, 0xf5, 0x0f, 0xea, 0xcb, 0xce, 0x63, 0x58, 0x4d, 0xe3, 0x62,
0x15, 0x4a, 0xbd, 0xd1, 0xbe, 0x3e, 0x72, 0xd0, 0x17, 0x2b, 0xcb, 0xf9, 0x69, 0x41, 0x23, 0x6b,
0x23, 0x9b, 0x44, 0x21, 0xa3, 0xd2, 0xc7, 0x8b, 0x68, 0x1a, 0xa6, 0x3e, 0xaa, 0x0d, 0x22, 0x94,
0x43, 0xfa, 0xc3, 0xb8, 0xa8, 0xd6, 0x92, 0xc9, 0x23, 0xee, 0x05, 0xca, 0x41, 0xc1, 0x54, 0x1b,
0x7c, 0x03, 0xb5, 0xa4, 0x6a, 0x4c, 0x78, 0x53, 0xda, 0x5e, 0xeb, 0x6e, 0xe9, 0xfb, 0x9b, 0xfa,
0x26, 0x8a, 0x24, 0xa5, 0x39, 0xbb, 0xd0, 0x3a, 0xa4, 0x26, 0x93, 0x11, 0xf7, 0xf8, 0x34, 0xad,
0xaa, 0xd4, 0xf5, 0xae, 0xa9, 0x4a, 0x46, 0xea, 0x8a, 0xb5, 0xf3, 0x11, 0xda, 0x77, 0xe9, 0x49,
0xf6, 0x39, 0x7c, 0x7c, 0x06, 0x65, 0xd9, 0x3f, 0x2a, 0xf7, 0xb5, 0x2e, 0x66, 0xb3, 0x39, 0x12,
0x08, 0x51, 0xb8, 0xe3, 0xce, 0xc6, 0xdd, 0x8f, 0x42, 0x4e, 0x43, 0xfe, 0xaf, 0x3c, 0x06, 0xf0,
0x28, 0x87, 0x9f, 0x24, 0xd2, 0x81, 0x6a, 0x22, 0xa1, 0xce, 0x14, 0xba, 0x60, 0x58, 0x4e, 0x13,
0x1a, 0xa7, 0x93, 0x4b, 0x8f, 0x53, 0x83, 0x68, 0x65, 0xa7, 0x05, 0x5b, 0x73, 0xdf, 0xb5, 0x82,
0xf3, 0xcb, 0x82, 0xad, 0xa3, 0x90, 0x09, 0xcf, 0x83, 0xec, 0x11, 0x7c, 0x2e, 0x4a, 0x28, 0xa7,
0x2d, 0x51, 0xde, 0xd4, 0xca, 0x7a, 0x24, 0xf7, 0xe5, 0x93, 0x68, 0x1c, 0x77, 0xa0, 0x72, 0xe3,
0x05, 0xe2, 0x4c, 0xd6, 0x9b, 0x84, 0xa9, 0x46, 0x95, 0x24, 0x0c, 0x6c, 0x41, 0xf5, 0x32, 0xbe,
0x3d, 0x8b, 0xa7, 0xa1, 0xaa, 0x77, 0x8d, 0x54, 0xc4, 0x96, 0x4c, 0xc3, 0xd4, 0x9a, 0xf2, 0x8c,
0x35, 0x47, 0xd0, 0x9c, 0x4f, 0xed, 0xbe, 0xbe, 0x88, 0xe6, 0x38, 0x0d, 0xfd, 0xdc, 0x7b, 0xe6,
0x15, 0xe5, 0x18, 0xda, 0x77, 0xe9, 0xf7, 0xd4, 0xee, 0xfe, 0x5e, 0x81, 0x0d, 0xd3, 0x67, 0x7a,
0x7a, 0xd1, 0x87, 0x07, 0xb3, 0x63, 0x83, 0x2f, 0x8a, 0x87, 0x7b, 0xee, 0x17, 0xca, 0xde, 0x59,
0x84, 0x9a, 0x14, 0x77, 0xe9, 0xb5, 0x85, 0x0c, 0xea, 0xf3, 0x7d, 0x8e, 0xbb, 0xf9, 0x31, 0x0a,
0xc6, 0xc7, 0x76, 0x17, 0xa5, 0x1b, 0x59, 0xbc, 0x81, 0xcd, 0x3b, 0x4d, 0x8d, 0xff, 0x0d, 0x93,
0x9d, 0x16, 0xbb, 0xb3, 0x30, 0x3f, 0xd5, 0xfd, 0x06, 0xeb, 0x99, 0x36, 0xc7, 0x02, 0xb7, 0xf2,
0x66, 0xc4, 0x7e, 0xb9, 0x10, 0x37, 0xd5, 0xba, 0x86, 0x8d, 0x6c, 0x77, 0x62, 0x41, 0x80, 0xdc,
0xf1, 0xb2, 0x5f, 0x2d, 0x46, 0x4e, 0xe5, 0x44, 0x1d, 0xe7, 0x5b, 0xb2, 0xa8, 0x8e, 0x05, 0x9d,
0x5e, 0x54, 0xc7, 0xa2, 0x4e, 0x77, 0x96, 0xf6, 0xe0, 0x73, 0xcd, 0xb0, 0xcf, 0x2b, 0xea, 0x9f,
0xf3, 0xed, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x39, 0x3c, 0xcd, 0x3c, 0xd3, 0x07, 0x00, 0x00,
// 720 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0xdd, 0x6e, 0xd3, 0x4a,
0x10, 0xae, 0x9b, 0x34, 0x49, 0xa7, 0x3f, 0x4a, 0xf7, 0xa4, 0x49, 0x8e, 0x2f, 0x8e, 0x8e, 0x8c,
0x80, 0x52, 0xa8, 0x03, 0xe1, 0x1e, 0x29, 0x6d, 0xa3, 0x52, 0x35, 0xa4, 0xd2, 0x86, 0x82, 0xc4,
0x05, 0x91, 0xdb, 0x6c, 0xa8, 0xc1, 0xf5, 0x06, 0xef, 0xa6, 0xa2, 0x8f, 0xc0, 0x1b, 0x71, 0xc5,
0xd3, 0xf0, 0x20, 0xec, 0x8f, 0xd7, 0x8a, 0x13, 0x1b, 0xa2, 0xde, 0x38, 0xbb, 0x3b, 0xdf, 0xce,
0x37, 0xfe, 0x66, 0x3e, 0x07, 0xec, 0x6b, 0x6f, 0xe2, 0xb7, 0x18, 0x89, 0x6e, 0xfd, 0x2b, 0xc2,
0x5a, 0xdc, 0x0f, 0x02, 0x12, 0xb9, 0x93, 0x88, 0x72, 0x8a, 0x6a, 0x32, 0xe6, 0x9a, 0x98, 0xab,
0x63, 0x76, 0x5d, 0xdd, 0xb8, 0xba, 0xf6, 0x22, 0xae, 0x9f, 0x1a, 0x6d, 0x37, 0x66, 0xcf, 0x69,
0x38, 0xf6, 0x3f, 0xc5, 0x01, 0x4d, 0x11, 0x91, 0x80, 0x78, 0x8c, 0x98, 0xdf, 0xd4, 0x25, 0x13,
0xf3, 0xc3, 0x31, 0xd5, 0x01, 0xe7, 0x97, 0x05, 0xff, 0xf4, 0x7c, 0xc6, 0xb1, 0x0e, 0x31, 0x4c,
0xbe, 0x4e, 0x09, 0xe3, 0xa8, 0x06, 0x6b, 0x81, 0x7f, 0xe3, 0xf3, 0xa6, 0xf5, 0xbf, 0xb5, 0x57,
0xc0, 0x7a, 0x83, 0xea, 0x50, 0xa2, 0xe3, 0x31, 0x23, 0xbc, 0xb9, 0x2a, 0x8e, 0xd7, 0x71, 0xbc,
0x43, 0xaf, 0xa0, 0xcc, 0x68, 0xc4, 0x87, 0x97, 0x77, 0xcd, 0x82, 0x08, 0x6c, 0xb7, 0x1f, 0xba,
0x59, 0xef, 0xe4, 0x4a, 0xa6, 0x81, 0x00, 0xba, 0xf2, 0x71, 0x78, 0x87, 0x4b, 0x4c, 0xfd, 0xca,
0xbc, 0x63, 0x3f, 0xe0, 0x24, 0x6a, 0x16, 0x75, 0x5e, 0xbd, 0x43, 0x27, 0x00, 0x2a, 0x2f, 0x8d,
0x46, 0x22, 0xb6, 0xa6, 0x52, 0xef, 0x2d, 0x91, 0xfa, 0x5c, 0xe2, 0xf1, 0x3a, 0x33, 0x4b, 0xe7,
0x23, 0x54, 0x0c, 0xc0, 0x69, 0x43, 0x49, 0xd3, 0xa3, 0x0d, 0x28, 0x5f, 0xf4, 0xcf, 0xfa, 0xe7,
0xef, 0xfb, 0xd5, 0x15, 0x54, 0x81, 0x62, 0xbf, 0xf3, 0xa6, 0x5b, 0xb5, 0xd0, 0x0e, 0x6c, 0xf5,
0x3a, 0x83, 0xb7, 0x43, 0xdc, 0xed, 0x75, 0x3b, 0x83, 0xee, 0x71, 0x75, 0xd5, 0xf9, 0x0f, 0xd6,
0x93, 0xbc, 0xa8, 0x0c, 0x85, 0xce, 0xe0, 0x48, 0x5f, 0x39, 0xee, 0x8a, 0x95, 0xe5, 0x7c, 0xb7,
0xa0, 0x96, 0x96, 0x91, 0x4d, 0x68, 0xc8, 0x88, 0xd4, 0xf1, 0x8a, 0x4e, 0xc3, 0x44, 0x47, 0xb5,
0x41, 0x08, 0x8a, 0x21, 0xf9, 0x66, 0x54, 0x54, 0x6b, 0x89, 0xe4, 0x94, 0x7b, 0x81, 0x52, 0x50,
0x20, 0xd5, 0x06, 0xbd, 0x80, 0x4a, 0xdc, 0x35, 0x26, 0xb4, 0x29, 0xec, 0x6d, 0xb4, 0x77, 0xf5,
0xfb, 0x9b, 0xfe, 0xc6, 0x8c, 0x38, 0x81, 0x39, 0x07, 0xd0, 0x38, 0x21, 0xa6, 0x92, 0x01, 0xf7,
0xf8, 0x34, 0xe9, 0xaa, 0xe4, 0xf5, 0x6e, 0x88, 0x2a, 0x46, 0xf2, 0x8a, 0xb5, 0xf3, 0x0e, 0x9a,
0x8b, 0xf0, 0xb8, 0xfa, 0x0c, 0x3c, 0x7a, 0x04, 0x45, 0x39, 0x3f, 0xaa, 0xf6, 0x8d, 0x36, 0x4a,
0x57, 0x73, 0x2a, 0x22, 0x58, 0xc5, 0x1d, 0x77, 0x36, 0xef, 0x11, 0x0d, 0x39, 0x09, 0xf9, 0x9f,
0xea, 0xe8, 0xc1, 0xbf, 0x19, 0xf8, 0xb8, 0x90, 0x16, 0x94, 0x63, 0x0a, 0x75, 0x27, 0x57, 0x05,
0x83, 0x72, 0xea, 0x50, 0xbb, 0x98, 0x8c, 0x3c, 0x4e, 0x4c, 0x44, 0x33, 0x3b, 0x0d, 0xd8, 0x9d,
0x3b, 0xd7, 0x0c, 0xce, 0x4f, 0x0b, 0x76, 0x4f, 0x43, 0x26, 0x34, 0x0f, 0xd2, 0x57, 0xd0, 0x63,
0xd1, 0x42, 0xe9, 0xb6, 0x98, 0x79, 0x47, 0x33, 0x6b, 0x4b, 0x1e, 0xc9, 0x27, 0xd6, 0x71, 0xb4,
0x0f, 0xa5, 0x5b, 0x2f, 0x10, 0x77, 0xd2, 0xda, 0xc4, 0x48, 0x65, 0x55, 0x1c, 0x23, 0x50, 0x03,
0xca, 0xa3, 0xe8, 0x6e, 0x18, 0x4d, 0x43, 0xd5, 0xef, 0x0a, 0x2e, 0x89, 0x2d, 0x9e, 0x86, 0x89,
0x34, 0xc5, 0x19, 0xc9, 0x1f, 0xc0, 0xd6, 0xc8, 0x67, 0xde, 0x65, 0x40, 0x86, 0xd7, 0x94, 0x7e,
0x61, 0xca, 0x09, 0x15, 0xbc, 0x19, 0x1f, 0xbe, 0x96, 0x67, 0xce, 0x29, 0xd4, 0xe7, 0xeb, 0xbf,
0xaf, 0x78, 0x18, 0x1a, 0x17, 0xa1, 0x9f, 0x29, 0x46, 0xd6, 0x44, 0x2c, 0x94, 0xb7, 0x9a, 0x51,
0xde, 0x19, 0x34, 0x17, 0x73, 0xde, 0xb3, 0xc0, 0xf6, 0x8f, 0x35, 0xd8, 0x36, 0x13, 0xab, 0xbf,
0x03, 0xc8, 0x87, 0xcd, 0x59, 0x03, 0xa2, 0x27, 0xf9, 0x9f, 0x89, 0xb9, 0x6f, 0x9d, 0xbd, 0xbf,
0x0c, 0x34, 0x1e, 0x93, 0x95, 0xe7, 0x16, 0x62, 0x50, 0x9d, 0x77, 0x0c, 0x3a, 0xc8, 0xce, 0x91,
0x63, 0x44, 0xdb, 0x5d, 0x16, 0x6e, 0x68, 0xd1, 0x2d, 0xec, 0x2c, 0xd8, 0x03, 0xfd, 0x35, 0x4d,
0xda, 0x77, 0x76, 0x6b, 0x69, 0x7c, 0xc2, 0xfb, 0x19, 0xb6, 0x52, 0x86, 0x41, 0x39, 0x6a, 0x65,
0xb9, 0xcd, 0x7e, 0xba, 0x14, 0x36, 0xe1, 0xba, 0x81, 0xed, 0xf4, 0x08, 0xa3, 0x9c, 0x04, 0x99,
0x46, 0xb5, 0x9f, 0x2d, 0x07, 0x4e, 0xe8, 0x44, 0x1f, 0xe7, 0x47, 0x32, 0xaf, 0x8f, 0x39, 0x76,
0xc8, 0xeb, 0x63, 0xde, 0xa4, 0x3b, 0x2b, 0x87, 0xf0, 0xa1, 0x62, 0xd0, 0x97, 0x25, 0xf5, 0x1f,
0xfc, 0xf2, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x38, 0x16, 0x87, 0x5f, 0x1d, 0x08, 0x00, 0x00,
}

Loading…
Cancel
Save