1#!/usr/local/bin/python3.8
2# Copyright: Ansible Project
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
5from __future__ import absolute_import, division, print_function
6
7__metaclass__ = type
8
9
10DOCUMENTATION = '''
11---
12module: cloudwatchlogs_log_group
13version_added: 1.0.0
14short_description: create or delete log_group in CloudWatchLogs
15notes:
16    - For details of the parameters and returns see U(http://boto3.readthedocs.io/en/latest/reference/services/logs.html).
17description:
18    - Create or delete log_group in CloudWatchLogs.
19author:
20    - Willian Ricardo (@willricardo) <willricardo@gmail.com>
21requirements: [ json, botocore, boto3 ]
22options:
23    state:
24      description:
25        - Whether the rule is present or absent.
26      choices: ["present", "absent"]
27      default: present
28      required: false
29      type: str
30    log_group_name:
31      description:
32        - The name of the log group.
33      required: true
34      type: str
35    kms_key_id:
36      description:
37        - The Amazon Resource Name (ARN) of the CMK to use when encrypting log data.
38      required: false
39      type: str
40    tags:
41      description:
42        - The key-value pairs to use for the tags.
43      required: false
44      type: dict
45    retention:
46      description:
47        - The number of days to retain the log events in the specified log group.
48        - "Valid values are: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]"
49        - Mutually exclusive with I(purge_retention_policy).
50      required: false
51      type: int
52    purge_retention_policy:
53      description:
54        - "Whether to purge the retention policy or not."
55        - "Mutually exclusive with I(retention) and I(overwrite)."
56      default: false
57      required: false
58      type: bool
59    overwrite:
60      description:
61        - Whether an existing log group should be overwritten on create.
62        - Mutually exclusive with I(purge_retention_policy).
63      default: false
64      required: false
65      type: bool
66extends_documentation_fragment:
67- amazon.aws.aws
68- amazon.aws.ec2
69
70'''
71
72EXAMPLES = '''
73# Note: These examples do not set authentication details, see the AWS Guide for details.
74
75- community.aws.cloudwatchlogs_log_group:
76    log_group_name: test-log-group
77
78- community.aws.cloudwatchlogs_log_group:
79    state: present
80    log_group_name: test-log-group
81    tags: { "Name": "test-log-group", "Env" : "QA" }
82
83- community.aws.cloudwatchlogs_log_group:
84    state: present
85    log_group_name: test-log-group
86    tags: { "Name": "test-log-group", "Env" : "QA" }
87    kms_key_id: arn:aws:kms:region:account-id:key/key-id
88
89- community.aws.cloudwatchlogs_log_group:
90    state: absent
91    log_group_name: test-log-group
92
93'''
94
95RETURN = '''
96log_groups:
97    description: Return the list of complex objects representing log groups
98    returned: success
99    type: complex
100    contains:
101        log_group_name:
102            description: The name of the log group.
103            returned: always
104            type: str
105        creation_time:
106            description: The creation time of the log group.
107            returned: always
108            type: int
109        retention_in_days:
110            description: The number of days to retain the log events in the specified log group.
111            returned: always
112            type: int
113        metric_filter_count:
114            description: The number of metric filters.
115            returned: always
116            type: int
117        arn:
118            description: The Amazon Resource Name (ARN) of the log group.
119            returned: always
120            type: str
121        stored_bytes:
122            description: The number of bytes stored.
123            returned: always
124            type: str
125        kms_key_id:
126            description: The Amazon Resource Name (ARN) of the CMK to use when encrypting log data.
127            returned: always
128            type: str
129'''
130
131try:
132    import botocore
133except ImportError:
134    pass  # Handled by AnsibleAWSModule
135
136from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
137
138from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
139
140
141def create_log_group(client, log_group_name, kms_key_id, tags, retention, module):
142    request = {'logGroupName': log_group_name}
143    if kms_key_id:
144        request['kmsKeyId'] = kms_key_id
145    if tags:
146        request['tags'] = tags
147
148    try:
149        client.create_log_group(**request)
150    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
151        module.fail_json_aws(e, msg="Unable to create log group")
152
153    if retention:
154        input_retention_policy(client=client,
155                               log_group_name=log_group_name,
156                               retention=retention, module=module)
157
158    desc_log_group = describe_log_group(client=client,
159                                        log_group_name=log_group_name,
160                                        module=module)
161
162    if 'logGroups' in desc_log_group:
163        for i in desc_log_group['logGroups']:
164            if log_group_name == i['logGroupName']:
165                return i
166    module.fail_json(msg="The aws CloudWatchLogs log group was not created. \n please try again!")
167
168
169def input_retention_policy(client, log_group_name, retention, module):
170    try:
171        permited_values = [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]
172
173        if retention in permited_values:
174            response = client.put_retention_policy(logGroupName=log_group_name,
175                                                   retentionInDays=retention)
176        else:
177            delete_log_group(client=client, log_group_name=log_group_name, module=module)
178            module.fail_json(msg="Invalid retention value. Valid values are: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]")
179    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
180        module.fail_json_aws(e, msg="Unable to put retention policy for log group {0}".format(log_group_name))
181
182
183def delete_retention_policy(client, log_group_name, module):
184    try:
185        client.delete_retention_policy(logGroupName=log_group_name)
186    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
187        module.fail_json_aws(e, msg="Unable to delete retention policy for log group {0}".format(log_group_name))
188
189
190def delete_log_group(client, log_group_name, module):
191    desc_log_group = describe_log_group(client=client,
192                                        log_group_name=log_group_name,
193                                        module=module)
194
195    try:
196        if 'logGroups' in desc_log_group:
197            for i in desc_log_group['logGroups']:
198                if log_group_name == i['logGroupName']:
199                    client.delete_log_group(logGroupName=log_group_name)
200
201    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
202        module.fail_json_aws(e, msg="Unable to delete log group {0}".format(log_group_name))
203
204
205def describe_log_group(client, log_group_name, module):
206    try:
207        desc_log_group = client.describe_log_groups(logGroupNamePrefix=log_group_name)
208        return desc_log_group
209    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
210        module.fail_json_aws(e, msg="Unable to describe log group {0}".format(log_group_name))
211
212
213def main():
214    argument_spec = dict(
215        log_group_name=dict(required=True, type='str'),
216        state=dict(choices=['present', 'absent'],
217                   default='present'),
218        kms_key_id=dict(required=False, type='str'),
219        tags=dict(required=False, type='dict'),
220        retention=dict(required=False, type='int'),
221        purge_retention_policy=dict(required=False, type='bool', default=False),
222        overwrite=dict(required=False, type='bool', default=False),
223    )
224
225    mutually_exclusive = [['retention', 'purge_retention_policy'], ['purge_retention_policy', 'overwrite']]
226    module = AnsibleAWSModule(argument_spec=argument_spec, mutually_exclusive=mutually_exclusive)
227
228    try:
229        logs = module.client('logs')
230    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
231        module.fail_json_aws(e, msg='Failed to connect to AWS')
232
233    state = module.params.get('state')
234    changed = False
235
236    # Determine if the log group exists
237    desc_log_group = describe_log_group(client=logs, log_group_name=module.params['log_group_name'], module=module)
238    found_log_group = {}
239    for i in desc_log_group.get('logGroups', []):
240        if module.params['log_group_name'] == i['logGroupName']:
241            found_log_group = i
242            break
243
244    if state == 'present':
245        if found_log_group:
246            if module.params['overwrite'] is True:
247                changed = True
248                delete_log_group(client=logs, log_group_name=module.params['log_group_name'], module=module)
249                found_log_group = create_log_group(client=logs,
250                                                   log_group_name=module.params['log_group_name'],
251                                                   kms_key_id=module.params['kms_key_id'],
252                                                   tags=module.params['tags'],
253                                                   retention=module.params['retention'],
254                                                   module=module)
255            elif module.params['purge_retention_policy']:
256                if found_log_group.get('retentionInDays'):
257                    changed = True
258                    delete_retention_policy(client=logs,
259                                            log_group_name=module.params['log_group_name'],
260                                            module=module)
261            elif module.params['retention'] != found_log_group.get('retentionInDays'):
262                if module.params['retention'] is not None:
263                    changed = True
264                    input_retention_policy(client=logs,
265                                           log_group_name=module.params['log_group_name'],
266                                           retention=module.params['retention'],
267                                           module=module)
268                    found_log_group['retentionInDays'] = module.params['retention']
269
270        elif not found_log_group:
271            changed = True
272            found_log_group = create_log_group(client=logs,
273                                               log_group_name=module.params['log_group_name'],
274                                               kms_key_id=module.params['kms_key_id'],
275                                               tags=module.params['tags'],
276                                               retention=module.params['retention'],
277                                               module=module)
278
279        module.exit_json(changed=changed, **camel_dict_to_snake_dict(found_log_group))
280
281    elif state == 'absent':
282        if found_log_group:
283            changed = True
284            delete_log_group(client=logs,
285                             log_group_name=module.params['log_group_name'],
286                             module=module)
287
288    module.exit_json(changed=changed)
289
290
291if __name__ == '__main__':
292    main()
293