@@ -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+
112152func (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+
218407func (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