Commit ac88aaf2 authored by Michelle Noorali's avatar Michelle Noorali Committed by Adam Reese

feat(*): add helm rollback functionality

This feature allows you to rollback release to the
previous version of release.
resolves #1004
parent 9547f471
......@@ -23,13 +23,15 @@ option go_package = "release";
// Hook defines a hook object.
message Hook {
enum Event {
UNKNOWN = 0;
PRE_INSTALL = 1;
POST_INSTALL = 2;
PRE_DELETE = 3;
POST_DELETE = 4;
PRE_UPGRADE = 5;
POST_UPGRADE = 6;
UNKNOWN = 0;
PRE_INSTALL = 1;
POST_INSTALL = 2;
PRE_DELETE = 3;
POST_DELETE = 4;
PRE_UPGRADE = 5;
POST_UPGRADE = 6;
PRE_ROLLBACK = 7;
POST_ROLLBACK = 8;
}
string name = 1;
// Kind is the Kubernetes kind.
......
......@@ -67,9 +67,14 @@ service ReleaseService {
rpc UninstallRelease(UninstallReleaseRequest) returns (UninstallReleaseResponse) {
}
// GetVersion returns the current version of the server.
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {
}
// GetVersion returns the current version of the server.
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {
}
// RollbackRelease rolls back a release to a previous version.
rpc RollbackRelease(RollbackReleaseRequest) returns (RollbackReleaseResponse) {
}
}
// ListReleasesRequest requests a list of releases.
......@@ -188,6 +193,20 @@ message UpdateReleaseResponse {
hapi.release.Release release = 1;
}
message RollbackReleaseRequest {
// The name of the release
string name = 1;
// dry_run, if true, will run through the release logic but no create
bool dry_run = 2;
// DisableHooks causes the server to skip running any hooks for the rollback
bool disable_hooks = 3;
}
// RollbackReleaseResponse is the response to an update request.
message RollbackReleaseResponse {
hapi.release.Release release = 1;
}
// InstallReleaseRequest is the request for an installation of a chart.
message InstallReleaseRequest {
// Chart is the protobuf representation of a chart.
......
......@@ -159,6 +159,10 @@ func (c *fakeReleaseClient) UpdateRelease(rlsName string, chStr string, opts ...
return nil, nil
}
func (c *fakeReleaseClient) RollbackRelease(rlsName string, opts ...helm.RollbackOption) (*rls.RollbackReleaseResponse, error) {
return nil, nil
}
func (c *fakeReleaseClient) ReleaseContent(rlsName string, opts ...helm.ContentOption) (resp *rls.GetReleaseContentResponse, err error) {
if len(c.rels) > 0 {
resp = &rls.GetReleaseContentResponse{
......
......@@ -27,9 +27,7 @@ import (
const rollbackDesc = `
This command rolls back a release to the previous version.
The rollback argument is the name of a release.
The argument of the rollback command is the name of a release.
`
type rollbackCmd struct {
......@@ -55,22 +53,25 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "release name"); err != nil {
return err
}
rollback.name = args[0]
rollback.client = ensureHelmClient(rollback.client)
return rollback.run()
},
}
f := cmd.Flags()
f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback")
return cmd
}
func (r *rollbackCmd) run() error {
_, err := r.client.RollbackRelease(r.name, helm.RollbackDryRun(r.dryRun), helm.RollbackDisableHooks(r.disableHooks))
if err != nil {
return prettyError(err)
}
msg := "This command is under construction. Coming soon to a Helm near you!"
fmt.Fprintf(r.out, msg)
fmt.Fprintf(r.out, "Rollback was a success! Happy Helming!\n")
return nil
}
......@@ -30,7 +30,7 @@ func TestRollbackCmd(t *testing.T) {
name: "rollback a release",
args: []string{"funny-honey"},
resp: nil,
expected: "This command is under construction. Coming soon to a Helm near you!",
expected: "Rollback was a success! Happy Helming!",
},
}
......
......@@ -30,21 +30,25 @@ import (
const hookAnno = "helm.sh/hook"
const (
preInstall = "pre-install"
postInstall = "post-install"
preDelete = "pre-delete"
postDelete = "post-delete"
preUpgrade = "pre-upgrade"
postUpgrade = "post-upgrade"
preInstall = "pre-install"
postInstall = "post-install"
preDelete = "pre-delete"
postDelete = "post-delete"
preUpgrade = "pre-upgrade"
postUpgrade = "post-upgrade"
preRollback = "pre-rollback"
postRollback = "post-rollback"
)
var events = map[string]release.Hook_Event{
preInstall: release.Hook_PRE_INSTALL,
postInstall: release.Hook_POST_INSTALL,
preDelete: release.Hook_PRE_DELETE,
postDelete: release.Hook_POST_DELETE,
preUpgrade: release.Hook_PRE_UPGRADE,
postUpgrade: release.Hook_POST_UPGRADE,
preInstall: release.Hook_PRE_INSTALL,
postInstall: release.Hook_POST_INSTALL,
preDelete: release.Hook_PRE_DELETE,
postDelete: release.Hook_POST_DELETE,
preUpgrade: release.Hook_PRE_UPGRADE,
postUpgrade: release.Hook_POST_UPGRADE,
preRollback: release.Hook_PRE_ROLLBACK,
postRollback: release.Hook_POST_ROLLBACK,
}
type simpleHead struct {
......
......@@ -30,6 +30,7 @@ import (
"github.com/ghodss/yaml"
"github.com/technosophos/moniker"
ctx "golang.org/x/net/context"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/helm/cmd/tiller/environment"
"k8s.io/helm/pkg/chartutil"
......@@ -39,7 +40,6 @@ import (
"k8s.io/helm/pkg/storage/driver"
"k8s.io/helm/pkg/timeconv"
"k8s.io/helm/pkg/version"
"k8s.io/kubernetes/pkg/api/unversioned"
)
var srv *releaseServer
......@@ -303,10 +303,7 @@ func (s *releaseServer) performUpdate(originalRelease, updatedRelease *release.R
}
}
kubeCli := s.env.KubeClient
original := bytes.NewBufferString(originalRelease.Manifest)
modified := bytes.NewBufferString(updatedRelease.Manifest)
if err := kubeCli.Update(updatedRelease.Namespace, original, modified); err != nil {
if err := s.performKubeUpdate(originalRelease, updatedRelease); err != nil {
return nil, err
}
......@@ -382,6 +379,114 @@ func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
return currentRelease, updatedRelease, nil
}
func (s *releaseServer) RollbackRelease(c ctx.Context, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) {
currentRelease, targetRelease, err := s.prepareRollback(req)
if err != nil {
return nil, err
}
rel, err := s.performRollback(currentRelease, targetRelease, req)
if err != nil {
return nil, err
}
if err := s.env.Releases.Create(targetRelease); err != nil {
return nil, err
}
return rel, nil
}
func (s *releaseServer) performRollback(currentRelease, targetRelease *release.Release, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) {
res := &services.RollbackReleaseResponse{Release: targetRelease}
if req.DryRun {
log.Printf("Dry run for %s", targetRelease.Name)
return res, nil
}
// pre-rollback hooks
if !req.DisableHooks {
if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, preRollback); err != nil {
return res, err
}
}
if err := s.performKubeUpdate(currentRelease, targetRelease); err != nil {
return nil, err
}
// post-rollback hooks
if !req.DisableHooks {
if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, postRollback); err != nil {
return res, err
}
}
currentRelease.Info.Status.Code = release.Status_SUPERSEDED
if err := s.env.Releases.Update(currentRelease); err != nil {
return nil, fmt.Errorf("Update of %s failed: %s", currentRelease.Name, err)
}
targetRelease.Info.Status.Code = release.Status_DEPLOYED
return res, nil
}
func (s *releaseServer) performKubeUpdate(currentRelease, targetRelease *release.Release) error {
kubeCli := s.env.KubeClient
current := bytes.NewBufferString(currentRelease.Manifest)
target := bytes.NewBufferString(targetRelease.Manifest)
if err := kubeCli.Update(targetRelease.Namespace, current, target); err != nil {
return err
}
return nil
}
// prepareRollback finds the previous release and prepares a new release object with
// the previous release's configuration
func (s *releaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*release.Release, *release.Release, error) {
if req.Name == "" {
return nil, nil, errMissingRelease
}
// finds the non-deleted release with the given name
currentRelease, err := s.env.Releases.Deployed(req.Name)
if err != nil {
return nil, nil, err
}
previousRelease, err := s.env.Releases.Get(req.Name, currentRelease.Version-1)
if err != nil {
return nil, nil, err
}
ts := timeconv.Now()
// Store a new release object with previous release's configuration
targetRelease := &release.Release{
Name: req.Name,
Namespace: currentRelease.Namespace,
Chart: previousRelease.Chart,
Config: previousRelease.Config,
Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: ts,
Status: &release.Status{
Code: release.Status_UNKNOWN,
Notes: previousRelease.Info.Status.Notes,
},
},
Version: currentRelease.Version + 1,
Manifest: previousRelease.Manifest,
Hooks: previousRelease.Hooks,
}
return currentRelease, targetRelease, nil
}
func (s *releaseServer) uniqName(start string, reuse bool) (string, error) {
// If a name is supplied, we check to see if that name is taken. If not, it
......
......@@ -60,6 +60,16 @@ data:
name: value
`
var manifestWithRollbackHooks = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-rollback,pre-rollback
data:
name: value
`
func rsFixture() *releaseServer {
return &releaseServer{
env: mockEnvironment(),
......@@ -117,6 +127,23 @@ func namedReleaseStub(name string, status release.Status_Code) *release.Release
}
}
func upgradeReleaseVersion(rel *release.Release) *release.Release {
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
rel.Info.Status.Code = release.Status_SUPERSEDED
return &release.Release{
Name: rel.Name,
Info: &release.Info{
FirstDeployed: rel.Info.FirstDeployed,
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
},
Chart: rel.Chart,
Config: rel.Config,
Version: rel.Version + 1,
}
}
func TestGetVersionSet(t *testing.T) {
rs := rsFixture()
vs, err := rs.getVersionSet()
......@@ -601,6 +628,150 @@ func TestUpdateReleaseNoChanges(t *testing.T) {
}
}
func TestRollbackReleaseNoHooks(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseStub()
rel.Hooks = []*release.Hook{
{
Name: "test-cm",
Kind: "ConfigMap",
Path: "test-cm",
Manifest: manifestWithRollbackHooks,
Events: []release.Hook_Event{
release.Hook_PRE_ROLLBACK,
release.Hook_POST_ROLLBACK,
},
},
}
rs.env.Releases.Create(rel)
upgradedRel := upgradeReleaseVersion(rel)
rs.env.Releases.Update(rel)
rs.env.Releases.Create(upgradedRel)
req := &services.RollbackReleaseRequest{
Name: rel.Name,
DisableHooks: true,
}
res, err := rs.RollbackRelease(c, req)
if err != nil {
t.Fatalf("Failed rollback: %s", err)
}
if hl := res.Release.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl)
}
}
func TestRollbackRelease(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
upgradedRel := upgradeReleaseVersion(rel)
upgradedRel.Hooks = []*release.Hook{
{
Name: "test-cm",
Kind: "ConfigMap",
Path: "test-cm",
Manifest: manifestWithRollbackHooks,
Events: []release.Hook_Event{
release.Hook_PRE_ROLLBACK,
release.Hook_POST_ROLLBACK,
},
},
}
upgradedRel.Manifest = "hello world"
rs.env.Releases.Update(rel)
rs.env.Releases.Create(upgradedRel)
req := &services.RollbackReleaseRequest{
Name: rel.Name,
}
res, err := rs.RollbackRelease(c, req)
if err != nil {
t.Fatalf("Failed rollback: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
}
if res.Release.Name != rel.Name {
t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name)
}
if res.Release.Namespace != rel.Namespace {
t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace)
}
if res.Release.Version != 3 {
t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version)
}
updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
}
if updated.Hooks[0].Manifest != manifestWithHook {
t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest)
}
anotherUpgradedRelease := upgradeReleaseVersion(upgradedRel)
rs.env.Releases.Update(upgradedRel)
rs.env.Releases.Create(anotherUpgradedRelease)
res, err = rs.RollbackRelease(c, req)
if err != nil {
t.Fatalf("Failed rollback: %s", err)
}
updated, err = rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
}
if updated.Hooks[0].Manifest != manifestWithRollbackHooks {
t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest)
}
if res.Release.Version != 4 {
t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version)
}
if updated.Hooks[0].Events[0] != release.Hook_PRE_ROLLBACK {
t.Errorf("Expected event 0 to be pre rollback")
}
if updated.Hooks[0].Events[1] != release.Hook_POST_ROLLBACK {
t.Errorf("Expected event 1 to be post rollback")
}
if len(res.Release.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release)
}
if len(updated.Manifest) == 0 {
t.Errorf("Expected manifest in %v", res)
}
if !strings.Contains(updated.Manifest, "hello world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
}
func TestUninstallRelease(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
......
......@@ -131,6 +131,17 @@ func (h *Client) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, err
return h.opts.rpcGetVersion(rls.NewReleaseServiceClient(c), opts...)
}
// RollbackRelease rolls back a release to the previous version
func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcRollbackRelease(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
// ReleaseStatus returns the given release's status.
//
// Note: there aren't currently any supported StatusOptions,
......
......@@ -27,6 +27,7 @@ type Interface interface {
DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error)
ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error)
UpdateRelease(rlsName, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error)
RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error)
ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error)
GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error)
}
......@@ -53,6 +53,8 @@ type options struct {
statusReq rls.GetReleaseStatusRequest
// release get content options are applied directly to the get release content request
contentReq rls.GetReleaseContentRequest
// release rollback options are applied directly to the rollback release request
rollbackReq rls.RollbackReleaseRequest
}
// Host specifies the host address of the Tiller release server, (default = ":44134").
......@@ -124,17 +126,17 @@ func ValueOverrides(raw []byte) InstallOption {
}
}
// UpdateValueOverrides specifies a list of values to include when upgrading
func UpdateValueOverrides(raw []byte) UpdateOption {
// ReleaseName specifies the name of the release when installing.
func ReleaseName(name string) InstallOption {
return func(opts *options) {
opts.updateReq.Values = &cpb.Config{Raw: string(raw)}
opts.instReq.Name = name
}
}
// ReleaseName specifies the name of the release when installing.
func ReleaseName(name string) InstallOption {
// UpdateValueOverrides specifies a list of values to include when upgrading
func UpdateValueOverrides(raw []byte) UpdateOption {
return func(opts *options) {
opts.instReq.Name = name
opts.updateReq.Values = &cpb.Config{Raw: string(raw)}
}
}
......@@ -159,38 +161,52 @@ func DeletePurge(purge bool) DeleteOption {
}
}
// UpgradeDisableHooks will disable hooks for an upgrade operation.
func UpgradeDisableHooks(disable bool) UpdateOption {
// InstallDryRun will (if true) execute an installation as a dry run.
func InstallDryRun(dry bool) InstallOption {
return func(opts *options) {
opts.dryRun = dry
}
}
// InstallDisableHooks disables hooks during installation.
func InstallDisableHooks(disable bool) InstallOption {
return func(opts *options) {
opts.disableHooks = disable
}
}
// UpgradeDryRun will (if true) execute an upgrade as a dry run.
func UpgradeDryRun(dry bool) UpdateOption {
// InstallReuseName will (if true) instruct Tiller to re-use an existing name.
func InstallReuseName(reuse bool) InstallOption {
return func(opts *options) {
opts.dryRun = dry
opts.reuseName = reuse
}
}
// InstallDisableHooks disables hooks during installation.
func InstallDisableHooks(disable bool) InstallOption {
// RollbackDisableHooks will disable hooks for a rollback operation
func RollbackDisableHooks(disable bool) RollbackOption {
return func(opts *options) {
opts.disableHooks = disable
}
}
// InstallDryRun will (if true) execute an installation as a dry run.
func InstallDryRun(dry bool) InstallOption {
// RollbackDryRun will (if true) execute a rollback as a dry run.
func RollbackDryRun(dry bool) RollbackOption {
return func(opts *options) {
opts.dryRun = dry
}
}
// InstallReuseName will (if true) instruct Tiller to re-use an existing name.
func InstallReuseName(reuse bool) InstallOption {
// UpgradeDisableHooks will disable hooks for an upgrade operation.
func UpgradeDisableHooks(disable bool) UpdateOption {
return func(opts *options) {
opts.reuseName = reuse
opts.disableHooks = disable
}
}
// UpgradeDryRun will (if true) execute an upgrade as a dry run.
func UpgradeDryRun(dry bool) UpdateOption {
return func(opts *options) {
opts.dryRun = dry
}
}
......@@ -230,6 +246,11 @@ type VersionOption func(*options)
// the defaults used when running the `helm upgrade` command.
type UpdateOption func(*options)
// RollbackOption allows specififying various settings configurable
// by the helm client user for overriding the defaults used when
// running the `helm rollback` command.
type RollbackOption func(*options)
// RPC helpers defined on `options` type. Note: These actually execute the
// the corresponding tiller RPC. There is no particular reason why these
// are APIs are hung off `options`, they are internal to pkg/helm to remain
......@@ -303,6 +324,18 @@ func (o *options) rpcUpdateRelease(rlsName string, chr *cpb.Chart, rlc rls.Relea
return rlc.UpdateRelease(NewContext(), &o.updateReq)
}
// Executes tiller.UpdateRelease RPC.
func (o *options) rpcRollbackRelease(rlsName string, rlc rls.ReleaseServiceClient, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) {
for _, opt := range opts {
opt(o)
}
o.rollbackReq.DryRun = o.dryRun
o.rollbackReq.Name = rlsName
return rlc.RollbackRelease(context.TODO(), &o.rollbackReq)
}
// Executes tiller.GetReleaseStatus RPC.
func (o *options) rpcGetReleaseStatus(rlsName string, rlc rls.ReleaseServiceClient, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) {
for _, opt := range opts {
......
......@@ -170,6 +170,7 @@ func (c *Client) Update(namespace string, currentReader, targetReader io.Reader)
updateErrors := []string{}
err = target.Visit(func(info *resource.Info, err error) error {
targetInfos = append(targetInfos, info)
if err != nil {
return err
......
......@@ -38,13 +38,15 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Hook_Event int32
const (
Hook_UNKNOWN Hook_Event = 0
Hook_PRE_INSTALL Hook_Event = 1
Hook_POST_INSTALL Hook_Event = 2
Hook_PRE_DELETE Hook_Event = 3
Hook_POST_DELETE Hook_Event = 4
Hook_PRE_UPGRADE Hook_Event = 5
Hook_POST_UPGRADE Hook_Event = 6
Hook_UNKNOWN Hook_Event = 0
Hook_PRE_INSTALL Hook_Event = 1
Hook_POST_INSTALL Hook_Event = 2
Hook_PRE_DELETE Hook_Event = 3
Hook_POST_DELETE Hook_Event = 4
Hook_PRE_UPGRADE Hook_Event = 5
Hook_POST_UPGRADE Hook_Event = 6
Hook_PRE_ROLLBACK Hook_Event = 7
Hook_POST_ROLLBACK Hook_Event = 8
)
var Hook_Event_name = map[int32]string{
......@@ -55,15 +57,19 @@ var Hook_Event_name = map[int32]string{
4: "POST_DELETE",
5: "PRE_UPGRADE",
6: "POST_UPGRADE",
7: "PRE_ROLLBACK",
8: "POST_ROLLBACK",
}
var Hook_Event_value = map[string]int32{
"UNKNOWN": 0,
"PRE_INSTALL": 1,
"POST_INSTALL": 2,
"PRE_DELETE": 3,
"POST_DELETE": 4,
"PRE_UPGRADE": 5,
"POST_UPGRADE": 6,
"UNKNOWN": 0,
"PRE_INSTALL": 1,
"POST_INSTALL": 2,
"PRE_DELETE": 3,
"POST_DELETE": 4,
"PRE_UPGRADE": 5,
"POST_UPGRADE": 6,
"PRE_ROLLBACK": 7,
"POST_ROLLBACK": 8,
}
func (x Hook_Event) String() string {
......@@ -106,24 +112,26 @@ func init() {
func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 301 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x4f, 0x4f, 0xc2, 0x30,
0x18, 0xc6, 0x1d, 0x8c, 0x01, 0x2f, 0x04, 0x97, 0x5e, 0x6c, 0xb8, 0x48, 0x38, 0x71, 0xea, 0x0c,
0xc6, 0x0f, 0x80, 0xa1, 0x51, 0x23, 0x19, 0xa4, 0x8c, 0x98, 0x78, 0x21, 0x25, 0x16, 0x58, 0x60,
0xed, 0xb2, 0x75, 0x5e, 0xfc, 0x7a, 0x7e, 0x30, 0xd3, 0xee, 0x4f, 0xbc, 0xbd, 0xfb, 0xbd, 0xbf,
0x3d, 0x7d, 0x1f, 0xb8, 0x3b, 0xf3, 0x34, 0x0e, 0x32, 0x71, 0x15, 0x3c, 0x17, 0xc1, 0x59, 0xa9,
0x0b, 0x49, 0x33, 0xa5, 0x15, 0x1a, 0x9a, 0x05, 0xa9, 0x16, 0xe3, 0xfb, 0x93, 0x52, 0xa7, 0xab,
0x08, 0xec, 0xee, 0x50, 0x1c, 0x03, 0x1d, 0x27, 0x22, 0xd7, 0x3c, 0x49, 0x4b, 0x7d, 0xfa, 0xdb,
0x02, 0xf7, 0x55, 0xa9, 0x0b, 0x42, 0xe0, 0x4a, 0x9e, 0x08, 0xec, 0x4c, 0x9c, 0x59, 0x9f, 0xd9,
0xd9, 0xb0, 0x4b, 0x2c, 0xbf, 0x70, 0xab, 0x64, 0x66, 0x36, 0x2c, 0xe5, 0xfa, 0x8c, 0xdb, 0x25,
0x33, 0x33, 0x1a, 0x43, 0x2f, 0xe1, 0x32, 0x3e, 0x8a, 0x5c, 0x63, 0xd7, 0xf2, 0xe6, 0x1b, 0x3d,
0x80, 0x27, 0xbe, 0x85, 0xd4, 0x39, 0xee, 0x4c, 0xda, 0xb3, 0xd1, 0x1c, 0x93, 0xff, 0x07, 0x12,
0xf3, 0x36, 0xa1, 0x46, 0x60, 0x95, 0x87, 0x9e, 0xa0, 0x77, 0xe5, 0xb9, 0xde, 0x67, 0x85, 0xc4,
0xde, 0xc4, 0x99, 0x0d, 0xe6, 0x63, 0x52, 0xd6, 0x20, 0x75, 0x0d, 0x12, 0xd5, 0x35, 0x58, 0xd7,
0xb8, 0xac, 0x90, 0xd3, 0x1f, 0xe8, 0xd8, 0x1c, 0x34, 0x80, 0xee, 0x2e, 0x7c, 0x0f, 0xd7, 0x1f,
0xa1, 0x7f, 0x83, 0x6e, 0x61, 0xb0, 0x61, 0x74, 0xff, 0x16, 0x6e, 0xa3, 0xc5, 0x6a, 0xe5, 0x3b,
0xc8, 0x87, 0xe1, 0x66, 0xbd, 0x8d, 0x1a, 0xd2, 0x42, 0x23, 0x00, 0xa3, 0x2c, 0xe9, 0x8a, 0x46,
0xd4, 0x6f, 0xdb, 0x5f, 0x8c, 0x51, 0x01, 0xb7, 0xce, 0xd8, 0x6d, 0x5e, 0xd8, 0x62, 0x49, 0xfd,
0x4e, 0x93, 0x51, 0x13, 0xef, 0xb9, 0xff, 0xd9, 0xad, 0x1a, 0x1d, 0x3c, 0x7b, 0xe4, 0xe3, 0x5f,
0x00, 0x00, 0x00, 0xff, 0xff, 0x16, 0x64, 0x61, 0x76, 0xa2, 0x01, 0x00, 0x00,
// 321 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x4d, 0x6e, 0xea, 0x30,
0x14, 0x85, 0x5f, 0x20, 0x24, 0x70, 0xe1, 0xd1, 0xd4, 0x93, 0x5a, 0x4c, 0x8a, 0x18, 0x31, 0x72,
0x2a, 0xaa, 0x2e, 0x00, 0x8a, 0xd5, 0x56, 0x44, 0x01, 0x99, 0xa0, 0x4a, 0x9d, 0x20, 0xa3, 0x1a,
0x88, 0x20, 0x71, 0x44, 0x4c, 0xd7, 0xd3, 0xf5, 0x75, 0x15, 0x95, 0x9d, 0x1f, 0x75, 0x76, 0xfd,
0xdd, 0xcf, 0xc7, 0x3e, 0x70, 0x77, 0xe4, 0x59, 0xec, 0x5f, 0xc4, 0x59, 0xf0, 0x5c, 0xf8, 0x47,
0x29, 0x4f, 0x24, 0xbb, 0x48, 0x25, 0x51, 0x4f, 0x2f, 0x48, 0xb9, 0x18, 0xdc, 0x1f, 0xa4, 0x3c,
0x9c, 0x85, 0x6f, 0x76, 0xbb, 0xeb, 0xde, 0x57, 0x71, 0x22, 0x72, 0xc5, 0x93, 0xac, 0xd0, 0x47,
0x3f, 0x0d, 0xb0, 0x5f, 0xa5, 0x3c, 0x21, 0x04, 0x76, 0xca, 0x13, 0x81, 0xad, 0xa1, 0x35, 0xee,
0x30, 0x33, 0x6b, 0x76, 0x8a, 0xd3, 0x4f, 0xdc, 0x28, 0x98, 0x9e, 0x35, 0xcb, 0xb8, 0x3a, 0xe2,
0x66, 0xc1, 0xf4, 0x8c, 0x06, 0xd0, 0x4e, 0x78, 0x1a, 0xef, 0x45, 0xae, 0xb0, 0x6d, 0x78, 0x7d,
0x46, 0x0f, 0xe0, 0x88, 0x2f, 0x91, 0xaa, 0x1c, 0xb7, 0x86, 0xcd, 0x71, 0x7f, 0x82, 0xc9, 0xdf,
0x0f, 0x12, 0xfd, 0x36, 0xa1, 0x5a, 0x60, 0xa5, 0x87, 0x9e, 0xa0, 0x7d, 0xe6, 0xb9, 0xda, 0x5e,
0xae, 0x29, 0x76, 0x86, 0xd6, 0xb8, 0x3b, 0x19, 0x90, 0xa2, 0x06, 0xa9, 0x6a, 0x90, 0xa8, 0xaa,
0xc1, 0x5c, 0xed, 0xb2, 0x6b, 0x3a, 0xfa, 0xb6, 0xa0, 0x65, 0x82, 0x50, 0x17, 0xdc, 0x4d, 0xb8,
0x08, 0x97, 0xef, 0xa1, 0xf7, 0x0f, 0xdd, 0x40, 0x77, 0xc5, 0xe8, 0xf6, 0x2d, 0x5c, 0x47, 0xd3,
0x20, 0xf0, 0x2c, 0xe4, 0x41, 0x6f, 0xb5, 0x5c, 0x47, 0x35, 0x69, 0xa0, 0x3e, 0x80, 0x56, 0xe6,
0x34, 0xa0, 0x11, 0xf5, 0x9a, 0xe6, 0x8a, 0x36, 0x4a, 0x60, 0x57, 0x19, 0x9b, 0xd5, 0x0b, 0x9b,
0xce, 0xa9, 0xd7, 0xaa, 0x33, 0x2a, 0xe2, 0x18, 0xc2, 0xe8, 0x96, 0x2d, 0x83, 0x60, 0x36, 0x7d,
0x5e, 0x78, 0x2e, 0xba, 0x85, 0xff, 0xc6, 0xa9, 0x51, 0x7b, 0xd6, 0xf9, 0x70, 0xcb, 0xde, 0x3b,
0xc7, 0x54, 0x79, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x2e, 0x6f, 0xbd, 0xc8, 0x01, 0x00,
0x00,
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment