66__metaclass__ = type
77
88
9- DOCUMENTATION = '''
9+ DOCUMENTATION = r '''
1010---
1111module: iam_managed_policy
1212version_added: 1.0.0
5555- amazon.aws.ec2
5656'''
5757
58- EXAMPLES = '''
58+ EXAMPLES = r '''
5959# Create Policy ex nihilo
6060- name: Create IAM Managed Policy
6161 community.aws.iam_managed_policy:
107107 state: absent
108108'''
109109
110- RETURN = '''
110+ RETURN = r '''
111111policy:
112112 description: Returns the policy json structure, when state == absent this will return the value of the removed policy.
113113 returned: success
114- type: str
114+ type: complex
115+ contains: {}
115116 sample: '{
116117 "arn": "arn:aws:iam::aws:policy/AdministratorAccess "
117118 "attachment_count": 0,
142143
143144
144145@AWSRetry .jittered_backoff (retries = 5 , delay = 5 , backoff = 2.0 )
145- def list_policies_with_backoff (iam ):
146- paginator = iam .get_paginator ('list_policies' )
146+ def list_policies_with_backoff ():
147+ paginator = client .get_paginator ('list_policies' )
147148 return paginator .paginate (Scope = 'Local' ).build_full_result ()
148149
149150
150- def get_policy_by_name (module , iam , name ):
151+ def get_policy_by_name (name ):
151152 try :
152- response = list_policies_with_backoff (iam )
153+ response = list_policies_with_backoff ()
153154 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
154155 module .fail_json_aws (e , msg = "Couldn't list policies" )
155156 for policy in response ['Policies' ]:
@@ -158,32 +159,36 @@ def get_policy_by_name(module, iam, name):
158159 return None
159160
160161
161- def delete_oldest_non_default_version (module , iam , policy ):
162+ def delete_oldest_non_default_version (policy ):
162163 try :
163- versions = [v for v in iam .list_policy_versions (PolicyArn = policy ['Arn' ])['Versions' ]
164+ versions = [v for v in client .list_policy_versions (PolicyArn = policy ['Arn' ])['Versions' ]
164165 if not v ['IsDefaultVersion' ]]
165166 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
166167 module .fail_json_aws (e , msg = "Couldn't list policy versions" )
167168 versions .sort (key = lambda v : v ['CreateDate' ], reverse = True )
168169 for v in versions [- 1 :]:
169170 try :
170- iam .delete_policy_version (PolicyArn = policy ['Arn' ], VersionId = v ['VersionId' ])
171+ client .delete_policy_version (PolicyArn = policy ['Arn' ], VersionId = v ['VersionId' ])
171172 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
172173 module .fail_json_aws (e , msg = "Couldn't delete policy version" )
173174
174175
175176# This needs to return policy_version, changed
176- def get_or_create_policy_version (module , iam , policy , policy_document ):
177+ def get_or_create_policy_version (policy , policy_document ):
177178 try :
178- versions = iam .list_policy_versions (PolicyArn = policy ['Arn' ])['Versions' ]
179+ versions = client .list_policy_versions (PolicyArn = policy ['Arn' ])['Versions' ]
179180 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
180181 module .fail_json_aws (e , msg = "Couldn't list policy versions" )
182+
181183 for v in versions :
182184 try :
183- document = iam .get_policy_version (PolicyArn = policy ['Arn' ],
184- VersionId = v ['VersionId' ])['PolicyVersion' ]['Document' ]
185+ document = client .get_policy_version (PolicyArn = policy ['Arn' ], VersionId = v ['VersionId' ])['PolicyVersion' ]['Document' ]
185186 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
186187 module .fail_json_aws (e , msg = "Couldn't get policy version {0}" .format (v ['VersionId' ]))
188+
189+ if module .check_mode and compare_policies (document , json .loads (to_native (policy_document ))):
190+ return v , True
191+
187192 # If the current policy matches the existing one
188193 if not compare_policies (document , json .loads (to_native (policy_document ))):
189194 return v , False
@@ -195,71 +200,145 @@ def get_or_create_policy_version(module, iam, policy, policy_document):
195200 # and if that doesn't work, delete the oldest non default policy version
196201 # and try again.
197202 try :
198- version = iam .create_policy_version (PolicyArn = policy ['Arn' ], PolicyDocument = policy_document )['PolicyVersion' ]
203+ version = client .create_policy_version (PolicyArn = policy ['Arn' ], PolicyDocument = policy_document )['PolicyVersion' ]
199204 return version , True
200205 except is_boto3_error_code ('LimitExceeded' ):
201- delete_oldest_non_default_version (module , iam , policy )
206+ delete_oldest_non_default_version (policy )
202207 try :
203- version = iam .create_policy_version (PolicyArn = policy ['Arn' ], PolicyDocument = policy_document )['PolicyVersion' ]
208+ version = client .create_policy_version (PolicyArn = policy ['Arn' ], PolicyDocument = policy_document )['PolicyVersion' ]
204209 return version , True
205210 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as second_e :
206211 module .fail_json_aws (second_e , msg = "Couldn't create policy version" )
207212 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e : # pylint: disable=duplicate-except
208213 module .fail_json_aws (e , msg = "Couldn't create policy version" )
209214
210215
211- def set_if_default (module , iam , policy , policy_version , is_default ):
216+ def set_if_default (policy , policy_version , is_default ):
212217 if is_default and not policy_version ['IsDefaultVersion' ]:
213218 try :
214- iam .set_default_policy_version (PolicyArn = policy ['Arn' ], VersionId = policy_version ['VersionId' ])
219+ client .set_default_policy_version (PolicyArn = policy ['Arn' ], VersionId = policy_version ['VersionId' ])
215220 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
216221 module .fail_json_aws (e , msg = "Couldn't set default policy version" )
217222 return True
218223 return False
219224
220225
221- def set_if_only (module , iam , policy , policy_version , is_only ):
226+ def set_if_only (policy , policy_version , is_only ):
222227 if is_only :
223228 try :
224- versions = [v for v in iam .list_policy_versions (PolicyArn = policy ['Arn' ])[
229+ versions = [v for v in client .list_policy_versions (PolicyArn = policy ['Arn' ])[
225230 'Versions' ] if not v ['IsDefaultVersion' ]]
226231 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
227232 module .fail_json_aws (e , msg = "Couldn't list policy versions" )
228233 for v in versions :
229234 try :
230- iam .delete_policy_version (PolicyArn = policy ['Arn' ], VersionId = v ['VersionId' ])
235+ client .delete_policy_version (PolicyArn = policy ['Arn' ], VersionId = v ['VersionId' ])
231236 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
232237 module .fail_json_aws (e , msg = "Couldn't delete policy version" )
233238 return len (versions ) > 0
234239 return False
235240
236241
237- def detach_all_entities (module , iam , policy , ** kwargs ):
242+ def detach_all_entities (policy , ** kwargs ):
238243 try :
239- entities = iam .list_entities_for_policy (PolicyArn = policy ['Arn' ], ** kwargs )
244+ entities = client .list_entities_for_policy (PolicyArn = policy ['Arn' ], ** kwargs )
240245 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
241246 module .fail_json_aws (e , msg = "Couldn't detach list entities for policy {0}" .format (policy ['PolicyName' ]))
242247
243248 for g in entities ['PolicyGroups' ]:
244249 try :
245- iam .detach_group_policy (PolicyArn = policy ['Arn' ], GroupName = g ['GroupName' ])
250+ client .detach_group_policy (PolicyArn = policy ['Arn' ], GroupName = g ['GroupName' ])
246251 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
247252 module .fail_json_aws (e , msg = "Couldn't detach group policy {0}" .format (g ['GroupName' ]))
248253 for u in entities ['PolicyUsers' ]:
249254 try :
250- iam .detach_user_policy (PolicyArn = policy ['Arn' ], UserName = u ['UserName' ])
255+ client .detach_user_policy (PolicyArn = policy ['Arn' ], UserName = u ['UserName' ])
251256 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
252257 module .fail_json_aws (e , msg = "Couldn't detach user policy {0}" .format (u ['UserName' ]))
253258 for r in entities ['PolicyRoles' ]:
254259 try :
255- iam .detach_role_policy (PolicyArn = policy ['Arn' ], RoleName = r ['RoleName' ])
260+ client .detach_role_policy (PolicyArn = policy ['Arn' ], RoleName = r ['RoleName' ])
256261 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
257262 module .fail_json_aws (e , msg = "Couldn't detach role policy {0}" .format (r ['RoleName' ]))
258263 if entities ['IsTruncated' ]:
259- detach_all_entities (module , iam , policy , marker = entities ['Marker' ])
264+ detach_all_entities (policy , marker = entities ['Marker' ])
265+
266+
267+ def create_or_update_policy (existing_policy ):
268+ name = module .params .get ('policy_name' )
269+ description = module .params .get ('policy_description' )
270+ default = module .params .get ('make_default' )
271+ only = module .params .get ('only_version' )
272+
273+ policy = None
274+
275+ if module .params .get ('policy' ) is not None :
276+ policy = json .dumps (json .loads (module .params .get ('policy' )))
277+
278+ if existing_policy is None :
279+ if module .check_mode :
280+ module .exit_json (changed = True )
281+
282+ # Create policy when none already exists
283+ try :
284+ rvalue = client .create_policy (PolicyName = name , Path = '/' , PolicyDocument = policy , Description = description )
285+ except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
286+ module .fail_json_aws (e , msg = "Couldn't create policy {0}" .format (name ))
287+
288+ module .exit_json (changed = True , policy = camel_dict_to_snake_dict (rvalue ['Policy' ]))
289+ else :
290+ policy_version , changed = get_or_create_policy_version (existing_policy , policy )
291+ changed = set_if_default (existing_policy , policy_version , default ) or changed
292+ changed = set_if_only (existing_policy , policy_version , only ) or changed
293+
294+ # If anything has changed we need to refresh the policy
295+ if changed :
296+ try :
297+ updated_policy = client .get_policy (PolicyArn = existing_policy ['Arn' ])['Policy' ]
298+ except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
299+ module .fail_json (msg = "Couldn't get policy" )
300+
301+ module .exit_json (changed = changed , policy = camel_dict_to_snake_dict (updated_policy ))
302+ else :
303+ module .exit_json (changed = changed , policy = camel_dict_to_snake_dict (existing_policy ))
304+
305+
306+ def delete_policy (existing_policy ):
307+ # Check for existing policy
308+ if existing_policy :
309+ if module .check_mode :
310+ module .exit_json (changed = True )
311+
312+ # Detach policy
313+ detach_all_entities (existing_policy )
314+ # Delete Versions
315+ try :
316+ versions = client .list_policy_versions (PolicyArn = existing_policy ['Arn' ])['Versions' ]
317+ except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
318+ module .fail_json_aws (e , msg = "Couldn't list policy versions" )
319+ for v in versions :
320+ if not v ['IsDefaultVersion' ]:
321+ try :
322+ client .delete_policy_version (PolicyArn = existing_policy ['Arn' ], VersionId = v ['VersionId' ])
323+ except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
324+ module .fail_json_aws (
325+ e , msg = "Couldn't delete policy version {0}" .format (v ['VersionId' ]))
326+ # Delete policy
327+ try :
328+ client .delete_policy (PolicyArn = existing_policy ['Arn' ])
329+ except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
330+ module .fail_json_aws (e , msg = "Couldn't delete policy {0}" .format (existing_policy ['PolicyName' ]))
331+
332+ # This is the one case where we will return the old policy
333+ module .exit_json (changed = True , policy = camel_dict_to_snake_dict (existing_policy ))
334+ else :
335+ module .exit_json (changed = False , policy = None )
260336
261337
262338def main ():
339+ global module
340+ global client
341+
263342 argument_spec = dict (
264343 policy_name = dict (required = True ),
265344 policy_description = dict (default = '' ),
@@ -273,75 +352,23 @@ def main():
273352 module = AnsibleAWSModule (
274353 argument_spec = argument_spec ,
275354 required_if = [['state' , 'present' , ['policy' ]]],
355+ supports_check_mode = True
276356 )
277357
278358 name = module .params .get ('policy_name' )
279- description = module .params .get ('policy_description' )
280359 state = module .params .get ('state' )
281- default = module .params .get ('make_default' )
282- only = module .params .get ('only_version' )
283-
284- policy = None
285-
286- if module .params .get ('policy' ) is not None :
287- policy = json .dumps (json .loads (module .params .get ('policy' )))
288360
289361 try :
290- iam = module .client ('iam' )
362+ client = module .client ('iam' , retry_decorator = AWSRetry . jittered_backoff () )
291363 except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
292364 module .fail_json_aws (e , msg = 'Failed to connect to AWS' )
293365
294- p = get_policy_by_name (module , iam , name )
295- if state == 'present' :
296- if p is None :
297- # No Policy so just create one
298- try :
299- rvalue = iam .create_policy (PolicyName = name , Path = '/' ,
300- PolicyDocument = policy , Description = description )
301- except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
302- module .fail_json_aws (e , msg = "Couldn't create policy {0}" .format (name ))
303-
304- module .exit_json (changed = True , policy = camel_dict_to_snake_dict (rvalue ['Policy' ]))
305- else :
306- policy_version , changed = get_or_create_policy_version (module , iam , p , policy )
307- changed = set_if_default (module , iam , p , policy_version , default ) or changed
308- changed = set_if_only (module , iam , p , policy_version , only ) or changed
309- # If anything has changed we needto refresh the policy
310- if changed :
311- try :
312- p = iam .get_policy (PolicyArn = p ['Arn' ])['Policy' ]
313- except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
314- module .fail_json (msg = "Couldn't get policy" )
366+ existing_policy = get_policy_by_name (name )
315367
316- module .exit_json (changed = changed , policy = camel_dict_to_snake_dict (p ))
368+ if state == 'present' :
369+ create_or_update_policy (existing_policy )
317370 else :
318- # Check for existing policy
319- if p :
320- # Detach policy
321- detach_all_entities (module , iam , p )
322- # Delete Versions
323- try :
324- versions = iam .list_policy_versions (PolicyArn = p ['Arn' ])['Versions' ]
325- except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
326- module .fail_json_aws (e , msg = "Couldn't list policy versions" )
327- for v in versions :
328- if not v ['IsDefaultVersion' ]:
329- try :
330- iam .delete_policy_version (PolicyArn = p ['Arn' ], VersionId = v ['VersionId' ])
331- except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
332- module .fail_json_aws (
333- e , msg = "Couldn't delete policy version {0}" .format (v ['VersionId' ]))
334- # Delete policy
335- try :
336- iam .delete_policy (PolicyArn = p ['Arn' ])
337- except (botocore .exceptions .ClientError , botocore .exceptions .BotoCoreError ) as e :
338- module .fail_json_aws (e , msg = "Couldn't delete policy {0}" .format (p ['PolicyName' ]))
339-
340- # This is the one case where we will return the old policy
341- module .exit_json (changed = True , policy = camel_dict_to_snake_dict (p ))
342- else :
343- module .exit_json (changed = False , policy = None )
344- # end main
371+ delete_policy (existing_policy )
345372
346373
347374if __name__ == '__main__' :
0 commit comments