File: //snap/google-cloud-cli/current/lib/surface/iam/roles/update.py
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command for updating a custom role."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.iam import util
from googlecloudsdk.api_lib.util import http_retry
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.iam import flags
from googlecloudsdk.command_lib.iam import iam_util
from googlecloudsdk.core.console import console_io
import six.moves.http_client
DETAILED_HELP = {
'EXAMPLES':
"""\
To update the role ``ProjectUpdater'' from a YAML file, run:
$ {command} ProjectUpdater --organization=123 --file=role_file_path
To update the role ``ProjectUpdater'' with flags, run:
$ {command} ProjectUpdater --project=myproject --permissions=permission1,permission2
"""
}
@base.UniverseCompatible
class Update(base.Command):
"""Update an IAM custom role.
This command updates an IAM custom role.
"""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
updated = parser.add_argument_group(
'The following flags determine the fields need to be updated. '
'You can update a role by specifying the following flags, or '
'you can update a role from a YAML file by specifying the file flag.')
updated.add_argument(
'--title', help='The title of the role you want to update.')
updated.add_argument(
'--description', help='The description of the role you want to update.')
updated.add_argument(
'--stage', help='The state of the role you want to update.')
updated.add_argument(
'--permissions',
help='The permissions of the role you want to set. '
'Use commas to separate them.')
updated.add_argument(
'--add-permissions',
help='The permissions you want to add to the role. '
'Use commas to separate them.')
updated.add_argument(
'--remove-permissions',
help='The permissions you want to remove from the '
'role. Use commas to separate them.')
parser.add_argument(
'--file',
help='The YAML file you want to use to update a role. '
'Can not be specified with other flags except role-id.')
flags.AddParentFlags(parser, 'update')
flags.GetCustomRoleFlag('update').AddToParser(parser)
def Run(self, args):
client, messages = util.GetClientAndMessages()
role_name = iam_util.GetRoleName(args.organization, args.project, args.role)
role = messages.Role()
if args.file:
if (args.title or args.description or args.stage or args.permissions or
args.add_permissions or args.remove_permissions):
raise exceptions.ConflictingArgumentsException('file', 'others')
role = iam_util.ParseYamlToRole(args.file, messages.Role)
if not role.etag:
msg = ('The specified role does not contain an "etag" field '
'identifying a specific version to replace. Updating a '
'role without an "etag" can overwrite concurrent role '
'changes.')
console_io.PromptContinue(
message=msg,
prompt_string='Replace existing role',
cancel_on_no=True)
if not args.quiet:
self.WarnPermissions(client, messages, role.includedPermissions,
args.project, args.organization)
try:
res = client.organizations_roles.Patch(
messages.IamOrganizationsRolesPatchRequest(
name=role_name, role=role))
iam_util.SetRoleStageIfAlpha(res)
return res
except apitools_exceptions.HttpConflictError as e:
raise exceptions.HttpException(
e, error_format=('Stale "etag": '
'Please use the etag from your latest describe '
'response. Or new changes have been made since '
'your latest describe operation. Please retry '
'the whole describe-update process. Or you can '
'leave the etag blank to overwrite concurrent '
'role changes.'))
except apitools_exceptions.HttpError as e:
raise exceptions.HttpException(e)
res = self.UpdateWithFlags(args, role_name, role, client, messages)
iam_util.SetRoleStageIfAlpha(res)
return res
@http_retry.RetryOnHttpStatus(six.moves.http_client.CONFLICT)
def UpdateWithFlags(self, args, role_name, role, iam_client, messages):
role, changed_fields = self.GetUpdatedRole(args, role_name, role,
iam_client, messages)
return iam_client.organizations_roles.Patch(
messages.IamOrganizationsRolesPatchRequest(
name=role_name, role=role, updateMask=','.join(changed_fields)))
def GetUpdatedRole(self, args, role_name, role, iam_client, messages):
"""Gets the updated role from flags."""
changed_fields = []
if args.description is not None:
changed_fields.append('description')
role.description = args.description
if args.title is not None:
changed_fields.append('title')
role.title = args.title
if args.stage:
changed_fields.append('stage')
role.stage = iam_util.StageTypeFromString(args.stage)
if args.permissions is not None and (args.add_permissions or
args.remove_permissions):
raise exceptions.ConflictingArgumentsException(
'--permissions', '-add-permissions or --remove-permissions')
if args.permissions is not None:
changed_fields.append('includedPermissions')
role.includedPermissions = args.permissions.split(',')
if not args.permissions:
role.includedPermissions = []
if not args.quiet:
self.WarnPermissions(iam_client, messages, role.includedPermissions,
args.project, args.organization)
origin_role = iam_client.organizations_roles.Get(
messages.IamOrganizationsRolesGetRequest(name=role_name))
if args.add_permissions or args.remove_permissions:
permissions = set(origin_role.includedPermissions)
changed = False
newly_added_permissions = set()
if args.add_permissions:
for permission in args.add_permissions.split(','):
if permission not in permissions:
permissions.add(permission)
newly_added_permissions.add(permission)
changed = True
if args.remove_permissions:
for permission in args.remove_permissions.split(','):
if permission in permissions:
permissions.remove(permission)
changed = True
if permission in newly_added_permissions:
newly_added_permissions.remove(permission)
if changed:
changed_fields.append('includedPermissions')
role.includedPermissions = list(sorted(permissions))
if not args.quiet:
self.WarnPermissions(iam_client, messages,
list(newly_added_permissions), args.project,
args.organization)
role.etag = origin_role.etag
return role, changed_fields
def WarnPermissions(self, iam_client, messages, permissions, project,
organization):
permissions_helper = util.PermissionsHelper(iam_client, messages,
iam_util.GetResourceReference(
project, organization),
permissions)
api_disabled_permissions = permissions_helper.GetApiDisabledPermissons()
iam_util.ApiDisabledPermissionsWarning(api_disabled_permissions)
testing_permissions = permissions_helper.GetTestingPermissions()
iam_util.TestingPermissionsWarning(testing_permissions)