feat(billing): Add cli subcommand for monthly active user counts

This commit is contained in:
Michael Milton authored and Timothy Messier committed 2024-02-23 15:44:10 +00:00
1 parent 111dc87ead
commit 2e69d59dd2
24 files changed
+791 -73

No files matched your search

+12
View File
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/boundary/internal/cmd/commands/authenticate"
"github.com/hashicorp/boundary/internal/cmd/commands/authmethodscmd"
"github.com/hashicorp/boundary/internal/cmd/commands/authtokenscmd"
"github.com/hashicorp/boundary/internal/cmd/commands/billingcmd"
"github.com/hashicorp/boundary/internal/cmd/commands/config"
"github.com/hashicorp/boundary/internal/cmd/commands/connect"
"github.com/hashicorp/boundary/internal/cmd/commands/credentiallibrariescmd"
@@ -243,6 +244,17 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "list",
}),
"billing": func() (cli.Command, error) {
return &billingcmd.Command{
Command: base.NewCommand(ui, opts...),
}, nil
},
"billing monthly-active-users": clientCacheWrapper(
&billingcmd.Command{
Command: base.NewCommand(ui),
Func: "monthly-active-users",
}),
"config": func() (cli.Command, error) {
return &config.Command{
Command: base.NewCommand(ui, opts...),
@@ -0,0 +1,201 @@
// Code generated by "make cli"; DO NOT EDIT.
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package billingcmd
import (
"fmt"
"sync"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/billing"
"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 := "billing"
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *Command) Help() string {
initFlags()
var helpStr string
helpMap := common.HelpMap("billing")
switch c.Func {
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{}
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, "billing", 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 = "billing"
switch c.Func {
case "list":
c.plural = "billing"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
var opts []billing.Option
if strutil.StrListContains(flagsMap[c.Func], "-id") {
switch c.Func {
}
}
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
}
billingClient := billing.NewClient(client)
if c.FlagFilter != "" {
opts = append(opts, billing.WithFilter(c.FlagFilter))
}
var version uint32
if ok := extraFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var resp *api.Response
resp, err = executeExtraActions(c, resp, err, billingClient, 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
}
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, *[]billing.Option) bool { return true }
executeExtraActions = func(_ *Command, inResp *api.Response, inErr error, _ *billing.Client, _ uint32, _ []billing.Option) (*api.Response, error) {
return inResp, inErr
}
printCustomActionOutput = func(*Command) (bool, error) { return false, nil }
)
+156
View File
@@ -0,0 +1,156 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package billingcmd
import (
"fmt"
"strings"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/billing"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/mitchellh/go-wordwrap"
)
func init() {
extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl
extraSynopsisFunc = extraSynopsisFuncImpl
extraFlagsFunc = extraFlagsFuncImpl
extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl
executeExtraActions = executeExtraActionsImpl
printCustomActionOutput = printCustomActionOutputImpl
}
type extraCmdVars struct {
flagStartTime string
flagEndTime string
monthlyActiveUsers *billing.MonthlyActiveUsersResult
}
func extraActionsFlagsMapFuncImpl() map[string][]string {
return map[string][]string{
"monthly-active-users": {"start-time", "end-time"},
}
}
func extraSynopsisFuncImpl(c *Command) string {
switch c.Func {
case "monthly-active-users":
var in string
switch {
case strings.HasPrefix(c.Func, "start-time"):
in = "Get monthly active users, starting from this time (YYYY-MM format)."
case strings.HasPrefix(c.Func, "end-time"):
in = "Get monthly active users, ending at this time (YYYY-MM format)."
}
return wordwrap.WrapString(in, base.TermWidth)
default:
return ""
}
}
func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) {
flagsMap[c.Func] = append(flagsMap[c.Func], "start-time", "end-time")
f.StringVar(&base.StringVar{
Name: "start-time",
Target: &c.flagStartTime,
Usage: "Get monthly active users, starting from this time (YYYY-MM format).",
})
f.StringVar(&base.StringVar{
Name: "end-time",
Target: &c.flagEndTime,
Usage: "Get monthly active users, ending at this time (YYYY-MM format).",
})
}
func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]billing.Option) bool {
switch c.Func {
case "monthly-active-users":
if len(c.flagStartTime) != 0 {
*opts = append(*opts, billing.WithStartTime(c.flagStartTime))
}
if len(c.flagEndTime) != 0 {
*opts = append(*opts, billing.WithEndTime(c.flagEndTime))
}
}
return true
}
func executeExtraActionsImpl(c *Command, origResp *api.Response, origError error, billingClient *billing.Client, _ uint32, opts []billing.Option) (*api.Response, error) {
switch c.Func {
case "monthly-active-users":
var err error
c.monthlyActiveUsers, err = billingClient.MonthlyActiveUsers(c.Context, opts...)
if err != nil {
return nil, err
}
}
return origResp, origError
}
func printCustomActionOutputImpl(c *Command) (bool, error) {
switch c.Func {
case "monthly-active-users":
switch base.Format(c.UI) {
case "table":
items := c.monthlyActiveUsers.GetItems().([]*billing.ActiveUsers)
var ret []string
ret = append(ret, "Billing information:")
ret = append(ret, "")
for i := range items {
ret = append(ret,
fmt.Sprintf(" Count: %d", items[i].Count),
fmt.Sprintf(" Start Time: %s", items[i].StartTime),
fmt.Sprintf(" End Time: %s", items[i].EndTime),
"",
)
}
c.UI.Output(base.WrapForHelpText(ret))
return true, nil
case "json":
if ok := c.PrintJsonItem(c.monthlyActiveUsers.GetResponse()); !ok {
return false, fmt.Errorf("error formatting as JSON")
}
return true, nil
}
}
return false, nil
}
func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
var helpStr string
switch c.Func {
case "":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary billing [sub command] [options] [args]",
"",
" This command allows for collecting Boundary billing reports. Example:",
"",
" Monthly active users:",
"",
` $ boundary billing monthly-active-users`,
"",
" Please see the billing subcommand help for detailed usage information.",
})
case "monthly-active-users":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary billing monthly-active-users [options]",
"",
" This command allows for collecting active Boundary user reports, by month. Example:",
"",
" Monthly active users between September 2023 and February 2024:",
"",
` $ boundary billing monthly-active-users -start-time="2023-09" -end-time="2024-02"`,
"",
" Please see the billing subcommand help for detailed usage information.",
})
}
return helpStr + c.Flags().Help()
}
+1
View File
@@ -34,6 +34,7 @@ func HelpMap(resType string) map[string]func() string {
resource.AuthToken.String(): "at",
resource.AuthMethod.String(): "am",
resource.Account.String(): "a",
resource.Billing.String(): "b",
resource.Role.String(): "r",
resource.Group.String(): "g",
resource.User.String(): "u",
+12
View File
@@ -17,6 +17,9 @@ type cmdInfo struct {
// Standard actions (with standard parameters) used by this resource
StdActions []string
// HasCustomList indicates if there is a custom list action
HasCustomList bool
// HasExtraCommandVars controls whether to generate an embedded struct with
// extra command variables
HasExtraCommandVars bool
@@ -211,6 +214,15 @@ var inputStructs = map[string][]*cmdInfo{
Container: "Scope",
},
},
"billing": {
{
ResourceType: resource.Billing.String(),
Pkg: "billing",
HasCustomList: true,
HasExtraCommandVars: true,
HasExtraHelpFunc: true,
},
},
"credentialstores": {
{
ResourceType: resource.CredentialStore.String(),
+9 -3
View File
@@ -478,16 +478,19 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
}
var resp *api.Response
{{ if $input.StdActions -}}
var item *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}
{{ if hasAction .StdActions "list" }}
var items []*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}
{{ end }}
{{ end }}
{{ range $i, $action := $input.StdActions }}
{{ if ( not ( hasAction $input.SkipClientCallActions $action) ) }}
var {{ $action }}Result *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}{{ camelCase $action }}Result
{{ end }}
{{ end }}
{{ if $input.StdActions }}
switch c.Func {
{{ range $i, $action := $input.StdActions }}
{{ if eq $action "create" }}
@@ -538,8 +541,9 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
{{ end }}
{{ end }}
}
{{ end }}
resp, item, {{ if hasAction .StdActions "list" }}items, {{ end }}err = executeExtra{{ camelCase .SubActionPrefix }}Actions(c, resp, item, {{ if hasAction .StdActions "list" }}items, {{ end }}err, {{ .Pkg }}Client, version, opts)
resp, {{ if $input.StdActions }}item, {{ if hasAction .StdActions "list" }}items, {{ end }}{{ end }}err = executeExtra{{ camelCase .SubActionPrefix }}Actions(c, resp, {{ if $input.StdActions }}item, {{ if hasAction .StdActions "list" }}items, {{ end }}{{ end }}err, {{ .Pkg }}Client, version, opts)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
@@ -553,6 +557,7 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
return base.CommandSuccess
}
{{ if $input.StdActions }}
switch c.Func {
{{ range $i, $action := .StdActions }}
{{ if eq $action "delete" }}
@@ -595,6 +600,7 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
return base.CommandCliError
}
}
{{ end }}
return base.CommandSuccess
}
@@ -619,8 +625,8 @@ var (
extra{{ camelCase .SubActionPrefix }}SynopsisFunc = func(*{{ camelCase .SubActionPrefix }}Command) string { return "" }
extra{{ camelCase .SubActionPrefix }}FlagsFunc = func(*{{ camelCase .SubActionPrefix }}Command, *base.FlagSets, *base.FlagSet) {}
extra{{ camelCase .SubActionPrefix }}FlagsHandlingFunc = func(*{{ camelCase .SubActionPrefix }}Command, *base.FlagSets, *[]{{ .Pkg }}.Option) bool { return true }
executeExtra{{ camelCase .SubActionPrefix }}Actions = func(_ *{{ camelCase .SubActionPrefix }}Command, inResp *api.Response, inItem *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ if hasAction .StdActions "list" }}inItems []*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ end }}inErr error, _ *{{ .Pkg }}.Client, _ uint32, _ []{{ .Pkg }}.Option) (*api.Response, *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ if hasAction .StdActions "list" }}[]*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ end }}error) {
return inResp, inItem, {{ if hasAction .StdActions "list" }}inItems, {{ end }}inErr
executeExtra{{ camelCase .SubActionPrefix }}Actions = func(_ *{{ camelCase .SubActionPrefix }}Command, inResp *api.Response, {{ if $input.StdActions }}inItem *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ if hasAction .StdActions "list" }}inItems []*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ end }}{{ end }}inErr error, _ *{{ .Pkg }}.Client, _ uint32, _ []{{ .Pkg }}.Option) (*api.Response, {{ if $input.StdActions }}*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ if hasAction .StdActions "list" }}[]*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ end }}{{ end }}error) {
return inResp, {{ if $input.StdActions }}inItem, {{ if hasAction .StdActions "list" }}inItems, {{ end }}{{ end }}inErr
}
printCustom{{ camelCase .SubActionPrefix }}ActionOutput = func(*{{ camelCase .SubActionPrefix }}Command) (bool, error) { return false, nil }
)