Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 17ad72d

Browse files
committedMar 20, 2025·
add new modules ssm_document and ssm_document_info
1 parent a9a900a commit 17ad72d

File tree

10 files changed

+1639
-4
lines changed

10 files changed

+1639
-4
lines changed
 

‎meta/runtime.yml

+2
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ action_groups:
178178
- sns_topic
179179
- sns_topic_info
180180
- sqs_queue
181+
- ssm_document_info
182+
- ssm_document
181183
- ssm_inventory_info
182184
- ssm_parameter
183185
- stepfunctions_state_machine

‎plugins/module_utils/ssm.py

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright: Contributors to the Ansible project
4+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
6+
from typing import Any
7+
from typing import Dict
8+
from typing import List
9+
10+
from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code
11+
from ansible_collections.amazon.aws.plugins.module_utils.errors import AWSErrorHandler
12+
from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError
13+
from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule
14+
from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry
15+
from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list
16+
from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags
17+
18+
19+
class AnsibleSSMError(AnsibleAWSError):
20+
pass
21+
22+
23+
# SSM Documents
24+
class SSMDocumentErrorHandler(AWSErrorHandler):
25+
_CUSTOM_EXCEPTION = AnsibleSSMError
26+
27+
@classmethod
28+
def _is_missing(cls):
29+
return is_boto3_error_code("InvalidDocument")
30+
31+
32+
@SSMDocumentErrorHandler.deletion_error_handler("delete document")
33+
@AWSRetry.jittered_backoff()
34+
def delete_document(client, name: str, **kwargs: Dict[str, Any]) -> bool:
35+
client.delete_document(Name=name, **kwargs)
36+
return True
37+
38+
39+
@SSMDocumentErrorHandler.list_error_handler("describe document", {})
40+
@AWSRetry.jittered_backoff()
41+
def describe_document(client, name: str, **params: Dict[str, str]) -> Dict[str, Any]:
42+
return client.describe_document(Name=name, **params)["Document"]
43+
44+
45+
@SSMDocumentErrorHandler.common_error_handler("create document")
46+
@AWSRetry.jittered_backoff()
47+
def create_document(client, name: str, content: str, **params: Dict[str, Any]) -> Dict[str, Any]:
48+
return client.create_document(Name=name, Content=content, **params)["DocumentDescription"]
49+
50+
51+
@SSMDocumentErrorHandler.common_error_handler("update document")
52+
@AWSRetry.jittered_backoff()
53+
def update_document(client, name: str, content: str, **params: Dict[str, Any]) -> Dict[str, Any]:
54+
return client.update_document(Name=name, Content=content, **params)["DocumentDescription"]
55+
56+
57+
@SSMDocumentErrorHandler.common_error_handler("update document default version")
58+
@AWSRetry.jittered_backoff()
59+
def update_document_default_version(client, name: str, default_version: str) -> Dict[str, Any]:
60+
return client.update_document_default_version(Name=name, DocumentVersion=default_version)
61+
62+
63+
@SSMDocumentErrorHandler.list_error_handler("list documents", {})
64+
@AWSRetry.jittered_backoff()
65+
def list_documents(client, **kwargs: Dict[str, Any]) -> List[Dict[str, Any]]:
66+
paginator = client.get_paginator("list_documents")
67+
return paginator.paginate(**kwargs).build_full_result()["DocumentIdentifiers"]
68+
69+
70+
@SSMDocumentErrorHandler.list_error_handler("list document versions", {})
71+
@AWSRetry.jittered_backoff()
72+
def list_document_versions(ssm: Any, name: str) -> List[Dict[str, Any]]:
73+
paginator = ssm.get_paginator("list_document_versions")
74+
return paginator.paginate(Name=name).build_full_result()["DocumentVersions"]
75+
76+
77+
# Tags
78+
def add_tags_to_resource(client, resource_type: str, resource_id: str, tags: List[Dict[str, Any]]) -> None:
79+
client.add_tags_to_resource(ResourceType=resource_type, ResourceId=resource_id, Tags=tags)
80+
81+
82+
def remove_tags_from_resource(client, resource_type: str, resource_id: str, tag_keys: List[str]) -> None:
83+
client.remove_tags_from_resource(ResourceType=resource_type, ResourceId=resource_id, TagKeys=tag_keys)
84+
85+
86+
def ensure_ssm_resource_tags(
87+
client, module: AnsibleAWSModule, current_tags: Dict[str, str], resource_id: str, resource_type: str
88+
) -> bool:
89+
"""Update resources tags"""
90+
tags = module.params.get("tags")
91+
purge_tags = module.params.get("purge_tags")
92+
tags_to_set, tags_to_unset = compare_aws_tags(current_tags, tags, purge_tags)
93+
94+
if purge_tags and not tags:
95+
tags_to_unset = current_tags
96+
97+
changed = False
98+
if tags_to_set:
99+
changed = True
100+
if not module.check_mode:
101+
add_tags_to_resource(
102+
client,
103+
resource_type=resource_type,
104+
resource_id=resource_id,
105+
tags=ansible_dict_to_boto3_tag_list(tags_to_set),
106+
)
107+
if tags_to_unset:
108+
changed = True
109+
if not module.check_mode:
110+
remove_tags_from_resource(
111+
client, resource_type=resource_type, resource_id=resource_id, tag_keys=tags_to_unset
112+
)
113+
return changed

