From 857b1fe036afc5d5141c8b8028619ac41d0ce5ac Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 6 May 2024 13:13:42 +0000 Subject: [PATCH 1/4] backport of commit 19d2599668f98ae80f9c1f352921d5016b2f7c7d --- internal/cmd/commands/ferry/pause.go | 111 ++++++++++++++++++++++++++ internal/cmd/commands/ferry/resume.go | 111 ++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 internal/cmd/commands/ferry/pause.go create mode 100644 internal/cmd/commands/ferry/resume.go diff --git a/internal/cmd/commands/ferry/pause.go b/internal/cmd/commands/ferry/pause.go new file mode 100644 index 0000000000..6e04e1df86 --- /dev/null +++ b/internal/cmd/commands/ferry/pause.go @@ -0,0 +1,111 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package ferry + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/go-retryablehttp" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var ( + _ cli.Command = (*PauseCommand)(nil) + _ cli.CommandAutocomplete = (*PauseCommand)(nil) +) + +type PauseCommand struct { + *base.Command +} + +func (c *PauseCommand) Synopsis() string { + return "Pauses the running boundary ferry daemon" +} + +func (c *PauseCommand) Help() string { + helpText := ` +Usage: boundary ferry pause + + Pause the boundary ferry daemon: + + $ boundary ferry pause + + For a full list of examples, please see the documentation. + +` + c.Flags().Help() + return strings.TrimSpace(helpText) +} + +func (c *PauseCommand) Flags() *base.FlagSets { + return c.FlagSet(base.FlagSetNone) +} + +func (c *PauseCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *PauseCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *PauseCommand) Run(args []string) int { + ctx := c.Context + + resp, apiErr, err := c.Pause(ctx) + if err != nil { + c.PrintCliError(err) + return base.CommandCliError + } + if apiErr != nil { + c.PrintApiError(apiErr, "Error from ferry daemon when attempting to pause") + return base.CommandApiError + } + + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + return base.CommandSuccess +} + +func (c *PauseCommand) Pause(ctx context.Context) (*api.Response, *api.Error, error) { + const op = "ferry.(PauseCommand).Status" + client := retryablehttp.NewClient() + client.Logger = nil + client.RetryWaitMin = 100 * time.Millisecond + client.RetryWaitMax = 1000 * time.Millisecond + + req, err := retryablehttp.NewRequestWithContext(ctx, "POST", ferryUrl(c.FlagFerryDaemonPort, "v1/pause"), nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("content-type", "application/json") + + if c.FlagOutputCurlString { + api.LastOutputStringError = &api.OutputStringError{Request: req} + return nil, nil, api.LastOutputStringError + } + + resp, err := client.Do(req) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed")) + } + apiResp := api.NewResponse(resp) + + res := &GetStatusResponse{} + apiErr, err := apiResp.Decode(&res) + if err != nil { + return nil, nil, fmt.Errorf("error when sending request to the ferry daemon: %w", err) + } + if apiErr != nil { + return apiResp, apiErr, nil + } + return apiResp, nil, nil +} diff --git a/internal/cmd/commands/ferry/resume.go b/internal/cmd/commands/ferry/resume.go new file mode 100644 index 0000000000..1d723ffb5b --- /dev/null +++ b/internal/cmd/commands/ferry/resume.go @@ -0,0 +1,111 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package ferry + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/go-retryablehttp" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var ( + _ cli.Command = (*ResumeCommand)(nil) + _ cli.CommandAutocomplete = (*ResumeCommand)(nil) +) + +type ResumeCommand struct { + *base.Command +} + +func (c *ResumeCommand) Synopsis() string { + return "Resumes the paused boundary ferry daemon" +} + +func (c *ResumeCommand) Help() string { + helpText := ` +Usage: boundary ferry resume + + Resume the boundary ferry daemon: + + $ boundary ferry resume + + For a full list of examples, please see the documentation. + +` + c.Flags().Help() + return strings.TrimSpace(helpText) +} + +func (c *ResumeCommand) Flags() *base.FlagSets { + return c.FlagSet(base.FlagSetNone) +} + +func (c *ResumeCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *ResumeCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *ResumeCommand) Run(args []string) int { + ctx := c.Context + + resp, apiErr, err := c.Resume(ctx) + if err != nil { + c.PrintCliError(err) + return base.CommandCliError + } + if apiErr != nil { + c.PrintApiError(apiErr, "Error from ferry daemon when attempting to resume") + return base.CommandApiError + } + + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + return base.CommandSuccess +} + +func (c *ResumeCommand) Resume(ctx context.Context) (*api.Response, *api.Error, error) { + const op = "ferry.(ResumeCommand).Status" + client := retryablehttp.NewClient() + client.Logger = nil + client.RetryWaitMin = 100 * time.Millisecond + client.RetryWaitMax = 1000 * time.Millisecond + + req, err := retryablehttp.NewRequestWithContext(ctx, "POST", ferryUrl(c.FlagFerryDaemonPort, "v1/resume"), nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("content-type", "application/json") + + if c.FlagOutputCurlString { + api.LastOutputStringError = &api.OutputStringError{Request: req} + return nil, nil, api.LastOutputStringError + } + + resp, err := client.Do(req) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed")) + } + apiResp := api.NewResponse(resp) + + res := &GetStatusResponse{} + apiErr, err := apiResp.Decode(&res) + if err != nil { + return nil, nil, fmt.Errorf("error when sending request to the ferry daemon: %w", err) + } + if apiErr != nil { + return apiResp, apiErr, nil + } + return apiResp, nil, nil +} From 2a329f263637e1c33ceb9d2db3c11dc4961840d0 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 6 May 2024 14:40:30 +0000 Subject: [PATCH 2/4] backport of commit 3bb0d4761530151f7290b9466e854ef45f4412c9 --- internal/cmd/commands/ferry/pause.go | 39 +++++++++++++++++---------- internal/cmd/commands/ferry/resume.go | 39 +++++++++++++++++---------- internal/cmd/ferry_cmd_darwin.go | 10 +++++++ internal/cmd/ferry_cmd_windows.go | 10 +++++++ internal/cmd/main.go | 2 +- 5 files changed, 71 insertions(+), 29 deletions(-) diff --git a/internal/cmd/commands/ferry/pause.go b/internal/cmd/commands/ferry/pause.go index 6e04e1df86..bddafae5e8 100644 --- a/internal/cmd/commands/ferry/pause.go +++ b/internal/cmd/commands/ferry/pause.go @@ -5,7 +5,6 @@ package ferry import ( "context" - "fmt" "strings" "time" @@ -45,7 +44,24 @@ Usage: boundary ferry pause } func (c *PauseCommand) Flags() *base.FlagSets { - return c.FlagSet(base.FlagSetNone) + set := c.FlagSet(base.FlagSetOutputFormat) + f := set.NewFlagSet("Client Options") + + f.BoolVar(&base.BoolVar{ + Name: "output-curl-string", + Target: &c.FlagOutputCurlString, + Usage: "Instead of executing the request, print an equivalent cURL command string and exit.", + }) + + f.UintVar(&base.UintVar{ + Name: "ferry-port", + Target: &c.FlagFerryDaemonPort, + Default: 9300, + EnvVar: base.EnvFerryDaemonPort, + Usage: "The port on which the ferry daemon is listening.", + }) + + return set } func (c *PauseCommand) AutocompleteArgs() complete.Predictor { @@ -58,6 +74,11 @@ func (c *PauseCommand) AutocompleteFlags() complete.Flags { func (c *PauseCommand) Run(args []string) int { ctx := c.Context + f := c.Flags() + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } resp, apiErr, err := c.Pause(ctx) if err != nil { @@ -69,14 +90,14 @@ func (c *PauseCommand) Run(args []string) int { return base.CommandApiError } - if ok := c.PrintJsonItem(resp); !ok { + if resp.StatusCode() != 200 { return base.CommandCliError } return base.CommandSuccess } func (c *PauseCommand) Pause(ctx context.Context) (*api.Response, *api.Error, error) { - const op = "ferry.(PauseCommand).Status" + const op = "ferry.(PauseCommand).Pause" client := retryablehttp.NewClient() client.Logger = nil client.RetryWaitMin = 100 * time.Millisecond @@ -86,7 +107,6 @@ func (c *PauseCommand) Pause(ctx context.Context) (*api.Response, *api.Error, er if err != nil { return nil, nil, err } - req.Header.Set("content-type", "application/json") if c.FlagOutputCurlString { api.LastOutputStringError = &api.OutputStringError{Request: req} @@ -98,14 +118,5 @@ func (c *PauseCommand) Pause(ctx context.Context) (*api.Response, *api.Error, er return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed")) } apiResp := api.NewResponse(resp) - - res := &GetStatusResponse{} - apiErr, err := apiResp.Decode(&res) - if err != nil { - return nil, nil, fmt.Errorf("error when sending request to the ferry daemon: %w", err) - } - if apiErr != nil { - return apiResp, apiErr, nil - } return apiResp, nil, nil } diff --git a/internal/cmd/commands/ferry/resume.go b/internal/cmd/commands/ferry/resume.go index 1d723ffb5b..ff5e944005 100644 --- a/internal/cmd/commands/ferry/resume.go +++ b/internal/cmd/commands/ferry/resume.go @@ -5,7 +5,6 @@ package ferry import ( "context" - "fmt" "strings" "time" @@ -45,7 +44,24 @@ Usage: boundary ferry resume } func (c *ResumeCommand) Flags() *base.FlagSets { - return c.FlagSet(base.FlagSetNone) + set := c.FlagSet(base.FlagSetOutputFormat) + f := set.NewFlagSet("Client Options") + + f.BoolVar(&base.BoolVar{ + Name: "output-curl-string", + Target: &c.FlagOutputCurlString, + Usage: "Instead of executing the request, print an equivalent cURL command string and exit.", + }) + + f.UintVar(&base.UintVar{ + Name: "ferry-port", + Target: &c.FlagFerryDaemonPort, + Default: 9300, + EnvVar: base.EnvFerryDaemonPort, + Usage: "The port on which the ferry daemon is listening.", + }) + + return set } func (c *ResumeCommand) AutocompleteArgs() complete.Predictor { @@ -58,6 +74,11 @@ func (c *ResumeCommand) AutocompleteFlags() complete.Flags { func (c *ResumeCommand) Run(args []string) int { ctx := c.Context + f := c.Flags() + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } resp, apiErr, err := c.Resume(ctx) if err != nil { @@ -69,14 +90,14 @@ func (c *ResumeCommand) Run(args []string) int { return base.CommandApiError } - if ok := c.PrintJsonItem(resp); !ok { + if resp.StatusCode() != 200 { return base.CommandCliError } return base.CommandSuccess } func (c *ResumeCommand) Resume(ctx context.Context) (*api.Response, *api.Error, error) { - const op = "ferry.(ResumeCommand).Status" + const op = "ferry.(ResumeCommand).Resume" client := retryablehttp.NewClient() client.Logger = nil client.RetryWaitMin = 100 * time.Millisecond @@ -86,7 +107,6 @@ func (c *ResumeCommand) Resume(ctx context.Context) (*api.Response, *api.Error, if err != nil { return nil, nil, err } - req.Header.Set("content-type", "application/json") if c.FlagOutputCurlString { api.LastOutputStringError = &api.OutputStringError{Request: req} @@ -98,14 +118,5 @@ func (c *ResumeCommand) Resume(ctx context.Context) (*api.Response, *api.Error, return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed")) } apiResp := api.NewResponse(resp) - - res := &GetStatusResponse{} - apiErr, err := apiResp.Decode(&res) - if err != nil { - return nil, nil, fmt.Errorf("error when sending request to the ferry daemon: %w", err) - } - if apiErr != nil { - return apiResp, apiErr, nil - } return apiResp, nil, nil } diff --git a/internal/cmd/ferry_cmd_darwin.go b/internal/cmd/ferry_cmd_darwin.go index 91568037fd..e7e7d742f4 100644 --- a/internal/cmd/ferry_cmd_darwin.go +++ b/internal/cmd/ferry_cmd_darwin.go @@ -21,5 +21,15 @@ func init() { Command: base.NewCommand(ui), }, nil } + Commands["ferry pause"] = func() (cli.Command, error) { + return &ferry.PauseCommand{ + Command: base.NewCommand(ui), + }, nil + } + Commands["ferry resume"] = func() (cli.Command, error) { + return &ferry.ResumeCommand{ + Command: base.NewCommand(ui), + }, nil + } }) } diff --git a/internal/cmd/ferry_cmd_windows.go b/internal/cmd/ferry_cmd_windows.go index 55df18e3b5..1919873103 100644 --- a/internal/cmd/ferry_cmd_windows.go +++ b/internal/cmd/ferry_cmd_windows.go @@ -23,5 +23,15 @@ func init() { Command: base.NewCommand(ui), }, nil } + Commands["ferry pause"] = func() (cli.Command, error) { + return &ferry.PauseCommand{ + Command: base.NewCommand(ui), + }, nil + } + Commands["ferry resume"] = func() (cli.Command, error) { + return &ferry.ResumeCommand{ + Command: base.NewCommand(ui), + }, nil + } }) } diff --git a/internal/cmd/main.go b/internal/cmd/main.go index a266a90a8e..27cc984e39 100644 --- a/internal/cmd/main.go +++ b/internal/cmd/main.go @@ -241,7 +241,7 @@ func RunCustom(args []string, runOpts *RunOptions) (exitCode int) { initCommands(ui, serverCmdUi, runOpts) - hiddenCommands := []string{"version", "ferry", "ferry status"} + hiddenCommands := []string{"version", "ferry", "ferry status", "ferry pause", "ferry resume"} cli := &cli.CLI{ Name: "boundary", From 04a939c7a6be5b912adac54fc5530673921fab6c Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 6 May 2024 20:40:54 +0000 Subject: [PATCH 3/4] backport of commit ba668d07f9799a29d3840ea11b711353a99b78e8 --- internal/cmd/commands/ferry/pause.go | 18 ++++++++++++++++-- internal/cmd/commands/ferry/resume.go | 18 ++++++++++++++++-- internal/cmd/commands/ferry/status.go | 4 +++- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/internal/cmd/commands/ferry/pause.go b/internal/cmd/commands/ferry/pause.go index bddafae5e8..05fb462bce 100644 --- a/internal/cmd/commands/ferry/pause.go +++ b/internal/cmd/commands/ferry/pause.go @@ -90,8 +90,13 @@ func (c *PauseCommand) Run(args []string) int { return base.CommandApiError } - if resp.StatusCode() != 200 { - return base.CommandCliError + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + default: + c.UI.Output("Ferry has been successfully paused.") } return base.CommandSuccess } @@ -118,5 +123,14 @@ func (c *PauseCommand) Pause(ctx context.Context) (*api.Response, *api.Error, er return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed")) } apiResp := api.NewResponse(resp) + + apiErr, err := apiResp.Decode(nil) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("error decoding Resume response")) + } + if apiErr != nil { + return nil, apiErr, nil + } + return apiResp, nil, nil } diff --git a/internal/cmd/commands/ferry/resume.go b/internal/cmd/commands/ferry/resume.go index ff5e944005..c274f8870a 100644 --- a/internal/cmd/commands/ferry/resume.go +++ b/internal/cmd/commands/ferry/resume.go @@ -90,8 +90,13 @@ func (c *ResumeCommand) Run(args []string) int { return base.CommandApiError } - if resp.StatusCode() != 200 { - return base.CommandCliError + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + default: + c.UI.Output("Ferry has been successfully resumed.") } return base.CommandSuccess } @@ -118,5 +123,14 @@ func (c *ResumeCommand) Resume(ctx context.Context) (*api.Response, *api.Error, return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed")) } apiResp := api.NewResponse(resp) + + apiErr, err := apiResp.Decode(nil) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("error decoding Resume response")) + } + if apiErr != nil { + return nil, apiErr, nil + } + return apiResp, nil, nil } diff --git a/internal/cmd/commands/ferry/status.go b/internal/cmd/commands/ferry/status.go index d04a732257..bd7f388d10 100644 --- a/internal/cmd/commands/ferry/status.go +++ b/internal/cmd/commands/ferry/status.go @@ -107,6 +107,7 @@ type GetStatusResponse struct { AuthTokenId string `json:"auth_token_id"` AuthTokenExpiry time.Time `json:"auth_token_expiry"` Version string `json:"version"` + Status string `json:"status"` Errors []string `json:"errors"` Warnings []string `json:"warnings"` } @@ -138,7 +139,7 @@ func (c *StatusCommand) Status(ctx context.Context) (*api.Response, *GetStatusRe res := &GetStatusResponse{} apiErr, err := apiResp.Decode(&res) if err != nil { - return nil, nil, nil, fmt.Errorf("Error when sending request to the ferry daemon: %w.", err) + return nil, nil, nil, fmt.Errorf("error when sending request to the ferry daemon: %w", err) } if apiErr != nil { return apiResp, nil, apiErr, nil @@ -152,6 +153,7 @@ func printStatusTable(status *GetStatusResponse) string { "Auth Token Id": status.AuthTokenId, "Auth Token Expiration": time.Until(status.AuthTokenExpiry).Round(time.Second).String(), "Version": status.Version, + "Status": status.Status, } maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) From 48c8815e6169c74eb10d5906cef869e87cac235f Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 6 May 2024 21:36:03 +0000 Subject: [PATCH 4/4] backport of commit bc90d0f97e2f6341851b06da21fb1c7d0c90c0a4 --- internal/cmd/commands/ferry/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/commands/ferry/status.go b/internal/cmd/commands/ferry/status.go index bd7f388d10..c3f9fae59f 100644 --- a/internal/cmd/commands/ferry/status.go +++ b/internal/cmd/commands/ferry/status.go @@ -139,7 +139,7 @@ func (c *StatusCommand) Status(ctx context.Context) (*api.Response, *GetStatusRe res := &GetStatusResponse{} apiErr, err := apiResp.Decode(&res) if err != nil { - return nil, nil, nil, fmt.Errorf("error when sending request to the ferry daemon: %w", err) + return nil, nil, nil, fmt.Errorf("Error when sending request to the ferry daemon: %w.", err) } if apiErr != nil { return apiResp, nil, apiErr, nil