From 2282d4b3897f886c7d84891dfe943e5c492ec6fd Mon Sep 17 00:00:00 2001 From: FingerLeader <43462394+FingerLeader@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:41:20 +0800 Subject: [PATCH] add reset branch (#239) --------- Signed-off-by: FingerLeader Co-authored-by: Xiang Zhang --- .../generate_doc/ticloud_serverless_branch.md | 1 + .../ticloud_serverless_branch_reset.md | 39 + internal/cli/serverless/branch/branch.go | 1 + internal/cli/serverless/branch/reset.go | 257 +++++ internal/cli/serverless/branch/reset_test.go | 115 +++ internal/mock/api_client.go | 30 + internal/service/cloud/api_client.go | 7 + .../v1beta1/serverless/branch.swagger.json | 942 +++++++++--------- .../v1beta1/serverless/branch/README.md | 1 + .../serverless/branch/api/openapi.yaml | 34 + .../serverless/branch/api_branch_service.go | 114 +++ .../serverless/branch/model_branch_state.go | 4 +- 12 files changed, 1093 insertions(+), 452 deletions(-) create mode 100644 docs/generate_doc/ticloud_serverless_branch_reset.md create mode 100644 internal/cli/serverless/branch/reset.go create mode 100644 internal/cli/serverless/branch/reset_test.go diff --git a/docs/generate_doc/ticloud_serverless_branch.md b/docs/generate_doc/ticloud_serverless_branch.md index 760e073a..1297a79e 100644 --- a/docs/generate_doc/ticloud_serverless_branch.md +++ b/docs/generate_doc/ticloud_serverless_branch.md @@ -23,5 +23,6 @@ Manage TiDB Cloud Serverless branches * [ticloud serverless branch delete](ticloud_serverless_branch_delete.md) - Delete a branch * [ticloud serverless branch describe](ticloud_serverless_branch_describe.md) - Describe a branch * [ticloud serverless branch list](ticloud_serverless_branch_list.md) - List branches +* [ticloud serverless branch reset](ticloud_serverless_branch_reset.md) - Reset a branch to its parent's latest state * [ticloud serverless branch shell](ticloud_serverless_branch_shell.md) - Connect to a branch diff --git a/docs/generate_doc/ticloud_serverless_branch_reset.md b/docs/generate_doc/ticloud_serverless_branch_reset.md new file mode 100644 index 00000000..d2376443 --- /dev/null +++ b/docs/generate_doc/ticloud_serverless_branch_reset.md @@ -0,0 +1,39 @@ +## ticloud serverless branch reset + +Reset a branch to its parent's latest state + +``` +ticloud serverless branch reset [flags] +``` + +### Examples + +``` + Reset a branch in interactive mode: + $ ticloud serverless branch reset + + Reset a branch in non-interactive mode: + $ ticloud serverless branch reset -c -b +``` + +### Options + +``` + -b, --branch-id string The ID of the branch to be reset. + -c, --cluster-id string The cluster ID of the branch to be reset. + --force Reset a branch without confirmation. + -h, --help help for reset +``` + +### Options inherited from parent commands + +``` + -D, --debug Enable debug mode + --no-color Disable color output + -P, --profile string Profile to use from your configuration file +``` + +### SEE ALSO + +* [ticloud serverless branch](ticloud_serverless_branch.md) - Manage TiDB Cloud Serverless branches + diff --git a/internal/cli/serverless/branch/branch.go b/internal/cli/serverless/branch/branch.go index e6668604..543c767c 100644 --- a/internal/cli/serverless/branch/branch.go +++ b/internal/cli/serverless/branch/branch.go @@ -30,6 +30,7 @@ func Cmd(h *internal.Helper) *cobra.Command { branchCmd.AddCommand(ListCmd(h)) branchCmd.AddCommand(DescribeCmd(h)) branchCmd.AddCommand(DeleteCmd(h)) + branchCmd.AddCommand(ResetCmd(h)) branchCmd.AddCommand(ShellCmd(h)) return branchCmd } diff --git a/internal/cli/serverless/branch/reset.go b/internal/cli/serverless/branch/reset.go new file mode 100644 index 00000000..ce758bb3 --- /dev/null +++ b/internal/cli/serverless/branch/reset.go @@ -0,0 +1,257 @@ +// Copyright 2024 PingCAP, Inc. +// +// 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 branch + +import ( + "context" + "fmt" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/tidbcloud/tidbcloud-cli/internal" + "github.com/tidbcloud/tidbcloud-cli/internal/config" + "github.com/tidbcloud/tidbcloud-cli/internal/flag" + "github.com/tidbcloud/tidbcloud-cli/internal/service/cloud" + "github.com/tidbcloud/tidbcloud-cli/internal/ui" + "github.com/tidbcloud/tidbcloud-cli/internal/util" + "github.com/tidbcloud/tidbcloud-cli/pkg/tidbcloud/v1beta1/serverless/branch" + + "github.com/AlecAivazis/survey/v2" + "github.com/AlecAivazis/survey/v2/terminal" + "github.com/fatih/color" + "github.com/juju/errors" + "github.com/spf13/cobra" +) + +type ResetOpts struct { + interactive bool +} + +func (c ResetOpts) NonInteractiveFlags() []string { + return []string{ + flag.ClusterID, + flag.BranchID, + } +} + +func (c *ResetOpts) MarkInteractive(cmd *cobra.Command) error { + flags := c.NonInteractiveFlags() + for _, fn := range flags { + f := cmd.Flags().Lookup(fn) + if f != nil && f.Changed { + c.interactive = false + break + } + } + // Mark required flags + if !c.interactive { + for _, fn := range flags { + err := cmd.MarkFlagRequired(fn) + if err != nil { + return err + } + } + } + return nil +} + +func ResetCmd(h *internal.Helper) *cobra.Command { + opts := ResetOpts{ + interactive: true, + } + + var force bool + var resetCmd = &cobra.Command{ + Use: "reset", + Short: "Reset a branch to its parent's latest state", + Args: cobra.NoArgs, + Example: fmt.Sprintf(` Reset a branch in interactive mode: + $ %[1]s serverless branch reset + + Reset a branch in non-interactive mode: + $ %[1]s serverless branch reset -c -b `, config.CliName), + PreRunE: func(cmd *cobra.Command, args []string) error { + err := opts.MarkInteractive(cmd) + if err != nil { + return errors.Trace(err) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + d, err := h.Client() + if err != nil { + return err + } + ctx := cmd.Context() + + var clusterID string + var branchID string + if opts.interactive { + if !h.IOStreams.CanPrompt { + return errors.New("The terminal doesn't support interactive mode, please use non-interactive mode") + } + + // interactive mode + project, err := cloud.GetSelectedProject(ctx, h.QueryPageSize, d) + if err != nil { + return err + } + cluster, err := cloud.GetSelectedCluster(ctx, project.ID, h.QueryPageSize, d) + if err != nil { + return err + } + clusterID = cluster.ID + + branch, err := cloud.GetSelectedBranch(ctx, clusterID, h.QueryPageSize, d) + if err != nil { + return err + } + branchID = branch.ID + } else { + // non-interactive mode, get values from flags + bID, err := cmd.Flags().GetString(flag.BranchID) + if err != nil { + return errors.Trace(err) + } + + cID, err := cmd.Flags().GetString(flag.ClusterID) + if err != nil { + return errors.Trace(err) + } + branchID = bID + clusterID = cID + } + + if !force { + if !h.IOStreams.CanPrompt { + return fmt.Errorf("the terminal doesn't support prompt, please run with --force to reset the branch") + } + + confirmationMessage := fmt.Sprintf("%s %s %s", color.BlueString("Please type"), color.HiBlueString(confirmed), color.BlueString("to confirm:")) + + prompt := &survey.Input{ + Message: confirmationMessage, + } + + var userInput string + err := survey.AskOne(prompt, &userInput) + if err != nil { + if err == terminal.InterruptErr { + return util.InterruptError + } else { + return err + } + } + + if userInput != confirmed { + return errors.New("incorrect confirm string entered, skipping branch reset") + } + } + + // print success for reset branch is a sync operation + if h.IOStreams.CanPrompt { + err := ResetAndSpinnerWait(ctx, h, d, clusterID, branchID) + if err != nil { + return errors.Trace(err) + } + } else { + err := ResetAndWaitReady(ctx, h, d, clusterID, branchID) + if err != nil { + return err + } + } + + return nil + }, + } + + resetCmd.Flags().BoolVar(&force, flag.Force, false, "Reset a branch without confirmation.") + resetCmd.Flags().StringP(flag.BranchID, flag.BranchIDShort, "", "The ID of the branch to be reset.") + resetCmd.Flags().StringP(flag.ClusterID, flag.ClusterIDShort, "", "The cluster ID of the branch to be reset.") + + return resetCmd +} + +func ResetAndWaitReady(ctx context.Context, h *internal.Helper, d cloud.TiDBCloudClient, clusterID string, branchID string) error { + _, err := d.ResetBranch(ctx, clusterID, branchID) + if err != nil { + return errors.Trace(err) + } + + fmt.Fprintln(h.IOStreams.Out, "... Waiting for branch to be ready") + ticker := time.NewTicker(WaitInterval) + defer ticker.Stop() + timer := time.After(WaitTimeout) + for { + select { + case <-timer: + return errors.New(fmt.Sprintf("Timeout waiting for branch %s to be ready, please check status on dashboard.", branchID)) + case <-ticker.C: + b, err := d.GetBranch(ctx, clusterID, branchID) + if err != nil { + return errors.Trace(err) + } + if *b.State == branch.BRANCHSTATE_ACTIVE { + fmt.Fprint(h.IOStreams.Out, color.GreenString("Branch %s is ready.", branchID)) + return nil + } + } + } +} + +func ResetAndSpinnerWait(ctx context.Context, h *internal.Helper, d cloud.TiDBCloudClient, clusterID string, branchID string) error { + // use spinner to indicate that the cluster is resetting + task := func() tea.Msg { + _, err := d.ResetBranch(ctx, clusterID, branchID) + if err != nil { + return errors.Trace(err) + } + + ticker := time.NewTicker(WaitInterval) + defer ticker.Stop() + timer := time.After(WaitTimeout) + for { + select { + case <-timer: + return ui.Result(fmt.Sprintf("Timeout waiting for branch %s to be ready, please check status on dashboard.", branchID)) + case <-ticker.C: + b, err := d.GetBranch(ctx, clusterID, branchID) + if err != nil { + return errors.Trace(err) + } + if *b.State == branch.BRANCHSTATE_ACTIVE { + return ui.Result(fmt.Sprintf("Branch %s is ready.", branchID)) + } + case <-ctx.Done(): + return util.InterruptError + } + } + } + + p := tea.NewProgram(ui.InitialSpinnerModel(task, "Waiting for branch to be ready")) + resetModel, err := p.Run() + if err != nil { + return errors.Trace(err) + } + if m, _ := resetModel.(ui.SpinnerModel); m.Interrupted { + return util.InterruptError + } + if m, _ := resetModel.(ui.SpinnerModel); m.Err != nil { + return m.Err + } else { + fmt.Fprintln(h.IOStreams.Out, color.GreenString(m.Output)) + } + return nil +} diff --git a/internal/cli/serverless/branch/reset_test.go b/internal/cli/serverless/branch/reset_test.go new file mode 100644 index 00000000..9a4815f0 --- /dev/null +++ b/internal/cli/serverless/branch/reset_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 PingCAP, Inc. +// +// 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 branch + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/tidbcloud/tidbcloud-cli/internal" + "github.com/tidbcloud/tidbcloud-cli/internal/iostream" + "github.com/tidbcloud/tidbcloud-cli/internal/mock" + "github.com/tidbcloud/tidbcloud-cli/internal/service/cloud" + "github.com/tidbcloud/tidbcloud-cli/pkg/tidbcloud/v1beta1/serverless/branch" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type ResetBranchSuite struct { + suite.Suite + h *internal.Helper + mockClient *mock.TiDBCloudClient +} + +func (suite *ResetBranchSuite) SetupTest() { + if err := os.Setenv("NO_COLOR", "true"); err != nil { + suite.T().Error(err) + } + + var pageSize int64 = 10 + suite.mockClient = new(mock.TiDBCloudClient) + suite.h = &internal.Helper{ + Client: func() (cloud.TiDBCloudClient, error) { + return suite.mockClient, nil + }, + QueryPageSize: pageSize, + IOStreams: iostream.Test(), + } +} + +func (suite *ResetBranchSuite) TestResetBranchArgs() { + assert := require.New(suite.T()) + ctx := context.Background() + + clusterID := "12345" + branchID := "12345" + suite.mockClient.On("ResetBranch", ctx, clusterID, branchID).Return(&branch.Branch{}, nil) + + body := &branch.Branch{} + err := json.Unmarshal([]byte(getBranchResultStr), body) + assert.Nil(err) + suite.mockClient.On("GetBranch", ctx, clusterID, branchID).Return(body, nil) + + tests := []struct { + name string + args []string + err error + stdoutString string + stderrString string + }{ + { + name: "reset branch success", + args: []string{"--cluster-id", clusterID, "--branch-id", branchID, "--force"}, + stdoutString: fmt.Sprintf("... Waiting for branch to be ready\nBranch %s is ready.", branchID), + }, + { + name: "reset branch without force", + args: []string{"--cluster-id", clusterID, "--branch-id", branchID}, + err: fmt.Errorf("the terminal doesn't support prompt, please run with --force to reset the branch"), + }, + { + name: "reset branch without required branch id", + args: []string{"-c", clusterID, "--force"}, + err: fmt.Errorf("required flag(s) \"branch-id\" not set"), + }, + } + + for _, tt := range tests { + suite.T().Run(tt.name, func(t *testing.T) { + cmd := ResetCmd(suite.h) + cmd.SetContext(ctx) + suite.h.IOStreams.Out.(*bytes.Buffer).Reset() + suite.h.IOStreams.Err.(*bytes.Buffer).Reset() + cmd.SetArgs(tt.args) + err := cmd.Execute() + assert.Equal(tt.err, err) + + assert.Equal(tt.stdoutString, suite.h.IOStreams.Out.(*bytes.Buffer).String()) + assert.Equal(tt.stderrString, suite.h.IOStreams.Err.(*bytes.Buffer).String()) + if tt.err == nil { + suite.mockClient.AssertExpectations(suite.T()) + } + }) + } +} + +func TestResetBranchSuite(t *testing.T) { + suite.Run(t, new(ResetBranchSuite)) +} diff --git a/internal/mock/api_client.go b/internal/mock/api_client.go index 8c27d31b..07be258e 100644 --- a/internal/mock/api_client.go +++ b/internal/mock/api_client.go @@ -950,6 +950,36 @@ func (_m *TiDBCloudClient) PartialUpdateCluster(ctx context.Context, clusterId s return r0, r1 } +// ResetBranch provides a mock function with given fields: ctx, clusterId, branchId +func (_m *TiDBCloudClient) ResetBranch(ctx context.Context, clusterId string, branchId string) (*branch.Branch, error) { + ret := _m.Called(ctx, clusterId, branchId) + + if len(ret) == 0 { + panic("no return value specified for ResetBranch") + } + + var r0 *branch.Branch + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*branch.Branch, error)); ok { + return rf(ctx, clusterId, branchId) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *branch.Branch); ok { + r0 = rf(ctx, clusterId, branchId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*branch.Branch) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, clusterId, branchId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Restore provides a mock function with given fields: ctx, body func (_m *TiDBCloudClient) Restore(ctx context.Context, body *br.V1beta1RestoreRequest) (*br.V1beta1RestoreResponse, error) { ret := _m.Called(ctx, body) diff --git a/internal/service/cloud/api_client.go b/internal/service/cloud/api_client.go index b802ee23..4da03555 100644 --- a/internal/service/cloud/api_client.go +++ b/internal/service/cloud/api_client.go @@ -74,6 +74,8 @@ type TiDBCloudClient interface { DeleteBranch(ctx context.Context, clusterId string, branchId string) (*branch.Branch, error) + ResetBranch(ctx context.Context, clusterId string, branchId string) (*branch.Branch, error) + Chat(ctx context.Context, chatInfo *pingchat.PingchatChatInfo) (*pingchat.PingchatChatResponse, error) DeleteBackup(ctx context.Context, backupId string) (*br.V1beta1Backup, error) @@ -289,6 +291,11 @@ func (d *ClientDelegate) DeleteBranch(ctx context.Context, clusterId string, bra return b, parseError(err, h) } +func (d *ClientDelegate) ResetBranch(ctx context.Context, clusterId string, branchId string) (*branch.Branch, error) { + b, h, err := d.bc.BranchServiceAPI.BranchServiceResetBranch(ctx, clusterId, branchId).Execute() + return b, parseError(err, h) +} + func (d *ClientDelegate) Chat(ctx context.Context, chatInfo *pingchat.PingchatChatInfo) (*pingchat.PingchatChatResponse, error) { r := d.pc.PingChatServiceAPI.Chat(ctx) if chatInfo != nil { diff --git a/pkg/tidbcloud/v1beta1/serverless/branch.swagger.json b/pkg/tidbcloud/v1beta1/serverless/branch.swagger.json index cbcd07ef..68e04fa1 100644 --- a/pkg/tidbcloud/v1beta1/serverless/branch.swagger.json +++ b/pkg/tidbcloud/v1beta1/serverless/branch.swagger.json @@ -1,501 +1,541 @@ { - "swagger": "2.0", - "info": { - "title": "TiDB Cloud Serverless Open API", - "description": "TiDB Cloud Serverless Open API", - "version": "v1beta1" - }, - "tags": [ - { - "name": "BranchService" - } - ], - "host": "serverless.tidbapi.com", - "schemes": [ - "https" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": { - "/v1beta1/clusters/{clusterId}/branches": { - "get": { - "summary": "Lists information about branches.", - "operationId": "BranchService_ListBranches", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/ListBranchesResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } + "swagger": "2.0", + "info": { + "title": "TiDB Cloud Serverless Open API", + "description": "TiDB Cloud Serverless Open API", + "version": "v1beta1" + }, + "tags": [ + { + "name": "BranchService" + } + ], + "host": "serverless.tidbapi.com", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1beta1/clusters/{clusterId}/branches": { + "get": { + "summary": "Lists information about branches.", + "operationId": "BranchService_ListBranches", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/ListBranchesResponse" } }, - "parameters": [ - { - "name": "clusterId", - "description": "Required. The ID of the project to which the clusters belong.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "pageSize", - "description": "Optional. Requested page size. Server may return fewer items than\nrequested. If unspecified, server will pick an appropriate default.", - "in": "query", - "required": false, - "type": "integer", - "format": "int32" - }, - { - "name": "pageToken", - "description": "Optional. A token identifying a page of results the server should return.", - "in": "query", - "required": false, - "type": "string" + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" } - ], - "tags": [ - "BranchService" - ] + } }, - "post": { - "summary": "Creates a branch.", - "operationId": "BranchService_CreateBranch", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/Branch" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } + "parameters": [ + { + "name": "clusterId", + "description": "Required. The ID of the project to which the clusters belong.", + "in": "path", + "required": true, + "type": "string" }, - "parameters": [ - { - "name": "clusterId", - "description": "Required. The cluster ID of the branch", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "branch", - "description": "Required. The resource being created", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/Branch" - } - } - ], - "tags": [ - "BranchService" - ] - } + { + "name": "pageSize", + "description": "Optional. Requested page size. Server may return fewer items than\nrequested. If unspecified, server will pick an appropriate default.", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pageToken", + "description": "Optional. A token identifying a page of results the server should return.", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "BranchService" + ] }, - "/v1beta1/clusters/{clusterId}/branches/{branchId}": { - "get": { - "summary": "Gets information about a branch.", - "operationId": "BranchService_GetBranch", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/Branch" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } + "post": { + "summary": "Creates a branch.", + "operationId": "BranchService_CreateBranch", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/Branch" } }, - "parameters": [ - { - "name": "clusterId", - "description": "Required. The cluster ID of the branch", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "branchId", - "description": "Required. The branch ID", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "view", - "description": "Optional. The view of the branch to return. Defaults to FULL\n\n - BASIC: Basic response contains basic information for a branch.\n - FULL: FULL response contains all detailed information for a branch.", - "in": "query", - "required": false, - "type": "string", - "enum": [ - "BASIC", - "FULL" - ] + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" } - ], - "tags": [ - "BranchService" - ] + } }, - "delete": { - "summary": "Deletes a branch.", - "operationId": "BranchService_DeleteBranch", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/Branch" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } + "parameters": [ + { + "name": "clusterId", + "description": "Required. The cluster ID of the branch", + "in": "path", + "required": true, + "type": "string" }, - "parameters": [ - { - "name": "clusterId", - "description": "Required. The cluster ID of the branch", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "branchId", - "description": "Required. The branch ID", - "in": "path", - "required": true, - "type": "string" + { + "name": "branch", + "description": "Required. The resource being created", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Branch" } - ], - "tags": [ - "BranchService" - ] - } + } + ], + "tags": [ + "BranchService" + ] } }, - "definitions": { - "Any": { - "type": "object", - "properties": { - "@type": { - "type": "string" + "/v1beta1/clusters/{clusterId}/branches/{branchId}": { + "get": { + "summary": "Gets information about a branch.", + "operationId": "BranchService_GetBranch", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/Branch" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } } }, - "additionalProperties": {} - }, - "Branch": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Output Only. The name of the resource.", - "readOnly": true - }, - "branchId": { - "type": "string", - "description": "Output only. The system-generated ID of the resource.", - "readOnly": true - }, - "displayName": { - "type": "string", - "description": "Required. User-settable and human-readable display name for the branch." - }, - "clusterId": { - "type": "string", - "description": "Output only. The cluster ID of this branch.", - "readOnly": true - }, - "parentId": { - "type": "string", - "description": "Optional. The parent ID of this branch." - }, - "createdBy": { - "type": "string", - "description": "Output only. The creator of the branch.", - "readOnly": true - }, - "state": { - "description": "Output only. The state of this branch.", - "readOnly": true, - "allOf": [ - { - "$ref": "#/definitions/Branch.State" - } - ] + "parameters": [ + { + "name": "clusterId", + "description": "Required. The cluster ID of the branch", + "in": "path", + "required": true, + "type": "string" }, - "endpoints": { - "description": "Optional. The endpoints of this branch.", - "allOf": [ - { - "$ref": "#/definitions/Branch.Endpoints" - } - ] + { + "name": "branchId", + "description": "Required. The branch ID", + "in": "path", + "required": true, + "type": "string" }, - "userPrefix": { + { + "name": "view", + "description": "Optional. The view of the branch to return. Defaults to FULL\n\n - BASIC: Basic response contains basic information for a branch.\n - FULL: FULL response contains all detailed information for a branch.", + "in": "query", + "required": false, "type": "string", - "x-nullable": true, - "description": "Output only. User name prefix of this branch. For each TiDB Serverless branch,\nTiDB Cloud generates a unique prefix to distinguish it from other branches.\nWhenever you use or set a database user name, you must include the prefix in the user name.", - "readOnly": true - }, - "usage": { - "description": "Output only. Usage metrics of this branch. Only display in FULL view.", - "readOnly": true, - "allOf": [ - { - "$ref": "#/definitions/Branch.Usage" - } + "enum": [ + "BASIC", + "FULL" ] - }, - "createTime": { - "type": "string", - "format": "date-time", - "title": "Output only. Create timestamp", - "readOnly": true - }, - "updateTime": { - "type": "string", - "format": "date-time", - "title": "Output only. Update timestamp", - "readOnly": true - }, - "annotations": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "Optional. The annotations of this branch.." - }, - "parentDisplayName": { - "type": "string", - "description": "Output only. The parent display name of this branch.", - "readOnly": true - }, - "parentTimestamp": { - "type": "string", - "format": "date-time", - "x-nullable": true, - "description": "Optional. The point in time on the parent branch the branch will be created from." } - }, - "title": "Message for branch", - "required": [ - "displayName" + ], + "tags": [ + "BranchService" ] }, - "Branch.Endpoints": { - "type": "object", - "properties": { - "public": { - "description": "Optional. Public Endpoint for this branch.", - "allOf": [ - { - "$ref": "#/definitions/Branch.Endpoints.Public" - } - ] + "delete": { + "summary": "Deletes a branch.", + "operationId": "BranchService_DeleteBranch", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/Branch" + } }, - "private": { - "description": "Output only. Private Endpoint for this branch.", - "readOnly": true, - "allOf": [ - { - "$ref": "#/definitions/Branch.Endpoints.Private" - } - ] + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } } }, - "description": "Message for the Endpoints for this branch." - }, - "Branch.Endpoints.Private": { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "Output Only. Host Name of Public Endpoint.", - "readOnly": true - }, - "port": { - "type": "integer", - "format": "int32", - "description": "Output Only. Port of Public Endpoint.", - "readOnly": true + "parameters": [ + { + "name": "clusterId", + "description": "Required. The cluster ID of the branch", + "in": "path", + "required": true, + "type": "string" }, - "aws": { - "title": "Message for AWS", - "readOnly": true, - "allOf": [ - { - "$ref": "#/definitions/Branch.Endpoints.Private.AWS" - } - ] + { + "name": "branchId", + "description": "Required. The branch ID", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "BranchService" + ] + } + }, + "/v1beta1/clusters/{clusterId}/branches/{branchId}:reset": { + "post": { + "summary": "Resets a branch.", + "operationId": "BranchService_ResetBranch", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/Branch" + } }, - "gcp": { - "title": "Message for GCP", - "readOnly": true, - "allOf": [ - { - "$ref": "#/definitions/Branch.Endpoints.Private.GCP" - } - ] + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } } }, - "description": "Message for Private Endpoint for this branch." - }, - "Branch.Endpoints.Private.AWS": { - "type": "object", - "properties": { - "serviceName": { - "type": "string", - "description": "Output Only. Service Name for Private Link Service.", - "readOnly": true + "parameters": [ + { + "name": "clusterId", + "description": "Required. The cluster ID of the branch", + "in": "path", + "required": true, + "type": "string" }, - "availabilityZone": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Output Only. Availability Zone for Private Link Service.", - "readOnly": true + { + "name": "branchId", + "description": "Required. The branch ID", + "in": "path", + "required": true, + "type": "string" } - }, - "description": "Message for AWS Private Link Service." + ], + "tags": [ + "BranchService" + ] + } + } + }, + "definitions": { + "Any": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } }, - "Branch.Endpoints.Private.GCP": { - "type": "object", - "properties": { - "serviceAttachmentName": { - "type": "string", - "description": "Output Only. Target Service Account for Private Link Service.", - "readOnly": true - } + "additionalProperties": {} + }, + "Branch": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Output Only. The name of the resource.", + "readOnly": true }, - "description": "Message for GCP Private Link Service." - }, - "Branch.Endpoints.Public": { - "type": "object", - "properties": { - "host": { - "type": "string", - "title": "Output Only. Host name of Public Endpoint", - "readOnly": true - }, - "port": { - "type": "integer", - "format": "int32", - "title": "Output Only. Port of Public Endpoint", - "readOnly": true + "branchId": { + "type": "string", + "description": "Output only. The system-generated ID of the resource.", + "readOnly": true + }, + "displayName": { + "type": "string", + "description": "Required. User-settable and human-readable display name for the branch." + }, + "clusterId": { + "type": "string", + "description": "Output only. The cluster ID of this branch.", + "readOnly": true + }, + "parentId": { + "type": "string", + "description": "Optional. The parent ID of this branch." + }, + "createdBy": { + "type": "string", + "description": "Output only. The creator of the branch.", + "readOnly": true + }, + "state": { + "description": "Output only. The state of this branch.", + "readOnly": true, + "allOf": [ + { + "$ref": "#/definitions/Branch.State" + } + ] + }, + "endpoints": { + "description": "Optional. The endpoints of this branch.", + "allOf": [ + { + "$ref": "#/definitions/Branch.Endpoints" + } + ] + }, + "userPrefix": { + "type": "string", + "x-nullable": true, + "description": "Output only. User name prefix of this branch. For each TiDB Serverless branch,\nTiDB Cloud generates a unique prefix to distinguish it from other branches.\nWhenever you use or set a database user name, you must include the prefix in the user name.", + "readOnly": true + }, + "usage": { + "description": "Output only. Usage metrics of this branch. Only display in FULL view.", + "readOnly": true, + "allOf": [ + { + "$ref": "#/definitions/Branch.Usage" + } + ] + }, + "createTime": { + "type": "string", + "format": "date-time", + "title": "Output only. Create timestamp", + "readOnly": true + }, + "updateTime": { + "type": "string", + "format": "date-time", + "title": "Output only. Update timestamp", + "readOnly": true + }, + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" }, - "disabled": { - "type": "boolean", - "title": "Optional. Disable Public Endpoint" - } + "description": "Optional. The annotations of this branch.." }, - "description": "Message for Public Endpoint for this branch." + "parentDisplayName": { + "type": "string", + "description": "Output only. The parent display name of this branch.", + "readOnly": true + }, + "parentTimestamp": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "description": "Optional. The point in time on the parent branch the branch will be created from." + } }, - "Branch.State": { - "type": "string", - "enum": [ - "CREATING", - "ACTIVE", - "DELETED", - "MAINTENANCE" - ], - "description": "Output Only. Branch State.\n\n - CREATING: The branch is being created.\n - ACTIVE: The branch is active and running.\n - DELETED: The branch is being deleted.\n - MAINTENANCE: The branch is under maintenance." + "title": "Message for branch", + "required": [ + "displayName" + ] + }, + "Branch.Endpoints": { + "type": "object", + "properties": { + "public": { + "description": "Optional. Public Endpoint for this branch.", + "allOf": [ + { + "$ref": "#/definitions/Branch.Endpoints.Public" + } + ] + }, + "private": { + "description": "Output only. Private Endpoint for this branch.", + "readOnly": true, + "allOf": [ + { + "$ref": "#/definitions/Branch.Endpoints.Private" + } + ] + } }, - "Branch.Usage": { - "type": "object", - "properties": { - "requestUnit": { - "type": "string", - "format": "int64", - "description": "Output Only. The latest value of Request Unit Metric for this cluster.", - "readOnly": true - }, - "rowStorage": { - "type": "number", - "format": "double", - "description": "Output Only. The latest value of Row Storage Metric for this cluster.", - "readOnly": true + "description": "Message for the Endpoints for this branch." + }, + "Branch.Endpoints.Private": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "Output Only. Host Name of Public Endpoint.", + "readOnly": true + }, + "port": { + "type": "integer", + "format": "int32", + "description": "Output Only. Port of Public Endpoint.", + "readOnly": true + }, + "aws": { + "title": "Message for AWS", + "readOnly": true, + "allOf": [ + { + "$ref": "#/definitions/Branch.Endpoints.Private.AWS" + } + ] + }, + "gcp": { + "title": "Message for GCP", + "readOnly": true, + "allOf": [ + { + "$ref": "#/definitions/Branch.Endpoints.Private.GCP" + } + ] + } + }, + "description": "Message for Private Endpoint for this branch." + }, + "Branch.Endpoints.Private.AWS": { + "type": "object", + "properties": { + "serviceName": { + "type": "string", + "description": "Output Only. Service Name for Private Link Service.", + "readOnly": true + }, + "availabilityZone": { + "type": "array", + "items": { + "type": "string" }, - "columnarStorage": { - "type": "number", - "format": "double", - "description": "Output Only. The latest value of Columnar Storage Metric for this cluster.", - "readOnly": true - } + "description": "Output Only. Availability Zone for Private Link Service.", + "readOnly": true + } + }, + "description": "Message for AWS Private Link Service." + }, + "Branch.Endpoints.Private.GCP": { + "type": "object", + "properties": { + "serviceAttachmentName": { + "type": "string", + "description": "Output Only. Target Service Account for Private Link Service.", + "readOnly": true + } + }, + "description": "Message for GCP Private Link Service." + }, + "Branch.Endpoints.Public": { + "type": "object", + "properties": { + "host": { + "type": "string", + "title": "Output Only. Host name of Public Endpoint", + "readOnly": true + }, + "port": { + "type": "integer", + "format": "int32", + "title": "Output Only. Port of Public Endpoint", + "readOnly": true }, - "description": "Message for usage metrics for this cluster." + "disabled": { + "type": "boolean", + "title": "Optional. Disable Public Endpoint" + } }, - "BranchView": { - "type": "string", - "enum": [ - "BASIC", - "FULL" - ], - "description": "View on branch. Pass this enum to control which subsets of fields to get.\n\n - BASIC: Basic response contains basic information for a branch.\n - FULL: FULL response contains all detailed information for a branch." + "description": "Message for Public Endpoint for this branch." + }, + "Branch.State": { + "type": "string", + "enum": [ + "CREATING", + "ACTIVE", + "DELETED", + "MAINTENANCE", + "RESTORING" + ], + "description": "Output Only. Branch State.\n\n - CREATING: The branch is being created.\n - ACTIVE: The branch is active and running.\n - DELETED: The branch is being deleted.\n - MAINTENANCE: The branch is under maintenance.\n - RESTORING: The branch is restoring." + }, + "Branch.Usage": { + "type": "object", + "properties": { + "requestUnit": { + "type": "string", + "format": "int64", + "description": "Output Only. The latest value of Request Unit Metric for this cluster.", + "readOnly": true + }, + "rowStorage": { + "type": "number", + "format": "double", + "description": "Output Only. The latest value of Row Storage Metric for this cluster.", + "readOnly": true + }, + "columnarStorage": { + "type": "number", + "format": "double", + "description": "Output Only. The latest value of Columnar Storage Metric for this cluster.", + "readOnly": true + } }, - "ListBranchesResponse": { - "type": "object", - "properties": { - "branches": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Branch" - }, - "title": "The list of branches" - }, - "nextPageToken": { - "type": "string", - "description": "A token identifying a page of results the server should return." + "description": "Message for usage metrics for this cluster." + }, + "BranchView": { + "type": "string", + "enum": [ + "BASIC", + "FULL" + ], + "description": "View on branch. Pass this enum to control which subsets of fields to get.\n\n - BASIC: Basic response contains basic information for a branch.\n - FULL: FULL response contains all detailed information for a branch." + }, + "ListBranchesResponse": { + "type": "object", + "properties": { + "branches": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Branch" }, - "totalSize": { - "type": "integer", - "format": "int64", - "title": "Total number of branches" - } + "title": "The list of branches" }, - "title": "Message for response to list branches" + "nextPageToken": { + "type": "string", + "description": "A token identifying a page of results the server should return." + }, + "totalSize": { + "type": "integer", + "format": "int64", + "title": "Total number of branches" + } }, - "Status": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - }, - "details": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Any" - } + "title": "Message for response to list branches" + }, + "Status": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Any" } } } } - } \ No newline at end of file + } +} diff --git a/pkg/tidbcloud/v1beta1/serverless/branch/README.md b/pkg/tidbcloud/v1beta1/serverless/branch/README.md index 6b1d1a65..06ffdcd3 100644 --- a/pkg/tidbcloud/v1beta1/serverless/branch/README.md +++ b/pkg/tidbcloud/v1beta1/serverless/branch/README.md @@ -82,6 +82,7 @@ Class | Method | HTTP request | Description *BranchServiceAPI* | [**BranchServiceDeleteBranch**](docs/BranchServiceAPI.md#branchservicedeletebranch) | **Delete** /v1beta1/clusters/{clusterId}/branches/{branchId} | Deletes a branch. *BranchServiceAPI* | [**BranchServiceGetBranch**](docs/BranchServiceAPI.md#branchservicegetbranch) | **Get** /v1beta1/clusters/{clusterId}/branches/{branchId} | Gets information about a branch. *BranchServiceAPI* | [**BranchServiceListBranches**](docs/BranchServiceAPI.md#branchservicelistbranches) | **Get** /v1beta1/clusters/{clusterId}/branches | Lists information about branches. +*BranchServiceAPI* | [**BranchServiceResetBranch**](docs/BranchServiceAPI.md#branchserviceresetbranch) | **Post** /v1beta1/clusters/{clusterId}/branches/{branchId}:reset | Resets a branch. ## Documentation For Models diff --git a/pkg/tidbcloud/v1beta1/serverless/branch/api/openapi.yaml b/pkg/tidbcloud/v1beta1/serverless/branch/api/openapi.yaml index 36c99093..d369a0e8 100644 --- a/pkg/tidbcloud/v1beta1/serverless/branch/api/openapi.yaml +++ b/pkg/tidbcloud/v1beta1/serverless/branch/api/openapi.yaml @@ -153,6 +153,38 @@ paths: summary: Gets information about a branch. tags: - BranchService + /v1beta1/clusters/{clusterId}/branches/{branchId}:reset: + post: + operationId: BranchService_ResetBranch + parameters: + - description: Required. The cluster ID of the branch + in: path + name: clusterId + required: true + schema: + type: string + - description: Required. The branch ID + in: path + name: branchId + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + description: A successful response. + default: + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + description: An unexpected error response. + summary: Resets a branch. + tags: + - BranchService components: schemas: Any: @@ -342,11 +374,13 @@ components: - ACTIVE: The branch is active and running. - DELETED: The branch is being deleted. - MAINTENANCE: The branch is under maintenance. + - RESTORING: The branch is restoring. enum: - CREATING - ACTIVE - DELETED - MAINTENANCE + - RESTORING type: string Branch.Usage: description: Message for usage metrics for this cluster. diff --git a/pkg/tidbcloud/v1beta1/serverless/branch/api_branch_service.go b/pkg/tidbcloud/v1beta1/serverless/branch/api_branch_service.go index 734acb99..d554c680 100644 --- a/pkg/tidbcloud/v1beta1/serverless/branch/api_branch_service.go +++ b/pkg/tidbcloud/v1beta1/serverless/branch/api_branch_service.go @@ -511,3 +511,117 @@ func (a *BranchServiceAPIService) BranchServiceListBranchesExecute(r ApiBranchSe return localVarReturnValue, localVarHTTPResponse, nil } + +type ApiBranchServiceResetBranchRequest struct { + ctx context.Context + ApiService *BranchServiceAPIService + clusterId string + branchId string +} + +func (r ApiBranchServiceResetBranchRequest) Execute() (*Branch, *http.Response, error) { + return r.ApiService.BranchServiceResetBranchExecute(r) +} + +/* +BranchServiceResetBranch Resets a branch. + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @param clusterId Required. The cluster ID of the branch + @param branchId Required. The branch ID + @return ApiBranchServiceResetBranchRequest +*/ +func (a *BranchServiceAPIService) BranchServiceResetBranch(ctx context.Context, clusterId string, branchId string) ApiBranchServiceResetBranchRequest { + return ApiBranchServiceResetBranchRequest{ + ApiService: a, + ctx: ctx, + clusterId: clusterId, + branchId: branchId, + } +} + +// Execute executes the request +// +// @return Branch +func (a *BranchServiceAPIService) BranchServiceResetBranchExecute(r ApiBranchServiceResetBranchRequest) (*Branch, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodPost + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue *Branch + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "BranchServiceAPIService.BranchServiceResetBranch") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/v1beta1/clusters/{clusterId}/branches/{branchId}:reset" + localVarPath = strings.Replace(localVarPath, "{"+"clusterId"+"}", url.PathEscape(parameterValueToString(r.clusterId, "clusterId")), -1) + localVarPath = strings.Replace(localVarPath, "{"+"branchId"+"}", url.PathEscape(parameterValueToString(r.branchId, "branchId")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + var v Status + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} diff --git a/pkg/tidbcloud/v1beta1/serverless/branch/model_branch_state.go b/pkg/tidbcloud/v1beta1/serverless/branch/model_branch_state.go index 0ca6e8f3..348c4689 100644 --- a/pkg/tidbcloud/v1beta1/serverless/branch/model_branch_state.go +++ b/pkg/tidbcloud/v1beta1/serverless/branch/model_branch_state.go @@ -15,7 +15,7 @@ import ( "fmt" ) -// BranchState Output Only. Branch State. - CREATING: The branch is being created. - ACTIVE: The branch is active and running. - DELETED: The branch is being deleted. - MAINTENANCE: The branch is under maintenance. +// BranchState Output Only. Branch State. - CREATING: The branch is being created. - ACTIVE: The branch is active and running. - DELETED: The branch is being deleted. - MAINTENANCE: The branch is under maintenance. - RESTORING: The branch is restoring. type BranchState string // List of Branch.State @@ -24,6 +24,7 @@ const ( BRANCHSTATE_ACTIVE BranchState = "ACTIVE" BRANCHSTATE_DELETED BranchState = "DELETED" BRANCHSTATE_MAINTENANCE BranchState = "MAINTENANCE" + BRANCHSTATE_RESTORING BranchState = "RESTORING" ) // All allowed values of BranchState enum @@ -32,6 +33,7 @@ var AllowedBranchStateEnumValues = []BranchState{ "ACTIVE", "DELETED", "MAINTENANCE", + "RESTORING", } func (v *BranchState) UnmarshalJSON(src []byte) error {