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