#!/usr/bin/python

# (c) 2023-2025, NetApp, Inc
# 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

DOCUMENTATION = """
module: na_ontap_vserver_audit
short_description: NetApp Ontap - create, delete or modify vserver audit configuration.
extends_documentation_fragment:
    - netapp.ontap.netapp.na_ontap_rest
version_added: '22.3.0'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
  - Create, delete or modify vserver audit configuration.
options:
  state:
    description:
      - Whether the specified vserver audit configuration should exist or not.
    choices: ['present', 'absent']
    default: present
    type: str
  vserver:
    description:
      - Specifies name of the Vserver.
    required: true
    type: str
  log_path:
    description:
      - The audit log destination path where consolidated audit logs are stored.
    type: str
  guarantee:
    description:
      - Indicates whether there is a strict Guarantee of Auditing.
      - This option requires ONTAP 9.10.1 or later.
    type: bool
  enabled:
    description:
      - Specifies whether or not auditing is enabled on the SVM.
    type: bool
  events:
    description:
      - Specifies events for which auditing is enabled on the SVM.
    type: dict
    suboptions:
      authorization_policy:
        description:
          - Authorization policy change events.
        type: bool
      cap_staging:
        description:
          - Central access policy staging events.
        type: bool
      cifs_logon_logoff:
        description:
          - CIFS logon and logoff events.
        type: bool
      file_operations:
        description:
          - File operation events.
        type: bool
      file_share:
        description:
          - File share category events.
        type: bool
      security_group:
        description:
          - Local security group management events.
        type: bool
      user_account:
        description:
          - Local user account management events.
        type: bool
  log:
    description:
      - Specifies events for which auditing is enabled on the SVM.
    type: dict
    suboptions:
      format:
        description:
          - This option describes the format in which the logs are generated by consolidation process.
            Possible values are,
          - xml - Data ONTAP-specific XML log format
          - evtx - Microsoft Windows EVTX log format
        choices: ['xml', 'evtx']
        type: str
      retention:
        description:
          - This option describes the count and time to retain the audit log file.
        type: dict
        suboptions:
          count:
            description:
              - Determines how many audit log files to retain before rotating the oldest log file out.
              - This is mutually exclusive with duration.
            type: int
          duration:
            description:
              - Specifies an ISO-8601 format date and time to retain the audit log file.
              - The audit log files are deleted once they reach the specified date/time.
              - This is mutually exclusive with count.
            type: str
      rotation:
        description:
          - Audit event log files are rotated when they reach a configured threshold log size or are on a configured schedule.
          - When an event log file is rotated, the scheduled consolidation task first renames the active converted file to a time-stamped archive file,
            and then creates a new active converted event log file.
        type: dict
        suboptions:
          size:
            description:
              - Rotates logs based on log size in bytes.
              - Default value is 104857600.
            type: int
          schedule:
            description:
              - Rotates the audit logs based on a schedule by using the time-based rotation parameters in any combination.
              - The rotation schedule is calculated by using all the time-related values.
            type: dict
            version_added: '22.11.0'
            suboptions:
              days:
                description:
                  - Specifies the day of the month schedule to rotate audit log. Specify -1 to rotate the audit logs all days of a month.
                type: list
                elements: int
              hours:
                description:
                  - Specifies the hourly schedule to rotate audit log. Specify -1 to rotate the audit logs every hour.
                type: list
                elements: int
              minutes:
                description:
                  - Specifies the minutes schedule to rotate the audit log.
                type: list
                elements: int
              months:
                description:
                  - Specifies the months schedule to rotate audit log. Specify -1 to rotate the audit logs every month.
                type: list
                elements: int
              weekdays:
                description:
                  - Specifies the weekdays schedule to rotate audit log. Specify -1 to rotate the audit logs every day.
                type: list
                elements: int

notes:
  - This module supports REST only.
  - At least one event should be enabled.
  - No other fields can be specified when enabled is specified for modify.
"""

