From 35a22960927caf3addcd87cdc17be90091266133 Mon Sep 17 00:00:00 2001 From: Jim Date: Wed, 17 May 2023 13:16:50 -0400 Subject: [PATCH] feat(cli): add session recordings: read and list cmds --- .../channel_recording.gen.go | 16 +- .../connection_recording.gen.go | 8 +- api/sessionrecordings/option.gen.go | 13 + .../session_recording.gen.go | 6 +- api/types_test.go | 5 + internal/adding-a-new-field-readme.md | 14 + internal/api/genapi/input.go | 50 +++- internal/cmd/commands.go | 19 ++ internal/cmd/commands/dev/dev.go | 1 + .../commands/sessionrecordingscmd/funcs.go | 255 ++++++++++++++++ .../sessionrecordings.gen.go | 283 ++++++++++++++++++ internal/cmd/gencli/input.go | 11 + 12 files changed, 655 insertions(+), 26 deletions(-) create mode 100644 internal/cmd/commands/sessionrecordingscmd/funcs.go create mode 100644 internal/cmd/commands/sessionrecordingscmd/sessionrecordings.gen.go diff --git a/api/sessionrecordings/channel_recording.gen.go b/api/sessionrecordings/channel_recording.gen.go index 2a240a218a..59ad47dc42 100644 --- a/api/sessionrecordings/channel_recording.gen.go +++ b/api/sessionrecordings/channel_recording.gen.go @@ -6,14 +6,16 @@ package sessionrecordings import ( "time" + + "github.com/hashicorp/boundary/api" ) type ChannelRecording struct { - Id string `json:"id,omitempty"` - BytesUp uint64 `json:"bytes_up,omitempty"` - BytesDown uint64 `json:"bytes_down,omitempty"` - StartTime time.Time `json:"start_time,omitempty"` - EndTime time.Time `json:"end_time,omitempty"` - Duration time.Duration `json:"duration,omitempty"` - MimeTypes []string `json:"mime_types,omitempty"` + Id string `json:"id,omitempty"` + BytesUp uint64 `json:"bytes_up,string,omitempty"` + BytesDown uint64 `json:"bytes_down,string,omitempty"` + StartTime time.Time `json:"start_time,omitempty"` + EndTime time.Time `json:"end_time,omitempty"` + Duration api.Duration `json:"duration,omitempty"` + MimeTypes []string `json:"mime_types,omitempty"` } diff --git a/api/sessionrecordings/connection_recording.gen.go b/api/sessionrecordings/connection_recording.gen.go index 1e2925b9e5..cc04338f54 100644 --- a/api/sessionrecordings/connection_recording.gen.go +++ b/api/sessionrecordings/connection_recording.gen.go @@ -6,16 +6,18 @@ package sessionrecordings import ( "time" + + "github.com/hashicorp/boundary/api" ) type ConnectionRecording struct { Id string `json:"id,omitempty"` ConnectionId string `json:"connection_id,omitempty"` - BytesUp uint64 `json:"bytes_up,omitempty"` - BytesDown uint64 `json:"bytes_down,omitempty"` + BytesUp uint64 `json:"bytes_up,string,omitempty"` + BytesDown uint64 `json:"bytes_down,string,omitempty"` StartTime time.Time `json:"start_time,omitempty"` EndTime time.Time `json:"end_time,omitempty"` - Duration time.Duration `json:"duration,omitempty"` + Duration api.Duration `json:"duration,omitempty"` MimeTypes []string `json:"mime_types,omitempty"` ChannelRecordings []*ChannelRecording `json:"channel_recordings,omitempty"` } diff --git a/api/sessionrecordings/option.gen.go b/api/sessionrecordings/option.gen.go index 86d9b76259..3b3c76b2ca 100644 --- a/api/sessionrecordings/option.gen.go +++ b/api/sessionrecordings/option.gen.go @@ -5,6 +5,7 @@ package sessionrecordings import ( + "strconv" "strings" "github.com/hashicorp/boundary/api" @@ -25,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRecursive bool } func getDefaultOptions() options { @@ -48,6 +50,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRecursive { + opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) + } return opts, apiOpts } @@ -67,3 +72,11 @@ func WithFilter(filter string) Option { o.withFilter = strings.TrimSpace(filter) } } + +// WithRecursive tells the API to use recursion for listing operations on this +// resource +func WithRecursive(recurse bool) Option { + return func(o *options) { + o.withRecursive = true + } +} diff --git a/api/sessionrecordings/session_recording.gen.go b/api/sessionrecordings/session_recording.gen.go index c74f57d378..c1eee1fc7f 100644 --- a/api/sessionrecordings/session_recording.gen.go +++ b/api/sessionrecordings/session_recording.gen.go @@ -19,11 +19,11 @@ type SessionRecording struct { Scope *scopes.ScopeInfo `json:"scope,omitempty"` SessionId string `json:"session_id,omitempty"` StorageBucketId string `json:"storage_bucket_id,omitempty"` - BytesUp uint64 `json:"bytes_up,omitempty"` - BytesDown uint64 `json:"bytes_down,omitempty"` + BytesUp uint64 `json:"bytes_up,string,omitempty"` + BytesDown uint64 `json:"bytes_down,string,omitempty"` StartTime time.Time `json:"start_time,omitempty"` EndTime time.Time `json:"end_time,omitempty"` - Duration time.Duration `json:"duration,omitempty"` + Duration api.Duration `json:"duration,omitempty"` Type string `json:"type,omitempty"` State string `json:"state,omitempty"` ErrorDetails string `json:"error_details,omitempty"` diff --git a/api/types_test.go b/api/types_test.go index 408e3cce52..7fbf3aaba7 100644 --- a/api/types_test.go +++ b/api/types_test.go @@ -24,6 +24,11 @@ func TestTypes(t *testing.T) { json: `"12345s"`, want: Duration{Duration: 12345000000000}, }, + { + name: "Duration-valid-with-float", + json: `"1.2345s"`, + want: Duration{Duration: 1234500000}, + }, { name: "Duration-valid", json: `"1h"`, diff --git a/internal/adding-a-new-field-readme.md b/internal/adding-a-new-field-readme.md index c191a46d0e..c96855173c 100644 --- a/internal/adding-a-new-field-readme.md +++ b/internal/adding-a-new-field-readme.md @@ -74,3 +74,17 @@ All that's left is to incorporate it into Boundary's CLI for the appropriate com * Run `make cli` and `make install`, before attempting to test cli changes [test example]: tests/api/authmethods/classification_test.go + +## Additional steps for new API/CLI commands: + +* After building out the gRPC service, add the new API service definition to the + `inputStructs` for API generation: `internal/api/genapi` then run `make api` + or `make gen` +* Add the new CLI command definition to the `inputStructs` for CLI generation: + `internal/cmd/gencli` then run `make cli` or `make gen` +* Add the new `cli.CommandFactory` to the `cmd.initCommands(...)`: + `internal/cmd` then run `make cli` or `make gen` +* Fill out the required funcs in the command's `func.go` following the same + suggestion to define consts for field names and reuse them everywhere they are + required. +* Run `make cli` and `make install`, before attempting to test cli changes \ No newline at end of file diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index f9f0ad815a..328a81f228 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -1008,19 +1008,6 @@ var inputStructs = []*structInfo{ versionEnabled: true, recursiveListing: true, }, - { - inProto: &session_recordings.SessionRecording{}, - outFile: "sessionrecordings/session_recording.gen.go", - templates: []*template.Template{ - clientTemplate, - readTemplate, - listTemplate, - }, - pluralResourceName: "session-recordings", - createResponseTypes: []string{ReadResponseType, ListResponseType}, - recursiveListing: true, - versionEnabled: false, - }, { inProto: &session_recordings.User{}, outFile: "sessionrecordings/user.gen.go", @@ -1040,10 +1027,47 @@ var inputStructs = []*structInfo{ { inProto: &session_recordings.ConnectionRecording{}, outFile: "sessionrecordings/connection_recording.gen.go", + fieldOverrides: []fieldInfo{ + // int64 fields get marshalled by protobuf as strings, so we have + // to tell the json parser that their json representation is a + // string but they go into Go int64 types. + {Name: "BytesUp", JsonTags: []string{"string"}}, + {Name: "BytesDown", JsonTags: []string{"string"}}, + }, }, { inProto: &session_recordings.ChannelRecording{}, outFile: "sessionrecordings/channel_recording.gen.go", + fieldOverrides: []fieldInfo{ + // int64 fields get marshalled by protobuf as strings, so we have + // to tell the json parser that their json representation is a + // string but they go into Go int64 types. + {Name: "BytesUp", JsonTags: []string{"string"}}, + {Name: "BytesDown", JsonTags: []string{"string"}}, + }, + }, + { + // this must be the last block of session recording blocks, otherwise + // the bits beyond inProto and outFile will get overwritten by + // subsequent session recording blocks + inProto: &session_recordings.SessionRecording{}, + outFile: "sessionrecordings/session_recording.gen.go", + templates: []*template.Template{ + clientTemplate, + readTemplate, + listTemplate, + }, + pluralResourceName: "session-recordings", + createResponseTypes: []string{ReadResponseType, ListResponseType}, + recursiveListing: true, + versionEnabled: false, + fieldOverrides: []fieldInfo{ + // int64 fields get marshalled by protobuf as strings, so we have + // to tell the json parser that their json representation is a + // string but they go into Go int64 types. + {Name: "BytesUp", JsonTags: []string{"string"}}, + {Name: "BytesDown", JsonTags: []string{"string"}}, + }, }, { inProto: &workers.Certificate{}, diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 151f76423d..5724ce0dde 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -25,6 +25,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/commands/rolescmd" "github.com/hashicorp/boundary/internal/cmd/commands/scopescmd" "github.com/hashicorp/boundary/internal/cmd/commands/server" + "github.com/hashicorp/boundary/internal/cmd/commands/sessionrecordingscmd" "github.com/hashicorp/boundary/internal/cmd/commands/sessionscmd" "github.com/hashicorp/boundary/internal/cmd/commands/storagebucketscmd" "github.com/hashicorp/boundary/internal/cmd/commands/targetscmd" @@ -1024,6 +1025,24 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, nil }, + "session-recordings": func() (cli.Command, error) { + return &sessionrecordingscmd.Command{ + Command: base.NewCommand(ui), + }, nil + }, + "session-recordings read": func() (cli.Command, error) { + return &sessionrecordingscmd.Command{ + Command: base.NewCommand(ui), + Func: "read", + }, nil + }, + "session-recordings list": func() (cli.Command, error) { + return &sessionrecordingscmd.Command{ + Command: base.NewCommand(ui), + Func: "list", + }, nil + }, + "storage-buckets": func() (cli.Command, error) { return &storagebucketscmd.Command{ Command: base.NewCommand(ui), diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index c0da07c737..35148ffc30 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -718,6 +718,7 @@ func (c *Command) Run(args []string) int { c.DevLoopbackPluginId = "pl_1234567890" c.EnabledPlugins = append(c.EnabledPlugins, base.EnabledPluginLoopback) c.Config.Controller.Scheduler.JobRunIntervalDuration = 100 * time.Millisecond + c.Info["Generated Dev Loopback plugin id"] = c.DevLoopbackPluginId } switch c.flagDatabaseUrl { case "": diff --git a/internal/cmd/commands/sessionrecordingscmd/funcs.go b/internal/cmd/commands/sessionrecordingscmd/funcs.go new file mode 100644 index 0000000000..a3c2bd07dd --- /dev/null +++ b/internal/cmd/commands/sessionrecordingscmd/funcs.go @@ -0,0 +1,255 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sessionrecordingscmd + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/sessionrecordings" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +type extraCmdVars struct{} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary session-recordings [sub command] [options] [args]", + "", + " This command allows operations on Boundary session recordings.", + "", + " Read a session recording:", + "", + ` $ boundary session-recordings read -id s_1234567890`, + "", + " Please see the sessions subcommand help for detailed usage information.", + }) + + default: + helpStr = helpMap["base"]() + } + + return helpStr + c.Flags().Help() +} + +func (c *Command) printListTable(items []*sessionrecordings.SessionRecording) string { + if len(items) == 0 { + return "No session recordings found" + } + var output []string + output = []string{ + "", + "Session Recording information:", + } + for i, item := range items { + if i > 0 { + output = append(output, "") + } + if item.Id != "" { + output = append(output, + fmt.Sprintf(" ID: %s", item.Id), + ) + } else { + output = append(output, + fmt.Sprintf(" ID: %s", "(not available)"), + ) + } + if c.FlagRecursive && item.Scope.Id != "" { + output = append(output, + fmt.Sprintf(" Scope ID: %s", item.Scope.Id), + ) + } + if item.SessionId != "" { + output = append(output, + fmt.Sprintf(" Session ID: %s", item.SessionId), + ) + } + if item.StorageBucketId != "" { + output = append(output, + fmt.Sprintf(" Storage Bucket ID: %s", item.StorageBucketId), + ) + } + if !item.StartTime.IsZero() { + output = append(output, + fmt.Sprintf(" Start Time: %s", item.StartTime.Local().Format(time.RFC1123)), + ) + } + if !item.EndTime.IsZero() { + output = append(output, + fmt.Sprintf(" End Time: %s", item.EndTime.Local().Format(time.RFC1123)), + ) + } + if item.Type != "" { + output = append(output, + fmt.Sprintf(" Type: %s", item.Type), + ) + } + if item.State != "" { + output = append(output, + fmt.Sprintf(" State: %s", item.State), + ) + } + if len(item.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, item.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(item *sessionrecordings.SessionRecording, resp *api.Response) string { + const ( + durationKey = "Duration (Seconds)" + ) + nonAttributeMap := map[string]any{} + if item.Id != "" { + nonAttributeMap["ID"] = item.Id + } + if item.Scope.Id != "" { + nonAttributeMap["Scope ID"] = item.Scope.Id + } + if item.SessionId != "" { + nonAttributeMap["Session ID"] = item.SessionId + } + if item.StorageBucketId != "" { + nonAttributeMap["Storage Bucket ID"] = item.StorageBucketId + } + if item.BytesUp != 0 { + nonAttributeMap["Bytes Up"] = item.BytesUp + } + if item.BytesDown != 0 { + nonAttributeMap["Bytes Down"] = item.BytesDown + } + if !item.StartTime.IsZero() { + nonAttributeMap["Start Time"] = item.StartTime.Local().Format(time.RFC1123) + } + if !item.EndTime.IsZero() { + nonAttributeMap["Updated Time"] = item.EndTime.Local().Format(time.RFC1123) + } + if item.Duration.Duration != 0 { + nonAttributeMap[durationKey] = item.Duration.Seconds() + } + if item.Type != "" { + nonAttributeMap["Type"] = item.Type + } + if item.State != "" { + nonAttributeMap["State"] = item.State + } + if item.ErrorDetails != "" { + nonAttributeMap["Error Details"] = item.ErrorDetails + } + if len(item.MimeTypes) > 0 { + nonAttributeMap["Mime Types"] = strings.Join(item.MimeTypes, ", ") + } + if item.Endpoint != "" { + nonAttributeMap["Endpoint"] = item.Endpoint + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + ret := []string{ + "", + "Session Recording information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + } + + if item.Scope != nil { + ret = append(ret, + "", + " Scope:", + base.ScopeInfoForOutput(item.Scope, maxLength), + ) + } + + if len(item.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, item.AuthorizedActions), + ) + } + + if len(item.ConnectionRecordings) > 0 { + maxAttrLen := len(durationKey) + ret = append(ret, + "", + " Connections Recordings:", + ) + for _, cr := range item.ConnectionRecordings { + cm := map[string]any{ + "ID": cr.Id, + "Connection ID": cr.ConnectionId, + } + if cr.BytesUp != 0 { + cm["Bytes Up"] = cr.BytesUp + } + if cr.BytesDown != 0 { + cm["Bytes Down"] = cr.BytesDown + } + if !cr.StartTime.IsZero() { + cm["Start Time"] = cr.StartTime.Local().Format(time.RFC1123) + } + if !cr.EndTime.IsZero() { + cm["End Time"] = cr.EndTime.Local().Format(time.RFC1123) + } + if cr.Duration.Duration != 0 { + cm[durationKey] = cr.Duration.Seconds() + } + if len(cr.MimeTypes) > 0 { + cm["Mime Types"] = strings.Join(cr.MimeTypes, ", ") + } + ret = append(ret, + base.WrapMap(4, maxAttrLen, cm), + "", + ) + + if len(cr.ChannelRecordings) > 0 { + var channelRecordings []map[string]any + for _, chr := range cr.ChannelRecordings { + chrm := map[string]any{ + "ID": chr.Id, + } + if chr.BytesUp != 0 { + chrm["Bytes Up"] = chr.BytesUp + } + if chr.BytesDown != 0 { + chrm["Bytes Down"] = chr.BytesDown + } + if !chr.StartTime.IsZero() { + chrm["Start Time"] = chr.StartTime.Local().Format(time.RFC1123) + } + if !chr.EndTime.IsZero() { + chrm["End Time"] = chr.EndTime.Local().Format(time.RFC1123) + } + if chr.Duration.Duration != 0 { + chrm[durationKey] = chr.Duration.Seconds() + } + if len(chr.MimeTypes) > 0 { + chrm["Mine Types"] = strings.Join(chr.MimeTypes, ", ") + } + channelRecordings = append(channelRecordings, chrm) + } + ret = append(ret, + "", + " Channel Recordings:", + ) + for _, cr := range channelRecordings { + ret = append(ret, + base.WrapMap(6, maxAttrLen, cr), + "", + ) + } + } + } + } + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/sessionrecordingscmd/sessionrecordings.gen.go b/internal/cmd/commands/sessionrecordingscmd/sessionrecordings.gen.go new file mode 100644 index 0000000000..784af59d1e --- /dev/null +++ b/internal/cmd/commands/sessionrecordingscmd/sessionrecordings.gen.go @@ -0,0 +1,283 @@ +// Code generated by "make cli"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sessionrecordingscmd + +import ( + "errors" + "fmt" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/sessionrecordings" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string + + extraCmdVars +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "session recording" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("session recording") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "list": {"scope-id", "filter", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "session recording", flagsMap, c.Func) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create": + return cli.RunResultHelp + + case "update": + return cli.RunResultHelp + + } + + c.plural = "session recording" + switch c.Func { + case "list": + c.plural = "session recordings" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []sessionrecordings.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + + case "list": + if c.FlagScopeId == "" { + c.PrintCliError(errors.New("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID")) + return base.CommandUserError + } + + } + } + + client, err := c.Client() + if c.WrapperCleanupFunc != nil { + defer func() { + if err := c.WrapperCleanupFunc(); err != nil { + c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err)) + } + }() + } + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %w", err)) + return base.CommandCliError + } + sessionrecordingsClient := sessionrecordings.NewClient(client) + + switch c.FlagRecursive { + case true: + opts = append(opts, sessionrecordings.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, sessionrecordings.WithFilter(c.FlagFilter)) + } + + var version uint32 + + if ok := extraFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var resp *api.Response + var item *sessionrecordings.SessionRecording + + var items []*sessionrecordings.SessionRecording + + var readResult *sessionrecordings.SessionRecordingReadResult + + var listResult *sessionrecordings.SessionRecordingListResult + + switch c.Func { + + case "read": + readResult, err = sessionrecordingsClient.Read(c.Context, c.FlagId, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = readResult.GetResponse() + item = readResult.GetItem() + + case "list": + listResult, err = sessionrecordingsClient.List(c.Context, c.FlagScopeId, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = listResult.GetResponse() + items = listResult.GetItems() + + } + + resp, item, items, err = executeExtraActions(c, resp, item, items, err, sessionrecordingsClient, version, opts) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + + case "list": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItems(resp); !ok { + return base.CommandCliError + } + + case "table": + c.UI.Output(c.printListTable(items)) + } + + return base.CommandSuccess + + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item, resp)) + + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +func (c *Command) checkFuncError(err error) int { + if err == nil { + return 0 + } + if apiErr := api.AsServerError(err); apiErr != nil { + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *base.FlagSets, *[]sessionrecordings.Option) bool { return true } + executeExtraActions = func(_ *Command, inResp *api.Response, inItem *sessionrecordings.SessionRecording, inItems []*sessionrecordings.SessionRecording, inErr error, _ *sessionrecordings.Client, _ uint32, _ []sessionrecordings.Option) (*api.Response, *sessionrecordings.SessionRecording, []*sessionrecordings.SessionRecording, error) { + return inResp, inItem, inItems, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index df19ae77ef..8d6b813ecd 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -566,6 +566,17 @@ var inputStructs = map[string][]*cmdInfo{ VersionedActions: []string{"cancel"}, }, }, + "sessionrecordings": { + { + ResourceType: resource.SessionRecording.String(), + Pkg: "sessionrecordings", + StdActions: []string{"read", "list"}, + Container: "Scope", + HasExtraCommandVars: true, + HasExtraHelpFunc: true, + HasId: true, + }, + }, "storagebuckets": { { ResourceType: resource.StorageBucket.String(),