Skip to content

Commit 4754eb9

Browse files
Merge branch 'main' into khisakuni/emit-default
2 parents 3251c8e + 7468c3e commit 4754eb9

6 files changed

Lines changed: 1195 additions & 20 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ require (
1919
go.temporal.io/sdk/contrib/envconfig v1.0.0
2020
golang.org/x/oauth2 v0.36.0
2121
golang.org/x/term v0.43.0
22-
google.golang.org/grpc v1.81.1
22+
google.golang.org/grpc v1.82.1
2323
)
2424

2525
require (

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:
227227
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
228228
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
229229
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
230-
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
231-
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
230+
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
231+
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
232232
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
233233
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
234234
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

temporalcloudcli/commands.gen.go

Lines changed: 188 additions & 7 deletions
Large diffs are not rendered by default.

temporalcloudcli/commands.service_account.go

Lines changed: 220 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ func (c *CloudServiceAccountCreateCommand) run(cctx *CommandContext, _ []string)
4848
if err != nil {
4949
return err
5050
}
51+
projectAccesses, err := parseProjectAccesses(c.ProjectAccess)
52+
if err != nil {
53+
return err
54+
}
5155
if c.Command.Flags().Changed("custom-role") {
5256
if accountAccess == nil {
5357
return errors.New("--custom-role requires --account-role; a principal must have a account role")
@@ -72,6 +76,7 @@ func (c *CloudServiceAccountCreateCommand) run(cctx *CommandContext, _ []string)
7276
Access: &identityv1.Access{
7377
AccountAccess: accountAccess,
7478
NamespaceAccesses: namespaceAccesses,
79+
ProjectAccesses: projectAccesses,
7580
},
7681
},
7782
AsyncOperationId: c.AsyncOperationId,
@@ -109,10 +114,47 @@ func (c *CloudServiceAccountCreateNamespaceScopedCommand) run(cctx *CommandConte
109114
return cctx.GetPoller(client, c.AsyncOperationOptions).HandleCreateAsyncOperationResponse(cctx, resp, err)
110115
}
111116

117+
func (c *CloudServiceAccountCreateProjectScopedCommand) run(cctx *CommandContext, _ []string) error {
118+
projectAccess, err := parseProjectRole(c.ProjectRole)
119+
if err != nil {
120+
return err
121+
}
122+
namespaceAccesses, err := parseNamespaceAccesses(c.NamespaceAccess)
123+
if err != nil {
124+
return err
125+
}
126+
yes, err := cctx.GetPrompter().PromptYes("Create")
127+
if err != nil {
128+
return err
129+
}
130+
if !yes {
131+
return errors.New("Aborting create.")
132+
}
133+
client, err := cctx.GetCloudClient(c.ClientOptions)
134+
if err != nil {
135+
return err
136+
}
137+
resp, err := client.CreateServiceAccount(cctx, &cloudservice.CreateServiceAccountRequest{
138+
Spec: &identityv1.ServiceAccountSpec{
139+
Name: c.Name,
140+
Description: c.Description,
141+
ProjectScopedAccess: &identityv1.ProjectScopedAccess{
142+
ProjectId: c.ProjectId,
143+
Access: projectAccess,
144+
NamespaceAccesses: namespaceAccesses,
145+
},
146+
},
147+
AsyncOperationId: c.AsyncOperationId,
148+
})
149+
return cctx.GetPoller(client, c.AsyncOperationOptions).HandleCreateAsyncOperationResponse(cctx, resp, err)
150+
}
151+
112152
func (c *CloudServiceAccountUpdateCommand) run(cctx *CommandContext, _ []string) error {
113153
// Validate input formats before any API call.
114154
accountRoleChanged := c.Command.Flags().Changed("account-role")
115155
namespaceAccessChanged := c.Command.Flags().Changed("namespace-access")
156+
projectAccessChanged := c.Command.Flags().Changed("project-access")
157+
projectRoleChanged := c.Command.Flags().Changed("project-role")
116158
namespacePermissionChanged := c.Command.Flags().Changed("namespace-permission")
117159
customRoleChanged := c.Command.Flags().Changed("custom-role")
118160

@@ -126,6 +168,16 @@ func (c *CloudServiceAccountUpdateCommand) run(cctx *CommandContext, _ []string)
126168
return err
127169
}
128170
}
171+
if projectAccessChanged {
172+
if _, err := applyProjectAccessChanges(nil, c.ProjectAccess); err != nil {
173+
return err
174+
}
175+
}
176+
if projectRoleChanged {
177+
if _, err := parseProjectRole(c.ProjectRole); err != nil {
178+
return err
179+
}
180+
}
129181
if namespacePermissionChanged {
130182
if _, ok := namespacePermissionNames[c.NamespacePermission]; !ok {
131183
return fmt.Errorf("invalid namespace permission %q: must be one of admin, write, read", c.NamespacePermission)
@@ -144,12 +196,22 @@ func (c *CloudServiceAccountUpdateCommand) run(cctx *CommandContext, _ []string)
144196
newSpec := proto.Clone(sa.Spec).(*identityv1.ServiceAccountSpec)
145197

146198
isNamespaceScoped := newSpec.NamespaceScopedAccess != nil
199+
isProjectScoped := newSpec.ProjectScopedAccess != nil
147200

148-
if isNamespaceScoped && (accountRoleChanged || namespaceAccessChanged || customRoleChanged) {
149-
return errors.New("--account-role, --namespace-access, and --custom-role are not valid for namespace-scoped service accounts")
201+
if isNamespaceScoped &&
202+
(accountRoleChanged || namespaceAccessChanged || projectAccessChanged || projectRoleChanged || customRoleChanged) {
203+
return errors.New("--account-role, --namespace-access, --project-access, --project-role, " +
204+
"and --custom-role are not valid for namespace-scoped service accounts")
205+
}
206+
if isProjectScoped && (accountRoleChanged || projectAccessChanged || namespacePermissionChanged || customRoleChanged) {
207+
return errors.New("--account-role, --project-access, --namespace-permission, " +
208+
"and --custom-role are not valid for project-scoped service accounts")
209+
}
210+
if !isProjectScoped && projectRoleChanged {
211+
return errors.New("--project-role is only valid for project-scoped service accounts")
150212
}
151213
if !isNamespaceScoped && namespacePermissionChanged {
152-
return errors.New("--namespace-permission is not valid for account-scoped service accounts")
214+
return errors.New("--namespace-permission is only valid for namespace-scoped service accounts")
153215
}
154216

155217
if c.Command.Flags().Changed("name") {
@@ -170,14 +232,40 @@ func (c *CloudServiceAccountUpdateCommand) run(cctx *CommandContext, _ []string)
170232
newSpec.Access.AccountAccess = newAccountAccess
171233
}
172234
if namespaceAccessChanged {
235+
if isProjectScoped {
236+
newSpec.ProjectScopedAccess.NamespaceAccesses, err = applyNamespaceAccessChanges(
237+
newSpec.ProjectScopedAccess.NamespaceAccesses,
238+
c.NamespaceAccess,
239+
)
240+
} else {
241+
if newSpec.Access == nil {
242+
newSpec.Access = &identityv1.Access{}
243+
}
244+
newSpec.Access.NamespaceAccesses, err = applyNamespaceAccessChanges(newSpec.Access.NamespaceAccesses, c.NamespaceAccess)
245+
}
246+
if err != nil {
247+
return err
248+
}
249+
}
250+
if projectAccessChanged {
173251
if newSpec.Access == nil {
174252
newSpec.Access = &identityv1.Access{}
175253
}
176-
newSpec.Access.NamespaceAccesses, err = applyNamespaceAccessChanges(newSpec.Access.NamespaceAccesses, c.NamespaceAccess)
254+
newSpec.Access.ProjectAccesses, err = applyProjectAccessChanges(newSpec.Access.ProjectAccesses, c.ProjectAccess)
177255
if err != nil {
178256
return err
179257
}
180258
}
259+
if projectRoleChanged {
260+
projectAccess, err := parseProjectRole(c.ProjectRole)
261+
if err != nil {
262+
return err
263+
}
264+
if newSpec.ProjectScopedAccess.Access == nil {
265+
newSpec.ProjectScopedAccess.Access = &identityv1.ProjectAccess{}
266+
}
267+
newSpec.ProjectScopedAccess.Access = projectAccess
268+
}
181269
if customRoleChanged {
182270
if newSpec.Access == nil || newSpec.Access.AccountAccess == nil {
183271
return errors.New("service account has no account access; assign an account role with --account-role first")
@@ -215,6 +303,107 @@ func (c *CloudServiceAccountUpdateCommand) run(cctx *CommandContext, _ []string)
215303
return cctx.GetPoller(client, c.AsyncOperationOptions).HandleUpdateOperation(cctx, resp, err)
216304
}
217305

306+
func (c *CloudServiceAccountSetProjectAccessCommand) run(cctx *CommandContext, _ []string) error {
307+
projectAccess, err := parseProjectRole(c.ProjectRole)
308+
if err != nil {
309+
return err
310+
}
311+
client, err := cctx.GetCloudClient(c.ClientOptions)
312+
if err != nil {
313+
return err
314+
}
315+
res, err := client.GetServiceAccount(cctx, &cloudservice.GetServiceAccountRequest{ServiceAccountId: c.ServiceAccountId})
316+
if err != nil {
317+
return err
318+
}
319+
yes, err := cctx.GetPrompter().PromptYes("Set project access")
320+
if err != nil {
321+
return err
322+
}
323+
if !yes {
324+
return errors.New("Aborting set.")
325+
}
326+
rv := res.ServiceAccount.ResourceVersion
327+
if c.ResourceVersion != "" {
328+
rv = c.ResourceVersion
329+
}
330+
resp, err := client.SetServiceAccountProjectAccess(cctx, &cloudservice.SetServiceAccountProjectAccessRequest{
331+
ProjectId: c.ProjectId,
332+
ServiceAccountId: c.ServiceAccountId,
333+
Access: projectAccess,
334+
ResourceVersion: rv,
335+
AsyncOperationId: c.AsyncOperationId,
336+
})
337+
return cctx.GetPoller(client, c.AsyncOperationOptions).HandleUpdateOperation(cctx, resp, err)
338+
}
339+
340+
func (c *CloudServiceAccountRemoveProjectAccessCommand) run(cctx *CommandContext, _ []string) error {
341+
client, err := cctx.GetCloudClient(c.ClientOptions)
342+
if err != nil {
343+
return err
344+
}
345+
res, err := client.GetServiceAccount(cctx, &cloudservice.GetServiceAccountRequest{ServiceAccountId: c.ServiceAccountId})
346+
if err != nil {
347+
return err
348+
}
349+
yes, err := cctx.GetPrompter().PromptYes("Remove project access")
350+
if err != nil {
351+
return err
352+
}
353+
if !yes {
354+
return errors.New("Aborting remove.")
355+
}
356+
rv := res.ServiceAccount.ResourceVersion
357+
if c.ResourceVersion != "" {
358+
rv = c.ResourceVersion
359+
}
360+
resp, err := client.SetServiceAccountProjectAccess(cctx, &cloudservice.SetServiceAccountProjectAccessRequest{
361+
ProjectId: c.ProjectId,
362+
ServiceAccountId: c.ServiceAccountId,
363+
ResourceVersion: rv,
364+
AsyncOperationId: c.AsyncOperationId,
365+
})
366+
return cctx.GetPoller(client, c.AsyncOperationOptions).HandleUpdateOperation(cctx, resp, err)
367+
}
368+
369+
func (c *CloudProjectServiceAccountListCommand) run(cctx *CommandContext, _ []string) error {
370+
return listServiceAccountProjectAssignments(cctx, c.ProjectId, c.PageSize, c.PageToken, c.ClientOptions)
371+
}
372+
373+
func listServiceAccountProjectAssignments(
374+
cctx *CommandContext,
375+
projectID string,
376+
pageSize int,
377+
pageToken string,
378+
clientOptions ClientOptions,
379+
) error {
380+
client, err := cctx.GetCloudClient(clientOptions)
381+
if err != nil {
382+
return err
383+
}
384+
res, err := client.GetServiceAccountProjectAssignments(cctx, &cloudservice.GetServiceAccountProjectAssignmentsRequest{
385+
ProjectId: projectID,
386+
PageSize: int32(pageSize),
387+
PageToken: pageToken,
388+
})
389+
if err != nil {
390+
return err
391+
}
392+
return cctx.Printer.PrintResourceList(
393+
struct {
394+
ServiceAccounts []*identityv1.ServiceAccountProjectAssignment
395+
NextPageToken string
396+
}{
397+
ServiceAccounts: res.ServiceAccounts,
398+
NextPageToken: res.NextPageToken,
399+
},
400+
printer.PrintResourceOptions{
401+
Fields: []string{"Id", "Name", "ProjectAccess", "InheritedAccess"},
402+
},
403+
printer.TableOptions{},
404+
)
405+
}
406+
218407
func (c *CloudServiceAccountEditCommand) run(cctx *CommandContext, _ []string) error {
219408
client, err := cctx.GetCloudClient(c.ClientOptions)
220409
if err != nil {
@@ -270,6 +459,30 @@ func (c *CloudServiceAccountListCommand) run(cctx *CommandContext, _ []string) e
270459
if err != nil {
271460
return err
272461
}
462+
if c.ProjectId != "" {
463+
res, err := client.GetProjectScopedServiceAccounts(cctx, &cloudservice.GetProjectScopedServiceAccountsRequest{
464+
ProjectId: c.ProjectId,
465+
PageSize: int32(c.PageSize),
466+
PageToken: c.PageToken,
467+
})
468+
if err != nil {
469+
return err
470+
}
471+
return cctx.Printer.PrintResourceList(
472+
struct {
473+
ServiceAccounts []*identityv1.ServiceAccount
474+
NextPageToken string
475+
}{
476+
ServiceAccounts: res.ServiceAccounts,
477+
NextPageToken: res.NextPageToken,
478+
},
479+
printer.PrintResourceOptions{
480+
Fields: []string{"Id", "State", "CreatedTime"},
481+
SpecFields: []string{"Name"},
482+
},
483+
printer.TableOptions{},
484+
)
485+
}
273486
res, err := client.GetServiceAccounts(cctx, &cloudservice.GetServiceAccountsRequest{
274487
PageSize: int32(c.PageSize),
275488
PageToken: c.PageToken,
@@ -306,6 +519,9 @@ func (c *CloudServiceAccountSetCustomRolesCommand) run(cctx *CommandContext, _ [
306519
if sa.Spec.NamespaceScopedAccess != nil {
307520
return errors.New("--custom-role is not valid for namespace-scoped service accounts")
308521
}
522+
if sa.Spec.ProjectScopedAccess != nil {
523+
return errors.New("--custom-role is not valid for project-scoped service accounts")
524+
}
309525
newSpec := proto.Clone(sa.Spec).(*identityv1.ServiceAccountSpec)
310526
if newSpec.Access == nil || newSpec.Access.AccountAccess == nil {
311527
return errors.New("service account has no account access; assign an account role with `temporal cloud service-account update --account-role` first")

0 commit comments

Comments
 (0)