EXAMPLES = """
- name: Create vserver audit configuration
  netapp.ontap.na_ontap_vserver_audit:
    state: present
    vserver: ansible
    enabled: true
    events:
      authorization_policy: false
      cap_staging: false
      cifs_logon_logoff: true
      file_operations: true
      file_share: false
      security_group: false
      user_account: false
    log_path: "/"
    log:
      format: xml
      retention:
        count: 4
      rotation:
        size: "1048576"
    guarantee: false
    hostname: "{{ netapp_hostname }}"
    username: "{{ netapp_username }}"
    password: "{{ netapp_password }}"

- name: Modify vserver audit configuration
  netapp.ontap.na_ontap_vserver_audit:
    state: present
    vserver: ansible
    enabled: true
    events:
      authorization_policy: true
      cap_staging: true
      cifs_logon_logoff: true
      file_operations: true
      file_share: true
      security_group: true
      user_account: true
    log_path: "/tmp"
    log:
      format: evtx
      retention:
        count: 5
      rotation:
        size: "104857600"
    guarantee: true
    hostname: "{{ netapp_hostname }}"
    username: "{{ netapp_username }}"
    password: "{{ netapp_password }}"

- name: Delete vserver audit configuration
  netapp.ontap.na_ontap_vserver_audit:
    state: absent
    vserver: ansible
    hostname: "{{ netapp_hostname }}"
    username: "{{ netapp_username }}"
    password: "{{ netapp_password }}"

# The audit logs are rotated in January and March on Monday, Wednesday, and Friday,
# at 6:15, 6:30, 6:45, 12:15, 12:30, 12:45, 18:15, 18:30, and 18:45
# The last 6 audit logs are retained
- name: Create vserver audit configuration
  netapp.ontap.na_ontap_vserver_audit:
    state: present
    vserver: ansible
    enabled: true
    events:
      authorization_policy: false
      cap_staging: false
      cifs_logon_logoff: true
      file_operations: true
      file_share: false
      security_group: false
      user_account: false
    log_path: "/"
    log:
      format: xml
      retention:
        count: 6
      rotation:
        schedule:
          hours: [6, 12, 18]
          minutes: [15, 30, 45]
          months: [1, 3]
          weekdays: [1, 3, 5]
    guarantee: false
    hostname: "{{ netapp_hostname }}"
    username: "{{ netapp_username }}"
    password: "{{ netapp_password }}"

# The audit logs are rotated monthly, all days of the week, at 12:30
- name: Modify vserver audit configuration
  netapp.ontap.na_ontap_vserver_audit:
    state: present
    vserver: ansible
    enabled: true
    events:
      authorization_policy: false
      cap_staging: false
      cifs_logon_logoff: true
      file_operations: true
      file_share: false
      security_group: false
      user_account: false
    log_path: "/"
    log:
      format: xml
      rotation:
        schedule:
          hours: [12]
          minutes: [30]
          months: [-1]
          weekdays: [-1]
    guarantee: false
    hostname: "{{ netapp_hostname }}"
    username: "{{ netapp_username }}"
    password: "{{ netapp_password }}"
"""

RETURN = """
"""

import time
from ansible.module_utils.basic import AnsibleModule
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic


