James Cooper

Modify role.delete to no-op if role missing

If "kappa delete" fails midway then re-running it will fail during
role removal.

This PR modifies `delete` to check if the role exists.  If it does not
then we log a debug line and return early.

I also consolidated various methods that were calling `get_role` so that
error handling is consistent, and removed `_find_all_roles` as
`get_role` is sufficient, and probably faster (particularly for accounts
with many roles).
...@@ -52,28 +52,26 @@ class Role(object): ...@@ -52,28 +52,26 @@ class Role(object):
52 @property 52 @property
53 def arn(self): 53 def arn(self):
54 if self._arn is None: 54 if self._arn is None:
55 - try: 55 + role = self._get_role()
56 - response = self._iam_client.call( 56 + if role:
57 - 'get_role', RoleName=self.name) 57 + self._arn = role['Arn']
58 - LOG.debug(response) 58 + else:
59 - self._arn = response['Role']['Arn']
60 - except Exception:
61 LOG.debug('Unable to find ARN for role: %s', self.name) 59 LOG.debug('Unable to find ARN for role: %s', self.name)
62 return self._arn 60 return self._arn
63 61
64 - def _find_all_roles(self): 62 + def _get_role(self):
65 try: 63 try:
66 - response = self._iam_client.call('list_roles') 64 + response = self._iam_client.call('get_role', RoleName=self.name)
65 + return response['Role']
66 + except ClientError as e:
67 + if e.response['Error']['Code'] != 'NoSuchEntity':
68 + LOG.exception('Error getting role')
67 except Exception: 69 except Exception:
68 - LOG.exception('Error listing roles') 70 + LOG.exception('Error getting role')
69 - response = {} 71 + return None
70 - return response.get('Roles', list())
71 72
72 def exists(self): 73 def exists(self):
73 - for role in self._find_all_roles(): 74 + return self._get_role() is not None
74 - if role['RoleName'] == self.name:
75 - return role
76 - return None
77 75
78 def create(self): 76 def create(self):
79 LOG.info('creating role %s', self.name) 77 LOG.info('creating role %s', self.name)
...@@ -99,6 +97,10 @@ class Role(object): ...@@ -99,6 +97,10 @@ class Role(object):
99 97
100 def delete(self): 98 def delete(self):
101 response = None 99 response = None
100 + if not self.exists():
101 + LOG.debug('role %s does not exist - skipping delete', self.name)
102 + return response
103 +
102 LOG.debug('deleting role %s', self.name) 104 LOG.debug('deleting role %s', self.name)
103 try: 105 try:
104 LOG.debug('First detach the policy from the role') 106 LOG.debug('First detach the policy from the role')
...@@ -117,11 +119,7 @@ class Role(object): ...@@ -117,11 +119,7 @@ class Role(object):
117 119
118 def status(self): 120 def status(self):
119 LOG.debug('getting status for role %s', self.name) 121 LOG.debug('getting status for role %s', self.name)
120 - try: 122 + role = self._get_role()
121 - response = self._iam_client.call( 123 + if not role:
122 - 'get_role', RoleName=self.name)
123 - LOG.debug(response)
124 - except ClientError:
125 LOG.debug('role %s not found', self.name) 124 LOG.debug('role %s not found', self.name)
126 - response = None 125 + return role
127 - return response
......
...@@ -108,8 +108,8 @@ def status(ctx): ...@@ -108,8 +108,8 @@ def status(ctx):
108 click.echo(click.style('Role', bold=True)) 108 click.echo(click.style('Role', bold=True))
109 if status['role']: 109 if status['role']:
110 line = ' {} ({})'.format( 110 line = ' {} ({})'.format(
111 - status['role']['Role']['RoleName'], 111 + status['role']['RoleName'],
112 - status['role']['Role']['Arn']) 112 + status['role']['Arn'])
113 click.echo(click.style(line, fg='green')) 113 click.echo(click.style(line, fg='green'))
114 click.echo(click.style('Function', bold=True)) 114 click.echo(click.style('Function', bold=True))
115 if status['function']: 115 if status['function']:
......