diff --git a/go.mod b/go.mod index bb5c9bb..f326e9c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/temporalio/cli/cliext v0.0.0-20260602200703-8bb57b77ad55 go.temporal.io/api v1.62.13 - go.temporal.io/cloud-sdk v0.13.0 + go.temporal.io/cloud-sdk v0.14.1-0.20260616191445-e6c2bafd1bf5 go.temporal.io/sdk v1.44.1 go.temporal.io/sdk/contrib/envconfig v1.0.0 golang.org/x/oauth2 v0.36.0 diff --git a/go.sum b/go.sum index 68dd564..2547aa1 100644 --- a/go.sum +++ b/go.sum @@ -154,8 +154,8 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.temporal.io/api v1.62.13 h1:xMa8Nt5oAMX+LvlCJA44wjTCc1H09i2rG9poB1/xvH4= go.temporal.io/api v1.62.13/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= -go.temporal.io/cloud-sdk v0.13.0 h1:Yhh6TEQG7xZgn8/A7C9WcxM+LS08qXlITRicuo2zGOA= -go.temporal.io/cloud-sdk v0.13.0/go.mod h1:W2O9t9tvo3Q/LhGgYdj8JijWbN5C84os+cz/BadIHYI= +go.temporal.io/cloud-sdk v0.14.1-0.20260616191445-e6c2bafd1bf5 h1:Bg9roiU3npnMoItFLKgfsr/py7pCAUA0AE6EuUMhCx8= +go.temporal.io/cloud-sdk v0.14.1-0.20260616191445-e6c2bafd1bf5/go.mod h1:W2O9t9tvo3Q/LhGgYdj8JijWbN5C84os+cz/BadIHYI= go.temporal.io/sdk v1.44.1 h1:Mt2OZLZpqkzDIdg9YyQzO0Rb/HqCDnnqHlIAGAJ5gqM= go.temporal.io/sdk v1.44.1/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= go.temporal.io/sdk/contrib/envconfig v1.0.0 h1:1Q/swVgB4EW/p3k7rI9/4hpU4/DC57FSRbU90+UisXw= diff --git a/internal/cloudservice/mock/mock.go b/internal/cloudservice/mock/mock.go index c9d047c..8dfb12b 100644 --- a/internal/cloudservice/mock/mock.go +++ b/internal/cloudservice/mock/mock.go @@ -4023,6 +4023,89 @@ func (_c *MockCloudServiceClient_GetServiceAccount_Call) RunAndReturn(run func(c return _c } +// GetServiceAccountNamespaceAssignments provides a mock function for the type MockCloudServiceClient +func (_mock *MockCloudServiceClient) GetServiceAccountNamespaceAssignments(ctx context.Context, in *cloudservice.GetServiceAccountNamespaceAssignmentsRequest, opts ...grpc.CallOption) (*cloudservice.GetServiceAccountNamespaceAssignmentsResponse, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, in, opts) + } else { + tmpRet = _mock.Called(ctx, in) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for GetServiceAccountNamespaceAssignments") + } + + var r0 *cloudservice.GetServiceAccountNamespaceAssignmentsResponse + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *cloudservice.GetServiceAccountNamespaceAssignmentsRequest, ...grpc.CallOption) (*cloudservice.GetServiceAccountNamespaceAssignmentsResponse, error)); ok { + return returnFunc(ctx, in, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *cloudservice.GetServiceAccountNamespaceAssignmentsRequest, ...grpc.CallOption) *cloudservice.GetServiceAccountNamespaceAssignmentsResponse); ok { + r0 = returnFunc(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*cloudservice.GetServiceAccountNamespaceAssignmentsResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *cloudservice.GetServiceAccountNamespaceAssignmentsRequest, ...grpc.CallOption) error); ok { + r1 = returnFunc(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetServiceAccountNamespaceAssignments' +type MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call struct { + *mock.Call +} + +// GetServiceAccountNamespaceAssignments is a helper method to define mock.On call +// - ctx context.Context +// - in *cloudservice.GetServiceAccountNamespaceAssignmentsRequest +// - opts ...grpc.CallOption +func (_e *MockCloudServiceClient_Expecter) GetServiceAccountNamespaceAssignments(ctx interface{}, in interface{}, opts ...interface{}) *MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call { + return &MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call{Call: _e.mock.On("GetServiceAccountNamespaceAssignments", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call) Run(run func(ctx context.Context, in *cloudservice.GetServiceAccountNamespaceAssignmentsRequest, opts ...grpc.CallOption)) *MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *cloudservice.GetServiceAccountNamespaceAssignmentsRequest + if args[1] != nil { + arg1 = args[1].(*cloudservice.GetServiceAccountNamespaceAssignmentsRequest) + } + var arg2 []grpc.CallOption + var variadicArgs []grpc.CallOption + if len(args) > 2 { + variadicArgs = args[2].([]grpc.CallOption) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call) Return(getServiceAccountNamespaceAssignmentsResponse *cloudservice.GetServiceAccountNamespaceAssignmentsResponse, err error) *MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call { + _c.Call.Return(getServiceAccountNamespaceAssignmentsResponse, err) + return _c +} + +func (_c *MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call) RunAndReturn(run func(ctx context.Context, in *cloudservice.GetServiceAccountNamespaceAssignmentsRequest, opts ...grpc.CallOption) (*cloudservice.GetServiceAccountNamespaceAssignmentsResponse, error)) *MockCloudServiceClient_GetServiceAccountNamespaceAssignments_Call { + _c.Call.Return(run) + return _c +} + // GetServiceAccounts provides a mock function for the type MockCloudServiceClient func (_mock *MockCloudServiceClient) GetServiceAccounts(ctx context.Context, in *cloudservice.GetServiceAccountsRequest, opts ...grpc.CallOption) (*cloudservice.GetServiceAccountsResponse, error) { var tmpRet mock.Arguments @@ -4438,6 +4521,89 @@ func (_c *MockCloudServiceClient_GetUserGroupMembers_Call) RunAndReturn(run func return _c } +// GetUserGroupNamespaceAssignments provides a mock function for the type MockCloudServiceClient +func (_mock *MockCloudServiceClient) GetUserGroupNamespaceAssignments(ctx context.Context, in *cloudservice.GetUserGroupNamespaceAssignmentsRequest, opts ...grpc.CallOption) (*cloudservice.GetUserGroupNamespaceAssignmentsResponse, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, in, opts) + } else { + tmpRet = _mock.Called(ctx, in) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for GetUserGroupNamespaceAssignments") + } + + var r0 *cloudservice.GetUserGroupNamespaceAssignmentsResponse + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *cloudservice.GetUserGroupNamespaceAssignmentsRequest, ...grpc.CallOption) (*cloudservice.GetUserGroupNamespaceAssignmentsResponse, error)); ok { + return returnFunc(ctx, in, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *cloudservice.GetUserGroupNamespaceAssignmentsRequest, ...grpc.CallOption) *cloudservice.GetUserGroupNamespaceAssignmentsResponse); ok { + r0 = returnFunc(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*cloudservice.GetUserGroupNamespaceAssignmentsResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *cloudservice.GetUserGroupNamespaceAssignmentsRequest, ...grpc.CallOption) error); ok { + r1 = returnFunc(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUserGroupNamespaceAssignments' +type MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call struct { + *mock.Call +} + +// GetUserGroupNamespaceAssignments is a helper method to define mock.On call +// - ctx context.Context +// - in *cloudservice.GetUserGroupNamespaceAssignmentsRequest +// - opts ...grpc.CallOption +func (_e *MockCloudServiceClient_Expecter) GetUserGroupNamespaceAssignments(ctx interface{}, in interface{}, opts ...interface{}) *MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call { + return &MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call{Call: _e.mock.On("GetUserGroupNamespaceAssignments", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call) Run(run func(ctx context.Context, in *cloudservice.GetUserGroupNamespaceAssignmentsRequest, opts ...grpc.CallOption)) *MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *cloudservice.GetUserGroupNamespaceAssignmentsRequest + if args[1] != nil { + arg1 = args[1].(*cloudservice.GetUserGroupNamespaceAssignmentsRequest) + } + var arg2 []grpc.CallOption + var variadicArgs []grpc.CallOption + if len(args) > 2 { + variadicArgs = args[2].([]grpc.CallOption) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call) Return(getUserGroupNamespaceAssignmentsResponse *cloudservice.GetUserGroupNamespaceAssignmentsResponse, err error) *MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call { + _c.Call.Return(getUserGroupNamespaceAssignmentsResponse, err) + return _c +} + +func (_c *MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call) RunAndReturn(run func(ctx context.Context, in *cloudservice.GetUserGroupNamespaceAssignmentsRequest, opts ...grpc.CallOption) (*cloudservice.GetUserGroupNamespaceAssignmentsResponse, error)) *MockCloudServiceClient_GetUserGroupNamespaceAssignments_Call { + _c.Call.Return(run) + return _c +} + // GetUserGroups provides a mock function for the type MockCloudServiceClient func (_mock *MockCloudServiceClient) GetUserGroups(ctx context.Context, in *cloudservice.GetUserGroupsRequest, opts ...grpc.CallOption) (*cloudservice.GetUserGroupsResponse, error) { var tmpRet mock.Arguments @@ -4521,6 +4687,89 @@ func (_c *MockCloudServiceClient_GetUserGroups_Call) RunAndReturn(run func(ctx c return _c } +// GetUserNamespaceAssignments provides a mock function for the type MockCloudServiceClient +func (_mock *MockCloudServiceClient) GetUserNamespaceAssignments(ctx context.Context, in *cloudservice.GetUserNamespaceAssignmentsRequest, opts ...grpc.CallOption) (*cloudservice.GetUserNamespaceAssignmentsResponse, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, in, opts) + } else { + tmpRet = _mock.Called(ctx, in) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for GetUserNamespaceAssignments") + } + + var r0 *cloudservice.GetUserNamespaceAssignmentsResponse + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *cloudservice.GetUserNamespaceAssignmentsRequest, ...grpc.CallOption) (*cloudservice.GetUserNamespaceAssignmentsResponse, error)); ok { + return returnFunc(ctx, in, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *cloudservice.GetUserNamespaceAssignmentsRequest, ...grpc.CallOption) *cloudservice.GetUserNamespaceAssignmentsResponse); ok { + r0 = returnFunc(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*cloudservice.GetUserNamespaceAssignmentsResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *cloudservice.GetUserNamespaceAssignmentsRequest, ...grpc.CallOption) error); ok { + r1 = returnFunc(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCloudServiceClient_GetUserNamespaceAssignments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUserNamespaceAssignments' +type MockCloudServiceClient_GetUserNamespaceAssignments_Call struct { + *mock.Call +} + +// GetUserNamespaceAssignments is a helper method to define mock.On call +// - ctx context.Context +// - in *cloudservice.GetUserNamespaceAssignmentsRequest +// - opts ...grpc.CallOption +func (_e *MockCloudServiceClient_Expecter) GetUserNamespaceAssignments(ctx interface{}, in interface{}, opts ...interface{}) *MockCloudServiceClient_GetUserNamespaceAssignments_Call { + return &MockCloudServiceClient_GetUserNamespaceAssignments_Call{Call: _e.mock.On("GetUserNamespaceAssignments", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockCloudServiceClient_GetUserNamespaceAssignments_Call) Run(run func(ctx context.Context, in *cloudservice.GetUserNamespaceAssignmentsRequest, opts ...grpc.CallOption)) *MockCloudServiceClient_GetUserNamespaceAssignments_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *cloudservice.GetUserNamespaceAssignmentsRequest + if args[1] != nil { + arg1 = args[1].(*cloudservice.GetUserNamespaceAssignmentsRequest) + } + var arg2 []grpc.CallOption + var variadicArgs []grpc.CallOption + if len(args) > 2 { + variadicArgs = args[2].([]grpc.CallOption) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockCloudServiceClient_GetUserNamespaceAssignments_Call) Return(getUserNamespaceAssignmentsResponse *cloudservice.GetUserNamespaceAssignmentsResponse, err error) *MockCloudServiceClient_GetUserNamespaceAssignments_Call { + _c.Call.Return(getUserNamespaceAssignmentsResponse, err) + return _c +} + +func (_c *MockCloudServiceClient_GetUserNamespaceAssignments_Call) RunAndReturn(run func(ctx context.Context, in *cloudservice.GetUserNamespaceAssignmentsRequest, opts ...grpc.CallOption) (*cloudservice.GetUserNamespaceAssignmentsResponse, error)) *MockCloudServiceClient_GetUserNamespaceAssignments_Call { + _c.Call.Return(run) + return _c +} + // GetUsers provides a mock function for the type MockCloudServiceClient func (_mock *MockCloudServiceClient) GetUsers(ctx context.Context, in *cloudservice.GetUsersRequest, opts ...grpc.CallOption) (*cloudservice.GetUsersResponse, error) { var tmpRet mock.Arguments diff --git a/temporalcloudcli/commands.gen.go b/temporalcloudcli/commands.gen.go index 46f23f6..c3f841c 100644 --- a/temporalcloudcli/commands.gen.go +++ b/temporalcloudcli/commands.gen.go @@ -1856,7 +1856,10 @@ func NewCloudNamespaceCommand(cctx *CommandContext, parent *CloudCommand) *Cloud s.Command.AddCommand(&NewCloudNamespaceMtlsCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceRetentionCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceSearchAttributeCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceServiceAccountCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceTagCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceUserCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceUserGroupCommand(cctx, &s).Command) return &s } @@ -3954,6 +3957,55 @@ func NewCloudNamespaceSearchAttributeRenameCommand(cctx *CommandContext, parent return &s } +type CloudNamespaceServiceAccountCommand struct { + Parent *CloudNamespaceCommand + Command cobra.Command +} + +func NewCloudNamespaceServiceAccountCommand(cctx *CommandContext, parent *CloudNamespaceCommand) *CloudNamespaceServiceAccountCommand { + var s CloudNamespaceServiceAccountCommand + s.Parent = parent + s.Command.Use = "service-account" + s.Command.Short = "Inspect service accounts with access to a namespace" + s.Command.Long = "Commands for inspecting the service accounts that have access to a\nTemporal Cloud namespace." + s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewCloudNamespaceServiceAccountListCommand(cctx, &s).Command) + return &s +} + +type CloudNamespaceServiceAccountListCommand struct { + Parent *CloudNamespaceServiceAccountCommand + Command cobra.Command + ClientOptions + NamespaceOptions + PageSize int + PageToken string +} + +func NewCloudNamespaceServiceAccountListCommand(cctx *CommandContext, parent *CloudNamespaceServiceAccountCommand) *CloudNamespaceServiceAccountListCommand { + var s CloudNamespaceServiceAccountListCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "list [flags]" + s.Command.Short = "List service accounts with access to a namespace" + if hasHighlighting { + s.Command.Long = "List the service accounts that have access to a Temporal Cloud namespace,\nincluding both directly-assigned and inherited access.\n\nExample:\n\n\x1b[1mtemporal cloud namespace service-account list --namespace my-namespace.my-account\x1b[0m" + } else { + s.Command.Long = "List the service accounts that have access to a Temporal Cloud namespace,\nincluding both directly-assigned and inherited access.\n\nExample:\n\n```\ntemporal cloud namespace service-account list --namespace my-namespace.my-account\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().IntVar(&s.PageSize, "page-size", 0, "Number of service accounts to return per page. Use for paginated results.") + s.Command.Flags().StringVar(&s.PageToken, "page-token", "", "Token for retrieving the next page of results in a paginated list.") + s.ClientOptions.BuildFlags(s.Command.Flags()) + s.NamespaceOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type CloudNamespaceTagCommand struct { Parent *CloudNamespaceCommand Command cobra.Command @@ -4110,6 +4162,104 @@ func NewCloudNamespaceTagUpdateCommand(cctx *CommandContext, parent *CloudNamesp return &s } +type CloudNamespaceUserCommand struct { + Parent *CloudNamespaceCommand + Command cobra.Command +} + +func NewCloudNamespaceUserCommand(cctx *CommandContext, parent *CloudNamespaceCommand) *CloudNamespaceUserCommand { + var s CloudNamespaceUserCommand + s.Parent = parent + s.Command.Use = "user" + s.Command.Short = "Inspect users with access to a namespace" + s.Command.Long = "Commands for inspecting the users that have access to a Temporal Cloud\nnamespace." + s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewCloudNamespaceUserListCommand(cctx, &s).Command) + return &s +} + +type CloudNamespaceUserListCommand struct { + Parent *CloudNamespaceUserCommand + Command cobra.Command + ClientOptions + NamespaceOptions + PageSize int + PageToken string +} + +func NewCloudNamespaceUserListCommand(cctx *CommandContext, parent *CloudNamespaceUserCommand) *CloudNamespaceUserListCommand { + var s CloudNamespaceUserListCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "list [flags]" + s.Command.Short = "List users with access to a namespace" + if hasHighlighting { + s.Command.Long = "List the users that have access to a Temporal Cloud namespace, including\nboth directly-assigned and inherited access.\n\nExample:\n\n\x1b[1mtemporal cloud namespace user list --namespace my-namespace.my-account\x1b[0m" + } else { + s.Command.Long = "List the users that have access to a Temporal Cloud namespace, including\nboth directly-assigned and inherited access.\n\nExample:\n\n```\ntemporal cloud namespace user list --namespace my-namespace.my-account\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().IntVar(&s.PageSize, "page-size", 0, "Number of users to return per page. Use for paginated results.") + s.Command.Flags().StringVar(&s.PageToken, "page-token", "", "Token for retrieving the next page of results in a paginated list.") + s.ClientOptions.BuildFlags(s.Command.Flags()) + s.NamespaceOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type CloudNamespaceUserGroupCommand struct { + Parent *CloudNamespaceCommand + Command cobra.Command +} + +func NewCloudNamespaceUserGroupCommand(cctx *CommandContext, parent *CloudNamespaceCommand) *CloudNamespaceUserGroupCommand { + var s CloudNamespaceUserGroupCommand + s.Parent = parent + s.Command.Use = "user-group" + s.Command.Short = "Inspect user groups with access to a namespace" + s.Command.Long = "Commands for inspecting the user groups that have access to a Temporal\nCloud namespace." + s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewCloudNamespaceUserGroupListCommand(cctx, &s).Command) + return &s +} + +type CloudNamespaceUserGroupListCommand struct { + Parent *CloudNamespaceUserGroupCommand + Command cobra.Command + ClientOptions + NamespaceOptions + PageSize int + PageToken string +} + +func NewCloudNamespaceUserGroupListCommand(cctx *CommandContext, parent *CloudNamespaceUserGroupCommand) *CloudNamespaceUserGroupListCommand { + var s CloudNamespaceUserGroupListCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "list [flags]" + s.Command.Short = "List user groups with access to a namespace" + if hasHighlighting { + s.Command.Long = "List the user groups that have access to a Temporal Cloud namespace,\nincluding both directly-assigned and inherited access.\n\nExample:\n\n\x1b[1mtemporal cloud namespace user-group list --namespace my-namespace.my-account\x1b[0m" + } else { + s.Command.Long = "List the user groups that have access to a Temporal Cloud namespace,\nincluding both directly-assigned and inherited access.\n\nExample:\n\n```\ntemporal cloud namespace user-group list --namespace my-namespace.my-account\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().IntVar(&s.PageSize, "page-size", 0, "Number of user groups to return per page. Use for paginated results.") + s.Command.Flags().StringVar(&s.PageToken, "page-token", "", "Token for retrieving the next page of results in a paginated list.") + s.ClientOptions.BuildFlags(s.Command.Flags()) + s.NamespaceOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type CloudNexusCommand struct { Parent *CloudCommand Command cobra.Command diff --git a/temporalcloudcli/commands.namespace.access.go b/temporalcloudcli/commands.namespace.access.go new file mode 100644 index 0000000..a2e95b4 --- /dev/null +++ b/temporalcloudcli/commands.namespace.access.go @@ -0,0 +1,96 @@ +package temporalcloudcli + +import ( + cloudservice "go.temporal.io/cloud-sdk/api/cloudservice/v1" + identityv1 "go.temporal.io/cloud-sdk/api/identity/v1" + + "github.com/temporalio/cloud-cli/temporalcloudcli/internal/printer" +) + +// AIDEV-NOTE: These commands wrap the read-only Get*NamespaceAssignments RPCs +// added in cloud-sdk-go v0.14.x. They list the identities (users, service +// accounts, user groups) that have explicit access to a namespace. + +func (c *CloudNamespaceUserListCommand) run(cctx *CommandContext, _ []string) error { + client, err := cctx.GetCloudClient(c.ClientOptions) + if err != nil { + return err + } + res, err := client.GetUserNamespaceAssignments(cctx, &cloudservice.GetUserNamespaceAssignmentsRequest{ + Namespace: c.Namespace, + PageSize: int32(c.PageSize), + PageToken: c.PageToken, + }) + if err != nil { + return err + } + return cctx.Printer.PrintResourceList( + struct { + Users []*identityv1.UserNamespaceAssignment + NextPageToken string + }{ + Users: res.Users, + NextPageToken: res.NextPageToken, + }, + printer.PrintResourceOptions{ + Fields: []string{"Id", "Email", "NamespaceAccess"}, + }, + printer.TableOptions{}, + ) +} + +func (c *CloudNamespaceServiceAccountListCommand) run(cctx *CommandContext, _ []string) error { + client, err := cctx.GetCloudClient(c.ClientOptions) + if err != nil { + return err + } + res, err := client.GetServiceAccountNamespaceAssignments(cctx, &cloudservice.GetServiceAccountNamespaceAssignmentsRequest{ + Namespace: c.Namespace, + PageSize: int32(c.PageSize), + PageToken: c.PageToken, + }) + if err != nil { + return err + } + return cctx.Printer.PrintResourceList( + struct { + ServiceAccounts []*identityv1.ServiceAccountNamespaceAssignment + NextPageToken string + }{ + ServiceAccounts: res.ServiceAccounts, + NextPageToken: res.NextPageToken, + }, + printer.PrintResourceOptions{ + Fields: []string{"Id", "Email", "NamespaceAccess"}, + }, + printer.TableOptions{}, + ) +} + +func (c *CloudNamespaceUserGroupListCommand) run(cctx *CommandContext, _ []string) error { + client, err := cctx.GetCloudClient(c.ClientOptions) + if err != nil { + return err + } + res, err := client.GetUserGroupNamespaceAssignments(cctx, &cloudservice.GetUserGroupNamespaceAssignmentsRequest{ + Namespace: c.Namespace, + PageSize: int32(c.PageSize), + PageToken: c.PageToken, + }) + if err != nil { + return err + } + return cctx.Printer.PrintResourceList( + struct { + Groups []*identityv1.UserGroupNamespaceAssignment + NextPageToken string + }{ + Groups: res.Groups, + NextPageToken: res.NextPageToken, + }, + printer.PrintResourceOptions{ + Fields: []string{"Id", "Email", "NamespaceAccess"}, + }, + printer.TableOptions{}, + ) +} diff --git a/temporalcloudcli/commands.namespace.access_test.go b/temporalcloudcli/commands.namespace.access_test.go new file mode 100644 index 0000000..74ad07d --- /dev/null +++ b/temporalcloudcli/commands.namespace.access_test.go @@ -0,0 +1,261 @@ +package temporalcloudcli_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/mock" + cloudservice "go.temporal.io/cloud-sdk/api/cloudservice/v1" + identityv1 "go.temporal.io/cloud-sdk/api/identity/v1" + + cloudmock "github.com/temporalio/cloud-cli/internal/cloudservice/mock" + "github.com/temporalio/cloud-cli/temporalcloudcli" +) + +func TestNamespaceUserList(t *testing.T) { + type listOutput struct { + Users []*identityv1.UserNamespaceAssignment + NextPageToken string + } + apiErr := errors.New("api error") + + tests := []struct { + name string + cmd temporalcloudcli.CloudNamespaceUserListCommand + cloudClientExpectations func(*cloudmock.MockCloudServiceClient) + expectedOutputJson any + expectedErr string + }{ + { + name: "Success", + cmd: temporalcloudcli.CloudNamespaceUserListCommand{NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}}, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetUserNamespaceAssignments(mock.Anything, &cloudservice.GetUserNamespaceAssignmentsRequest{ + Namespace: "my-namespace.my-account", + }, mock.Anything). + Return(&cloudservice.GetUserNamespaceAssignmentsResponse{ + Users: []*identityv1.UserNamespaceAssignment{ + {Id: "user-1", Email: "alice@example.com", NamespaceAccess: &identityv1.NamespaceAccess{Permission: identityv1.NamespaceAccess_PERMISSION_WRITE}}, + {Id: "user-2", Email: "bob@example.com", InheritedAccess: true}, + }, + }, nil) + }, + expectedOutputJson: listOutput{ + Users: []*identityv1.UserNamespaceAssignment{ + {Id: "user-1", Email: "alice@example.com", NamespaceAccess: &identityv1.NamespaceAccess{Permission: identityv1.NamespaceAccess_PERMISSION_WRITE}}, + {Id: "user-2", Email: "bob@example.com", InheritedAccess: true}, + }, + }, + }, + { + name: "WithPagination", + cmd: temporalcloudcli.CloudNamespaceUserListCommand{ + NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}, + PageSize: 10, + PageToken: "tok-abc", + }, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetUserNamespaceAssignments(mock.Anything, &cloudservice.GetUserNamespaceAssignmentsRequest{ + Namespace: "my-namespace.my-account", + PageSize: 10, + PageToken: "tok-abc", + }, mock.Anything). + Return(&cloudservice.GetUserNamespaceAssignmentsResponse{ + Users: []*identityv1.UserNamespaceAssignment{{Id: "user-3", Email: "carol@example.com"}}, + NextPageToken: "tok-def", + }, nil) + }, + expectedOutputJson: listOutput{ + Users: []*identityv1.UserNamespaceAssignment{{Id: "user-3", Email: "carol@example.com"}}, + NextPageToken: "tok-def", + }, + }, + { + name: "ApiError", + cmd: temporalcloudcli.CloudNamespaceUserListCommand{NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}}, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetUserNamespaceAssignments(mock.Anything, mock.Anything, mock.Anything). + Return(nil, apiErr) + }, + expectedErr: "api error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + temporalcloudcli.TestCommand(t, &tt.cmd, temporalcloudcli.TestCommandOptions{ + CloudClientExpectations: tt.cloudClientExpectations, + JSONOutput: true, + ExpectedError: tt.expectedErr, + ExpectedOutputJson: tt.expectedOutputJson, + }) + }) + } +} + +func TestNamespaceServiceAccountList(t *testing.T) { + type listOutput struct { + ServiceAccounts []*identityv1.ServiceAccountNamespaceAssignment + NextPageToken string + } + apiErr := errors.New("api error") + + tests := []struct { + name string + cmd temporalcloudcli.CloudNamespaceServiceAccountListCommand + cloudClientExpectations func(*cloudmock.MockCloudServiceClient) + expectedOutputJson any + expectedErr string + }{ + { + name: "Success", + cmd: temporalcloudcli.CloudNamespaceServiceAccountListCommand{NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}}, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetServiceAccountNamespaceAssignments(mock.Anything, &cloudservice.GetServiceAccountNamespaceAssignmentsRequest{ + Namespace: "my-namespace.my-account", + }, mock.Anything). + Return(&cloudservice.GetServiceAccountNamespaceAssignmentsResponse{ + ServiceAccounts: []*identityv1.ServiceAccountNamespaceAssignment{ + {Id: "sa-1", Name: "ci-runner", NamespaceAccess: &identityv1.NamespaceAccess{Permission: identityv1.NamespaceAccess_PERMISSION_READ}}, + }, + }, nil) + }, + expectedOutputJson: listOutput{ + ServiceAccounts: []*identityv1.ServiceAccountNamespaceAssignment{ + {Id: "sa-1", Name: "ci-runner", NamespaceAccess: &identityv1.NamespaceAccess{Permission: identityv1.NamespaceAccess_PERMISSION_READ}}, + }, + }, + }, + { + name: "WithPagination", + cmd: temporalcloudcli.CloudNamespaceServiceAccountListCommand{ + NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}, + PageSize: 5, + PageToken: "tok-abc", + }, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetServiceAccountNamespaceAssignments(mock.Anything, &cloudservice.GetServiceAccountNamespaceAssignmentsRequest{ + Namespace: "my-namespace.my-account", + PageSize: 5, + PageToken: "tok-abc", + }, mock.Anything). + Return(&cloudservice.GetServiceAccountNamespaceAssignmentsResponse{ + ServiceAccounts: []*identityv1.ServiceAccountNamespaceAssignment{{Id: "sa-2", Name: "deploy-bot"}}, + NextPageToken: "tok-def", + }, nil) + }, + expectedOutputJson: listOutput{ + ServiceAccounts: []*identityv1.ServiceAccountNamespaceAssignment{{Id: "sa-2", Name: "deploy-bot"}}, + NextPageToken: "tok-def", + }, + }, + { + name: "ApiError", + cmd: temporalcloudcli.CloudNamespaceServiceAccountListCommand{NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}}, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetServiceAccountNamespaceAssignments(mock.Anything, mock.Anything, mock.Anything). + Return(nil, apiErr) + }, + expectedErr: "api error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + temporalcloudcli.TestCommand(t, &tt.cmd, temporalcloudcli.TestCommandOptions{ + CloudClientExpectations: tt.cloudClientExpectations, + JSONOutput: true, + ExpectedError: tt.expectedErr, + ExpectedOutputJson: tt.expectedOutputJson, + }) + }) + } +} + +func TestNamespaceUserGroupList(t *testing.T) { + type listOutput struct { + Groups []*identityv1.UserGroupNamespaceAssignment + NextPageToken string + } + apiErr := errors.New("api error") + + tests := []struct { + name string + cmd temporalcloudcli.CloudNamespaceUserGroupListCommand + cloudClientExpectations func(*cloudmock.MockCloudServiceClient) + expectedOutputJson any + expectedErr string + }{ + { + name: "Success", + cmd: temporalcloudcli.CloudNamespaceUserGroupListCommand{NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}}, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetUserGroupNamespaceAssignments(mock.Anything, &cloudservice.GetUserGroupNamespaceAssignmentsRequest{ + Namespace: "my-namespace.my-account", + }, mock.Anything). + Return(&cloudservice.GetUserGroupNamespaceAssignmentsResponse{ + Groups: []*identityv1.UserGroupNamespaceAssignment{ + {Id: "grp-1", DisplayName: "Platform", NamespaceAccess: &identityv1.NamespaceAccess{Permission: identityv1.NamespaceAccess_PERMISSION_ADMIN}}, + }, + }, nil) + }, + expectedOutputJson: listOutput{ + Groups: []*identityv1.UserGroupNamespaceAssignment{ + {Id: "grp-1", DisplayName: "Platform", NamespaceAccess: &identityv1.NamespaceAccess{Permission: identityv1.NamespaceAccess_PERMISSION_ADMIN}}, + }, + }, + }, + { + name: "WithPagination", + cmd: temporalcloudcli.CloudNamespaceUserGroupListCommand{ + NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}, + PageSize: 5, + PageToken: "tok-abc", + }, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetUserGroupNamespaceAssignments(mock.Anything, &cloudservice.GetUserGroupNamespaceAssignmentsRequest{ + Namespace: "my-namespace.my-account", + PageSize: 5, + PageToken: "tok-abc", + }, mock.Anything). + Return(&cloudservice.GetUserGroupNamespaceAssignmentsResponse{ + Groups: []*identityv1.UserGroupNamespaceAssignment{{Id: "grp-2", DisplayName: "SRE"}}, + NextPageToken: "tok-def", + }, nil) + }, + expectedOutputJson: listOutput{ + Groups: []*identityv1.UserGroupNamespaceAssignment{{Id: "grp-2", DisplayName: "SRE"}}, + NextPageToken: "tok-def", + }, + }, + { + name: "ApiError", + cmd: temporalcloudcli.CloudNamespaceUserGroupListCommand{NamespaceOptions: temporalcloudcli.NamespaceOptions{Namespace: "my-namespace.my-account"}}, + cloudClientExpectations: func(c *cloudmock.MockCloudServiceClient) { + c.EXPECT(). + GetUserGroupNamespaceAssignments(mock.Anything, mock.Anything, mock.Anything). + Return(nil, apiErr) + }, + expectedErr: "api error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + temporalcloudcli.TestCommand(t, &tt.cmd, temporalcloudcli.TestCommandOptions{ + CloudClientExpectations: tt.cloudClientExpectations, + JSONOutput: true, + ExpectedError: tt.expectedErr, + ExpectedOutputJson: tt.expectedOutputJson, + }) + }) + } +} diff --git a/temporalcloudcli/commands.yml b/temporalcloudcli/commands.yml index 89bdb8c..1f9ee08 100644 --- a/temporalcloudcli/commands.yml +++ b/temporalcloudcli/commands.yml @@ -1638,6 +1638,98 @@ commands: must be greater than 0 when --capacity-mode is 'provisioned'. Ignored when --capacity-mode is 'on_demand'. + # Namespace access commands (who has access to a namespace) + - name: cloud namespace user + summary: Inspect users with access to a namespace + description: | + Commands for inspecting the users that have access to a Temporal Cloud + namespace. + has-init: false + - name: cloud namespace user list + summary: List users with access to a namespace + description: | + List the users that have access to a Temporal Cloud namespace, including + both directly-assigned and inherited access. + + Example: + + ``` + temporal cloud namespace user list --namespace my-namespace.my-account + ``` + has-init: false + option-sets: + - client + - namespace + options: + - name: page-size + type: int + description: | + Number of users to return per page. Use for paginated results. + - name: page-token + type: string + description: | + Token for retrieving the next page of results in a paginated list. + - name: cloud namespace service-account + summary: Inspect service accounts with access to a namespace + description: | + Commands for inspecting the service accounts that have access to a + Temporal Cloud namespace. + has-init: false + - name: cloud namespace service-account list + summary: List service accounts with access to a namespace + description: | + List the service accounts that have access to a Temporal Cloud namespace, + including both directly-assigned and inherited access. + + Example: + + ``` + temporal cloud namespace service-account list --namespace my-namespace.my-account + ``` + has-init: false + option-sets: + - client + - namespace + options: + - name: page-size + type: int + description: | + Number of service accounts to return per page. Use for paginated results. + - name: page-token + type: string + description: | + Token for retrieving the next page of results in a paginated list. + - name: cloud namespace user-group + summary: Inspect user groups with access to a namespace + description: | + Commands for inspecting the user groups that have access to a Temporal + Cloud namespace. + has-init: false + - name: cloud namespace user-group list + summary: List user groups with access to a namespace + description: | + List the user groups that have access to a Temporal Cloud namespace, + including both directly-assigned and inherited access. + + Example: + + ``` + temporal cloud namespace user-group list --namespace my-namespace.my-account + ``` + has-init: false + option-sets: + - client + - namespace + options: + - name: page-size + type: int + description: | + Number of user groups to return per page. Use for paginated results. + - name: page-token + type: string + description: | + Token for retrieving the next page of results in a paginated list. + # Nexus endpoint commands - name: cloud nexus summary: Manage Temporal Cloud Nexus Operations