class NetAppONTAPVserverAudit:
    """
    Class with vserver audit configuration methods
    """

    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_rest_only_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
            vserver=dict(required=True, type='str'),
            enabled=dict(required=False, type='bool'),
            guarantee=dict(required=False, type='bool'),
            log_path=dict(required=False, type='str'),
            log=dict(type='dict', options=dict(
                format=dict(type='str', choices=['xml', 'evtx']),
                retention=dict(type='dict', options=dict(
                    count=dict(type='int'),
                    duration=dict(type='str'),
                )),
                rotation=dict(type='dict', options=dict(
                    size=dict(type='int'),
                    schedule=dict(type='dict', options=dict(
                        days=dict(type='list', elements='int'),
                        hours=dict(type='list', elements='int'),
                        minutes=dict(type='list', elements='int'),
                        months=dict(type='list', elements='int'),
                        weekdays=dict(type='list', elements='int'),
                    )),
                )),
            )),
            events=dict(type='dict', options=dict(
                authorization_policy=dict(type='bool'),
                cap_staging=dict(type='bool'),
                cifs_logon_logoff=dict(type='bool'),
                file_operations=dict(type='bool'),
                file_share=dict(type='bool'),
                security_group=dict(type='bool'),
                user_account=dict(type='bool'),
            ))
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True
        )

        # set up variables
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.filter_out_none_entries(self.na_helper.set_parameters(self.module.params))

        self.rest_api = netapp_utils.OntapRestAPI(self.module)
        self.rest_api.fail_if_not_rest_minimum_version('na_ontap_vserver_audit', 9, 6)
        partially_supported_rest_properties = [['guarantee', (9, 10, 1)]]
        self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, None, partially_supported_rest_properties)
        self.svm_uuid = None
        if 'events' in self.parameters and self.parameters['state'] == 'present':
            if all(self.parameters['events'][value] is False for value in self.parameters['events']) is True:
                self.module.fail_json(msg="Error: At least one event should be enabled")

    def get_vserver_audit_configuration_rest(self):
        """
        Retrieves audit configurations.
        """
        api = "protocols/audit"
        query = {
            'svm.name': self.parameters['vserver'],
            'fields': 'svm.uuid,enabled,events,log,log_path,'
        }
        if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
            query['fields'] += 'guarantee,'
        record, error = rest_generic.get_one_record(self.rest_api, api, query)
        if error:
            self.module.fail_json(msg="Error on fetching vserver audit configuration: %s" % error)
        if record:
            self.svm_uuid = self.na_helper.safe_get(record, ['svm', 'uuid'])
            return {
                'enabled': self.na_helper.safe_get(record, ['enabled']),
                'events': self.na_helper.safe_get(record, ['events']),
                'log': self.na_helper.safe_get(record, ['log']),
                'log_path': self.na_helper.safe_get(record, ['log_path']),
                'guarantee': record.get('guarantee', False),
            }
        return record

    def schedule_rotation_key_value(self, key):
        value = self.na_helper.safe_get(self.parameters, ['log', 'rotation', 'schedule', key])
        return [] if -1 in value else value

    def create_vserver_audit_config_body_rest(self):
        """
        Vserver audit config body for create and modify with rest API.
        """
        body = {}
        if 'events' in self.parameters:
            body['events'] = self.parameters['events']
        if 'guarantee' in self.parameters:
            body['guarantee'] = self.parameters['guarantee']
        if self.na_helper.safe_get(self.parameters, ['log', 'retention', 'count']):
            body['log.retention.count'] = self.parameters['log']['retention']['count']
        if self.na_helper.safe_get(self.parameters, ['log', 'retention', 'duration']):
            body['log.retention.duration'] = self.parameters['log']['retention']['duration']
        if self.na_helper.safe_get(self.parameters, ['log', 'rotation']):
            if self.na_helper.safe_get(self.parameters, ['log', 'rotation', 'size']):
                body['log.rotation.size'] = self.parameters['log']['rotation']['size']
            else:
                for schedule_rotation_key in ['days', 'hours', 'minutes', 'months', 'weekdays']:
                    if self.na_helper.safe_get(self.parameters, ['log', 'rotation', 'schedule', schedule_rotation_key]) is not None:
                        key = 'log.rotation.schedule.' + schedule_rotation_key
                        body[key] = self.schedule_rotation_key_value(schedule_rotation_key)
        if self.na_helper.safe_get(self.parameters, ['log', 'format']):
            body['log.format'] = self.parameters['log']['format']
        if 'log_path' in self.parameters:
            body['log_path'] = self.parameters['log_path']
        return body

    def create_vserver_audit_configuration_rest(self):
        """
        Creates an audit configuration.
        """
        api = "protocols/audit"
        body = self.create_vserver_audit_config_body_rest()
        if 'vserver' in self.parameters:
            body['svm.name'] = self.parameters.get('vserver')
        if 'enabled' in self.parameters:
            body['enabled'] = self.parameters['enabled']
        record, error = rest_generic.post_async(self.rest_api, api, body)
        if error:
            self.module.fail_json(msg="Error on creating vserver audit configuration: %s" % error)

    def delete_vserver_audit_configuration_rest(self, current):
        """
        Deletes an audit configuration.
        """
        api = "protocols/audit/%s" % self.svm_uuid
        if current['enabled'] is True:
            modify = {'enabled': False}
            self.modify_vserver_audit_configuration_rest(modify)
            current = self.get_vserver_audit_configuration_rest()
        retry = 2
        while retry > 0:
            record, error = rest_generic.delete_async(self.rest_api, api, None)
            # Delete throws retry after sometime error during first run by default, hence retrying after sometime.
            if error and '9699350' in error:
                time.sleep(120)
                retry -= 1
            elif error:
                self.module.fail_json(msg="Error on deleting vserver audit configuration: %s" % error)
            else:
                return

    def modify_vserver_audit_configuration_rest(self, modify):
        """
        Updates audit configuration.
        """
        body = {}
        if 'enabled' in modify:
            body['enabled'] = modify['enabled']
        else:
            body = self.create_vserver_audit_config_body_rest()
        api = "protocols/audit"
        record, error = rest_generic.patch_async(self.rest_api, api, self.svm_uuid, body)
        if error:
            self.module.fail_json(msg="Error on modifying vserver audit configuration: %s" % error)

    def apply(self):
        current = self.get_vserver_audit_configuration_rest()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(current, self.parameters) if cd_action is None else None
        if self.na_helper.changed and not self.module.check_mode:
            if cd_action == 'create':
                self.create_vserver_audit_configuration_rest()
            elif cd_action == 'delete':
                self.delete_vserver_audit_configuration_rest(current)
            elif modify:
                # No other fields can be specified when enabled is specified for modify
                if 'enabled' in modify:
                    self.modify_vserver_audit_configuration_rest(modify)
                    modify.pop('enabled')
                if modify:
                    # This method will be called to modify fields other than enabled
                    self.modify_vserver_audit_configuration_rest(modify)
        result = netapp_utils.generate_result(self.na_helper.changed, cd_action)
        self.module.exit_json(**result)


def main():
    """
    Creates the NetApp Ontap vserver audit configuration object and runs the correct play task
    """
    obj = NetAppONTAPVserverAudit()
    obj.apply()


if __name__ == '__main__':
    main()