‎plugins/modules/ssm_document.py

+583
Large diffs are not rendered by default.

‎plugins/modules/ssm_document_info.py

+492
Large diffs are not rendered by default.

‎tests/integration/targets/setup_connection_aws_ssm/tasks/cleanup.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@
8181
alias: '{{ kms_key_name }}'
8282

8383
- name: Delete SSM document
84-
command: "aws ssm delete-document --name {{ ssm_document_name }}"
85-
environment: "{{ connection_env }}"
84+
community.aws.ssm_document:
85+
state: absent
86+
name: "{{ ssm_document_name }}"
8687
ignore_errors: true
8788

8889
- name: Delete AWS keys environement

‎tests/integration/targets/setup_connection_aws_ssm/tasks/ssm_document.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
---
22
- block:
33
- name: Create custom SSM document
4-
command: "aws ssm create-document --content file://{{ role_path }}/files/ssm-document.json --name {{ ssm_document_name }} --document-type Session"
5-
environment: "{{ connection_env }}"
4+
community.aws.ssm_document:
5+
state: present
6+
document_type: Session
7+
content_path: "{{ role_path }}/files/ssm-document.json"
8+
name: "{{ ssm_document_name }}"
69
always:
710
- name: Create SSM vars_to_delete.yml
811
template:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
time=4m
2+
cloud/aws
3+
ssm_document_info
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"schemaVersion": "2.2",
3+
"description": "Sample document to display a message",
4+
"parameters": {
5+
"Message": {
6+
"type": "String",
7+
"description": "message to display",
8+
"default": "Ansible is super"
9+
},
10+
"Users": {
11+
"type": "String",
12+
"description": "users",
13+
"default": "partners"
14+
}
15+
},
16+
"mainSteps": [
17+
{
18+
"action": "aws:runPowerShellScript",
19+
"name": "example",
20+
"inputs": {
21+
"runCommand": ["Write-Output {{Message}} for {{Users}}"]
22+
}
23+
}
24+
]
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"schemaVersion": "2.2",
3+
"description": "Sample document to execute hello world",
4+
"parameters": {
5+
"Message": {
6+
"type": "String",
7+
"description": "message to display",
8+
"default": "Ansible is super"
9+
}
10+
},
11+
"mainSteps": [
12+
{
13+
"action": "aws:runPowerShellScript",
14+
"name": "example",
15+
"inputs": {
16+
"runCommand": ["Write-Output {{Message}}"]
17+
}
18+
}
19+
]
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
---
2+
- name: Test modules ssm_document and ssm_document_info
3+
vars:
4+
document_name: "{{ resource_prefix }}"
5+
document_type: Command
6+
resource_tags:
7+
Test: Integration
8+
ResourcePrefix: "{{ resource_prefix }}"
9+
updated_tags:
10+
Foo: Bar
11+
files_path:
12+
- "{{ role_path }}/files/ssm-custom-document.json"
13+
- "{{ role_path }}/files/ssm-custom-document-2.json"
14+
module_defaults:
15+
group/aws:
16+
access_key: '{{ aws_access_key }}'
17+
secret_key: '{{ aws_secret_key }}'
18+
session_token: '{{ security_token | default(omit) }}'
19+
region: '{{ aws_region }}'
20+
block:
21+
- name: Create SSM document (check_mode=true)
22+
community.aws.ssm_document:
23+
name: "{{ document_name }}"
24+
content_path: "{{ files_path[0] }}"
25+
document_type: "{{ document_type }}"
26+
check_mode: true
27+
register: create_check
28+
29+
- name: Describe document
30+
community.aws.ssm_document_info:
31+
filters:
32+
Name: "{{ document_name }}"
33+
register: ssm_docs
34+
35+
- name: Ensure the module reported change while the document was not created
36+
ansible.builtin.assert:
37+
that:
38+
- create_check is changed
39+
- ssm_docs.documents | length == 0
40+
41+
# Create
42+
- name: Create SSM document
43+
community.aws.ssm_document:
44+
name: "{{ document_name }}"
45+
content_path: "{{ files_path[0] }}"
46+
document_type: "{{ document_type }}"
47+
register: create_doc
48+
49+
- name: Describe document
50+
community.aws.ssm_document_info:
51+
name: "{{ document_name }}"
52+
register: document_info
53+
54+
- name: Ensure the module reported and the document was created
55+
ansible.builtin.assert:
56+
that:
57+
- create_doc is changed
58+
- '"document" in create_doc'
59+
- document_info.document is defined
60+
- document_info.document.default_version == "1"
61+
- document_info.document.document_versions | length == 1
62+
63+
# Create idempotency
64+
- name: Create SSM document (idempotency)
65+
community.aws.ssm_document:
66+
name: "{{ document_name }}"
67+
content_path: "{{ files_path[0] }}"
68+
document_type: "{{ document_type }}"
69+
register: create_idempotency
70+
71+
- name: Describe document
72+
community.aws.ssm_document_info:
73+
name: "{{ document_name }}"
74+
register: document_info
75+
76+
- name: Ensure the module reported and the document was created
77+
ansible.builtin.assert:
78+
that:
79+
- create_idempotency is not changed
80+
- '"document" in create_doc'
81+
- document_info.document is defined
82+
- document_info.document.default_version == "1"
83+
- document_info.document.document_versions | length == 1
84+
85+
# Update (add new document version and update default version)
86+
- name: Update SSM document (check_mode=true)
87+
community.aws.ssm_document:
88+
name: "{{ document_name }}"
89+
content_path: "{{ files_path[1] }}"
90+
document_type: "{{ document_type }}"
91+
register: update_check
92+
check_mode: true
93+
94+
- name: Describe document
95+
community.aws.ssm_document_info:
96+
name: "{{ document_name }}"
97+
register: document_info
98+
99+
- name: Ensure the module reported and the document was created
100+
ansible.builtin.assert:
101+
that:
102+
- update_check is changed
103+
- document_info.document is defined
104+
- document_info.document.default_version == "1"
105+
- document_info.document.document_versions | length == 1
106+
107+
- name: Update SSM document
108+
community.aws.ssm_document:
109+
name: "{{ document_name }}"
110+
content_path: "{{ files_path[1] }}"
111+
document_type: "{{ document_type }}"
112+
register: update_doc
113+
114+
- name: Describe document
115+
community.aws.ssm_document_info:
116+
name: "{{ document_name }}"
117+
register: document_info
118+
119+
- name: Ensure the module reported and the document was created
120+
ansible.builtin.assert:
121+
that:
122+
- update_doc is changed
123+
- document_info.document is defined
124+
- document_info.document.default_version == "1"
125+
- document_info.document.document_versions | length == 2
126+
127+
# Update document default version
128+
- name: Update document default version (check_mode=true)
129+
community.aws.ssm_document:
130+
name: "{{ document_name }}"
131+
document_default_version: "2"
132+
register: update_version_check
133+
check_mode: true
134+
135+
- name: Describe document
136+
community.aws.ssm_document_info:
137+
name: "{{ document_name }}"
138+
register: document_info
139+
140+
- name: Ensure document default version has not changed
141+
ansible.builtin.assert:
142+
that:
143+
- update_version_check is changed
144+
- document_info.document is defined
145+
- document_info.document.default_version == "1"
146+
- document_info.document.document_versions | length == 2
147+
148+
- name: Update document default version
149+
community.aws.ssm_document:
150+
name: "{{ document_name }}"
151+
document_default_version: "2"
152+
register: update_version
153+
154+
- name: Describe document
155+
community.aws.ssm_document_info:
156+
name: "{{ document_name }}"
157+
register: document_info
158+
159+
- name: Ensure document default version has changed
160+
ansible.builtin.assert:
161+
that:
162+
- update_version is changed
163+
- document_info.document is defined
164+
- document_info.document.default_version == "2"
165+
- document_info.document.document_versions | length == 2
166+
167+
- name: Update document default version (idempotency)
168+
community.aws.ssm_document:
169+
name: "{{ document_name }}"
170+
document_default_version: "2"
171+
register: update_version_idempotency
172+
173+
- name: Describe document
174+
community.aws.ssm_document_info:
175+
name: "{{ document_name }}"
176+
register: document_info
177+
178+
- name: Ensure document default version has not changed
179+
ansible.builtin.assert:
180+
that:
181+
- update_version_idempotency is not changed
182+
- document_info.document is defined
183+
- document_info.document.default_version == "2"
184+
- document_info.document.document_versions | length == 2
185+
186+
# Update tags
187+
- name: Update document tags (check_mode=true)
188+
community.aws.ssm_document:
189+
name: "{{ document_name }}"
190+
resource_tags: "{{ resource_tags }}"
191+
register: update_tags_check
192+
check_mode: true
193+
194+
- name: Describe document
195+
community.aws.ssm_document_info:
196+
name: "{{ document_name }}"
197+
register: document_info
198+
199+
- name: Ensure module reported change but the resource tags were not updated
200+
ansible.builtin.assert:
201+
that:
202+
- update_tags_check is changed
203+
- document_info.document is defined
204+
- document_info.document.tags == {}
205+
206+
- name: Update document tags
207+
community.aws.ssm_document:
208+
name: "{{ document_name }}"
209+
resource_tags: "{{ resource_tags }}"
210+
register: update_tags
211+
212+
- name: Describe document
213+
community.aws.ssm_document_info:
214+
name: "{{ document_name }}"
215+
register: document_info
216+
217+
- name: Ensure module reported change but the resource tags were not updated
218+
ansible.builtin.assert:
219+
that:
220+
- update_tags is changed
221+
- document_info.document is defined
222+
- document_info.document.tags == resource_tags
223+
224+
- name: Update document tags (idempotency)
225+
community.aws.ssm_document:
226+
name: "{{ document_name }}"
227+
resource_tags: "{{ resource_tags }}"
228+
register: update_tags_idempotency
229+
230+
- name: Describe document
231+
community.aws.ssm_document_info:
232+
name: "{{ document_name }}"
233+
register: document_info
234+
235+
- name: Ensure module did not reported change
236+
ansible.builtin.assert:
237+
that:
238+
- update_tags_idempotency is not changed
239+
- document_info.document is defined
240+
- document_info.document.tags == resource_tags
241+
242+
- name: Update document tags (purge_tags=False)
243+
community.aws.ssm_document:
244+
name: "{{ document_name }}"
245+
resource_tags: "{{ updated_tags }}"
246+
purge_tags: false
247+
register: update_tags_not_purge
248+
249+
- name: Describe document
250+
community.aws.ssm_document_info:
251+
name: "{{ document_name }}"
252+
register: document_info
253+
254+
- name: Ensure resource tags were updated
255+
ansible.builtin.assert:
256+
that:
257+
- update_tags_not_purge is changed
258+
- document_info.document is defined
259+
- document_info.document.tags == resource_tags | combine(updated_tags)
260+
261+
- name: Update document tags (purge_tags=true)
262+
community.aws.ssm_document:
263+
name: "{{ document_name }}"
264+
resource_tags: "{{ updated_tags }}"
265+
purge_tags: true
266+
register: update_tags_purge
267+
268+
- name: Describe document
269+
community.aws.ssm_document_info:
270+
name: "{{ document_name }}"
271+
register: document_info
272+
273+
- name: Ensure resource tags were updated
274+
ansible.builtin.assert:
275+
that:
276+
- update_tags_purge is changed
277+
- document_info.document is defined
278+
- document_info.document.tags == updated_tags
279+
280+
# Delete Document version
281+
- name: Delete document version (check_mode=true)
282+
community.aws.ssm_document:
283+
name: "{{ document_name }}"
284+
document_version: "1"
285+
state: absent
286+
check_mode: true
287+
register: delete_check
288+
289+
- name: Describe document
290+
community.aws.ssm_document_info:
291+
name: "{{ document_name }}"
292+
register: document_info
293+
294+
- name: Ensure module reported change while the document version was not deleted
295+
ansible.builtin.assert:
296+
that:
297+
- delete_check is changed
298+
- document_info.document is defined
299+
- '"1" in document_info.document.document_versions | map(attribute="document_version") | list'
300+
301+
- name: Delete document version
302+
community.aws.ssm_document:
303+
name: "{{ document_name }}"
304+
document_version: "1"
305+
state: absent
306+
register: delete_version
307+
308+
- name: Describe document
309+
community.aws.ssm_document_info:
310+
name: "{{ document_name }}"
311+
register: document_info
312+
313+
- name: Ensure module did reported change and the document version was not deleted
314+
ansible.builtin.assert:
315+
that:
316+
- delete_version is changed
317+
- document_info.document is defined
318+
- '"1" not in document_info.document.document_versions | map(attribute="document_version") | list'
319+
- document_info.document.document_versions | length > 0
320+
321+
- name: Delete document version (idempotency)
322+
community.aws.ssm_document:
323+
name: "{{ document_name }}"
324+
document_version: "1"
325+
state: absent
326+
register: delete_version_idempotency
327+
328+
- name: Describe document
329+
community.aws.ssm_document_info:
330+
name: "{{ document_name }}"
331+
register: document_info
332+
333+
- name: Ensure version deletion idempotency
334+
ansible.builtin.assert:
335+
that:
336+
- delete_version_idempotency is not changed
337+
- document_info.document is defined
338+
- '"1" not in document_info.document.document_versions | map(attribute="document_version") | list'
339+
- document_info.document.document_versions | length > 0
340+
341+
# Delete document
342+
- name: Delete document (check_mode=true)
343+
community.aws.ssm_document:
344+
name: "{{ document_name }}"
345+
state: absent
346+
register: delete_check
347+
check_mode: true
348+
349+
- name: Describe document
350+
community.aws.ssm_document_info:
351+
name: "{{ document_name }}"
352+
register: document_info
353+
354+
- name: Ensure module reported change while the document was not deleted
355+
ansible.builtin.assert:
356+
that:
357+
- delete_check is changed
358+
- document_info.document is defined
359+
- document_info.document.document_versions | length > 0
360+
361+
- name: Delete document
362+
community.aws.ssm_document:
363+
name: "{{ document_name }}"
364+
state: absent
365+
register: delete_doc
366+
367+
- name: Describe document
368+
community.aws.ssm_document_info:
369+
name: "{{ document_name }}"
370+
register: document_info
371+
372+
- name: Ensure module reported change and the document was deleted
373+
ansible.builtin.assert:
374+
that:
375+
- delete_doc is changed
376+
- document_info.document == {}
377+
378+
- name: Delete document (idempotency)
379+
community.aws.ssm_document:
380+
name: "{{ document_name }}"
381+
state: absent
382+
register: delete_idempotency
383+
384+
- name: Ensure module did not reported change (idempotency)
385+
ansible.builtin.assert:
386+
that:
387+
- delete_idempotency is not changed
388+
389+
always:
390+
- name: Delete SSM document
391+
community.aws.ssm_document:
392+
state: absent
393+
name: "{{ document_name }}"

0 commit comments

Comments
 (0)
Please sign in to comment.