diff --git a/lib/ansible/modules/cloud/azure/azure_rm_monitorlogprofile.py b/lib/ansible/modules/cloud/azure/azure_rm_monitorlogprofile.py new file mode 100644 index 00000000000..a27ac834a7f --- /dev/null +++ b/lib/ansible/modules/cloud/azure/azure_rm_monitorlogprofile.py @@ -0,0 +1,392 @@ +#!/usr/bin/python +# +# Copyright (c) 2019 Yunge Zhu, +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: azure_rm_monitorlogprofile +version_added: "2.9" +short_description: Manage Azure Monitor log profile +description: + - Create, update and delete instance of Azure Monitor log profile. + +options: + name: + description: + - Unique name of the log profile to create or update. + required: True + type: str + location: + description: + - Resource location. + type: str + locations: + description: + - List of regions for which Activity Log events should be stored. + type: list + categories: + description: + - List of categories of logs. These categories are created as is convenient to user. Some Values are C(Write), C(Delete) and/or C(Action). + type: list + retention_policy: + description: + - Retention policy for events in the log. + type: dict + suboptions: + enabled: + description: + - Whether the retention policy is enabled. + type: bool + days: + description: + - The number of days for the retention. A value of 0 will retain the events indefinitely. + type: int + service_bus_rule_id: + description: + - The service bus rule ID of the service bus namespace in which you would like to have Event Hubs created for streaming in the Activity Log. + - Format like {service_bus_resource_id}/authorizationrules{key_name}. + type: str + storage_account: + description: + - The storage account to which send the Activity Log. + - It could be a resource ID. + - It could be a dict containing I(resource_grorup) and I(name). + type: raw + state: + description: + - Assert the state of the log profile. + - Use C(present) to create or update a log profile and C(absent) to delete it. + default: present + type: str + choices: + - absent + - present + +extends_documentation_fragment: + - azure + - azure_tags + +author: + - Yunge Zhu(@yungezz) + +''' + +EXAMPLES = ''' + - name: Create a log profile + azure_rm_monitorlogprofile: + name: myProfile + location: eastus + locations: + - eastus + - westus + categories: + - Write + - Action + retention_policy: + enabled: False + days: 1 + storage_account: + resource_group: myResourceGroup + name: myStorageAccount + register: output + + - name: Delete a log profile + azure_rm_monitorlogprofile: + name: myProfile + state: absent +''' + +RETURN = ''' +id: + description: + - ID of the log profile. + returned: always + type: str + sample: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/microsoft.insights/logprofiles/myProfile + +''' + +import time +from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id + +try: + from msrestazure.azure_exceptions import CloudError + from msrestazure.azure_operation import AzureOperationPoller + from msrestazure.tools import is_valid_resource_id + from msrest.serialization import Model + from azure.mgmt.monitor.models import (RetentionPolicy, LogProfileResource, ErrorResponseException) +except ImportError: + # This is handled in azure_rm_common + pass + + +retention_policy_spec = dict( + enabled=dict(type='bool'), + days=dict(type='int') +) + + +def logprofile_to_dict(profile): + return dict( + id=profile.id, + name=profile.name, + location=profile.location, + locations=profile.locations, + categories=profile.categories, + storage_account=profile.storage_account_id, + service_bus_rule_id=profile.service_bus_rule_id, + retention_policy=dict( + enabled=profile.retention_policy.enabled, + days=profile.retention_policy.days + ), + tags=profile.tags if profile.tags else None + ) + + +class Actions: + NoAction, CreateOrUpdate, Delete = range(3) + + +class AzureRMMonitorLogprofile(AzureRMModuleBase): + """Configuration class for an Azure RM Monitor log profile""" + + def __init__(self): + self.module_arg_spec = dict( + name=dict( + type='str', + required=True + ), + location=dict( + type='str' + ), + locations=dict( + type='list', + elements='str' + ), + categories=dict( + type='list', + elements='str' + ), + retention_policy=dict( + type='dict', + options=retention_policy_spec + ), + service_bus_rule_id=dict( + type='str' + ), + storage_account=dict( + type='raw' + ), + state=dict( + type='str', + default='present', + choices=['present', 'absent'] + ) + ) + + self._client = None + + self.name = None + self.location = None + + self.locations = None + self.categories = None + self.retention_policy = False + self.service_bus_rule_id = None + self.storage_account = None + + self.tags = None + + self.results = dict( + changed=False, + id=None + ) + self.state = None + + super(AzureRMMonitorLogprofile, self).__init__(derived_arg_spec=self.module_arg_spec, + supports_check_mode=True, + supports_tags=True) + + def exec_module(self, **kwargs): + """Main module execution method""" + + for key in list(self.module_arg_spec.keys()) + ['tags']: + setattr(self, key, kwargs[key]) + + old_response = None + response = None + to_be_updated = False + + # get storage account id + if self.storage_account: + if isinstance(self.storage_account, dict): + self.storage_account = format_resource_id(val=self.storage_account['name'], + subscription_id=self.storage_account.get('subscription') or self.subscription_id, + namespace='Microsoft.Storage', + types='storageAccounts', + resource_group=self.storage_account.get('resource_group')) + elif not is_valid_resource_id(self.storage_account): + self.fail("storage_account either be a resource id or a dict containing resource_group and name") + + # get existing log profile + old_response = self.get_logprofile() + + if old_response: + self.results['id'] = old_response['id'] + + if self.state == 'present': + # if profile not exists, create new + if not old_response: + self.log("Log profile instance doesn't exist") + + to_be_updated = True + self.to_do = Actions.CreateOrUpdate + + else: + # log profile exists already, do update + self.log("Log profile instance already exists") + + update_tags, self.tags = self.update_tags(old_response.get('tags', None)) + + if update_tags: + to_be_updated = True + self.to_do = Actions.CreateOrUpdate + + # check if update + if self.check_update(old_response): + to_be_updated = True + self.to_do = Actions.CreateOrUpdate + + elif self.state == 'absent': + if old_response: + self.log("Delete log profile instance") + self.results['id'] = old_response['id'] + to_be_updated = True + self.to_do = Actions.Delete + else: + self.results['changed'] = False + self.log("Log profile {0} not exists.".format(self.name)) + + if to_be_updated: + self.log('Need to Create/Update log profile') + self.results['changed'] = True + + if self.check_mode: + return self.results + + if self.to_do == Actions.CreateOrUpdate: + response = self.create_or_update_logprofile() + self.results['id'] = response['id'] + + if self.to_do == Actions.Delete: + self.delete_logprofile() + self.log('Log profile instance deleted') + + return self.results + + def check_update(self, existing): + if self.locations is not None and existing['locations'] != self.locations: + self.log("locations diff: origin {0} / update {1}".format(existing['locations'], self.locations)) + return True + if self.retention_policy is not None: + if existing['retention_policy']['enabled'] != self.retention_policy['enabled']: + self.log("retention_policy diff: origin {0} / update {1}".format(str(existing['sku']['name']), str(self.retention_policy['enabled']))) + return True + if existing['retention_policy']['days'] != self.retention_policy['days']: + self.log("retention_policy diff: origin {0} / update {1}".format(existing['retention_policy']['days'], str(self.retention_policy['days']))) + return True + if self.storage_account is not None and existing['storage_account'] != self.storage_account: + self.log("storage_account diff: origin {0} / update {1}".format(existing['storage_account'], self.storage_account)) + return True + if self.service_bus_rule_id is not None and existing['service_bus_rule_id'] != self.service_bus_rule_id: + self.log("service_bus_rule_id diff: origin {0} / update {1}".format(existing['service_bus_rule_id'], self.service_bus_rule_id)) + return True + return False + + def create_or_update_logprofile(self): + ''' + Creates or Update log profile. + + :return: deserialized log profile state dictionary + ''' + self.log( + "Creating log profile instance {0}".format(self.name)) + + try: + params = LogProfileResource( + location=self.location, + locations=self.locations, + categories=self.categories, + retention_policy=RetentionPolicy(days=self.retention_policy['days'], + enabled=self.retention_policy['enabled']) if self.retention_policy else None, + storage_account_id=self.storage_account if self.storage_account else None, + service_bus_rule_id=self.service_bus_rule_id if self.service_bus_rule_id else None, + tags=self.tags + ) + + response = self.monitor_client.log_profiles.create_or_update(log_profile_name=self.name, + parameters=params) + if isinstance(response, AzureOperationPoller): + response = self.get_poller_result(response) + + except CloudError as exc: + self.log('Error attempting to create/update log profile.') + self.fail("Error creating/updating log profile: {0}".format(str(exc))) + return logprofile_to_dict(response) + + def delete_logprofile(self): + ''' + Deletes specified log profile. + + :return: True + ''' + self.log("Deleting the log profile instance {0}".format(self.name)) + try: + response = self.monitor_client.log_profiles.delete(log_profile_name=self.name) + except CloudError as e: + self.log('Error attempting to delete the log profile.') + self.fail( + "Error deleting the log profile: {0}".format(str(e))) + return True + + def get_logprofile(self): + ''' + Gets the properties of the specified log profile. + + :return: log profile state dictionary + ''' + self.log("Checking if the log profile {0} is present".format(self.name)) + + response = None + + try: + response = self.monitor_client.log_profiles.get(log_profile_name=self.name) + + self.log("Response : {0}".format(response)) + self.log("log profile : {0} found".format(response.name)) + return logprofile_to_dict(response) + + except ErrorResponseException as ex: + self.log("Didn't find log profile {0}".format(self.name)) + + return False + + +def main(): + """Main execution""" + AzureRMMonitorLogprofile() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/azure_rm_monitorlogprofile/aliases b/test/integration/targets/azure_rm_monitorlogprofile/aliases new file mode 100644 index 00000000000..35b94011518 --- /dev/null +++ b/test/integration/targets/azure_rm_monitorlogprofile/aliases @@ -0,0 +1,3 @@ +cloud/azure +destructive +unsupported \ No newline at end of file diff --git a/test/integration/targets/azure_rm_monitorlogprofile/meta/main.yml b/test/integration/targets/azure_rm_monitorlogprofile/meta/main.yml new file mode 100644 index 00000000000..95e1952f989 --- /dev/null +++ b/test/integration/targets/azure_rm_monitorlogprofile/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_azure diff --git a/test/integration/targets/azure_rm_monitorlogprofile/tasks/main.yml b/test/integration/targets/azure_rm_monitorlogprofile/tasks/main.yml new file mode 100644 index 00000000000..9019f59b28f --- /dev/null +++ b/test/integration/targets/azure_rm_monitorlogprofile/tasks/main.yml @@ -0,0 +1,104 @@ +- name: Prepare random number + set_fact: + profile_name: "profile{{ resource_group | hash('md5') | truncate(5, True, '') }}{{ 1000 | random }}" + location: eastus + run_once: yes + +- name: create Azure storage account + azure_rm_storageaccount: + name: '{{ profile_name }}' + resource_group: "{{ resource_group }}" + account_type: Standard_LRS + +- name: create log profile (idempotence) + azure_rm_monitorlogprofile: + name: "{{ profile_name }}" + location: "{{ location }}" + locations: + - eastus + - westus + categories: + - Write + - Action + retention_policy: + enabled: False + days: 1 + storage_account: + resource_group: "{{ resource_group }}" + name: "{{ profile_name }}" + check_mode: yes + register: output + +- name: assert create idempotence + assert: + that: + - output.changed + +- name: create log profile + azure_rm_monitorlogprofile: + name: "{{ profile_name }}" + location: "{{ location }}" + locations: + - eastus + - westus + categories: + - Write + - Action + retention_policy: + enabled: False + days: 1 + storage_account: + resource_group: "{{ resource_group }}" + name: "{{ profile_name }}" + register: output + +- name: assert create + assert: + that: + - output.changed + - output.id + +- name: update log profile (idempotence) + azure_rm_monitorlogprofile: + name: "{{ profile_name }}" + location: "{{ location }}" + locations: + - eastus + - westus + categories: + - Write + - Action + retention_policy: + enabled: False + days: 1 + storage_account: + resource_group: "{{ resource_group }}" + name: "{{ storage_name }}" + register: output + +- name: assert update idempotence + assert: + that: + - output.changed == False + +- name: update log profile + azure_rm_monitorlogprofile: + name: "{{ profile_name }}" + location: "{{ location }}" + locations: + - eastus + categories: + - Write + - Action + retention_policy: + enabled: False + days: 2 + storage_account: + resource_group: "{{ resource_group }}" + name: "{{ profile_name }}" + register: output + +- name: assert update + assert: + that: + - output.changed