internal/cmd/ferry: add sessions command (#4800)

pull/4806/head
Johan Brandhorst-Satzkorn 2 years ago committed by GitHub
parent 590182cf72
commit 111b8fbecf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -35,6 +35,15 @@ Usage: boundary ferry [sub command] [options]
$ boundary ferry status
Pause and resume the daemon:
$ boundary ferry pause
$ boundary ferry resume
List active transparent sessions:
$ boundary ferry sessions
For a full list of examples, please see the documentation.
`

@ -0,0 +1,246 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package ferry
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/targets"
"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 = (*SessionsCommand)(nil)
_ cli.CommandAutocomplete = (*SessionsCommand)(nil)
)
type SessionsCommand struct {
*base.Command
}
func (c *SessionsCommand) Synopsis() string {
return "List active transparent sessions managed by the Ferry daemon."
}
func (c *SessionsCommand) Help() string {
helpText := `
Usage: boundary ferry sessions [options]
List the active transparent sessions:
$ boundary ferry sessions
For a full list of examples, please see the documentation.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *SessionsCommand) Flags() *base.FlagSets {
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 *SessionsCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *SessionsCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *SessionsCommand) Run(args []string) int {
ctx := c.Context
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
resp, result, apiErr, err := c.Sessions(ctx)
if err != nil {
c.PrintCliError(err)
return base.CommandCliError
}
if apiErr != nil {
c.PrintApiError(apiErr, "Error from ferry daemon when getting its Sessions")
return base.CommandApiError
}
switch base.Format(c.UI) {
case "json":
if ok := c.PrintJsonItems(resp); !ok {
return base.CommandCliError
}
default:
c.UI.Output(c.printListTable(result.Items))
}
return base.CommandSuccess
}
type Session struct {
Alias string `json:"alias"`
SessionAuthorization struct {
SessionId string `json:"session_id"`
CreatedTime time.Time `json:"created_time"`
Credentials []*targets.SessionCredential `json:"credentials,omitempty"`
} `json:"session_authorization"`
}
type ListSessionsResponse struct {
Items []*Session `json:"items"`
}
func (c *SessionsCommand) Sessions(ctx context.Context) (*api.Response, *ListSessionsResponse, *api.Error, error) {
const op = "ferry.(SessionsCommand).Sessions"
client := retryablehttp.NewClient()
client.Logger = nil
client.RetryWaitMin = 100 * time.Millisecond
client.RetryWaitMax = 1500 * time.Millisecond
req, err := retryablehttp.NewRequestWithContext(ctx, "GET", ferryUrl(c.FlagFerryDaemonPort, "v1/sessions"), nil)
if err != nil {
return nil, nil, nil, err
}
req.Header.Set("content-type", "application/json")
if c.FlagOutputCurlString {
api.LastOutputStringError = &api.OutputStringError{Request: req}
return nil, nil, nil, api.LastOutputStringError
}
resp, err := client.Do(req)
if err != nil {
return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed"))
}
apiResp := api.NewResponse(resp)
res := &ListSessionsResponse{}
apiErr, err := apiResp.Decode(&res)
if err != nil {
return nil, nil, nil, fmt.Errorf("Error when sending request to the ferry daemon: %w.", err)
}
if apiErr != nil {
return apiResp, nil, apiErr, nil
}
return apiResp, res, nil, nil
}
func generateCredentialTableOutputSlice(prefixIndent int, creds []*targets.SessionCredential) []string {
var ret []string
prefixString := strings.Repeat(" ", prefixIndent)
if len(creds) > 0 {
// Add credential header
ret = append(ret, fmt.Sprintf("%sCredentials:", prefixString))
}
for _, crd := range creds {
credMap := map[string]any{
"Credential Store ID": crd.CredentialSource.CredentialStoreId,
"Credential Source ID": crd.CredentialSource.Id,
"Credential Store Type": crd.CredentialSource.Type,
}
if crd.CredentialSource.Name != "" {
credMap["Credential Source Name"] = crd.CredentialSource.Name
}
if crd.CredentialSource.Description != "" {
credMap["Credential Source Description"] = crd.CredentialSource.Description
}
if crd.CredentialSource.CredentialType != "" {
credMap["Credential Type"] = crd.CredentialSource.CredentialType
}
maxLength := base.MaxAttributesLength(credMap, nil, nil)
ret = append(ret,
base.WrapMap(2+prefixIndent, maxLength, credMap),
fmt.Sprintf("%s Secret:", prefixString))
ret = append(ret,
fmtSecretForTable(2+prefixIndent, crd)...,
)
ret = append(ret, "")
}
return ret
}
func fmtSecretForTable(indent int, sc *targets.SessionCredential) []string {
prefixStr := strings.Repeat(" ", indent)
origSecret := []string{fmt.Sprintf("%s %s", prefixStr, sc.Secret.Raw)}
if sc.Credential != nil {
maxLength := 0
for k := range sc.Credential {
if len(k) > maxLength {
maxLength = len(k)
}
}
return []string{fmt.Sprintf("%s %s", prefixStr, base.WrapMap(2, maxLength+2, sc.Credential))}
}
in, err := base64.StdEncoding.DecodeString(strings.Trim(string(sc.Secret.Raw), `"`))
if err != nil {
return origSecret
}
dst := new(bytes.Buffer)
if err := json.Indent(dst, in, fmt.Sprintf("%s ", prefixStr), fmt.Sprintf("%s ", prefixStr)); err != nil {
return origSecret
}
secretStr := strings.Split(dst.String(), "\n")
if len(secretStr) > 0 {
secretStr[0] = fmt.Sprintf("%s %s", prefixStr, secretStr[0])
}
return secretStr
}
func (c *SessionsCommand) printListTable(items []*Session) string {
if len(items) == 0 {
return "No sessions found"
}
var output []string
output = []string{
"",
"Session information:",
}
for i, item := range items {
if i > 0 {
output = append(output, "")
}
output = append(output,
fmt.Sprintf(" Alias: %s", item.Alias),
" Session authorization:",
fmt.Sprintf(" Session ID: %s", item.SessionAuthorization.SessionId),
fmt.Sprintf(" Created time: %s", item.SessionAuthorization.CreatedTime),
)
if len(item.SessionAuthorization.Credentials) > 0 {
output = append(output, generateCredentialTableOutputSlice(4, item.SessionAuthorization.Credentials)...)
}
}
return base.WrapForHelpText(output)
}

@ -31,5 +31,10 @@ func init() {
Command: base.NewCommand(ui),
}, nil
}
Commands["ferry sessions"] = func() (cli.Command, error) {
return &ferry.SessionsCommand{
Command: base.NewCommand(ui),
}, nil
}
})
}

@ -33,5 +33,10 @@ func init() {
Command: base.NewCommand(ui),
}, nil
}
Commands["ferry sessions"] = func() (cli.Command, error) {
return &ferry.SessionsCommand{
Command: base.NewCommand(ui),
}, nil
}
})
}

Loading…
Cancel
Save