diff --git a/api/managedgroups/managedgroups.gen.go b/api/managedgroups/managedgroups.gen.go new file mode 100644 index 000000000..94741999b --- /dev/null +++ b/api/managedgroups/managedgroups.gen.go @@ -0,0 +1,323 @@ +// Code generated by "make api"; DO NOT EDIT. +package managedgroups + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/scopes" +) + +type ManagedGroup struct { + Id string `json:"id,omitempty"` + Scope *scopes.ScopeInfo `json:"scope,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + CreatedTime time.Time `json:"created_time,omitempty"` + UpdatedTime time.Time `json:"updated_time,omitempty"` + Version uint32 `json:"version,omitempty"` + Type string `json:"type,omitempty"` + AuthMethodId string `json:"auth_method_id,omitempty"` + Attributes map[string]interface{} `json:"attributes,omitempty"` + MemberIds []string `json:"member_ids,omitempty"` + AuthorizedActions []string `json:"authorized_actions,omitempty"` + + response *api.Response +} + +type ManagedGroupReadResult struct { + Item *ManagedGroup + response *api.Response +} + +func (n ManagedGroupReadResult) GetItem() interface{} { + return n.Item +} + +func (n ManagedGroupReadResult) GetResponse() *api.Response { + return n.response +} + +type ( + ManagedGroupCreateResult = ManagedGroupReadResult + ManagedGroupUpdateResult = ManagedGroupReadResult +) + +type ManagedGroupDeleteResult struct { + response *api.Response +} + +// GetItem will always be nil for ManagedGroupDeleteResult +func (n ManagedGroupDeleteResult) GetItem() interface{} { + return nil +} + +func (n ManagedGroupDeleteResult) GetResponse() *api.Response { + return n.response +} + +type ManagedGroupListResult struct { + Items []*ManagedGroup + response *api.Response +} + +func (n ManagedGroupListResult) GetItems() interface{} { + return n.Items +} + +func (n ManagedGroupListResult) GetResponse() *api.Response { + return n.response +} + +// Client is a client for this collection +type Client struct { + client *api.Client +} + +// Creates a new client for this collection. The submitted API client is cloned; +// modifications to it after generating this client will not have effect. If you +// need to make changes to the underlying API client, use ApiClient() to access +// it. +func NewClient(c *api.Client) *Client { + return &Client{client: c.Clone()} +} + +// ApiClient returns the underlying API client +func (c *Client) ApiClient() *api.Client { + return c.client +} + +func (c *Client) Create(ctx context.Context, authMethodId string, opt ...Option) (*ManagedGroupCreateResult, error) { + if authMethodId == "" { + return nil, fmt.Errorf("empty authMethodId value passed into Create request") + } + + opts, apiOpts := getOpts(opt...) + + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts.postMap["auth_method_id"] = authMethodId + + req, err := c.client.NewRequest(ctx, "POST", "managed-groups", opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Create request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during Create call: %w", err) + } + + target := new(ManagedGroupCreateResult) + target.Item = new(ManagedGroup) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Create response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) Read(ctx context.Context, managedGroupId string, opt ...Option) (*ManagedGroupReadResult, error) { + if managedGroupId == "" { + return nil, fmt.Errorf("empty managedGroupId value passed into Read request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("managed-groups/%s", managedGroupId), nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Read request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error performing client request during Read call: %w", err) + } + + target := new(ManagedGroupReadResult) + target.Item = new(ManagedGroup) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Read response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) Update(ctx context.Context, managedGroupId string, version uint32, opt ...Option) (*ManagedGroupUpdateResult, error) { + if managedGroupId == "" { + return nil, fmt.Errorf("empty managedGroupId value passed into Update request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") + } + existingTarget, existingErr := c.Read(ctx, managedGroupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("managed-groups/%s", managedGroupId), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Update request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during Update call: %w", err) + } + + target := new(ManagedGroupUpdateResult) + target.Item = new(ManagedGroup) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Update response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) Delete(ctx context.Context, managedGroupId string, opt ...Option) (*ManagedGroupDeleteResult, error) { + if managedGroupId == "" { + return nil, fmt.Errorf("empty managedGroupId value passed into Delete request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("managed-groups/%s", managedGroupId), nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Delete request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during Delete call: %w", err) + } + + apiErr, err := resp.Decode(nil) + if err != nil { + return nil, fmt.Errorf("error decoding Delete response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + + target := &ManagedGroupDeleteResult{ + response: resp, + } + return target, nil +} + +func (c *Client) List(ctx context.Context, authMethodId string, opt ...Option) (*ManagedGroupListResult, error) { + if authMethodId == "" { + return nil, fmt.Errorf("empty authMethodId value passed into List request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + opts.queryMap["auth_method_id"] = authMethodId + + req, err := c.client.NewRequest(ctx, "GET", "managed-groups", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + target := new(ManagedGroupListResult) + apiErr, err := resp.Decode(target) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} diff --git a/api/managedgroups/oidc_managed_group_attributes.gen.go b/api/managedgroups/oidc_managed_group_attributes.gen.go new file mode 100644 index 000000000..73eba7540 --- /dev/null +++ b/api/managedgroups/oidc_managed_group_attributes.gen.go @@ -0,0 +1,6 @@ +// Code generated by "make api"; DO NOT EDIT. +package managedgroups + +type OidcManagedGroupAttributes struct { + Filter string `json:"filter,omitempty"` +} diff --git a/api/managedgroups/option.gen.go b/api/managedgroups/option.gen.go new file mode 100644 index 000000000..1a65ba439 --- /dev/null +++ b/api/managedgroups/option.gen.go @@ -0,0 +1,123 @@ +package managedgroups + +import ( + "strings" + + "github.com/hashicorp/boundary/api" +) + +// Option is a func that sets optional attributes for a call. This does not need +// to be used directly, but instead option arguments are built from the +// functions in this package. WithX options set a value to that given in the +// argument; DefaultX options indicate that the value should be set to its +// default. When an API call is made options are processed in ther order they +// appear in the function call, so for a given argument X, a succession of WithX +// or DefaultX calls will result in the last call taking effect. +type Option func(*options) + +type options struct { + postMap map[string]interface{} + queryMap map[string]string + withAutomaticVersioning bool + withSkipCurlOutput bool + withFilter string +} + +func getDefaultOptions() options { + return options{ + postMap: make(map[string]interface{}), + queryMap: make(map[string]string), + } +} + +func getOpts(opt ...Option) (options, []api.Option) { + opts := getDefaultOptions() + for _, o := range opt { + if o != nil { + o(&opts) + } + } + var apiOpts []api.Option + if opts.withSkipCurlOutput { + apiOpts = append(apiOpts, api.WithSkipCurlOutput(true)) + } + if opts.withFilter != "" { + opts.queryMap["filter"] = opts.withFilter + } + return opts, apiOpts +} + +// If set, and if the version is zero during an update, the API will perform a +// fetch to get the current version of the resource and populate it during the +// update call. This is convenient but opens up the possibility for subtle +// order-of-modification issues, so use carefully. +func WithAutomaticVersioning(enable bool) Option { + return func(o *options) { + o.withAutomaticVersioning = enable + } +} + +// WithSkipCurlOutput tells the API to not use the current call for cURL output. +// Useful for when we need to look up versions. +func WithSkipCurlOutput(skip bool) Option { + return func(o *options) { + o.withSkipCurlOutput = true + } +} + +// WithFilter tells the API to filter the items returned using the provided +// filter term. The filter should be in a format supported by +// hashicorp/go-bexpr. +func WithFilter(filter string) Option { + return func(o *options) { + o.withFilter = strings.TrimSpace(filter) + } +} + +func WithAttributes(inAttributes map[string]interface{}) Option { + return func(o *options) { + o.postMap["attributes"] = inAttributes + } +} + +func DefaultAttributes() Option { + return func(o *options) { + o.postMap["attributes"] = nil + } +} + +func WithDescription(inDescription string) Option { + return func(o *options) { + o.postMap["description"] = inDescription + } +} + +func DefaultDescription() Option { + return func(o *options) { + o.postMap["description"] = nil + } +} + +func WithOidcManagedGroupFilter(inFilter string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["filter"] = inFilter + o.postMap["attributes"] = val + } +} + +func WithName(inName string) Option { + return func(o *options) { + o.postMap["name"] = inName + } +} + +func DefaultName() Option { + return func(o *options) { + o.postMap["name"] = nil + } +} diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index 0c3821a19..060c9c6e4 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hostcatalogs" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hostsets" + "github.com/hashicorp/boundary/internal/gen/controller/api/resources/managedgroups" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/roles" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/sessions" @@ -87,6 +88,10 @@ type structInfo struct { // conveyed within the item itself extraOptions []fieldInfo + // fieldOverrides allows overriding some field behavior without making them + // "new" fields like with extraOptions + fieldOverrides []fieldInfo + // createResponseTypes controls for which structs response types are created createResponseTypes bool @@ -298,6 +303,34 @@ var inputStructs = []*structInfo{ versionEnabled: true, createResponseTypes: true, }, + // Managed Groups + { + inProto: &managedgroups.OidcManagedGroupAttributes{}, + outFile: "managedgroups/oidc_managed_group_attributes.gen.go", + subtypeName: "OidcManagedGroup", + fieldOverrides: []fieldInfo{ + { + Name: "Filter", + SkipDefault: true, + }, + }, + }, + { + inProto: &managedgroups.ManagedGroup{}, + outFile: "managedgroups/managedgroups.gen.go", + templates: []*template.Template{ + clientTemplate, + createTemplate, + readTemplate, + updateTemplate, + deleteTemplate, + listTemplate, + }, + pathArgs: []string{"managed-group"}, + parentTypeName: "auth-method", + versionEnabled: true, + createResponseTypes: true, + }, // Auth Tokens { inProto: &authtokens.AuthToken{}, diff --git a/internal/api/genapi/templates.go b/internal/api/genapi/templates.go index 5277e9ea7..2429ff6ea 100644 --- a/internal/api/genapi/templates.go +++ b/internal/api/genapi/templates.go @@ -123,6 +123,19 @@ func fillTemplates() { } optionsMap[input.Package] = optionMap } + // Override some defined options + if len(in.fieldOverrides) > 0 && optionsMap != nil { + for _, override := range in.fieldOverrides { + inOpts := optionsMap[input.Package] + if inOpts != nil { + if override.SkipDefault { + fieldInfo := inOpts[override.Name] + fieldInfo.SkipDefault = true + inOpts[override.Name] = fieldInfo + } + } + } + } outFile, err := filepath.Abs(fmt.Sprintf("%s/%s", os.Getenv("API_GEN_BASEPATH"), in.outFile)) if err != nil { diff --git a/internal/auth/oidc/repository_managed_group_members.go b/internal/auth/oidc/repository_managed_group_members.go index bdf9b3a93..b2b9fc8e2 100644 --- a/internal/auth/oidc/repository_managed_group_members.go +++ b/internal/auth/oidc/repository_managed_group_members.go @@ -214,3 +214,28 @@ func (r *Repository) ListManagedGroupMembershipsByMember(ctx context.Context, wi } return mgs, nil } + +// ListManagedGroupMembershipsByGroup lists managed group memberships via the +// group ID and supports WithLimit option. +func (r *Repository) ListManagedGroupMembershipsByGroup(ctx context.Context, withGroupId string, opt ...Option) ([]*ManagedGroupMemberAccount, error) { + const op = "oidc.(Repository).ListManagedGroupMembershipsByGroup" + if withGroupId == "" { + return nil, errors.New(errors.InvalidParameter, op, "missing managed group id") + } + opts := getOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + reader := r.reader + if opts.withReader != nil { + reader = opts.withReader + } + var mgs []*ManagedGroupMemberAccount + err := reader.SearchWhere(ctx, &mgs, "managed_group_id = ?", []interface{}{withGroupId}, db.WithLimit(limit)) + if err != nil { + return nil, errors.Wrap(err, op) + } + return mgs, nil +} diff --git a/internal/auth/oidc/repository_managed_group_members_test.go b/internal/auth/oidc/repository_managed_group_members_test.go index 0062bf5ba..cbd502813 100644 --- a/internal/auth/oidc/repository_managed_group_members_test.go +++ b/internal/auth/oidc/repository_managed_group_members_test.go @@ -258,6 +258,18 @@ func Test_ManagedGroupMemberships(t *testing.T) { // been in the finalMgs map, and when they are all removed there // should be nothing left. for _, mship := range memberships { + // Randomly check a few to ensure the MembershipsByGroup function works + members, err := repo.ListManagedGroupMembershipsByGroup(ctx, mship.ManagedGroupId) + require.NoError(err) + require.NotEmpty(members) + var found bool + for _, v := range members { + if v.MemberId == tt.account.GetPublicId() { + found = true + break + } + } + assert.True(found) assert.Contains(finalMgs, mship.ManagedGroupId) delete(finalMgs, mship.ManagedGroupId) } diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index e84dfa8b7..9cd8fdc75 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/commands/hostscmd" "github.com/hashicorp/boundary/internal/cmd/commands/hostsetscmd" "github.com/hashicorp/boundary/internal/cmd/commands/logout" + "github.com/hashicorp/boundary/internal/cmd/commands/managedgroupscmd" "github.com/hashicorp/boundary/internal/cmd/commands/rolescmd" "github.com/hashicorp/boundary/internal/cmd/commands/scopescmd" "github.com/hashicorp/boundary/internal/cmd/commands/server" @@ -546,6 +547,54 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, nil }, + "managed-groups": func() (cli.Command, error) { + return &managedgroupscmd.Command{ + Command: base.NewCommand(ui), + }, nil + }, + "managed-groups read": func() (cli.Command, error) { + return &managedgroupscmd.Command{ + Command: base.NewCommand(ui), + Func: "read", + }, nil + }, + "managed-groups delete": func() (cli.Command, error) { + return &managedgroupscmd.Command{ + Command: base.NewCommand(ui), + Func: "delete", + }, nil + }, + "managed-groups list": func() (cli.Command, error) { + return &managedgroupscmd.Command{ + Command: base.NewCommand(ui), + Func: "list", + }, nil + }, + "managed-groups create": func() (cli.Command, error) { + return &managedgroupscmd.Command{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "managed-groups create oidc": func() (cli.Command, error) { + return &managedgroupscmd.OidcCommand{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "managed-groups update": func() (cli.Command, error) { + return &managedgroupscmd.Command{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + "managed-groups update oidc": func() (cli.Command, error) { + return &managedgroupscmd.OidcCommand{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + "roles": func() (cli.Command, error) { return &rolescmd.Command{ Command: base.NewCommand(ui), diff --git a/internal/cmd/commands/accountscmd/accounts.gen.go b/internal/cmd/commands/accountscmd/accounts.gen.go index 7f951afcb..1a8e8b766 100644 --- a/internal/cmd/commands/accountscmd/accounts.gen.go +++ b/internal/cmd/commands/accountscmd/accounts.gen.go @@ -103,7 +103,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "account", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "account", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/accountscmd/oidc_accounts.gen.go b/internal/cmd/commands/accountscmd/oidc_accounts.gen.go index 659d4a8c0..ec8b1573f 100644 --- a/internal/cmd/commands/accountscmd/oidc_accounts.gen.go +++ b/internal/cmd/commands/accountscmd/oidc_accounts.gen.go @@ -91,7 +91,7 @@ func (c *OidcCommand) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "oidc-type account", flagsOidcMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "oidc-type account", flagsOidcMap, c.Func) extraOidcFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/accountscmd/password_accounts.gen.go b/internal/cmd/commands/accountscmd/password_accounts.gen.go index 49ada7e0e..0a8e502ba 100644 --- a/internal/cmd/commands/accountscmd/password_accounts.gen.go +++ b/internal/cmd/commands/accountscmd/password_accounts.gen.go @@ -91,7 +91,7 @@ func (c *PasswordCommand) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "password-type account", flagsPasswordMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "password-type account", flagsPasswordMap, c.Func) extraPasswordFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/authmethodscmd/authmethods.gen.go b/internal/cmd/commands/authmethodscmd/authmethods.gen.go index 186a291a8..41a667133 100644 --- a/internal/cmd/commands/authmethodscmd/authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/authmethods.gen.go @@ -101,7 +101,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "auth method", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "auth method", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go b/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go index eeabfd01c..544769b1e 100644 --- a/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go @@ -91,7 +91,7 @@ func (c *OidcCommand) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "oidc-type auth method", flagsOidcMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "oidc-type auth method", flagsOidcMap, c.Func) extraOidcFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go b/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go index 41aa2fb24..6dc05928a 100644 --- a/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go @@ -91,7 +91,7 @@ func (c *PasswordCommand) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "password-type auth method", flagsPasswordMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "password-type auth method", flagsPasswordMap, c.Func) extraPasswordFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/authtokenscmd/authtokens.gen.go b/internal/cmd/commands/authtokenscmd/authtokens.gen.go index e2bcbd5fb..7fe2828e2 100644 --- a/internal/cmd/commands/authtokenscmd/authtokens.gen.go +++ b/internal/cmd/commands/authtokenscmd/authtokens.gen.go @@ -101,7 +101,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "auth token", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "auth token", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/groupscmd/groups.gen.go b/internal/cmd/commands/groupscmd/groups.gen.go index 92b452eac..016100e8f 100644 --- a/internal/cmd/commands/groupscmd/groups.gen.go +++ b/internal/cmd/commands/groupscmd/groups.gen.go @@ -113,7 +113,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "group", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "group", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go index 2b9b14ab7..c2739db6a 100644 --- a/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go +++ b/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go @@ -101,7 +101,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "host catalog", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "host catalog", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go index 708153ce2..54ffa11c0 100644 --- a/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go +++ b/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go @@ -89,7 +89,7 @@ func (c *StaticCommand) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "static-type host catalog", flagsStaticMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "static-type host catalog", flagsStaticMap, c.Func) extraStaticFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/hostscmd/hosts.gen.go b/internal/cmd/commands/hostscmd/hosts.gen.go index c741b89ab..329d7bf8d 100644 --- a/internal/cmd/commands/hostscmd/hosts.gen.go +++ b/internal/cmd/commands/hostscmd/hosts.gen.go @@ -101,7 +101,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "host", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "host", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/hostscmd/static_hosts.gen.go b/internal/cmd/commands/hostscmd/static_hosts.gen.go index 44c96265c..f981916c3 100644 --- a/internal/cmd/commands/hostscmd/static_hosts.gen.go +++ b/internal/cmd/commands/hostscmd/static_hosts.gen.go @@ -91,7 +91,7 @@ func (c *StaticCommand) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "static-type host", flagsStaticMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "static-type host", flagsStaticMap, c.Func) extraStaticFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/hostsetscmd/hostsets.gen.go b/internal/cmd/commands/hostsetscmd/hostsets.gen.go index d21f2b078..90757f031 100644 --- a/internal/cmd/commands/hostsetscmd/hostsets.gen.go +++ b/internal/cmd/commands/hostsetscmd/hostsets.gen.go @@ -103,7 +103,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "host set", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "host set", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go b/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go index ecf825633..30e0684fe 100644 --- a/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go +++ b/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go @@ -89,7 +89,7 @@ func (c *StaticCommand) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "static-type host set", flagsStaticMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "static-type host set", flagsStaticMap, c.Func) extraStaticFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/managedgroupscmd/funcs.go b/internal/cmd/commands/managedgroupscmd/funcs.go new file mode 100644 index 000000000..212cbd14a --- /dev/null +++ b/internal/cmd/commands/managedgroupscmd/funcs.go @@ -0,0 +1,138 @@ +package managedgroupscmd + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/managedgroups" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *Command) printListTable(items []*managedgroups.ManagedGroup) string { + if len(items) == 0 { + return "No managed groups found" + } + var output []string + output = []string{ + "", + "Managed Group 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 item.Version > 0 { + output = append(output, + fmt.Sprintf(" Version: %d", item.Version), + ) + } + if item.Type != "" { + output = append(output, + fmt.Sprintf(" Type: %s", item.Type), + ) + } + if item.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", item.Name), + ) + } + if item.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", item.Description), + ) + } + if len(item.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, item.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(result api.GenericResult) string { + item := result.GetItem().(*managedgroups.ManagedGroup) + nonAttributeMap := map[string]interface{}{} + if item.Id != "" { + nonAttributeMap["ID"] = item.Id + } + if item.Version != 0 { + nonAttributeMap["Version"] = item.Version + } + if item.Type != "" { + nonAttributeMap["Type"] = item.Type + } + if !item.CreatedTime.IsZero() { + nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123) + } + if !item.UpdatedTime.IsZero() { + nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123) + } + if item.AuthMethodId != "" { + nonAttributeMap["Auth Method ID"] = item.AuthMethodId + } + if item.Name != "" { + nonAttributeMap["Name"] = item.Name + } + if item.Description != "" { + nonAttributeMap["Description"] = item.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) + + ret := []string{ + "", + "Managed Group 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.MemberIds) > 0 { + ret = append(ret, + "", + " Member IDs:", + base.WrapSlice(4, item.MemberIds), + ) + } + + if len(item.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, item.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +var keySubstMap = map[string]string{ + "filter": "Filter", +} diff --git a/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go b/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go new file mode 100644 index 000000000..74b2f801f --- /dev/null +++ b/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go @@ -0,0 +1,278 @@ +// Code generated by "make api"; DO NOT EDIT. +package managedgroupscmd + +import ( + "errors" + "fmt" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/managedgroups" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/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 +} + +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 := "managed group" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("managed group") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = helpMap["base"]() + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"auth-method-id", "filter"}, +} + +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, "managed group", 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", "update": + return cli.RunResultHelp + + } + + c.plural = "managed group" + switch c.Func { + case "list": + c.plural = "managed groups" + } + + 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 []managedgroups.Option + + if strutil.StrListContains(flagsMap[c.Func], "auth-method-id") { + switch c.Func { + case "list": + if c.FlagAuthMethodId == "" { + c.PrintCliError(errors.New("AuthMethod ID must be passed in via -auth-method-id or BOUNDARY_AUTH_METHOD_ID")) + return base.CommandUserError + } + } + } + + client, err := c.Client() + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error())) + return base.CommandCliError + } + managedgroupsClient := managedgroups.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, managedgroups.DefaultName()) + default: + opts = append(opts, managedgroups.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, managedgroups.DefaultDescription()) + default: + opts = append(opts, managedgroups.WithDescription(c.FlagDescription)) + } + + if c.FlagFilter != "" { + opts = append(opts, managedgroups.WithFilter(c.FlagFilter)) + } + + var version uint32 + + if ok := extraFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = managedgroupsClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + result, err = managedgroupsClient.Delete(c.Context, c.FlagId, opts...) + + case "list": + listResult, err = managedgroupsClient.List(c.Context, c.FlagAuthMethodId, opts...) + + } + + result, err = executeExtraActions(c, result, err, managedgroupsClient, version, opts) + + if err != nil { + 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 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + + case "table": + c.UI.Output("The delete operation completed successfully.") + } + + return base.CommandSuccess + + case "list": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItems(listResult); !ok { + return base.CommandCliError + } + + case "table": + listedItems := listResult.GetItems().([]*managedgroups.ManagedGroup) + c.UI.Output(c.printListTable(listedItems)) + } + + return base.CommandSuccess + + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(result)) + + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +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, *[]managedgroups.Option) bool { return true } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *managedgroups.Client, _ uint32, _ []managedgroups.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/managedgroupscmd/oidc_funcs.go b/internal/cmd/commands/managedgroupscmd/oidc_funcs.go new file mode 100644 index 000000000..a9f28de53 --- /dev/null +++ b/internal/cmd/commands/managedgroupscmd/oidc_funcs.go @@ -0,0 +1,95 @@ +package managedgroupscmd + +import ( + "fmt" + + "github.com/hashicorp/boundary/api/managedgroups" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/go-bexpr" +) + +const ( + filterFlagName = "filter" +) + +func init() { + extraOidcFlagsFunc = extraOidcFlagsFuncImpl + extraOidcActionsFlagsMapFunc = extraOidcActionsFlagsMapFuncImpl + extraOidcFlagsHandlingFunc = extraOidcFlagsHandlingFuncImpl +} + +func extraOidcActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {filterFlagName}, + "update": {filterFlagName}, + } +} + +type extraOidcCmdVars struct { + flagFilter string +} + +func (c *OidcCommand) extraOidcHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary managed-groups create oidc [options] [args]", + "", + " Create a oidc-type managed group. Example:", + "", + ` $ boundary managed-groups create oidc -filter '"/token/email" == "doe@example.com"' -description "Oidc managed group for ProdOps"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary managed-groups update oidc [options] [args]", + "", + " Update a oidc-type managed group given its ID. Example:", + "", + ` $ boundary managed-groups update oidc -id acctoidc_1234567890 -name "devops" -description "Oidc managed group for DevOps"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraOidcFlagsFuncImpl(c *OidcCommand, _ *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsOidcMap[c.Func] { + switch name { + case "filter": + f.StringVar(&base.StringVar{ + Name: filterFlagName, + Target: &c.flagFilter, + Usage: "The filter defining the criteria against which an accounts's OIDC token and/or userinfo will be evaluated to determined membership at login time.", + }) + } + } +} + +func extraOidcFlagsHandlingFuncImpl(c *OidcCommand, _ *base.FlagSets, opts *[]managedgroups.Option) bool { + switch c.flagFilter { + case "null": + c.UI.Error("Filter must be defined, and cannot be cleared.") + return false + + case "": + if c.Func == "create" { + c.UI.Error("The -filter flag must be provided when creating a managed group.") + return false + } + + default: + _, err := bexpr.CreateEvaluator(c.flagFilter) + if err != nil { + c.UI.Error(fmt.Sprintf("Error when parsing filter to check validity: %v", err)) + return false + } + *opts = append(*opts, managedgroups.WithAttributes(map[string]interface{}{filterFlagName: c.flagFilter})) + } + + return true +} diff --git a/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go b/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go new file mode 100644 index 000000000..98e04e911 --- /dev/null +++ b/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go @@ -0,0 +1,239 @@ +// Code generated by "make api"; DO NOT EDIT. +package managedgroupscmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/managedgroups" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initOidcFlags() { + flagsOnce.Do(func() { + extraFlags := extraOidcActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsOidcMap[k] = append(flagsOidcMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*OidcCommand)(nil) + _ cli.CommandAutocomplete = (*OidcCommand)(nil) +) + +type OidcCommand struct { + *base.Command + + Func string + + plural string + + extraOidcCmdVars +} + +func (c *OidcCommand) AutocompleteArgs() complete.Predictor { + initOidcFlags() + return complete.PredictAnything +} + +func (c *OidcCommand) AutocompleteFlags() complete.Flags { + initOidcFlags() + return c.Flags().Completions() +} + +func (c *OidcCommand) Synopsis() string { + if extra := extraOidcSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "managed group" + + synopsisStr = fmt.Sprintf("%s %s", "oidc-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *OidcCommand) Help() string { + initOidcFlags() + + var helpStr string + helpMap := common.HelpMap("managed group") + + switch c.Func { + default: + + helpStr = c.extraOidcHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsOidcMap = map[string][]string{ + + "create": {"auth-method-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *OidcCommand) Flags() *base.FlagSets { + if len(flagsOidcMap[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, "oidc-type managed group", flagsOidcMap, c.Func) + + extraOidcFlagsFunc(c, set, f) + + return set +} + +func (c *OidcCommand) Run(args []string) int { + initOidcFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "oidc-type managed group" + switch c.Func { + case "list": + c.plural = "oidc-type managed groups" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsOidcMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []managedgroups.Option + + if strutil.StrListContains(flagsOidcMap[c.Func], "auth-method-id") { + switch c.Func { + case "create": + if c.FlagAuthMethodId == "" { + c.PrintCliError(errors.New("AuthMethod ID must be passed in via -auth-method-id or BOUNDARY_AUTH_METHOD_ID")) + return base.CommandUserError + } + } + } + + client, err := c.Client() + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error())) + return base.CommandCliError + } + managedgroupsClient := managedgroups.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, managedgroups.DefaultName()) + default: + opts = append(opts, managedgroups.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, managedgroups.DefaultDescription()) + default: + opts = append(opts, managedgroups.WithDescription(c.FlagDescription)) + } + + if c.FlagFilter != "" { + opts = append(opts, managedgroups.WithFilter(c.FlagFilter)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, managedgroups.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ok := extraOidcFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = managedgroupsClient.Create(c.Context, c.FlagAuthMethodId, opts...) + + case "update": + result, err = managedgroupsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraOidcActions(c, result, err, managedgroupsClient, version, opts) + + if err != nil { + 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 + } + + output, err := printCustomOidcActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(result)) + + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +var ( + extraOidcActionsFlagsMapFunc = func() map[string][]string { return nil } + extraOidcSynopsisFunc = func(*OidcCommand) string { return "" } + extraOidcFlagsFunc = func(*OidcCommand, *base.FlagSets, *base.FlagSet) {} + extraOidcFlagsHandlingFunc = func(*OidcCommand, *base.FlagSets, *[]managedgroups.Option) bool { return true } + executeExtraOidcActions = func(_ *OidcCommand, inResult api.GenericResult, inErr error, _ *managedgroups.Client, _ uint32, _ []managedgroups.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomOidcActionOutput = func(*OidcCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/rolescmd/roles.gen.go b/internal/cmd/commands/rolescmd/roles.gen.go index b36f0cbfd..680c9b7ae 100644 --- a/internal/cmd/commands/rolescmd/roles.gen.go +++ b/internal/cmd/commands/rolescmd/roles.gen.go @@ -113,7 +113,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "role", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "role", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/scopescmd/scopes.gen.go b/internal/cmd/commands/scopescmd/scopes.gen.go index dc09630a3..2ddbe47fa 100644 --- a/internal/cmd/commands/scopescmd/scopes.gen.go +++ b/internal/cmd/commands/scopescmd/scopes.gen.go @@ -113,7 +113,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "scope", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "scope", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/sessionscmd/sessions.gen.go b/internal/cmd/commands/sessionscmd/sessions.gen.go index bfd254d6e..f1f26d845 100644 --- a/internal/cmd/commands/sessionscmd/sessions.gen.go +++ b/internal/cmd/commands/sessionscmd/sessions.gen.go @@ -96,7 +96,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "session", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "session", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/targetscmd/targets.gen.go b/internal/cmd/commands/targetscmd/targets.gen.go index a8ac4ebe7..4070e9b25 100644 --- a/internal/cmd/commands/targetscmd/targets.gen.go +++ b/internal/cmd/commands/targetscmd/targets.gen.go @@ -104,7 +104,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "target", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "target", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/targetscmd/tcp_targets.gen.go b/internal/cmd/commands/targetscmd/tcp_targets.gen.go index aaf06169e..977795104 100644 --- a/internal/cmd/commands/targetscmd/tcp_targets.gen.go +++ b/internal/cmd/commands/targetscmd/tcp_targets.gen.go @@ -91,7 +91,7 @@ func (c *TcpCommand) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "tcp-type target", flagsTcpMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "tcp-type target", flagsTcpMap, c.Func) extraTcpFlagsFunc(c, set, f) diff --git a/internal/cmd/commands/userscmd/users.gen.go b/internal/cmd/commands/userscmd/users.gen.go index ae7fff215..775811b43 100644 --- a/internal/cmd/commands/userscmd/users.gen.go +++ b/internal/cmd/commands/userscmd/users.gen.go @@ -113,7 +113,7 @@ func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "user", flagsMap[c.Func]) + common.PopulateCommonFlags(c.Command, f, "user", flagsMap, c.Func) extraFlagsFunc(c, set, f) diff --git a/internal/cmd/common/flags.go b/internal/cmd/common/flags.go index 693a2bd6a..d2977f74d 100644 --- a/internal/cmd/common/flags.go +++ b/internal/cmd/common/flags.go @@ -7,8 +7,8 @@ import ( "github.com/posener/complete" ) -func PopulateCommonFlags(c *base.Command, f *base.FlagSet, resourceType string, flagNames []string) { - for _, name := range flagNames { +func PopulateCommonFlags(c *base.Command, f *base.FlagSet, resourceType string, flagNames map[string][]string, command string) { + for _, name := range flagNames[command] { switch name { case "scope-id": f.StringVar(&base.StringVar{ @@ -71,12 +71,18 @@ func PopulateCommonFlags(c *base.Command, f *base.FlagSet, resourceType string, Target: &c.FlagRecursive, Usage: "If set, the list operation will be applied recursively into child scopes, if supported by the type.", }) - case "filter": - f.StringVar(&base.StringVar{ - Name: "filter", - Target: &c.FlagFilter, - Usage: "If set, the list operation will be filtered before being returned. The filter operates against each item in the list. Using single quotes is recommended as filters contain double quotes. See https://www.boundaryproject.io/docs/concepts/filtering/resource-listing for details.", - }) + } + } + if command == "list" { + for _, name := range flagNames[command] { + switch name { + case "filter": + f.StringVar(&base.StringVar{ + Name: "filter", + Target: &c.FlagFilter, + Usage: "If set, the list operation will be filtered before being returned. The filter operates against each item in the list. Using single quotes is recommended as filters contain double quotes. See https://www.boundaryproject.io/docs/concepts/filtering/resource-listing for details.", + }) + } } } } diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index 88edb129c..9b902d53e 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -251,6 +251,32 @@ var inputStructs = map[string][]*cmdInfo{ VersionedActions: []string{"update"}, }, }, + "managedgroups": { + { + ResourceType: resource.ManagedGroup.String(), + Pkg: "managedgroups", + StdActions: []string{"read", "delete", "list"}, + IsAbstractType: true, + Container: "AuthMethod", + HasId: true, + HasName: true, + HasDescription: true, + }, + { + ResourceType: resource.ManagedGroup.String(), + Pkg: "managedgroups", + StdActions: []string{"create", "update"}, + SubActionPrefix: "oidc", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + Container: "AuthMethod", + HasDescription: true, + VersionedActions: []string{"update"}, + }, + }, "roles": { { ResourceType: resource.Role.String(), diff --git a/internal/cmd/gencli/templates.go b/internal/cmd/gencli/templates.go index 50b6cd35b..0f1e463a1 100644 --- a/internal/cmd/gencli/templates.go +++ b/internal/cmd/gencli/templates.go @@ -195,7 +195,7 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "{{ if .SubActionPrefix }}{{ .SubActionPrefix }}-type {{ end }}{{ lowerSpaceCase .ResourceType }}", flags{{ camelCase .SubActionPrefix }}Map[c.Func]) + common.PopulateCommonFlags(c.Command, f, "{{ if .SubActionPrefix }}{{ .SubActionPrefix }}-type {{ end }}{{ lowerSpaceCase .ResourceType }}", flags{{ camelCase .SubActionPrefix }}Map, c.Func) extra{{ camelCase .SubActionPrefix }}FlagsFunc(c, set, f) diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 1d34c0216..231bdbda2 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -3200,6 +3200,14 @@ "type": "object", "description": "The attributes that are applicable for the specific ManagedGroup type." }, + "member_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output only. The IDs of the current set of members (accounts) that are associated with this ManagedGroup.", + "readOnly": true + }, "authorized_actions": { "type": "array", "items": { diff --git a/internal/gen/controller/api/resources/managedgroups/managed_group.pb.go b/internal/gen/controller/api/resources/managedgroups/managed_group.pb.go index 1a6891c78..73ddd729e 100644 --- a/internal/gen/controller/api/resources/managedgroups/managed_group.pb.go +++ b/internal/gen/controller/api/resources/managedgroups/managed_group.pb.go @@ -52,6 +52,8 @@ type ManagedGroup struct { AuthMethodId string `protobuf:"bytes,90,opt,name=auth_method_id,proto3" json:"auth_method_id,omitempty"` // The attributes that are applicable for the specific ManagedGroup type. Attributes *structpb.Struct `protobuf:"bytes,100,opt,name=attributes,proto3" json:"attributes,omitempty"` + // Output only. The IDs of the current set of members (accounts) that are associated with this ManagedGroup. + MemberIds []string `protobuf:"bytes,110,rep,name=member_ids,proto3" json:"member_ids,omitempty"` // Output only. The available actions on this resource for this user. AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` } @@ -158,6 +160,13 @@ func (x *ManagedGroup) GetAttributes() *structpb.Struct { return nil } +func (x *ManagedGroup) GetMemberIds() []string { + if x != nil { + return x.MemberIds + } + return nil +} + func (x *ManagedGroup) GetAuthorizedActions() []string { if x != nil { return x.AuthorizedActions @@ -235,7 +244,7 @@ var file_controller_api_resources_managedgroups_v1_managed_group_proto_rawDesc = 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd5, 0x04, 0x0a, 0x0c, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf5, 0x04, 0x0a, 0x0c, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, @@ -269,7 +278,9 @@ var file_controller_api_resources_managedgroups_v1_managed_group_proto_rawDesc = 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, - 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12, + 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, + 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x6e, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x59, 0x0a, diff --git a/internal/proto/local/controller/api/resources/managedgroups/v1/managed_group.proto b/internal/proto/local/controller/api/resources/managedgroups/v1/managed_group.proto index 716bf64a9..618fc499c 100644 --- a/internal/proto/local/controller/api/resources/managedgroups/v1/managed_group.proto +++ b/internal/proto/local/controller/api/resources/managedgroups/v1/managed_group.proto @@ -43,6 +43,9 @@ message ManagedGroup { // The attributes that are applicable for the specific ManagedGroup type. google.protobuf.Struct attributes = 100 [(custom_options.v1.generate_sdk_option) = true]; + // Output only. The IDs of the current set of members (accounts) that are associated with this ManagedGroup. + repeated string member_ids = 110 [json_name="member_ids"]; + // Output only. The available actions on this resource for this user. repeated string authorized_actions = 300 [json_name="authorized_actions"]; } diff --git a/internal/servers/controller/handlers/managed_groups/managed_group_service.go b/internal/servers/controller/handlers/managed_groups/managed_group_service.go index 37b94d089..af605735d 100644 --- a/internal/servers/controller/handlers/managed_groups/managed_group_service.go +++ b/internal/servers/controller/handlers/managed_groups/managed_group_service.go @@ -145,7 +145,7 @@ func (s Service) GetManagedGroup(ctx context.Context, req *pbs.GetManagedGroupRe if authResults.Error != nil { return nil, authResults.Error } - mg, err := s.getFromRepo(ctx, req.GetId()) + mg, memberIds, err := s.getFromRepo(ctx, req.GetId()) if err != nil { return nil, err } @@ -163,6 +163,9 @@ func (s Service) GetManagedGroup(ctx context.Context, req *pbs.GetManagedGroupRe if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, mg.GetPublicId(), IdActions[auth.SubtypeFromId(mg.GetPublicId())]).Strings())) } + if outputFields.Has(globals.MemberIdsField) { + outputOpts = append(outputOpts, handlers.WithMemberIds(memberIds)) + } item, err := toProto(ctx, mg, outputOpts...) if err != nil { @@ -266,26 +269,37 @@ func (s Service) DeleteManagedGroup(ctx context.Context, req *pbs.DeleteManagedG return nil, nil } -func (s Service) getFromRepo(ctx context.Context, id string) (auth.ManagedGroup, error) { +func (s Service) getFromRepo(ctx context.Context, id string) (auth.ManagedGroup, []string, error) { var out auth.ManagedGroup + var memberIds []string switch auth.SubtypeFromId(id) { case auth.OidcSubtype: repo, err := s.oidcRepoFn() if err != nil { - return nil, err + return nil, nil, err } mg, err := repo.LookupManagedGroup(ctx, id) if err != nil { if errors.IsNotFoundError(err) { - return nil, handlers.NotFoundErrorf("ManagedGroup %q doesn't exist.", id) + return nil, nil, handlers.NotFoundErrorf("ManagedGroup %q doesn't exist.", id) + } + return nil, nil, err + } + ids, err := repo.ListManagedGroupMembershipsByGroup(ctx, mg.GetPublicId()) + if err != nil { + return nil, nil, err + } + if len(ids) > 0 { + memberIds = make([]string, len(ids)) + for i, v := range ids { + memberIds[i] = v.MemberId } - return nil, err } out = mg default: - return nil, handlers.NotFoundErrorf("Unrecognized id.") + return nil, nil, handlers.NotFoundErrorf("Unrecognized id.") } - return out, nil + return out, memberIds, nil } func (s Service) createOidcInRepo(ctx context.Context, am auth.AuthMethod, item *pb.ManagedGroup) (*oidc.ManagedGroup, error) { @@ -529,6 +543,9 @@ func toProto(ctx context.Context, in auth.ManagedGroup, opt ...handlers.Option) if outputFields.Has(globals.AuthorizedActionsField) { out.AuthorizedActions = opts.WithAuthorizedActions } + if outputFields.Has(globals.MemberIdsField) { + out.MemberIds = opts.WithMemberIds + } switch i := in.(type) { case *oidc.ManagedGroup: if outputFields.Has(globals.TypeField) { diff --git a/internal/servers/controller/handlers/managed_groups/managed_group_service_test.go b/internal/servers/controller/handlers/managed_groups/managed_group_service_test.go index 1b7b42274..d8168c9d4 100644 --- a/internal/servers/controller/handlers/managed_groups/managed_group_service_test.go +++ b/internal/servers/controller/handlers/managed_groups/managed_group_service_test.go @@ -106,18 +106,33 @@ func TestGet(t *testing.T) { oidc.WithSigningAlgs(oidc.RS256), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]), ) + oidcA := oidc.TestAccount(t, conn, oidcAm, "test-subject") omg := oidc.TestManagedGroup(t, conn, oidcAm, testFakeManagedGroupFilter) + // Set up managed group before getting. First get the current + // managed group to make sure we have the right version, then ensure + // the account is a member so we can test that return value. + oidcRepo, err := oidcRepoFn() + require.NoError(t, err) + currMg, err := oidcRepo.LookupManagedGroup(ctx, omg.GetPublicId()) + require.NoError(t, err) + _, _, err = oidcRepo.SetManagedGroupMemberships(ctx, oidcAm, oidcA, []*oidc.ManagedGroup{currMg}) + require.NoError(t, err) + // Fetch the group once more to get the updated time + currMg, err = oidcRepo.LookupManagedGroup(ctx, omg.GetPublicId()) + require.NoError(t, err) + oidcWireManagedGroup := pb.ManagedGroup{ Id: omg.GetPublicId(), AuthMethodId: omg.GetAuthMethodId(), CreatedTime: omg.GetCreateTime().GetTimestamp(), - UpdatedTime: omg.GetUpdateTime().GetTimestamp(), + UpdatedTime: currMg.GetUpdateTime().GetTimestamp(), Scope: &scopepb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, - Version: 1, + Version: currMg.Version, Type: "oidc", Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{"filter": structpb.NewStringValue(omg.GetFilter())}}, AuthorizedActions: oidcAuthorizedActions, + MemberIds: []string{oidcA.GetPublicId()}, } cases := []struct { diff --git a/internal/servers/controller/handlers/option_test.go b/internal/servers/controller/handlers/option_test.go index cdeecfbe2..6f5eb5dd3 100644 --- a/internal/servers/controller/handlers/option_test.go +++ b/internal/servers/controller/handlers/option_test.go @@ -72,4 +72,16 @@ func Test_GetOpts(t *testing.T) { opts = GetOpts(WithManagedGroupIds(out)) require.Equal(out, opts.WithManagedGroupIds) }) + t.Run("WithMemberIds", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + opts := GetOpts() + assert.Nil(opts.WithMemberIds) + + out := []string{"foobar"} + + opts = GetOpts(WithMemberIds(out)) + require.Equal(out, opts.WithMemberIds) + }) } diff --git a/internal/servers/controller/handlers/options.go b/internal/servers/controller/handlers/options.go index 59af1ed97..3735a5ddd 100644 --- a/internal/servers/controller/handlers/options.go +++ b/internal/servers/controller/handlers/options.go @@ -31,6 +31,7 @@ type options struct { WithAuthorizedActions []string WithAuthorizedCollectionActions map[string]*structpb.ListValue WithManagedGroupIds []string + WithMemberIds []string } func getDefaultOptions() options { @@ -100,3 +101,11 @@ func WithManagedGroupIds(ids []string) Option { o.WithManagedGroupIds = ids } } + +// WithMemberIds provides an option when creating responses to include the given +// member IDs if allowed +func WithMemberIds(ids []string) Option { + return func(o *options) { + o.WithMemberIds = ids + } +}