<reponame>usegalaxy-no/usegalaxy<filename>venv/lib/python3.6/site-packages/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_policy_rule.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2017, F5 Networks 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 = r'''
---
module: bigip_policy_rule
short_description: Manage LTM policy rules on a BIG-IP
description:
  - This module manages LTM policy rules on a BIG-IP.
version_added: "1.0.0"
options:
  name:
    description:
      - The name of the rule.
    type: str
    required: True
  policy:
    description:
      - The name of the policy you want to associate this rule with.
    type: str
    required: True
  replace_with:
    description:
      - Specifies if the C(conditions)/C(actions) given by the user should overwrite what exists on the device.
      - The option is useful when a subset of C(conditions)/C(actions) needs to be removed. This option is similar to the
        replace-all-with flag available in TMSH commands.
      - Using this option is not idempotent.
    type: bool
    default: no
  rule_order:
    description:
      - Specifies a number that indicates the order of this rule relative to other rules in the policy.
      - When not set, the device sets the parameter to 0.
      - If there are rules with the same rule order number, the device uses rule names
        to determine how the rules are ordered.
      - The lower the number, the lower the rule is in the general order, with the lowest number C(0) being the
        topmost one.
      - Valid range of values is between C(0) and C(4294967295) inclusive.
    type: int
    version_added: "1.10.0"
  description:
    description:
      - Description of the policy rule.
    type: str
  actions:
    description:
      - The actions you want the policy rule to perform.
      - The available attributes vary by the action, however, each action requires
        you specify a C(type).
      - These conditions can be specified in any order. Despite the fact they are in a list,
        the order in the list does not matter to the BIG-IP.
    type: list
    elements: dict
    suboptions:
      type:
        description:
          - The action type. This value controls which of the following options are required.
          - When C(type) is C(forward), the system associates a given C(pool), or C(virtual),
            or C(node) with this rule.
          - When C(type) is C(enable), the system associates a given C(asm_policy) with
            this rule.
          - When C(type) is C(ignore), the system removes all existing actions from this
            rule.
          - When C(type) is C(redirect), the system redirects an HTTP request to a different URL.
          - When C(type) is C(reset), the system resets the connection upon C(event).
          - When C(type) is C(persist), the system associates C(cookie_insert) and C(cookie_expiry) with this rule.
          - When C(type) is C(set_variable), the system sets a variable based on the evaluated Tcl C(expression) based on C(event).
          - When C(type) is C(remove), the system removes C(http_set_cookie), C(http_referer), C(http_header) or C(http_cookie) with this rule.
          - When C(type) is C(insert), the system inserts C(http_set_cookie), C(http_referer), C(http_header) or C(http_cookie) with this rule.
          - When C(type) is C(replace), the system replaces C(http_connect), C(http_referer), C(http_header), C(http_uri) or C(http_host) with this rule.
          - When C(type) is C(disable), the system disables C(disable_target) with this rule.
        type: str
        required: true
        choices:
          - forward
          - enable
          - ignore
          - redirect
          - reset
          - persist
          - set_variable
          - remove
          - insert
          - replace
          - disable
      pool:
        description:
          - Pool to which you want to forward traffic.
          - This parameter is only valid with the C(forward) type.
        type: str
      virtual:
        description:
          - Virtual server to which you want to forward traffic.
          - This parameter is only valid with the C(forward) type.
        type: str
      node:
        description:
          - Node to which you want to forward traffic.
          - This parameter is only valid with the C(forward) type.
        type: str
        version_added: "1.2.0"
      disable_target:
        description:
          - Target you want to disable.
          - This parameter is only valid with the C(disable) type.
        type: str
        version_added: "1.8.0"
        choices:
          - server_ssl
          - persist
      asm_policy:
        description:
          - ASM policy to enable.
          - This parameter is only valid with the C(enable) type.
        type: str
      location:
        description:
          - The new URL for which a redirect response is sent.
          - A Tcl command substitution can be used for this field.
        type: str
      event:
        description:
          - Events on which actions, such as reset and forward, can be triggered.
          - With the C(set_variable) action, it is used for specifying
            an action event, such as request or response.
          - "Valid event choices for C(forward) action type are: client_accepted, proxy_request
            request, ssl_client_hello and ssl_client_server_hello_send."
          - "Valid event choices for C(reset) acton type are: client_accepted, proxy_connect
            proxy_request, proxy_response, request, response, server_connected, ssl_client_hello,
            ssl_client_server_hello_send, ssl_server_handshake, ssl_server_hello, websocket_request,
            websocket_response."
          - "Valid event choices for C(disable) acton type are: client_accepted, proxy_connect
            proxy_request, proxy_response, request, server_connected."
        type: str
      expression:
        description:
          - A Tcl expression used with the C(set_variable) action.
        type: str
      variable_name:
        description:
           - Variable name used with the C(set_variable) action.
        type: str
      cookie_insert:
        description:
          - Cookie name on which you want to persist.
          - This parameter is only valid with the C(persist) type.
        type: str
        version_added: "1.1.0"
      cookie_expiry:
        description:
          - Optional argument, specifying the time for which the session is persisted.
          - This parameter is only valid with the C(persist) type.
        type: int
        version_added: "1.1.0"
      http_header:
        description:
          - HTTP Header that you want to remove or insert.
          - This parameter is only valid with the C(remove), C(insert) and C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_header) is removed, replaced, or inserted.
              - The C(request) and C(response) events are only choices with C(remove) and C(insert) type.
              - All of events are valid with C(replace) type action.
            type: str
            required: True
            choices:
              - request
              - response
              - proxy_connect
              - proxy_request
              - proxy_response
          name:
            description:
              - The name of C(http_header).
            type: str
            required: True
          value:
            description:
              - The value of C(http_header).
              - Mandatory parameter when configured with C(insert) or C(replace) type.
            type: str
        version_added: "1.8.0"
      http_referer:
        description:
          - HTTP Referer header you want to remove, replace, or insert.
          - This parameter is only valid with the C(remove), C(insert) and C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the c(http_referer) is removed, replaced, or inserted.
            required: True
            type: str
            choices:
              - request
              - proxy_connect
              - proxy_request
          value:
            description:
              - The value of C(http_referer).
              - This is a mandatory parameter when configured with C(insert) type action.
              - This parameter is ignored for the C(remove) type.
              - This parameter is optional for the C(replace) type.
            type: str
        version_added: "1.8.0"
      http_set_cookie:
        description:
          - HTTP Set-Cookie header you want to remove or insert.
          - This parameter is only valid with the C(remove) or c(insert) type.
        type: dict
        suboptions:
          name:
            description:
              - The name of C(http_set_cookie).
            type: str
            required: True
          value:
            description:
              - The value of C(http_set_cookie).
              - This is a mandatory parameter when configured with C(insert) type action.
            type: str
        version_added: "1.8.0"
      http_cookie:
        description:
          - HTTP Cookie header you want to remove or insert.
          - This parameter is only valid with the C(remove) and C(insert) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_cookie) is removed or inserted.
            type: str
            required: True
            choices:
              - request
              - proxy_connect
              - proxy_request
          name:
            description:
              - The name of C(http_cookie).
            type: str
            required: True
          value:
            description:
              - The value of C(http_cookie).
              - This is a mandatory parameter when configured with C(insert) type action.
            type: str
        version_added: "1.8.0"
      http_connect:
        description:
          - HTTP Connect header you want to replace.
          - This parameter is only valid with the C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_connect) header is replaced.
            required: True
            type: str
            choices:
              - client_accepted
              - proxy_connect
              - proxy_request
              - proxy_response
              - request
              - server_connected
              - ssl_client_hello
          value:
            description:
              - The value of C(http_connect).
            type: str
            required: True
          port:
            description:
              - The port number.
              - If a port number is not provided, the value is set to 0 by default.
              - Be explicit when defining rules, so the system does not override port values.
            type: int
        version_added: "1.8.0"
      http_host:
        description:
          - HTTP Host header you want to replace.
          - This parameter is only valid with the C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_host) is replaced.
            type: str
            required: True
            choices:
              - request
              - proxy_connect
              - proxy_request
          value:
            description:
              - The value of C(http_host).
            type: str
            required: True
        version_added: "1.8.0"
      http_uri:
        description:
          - Replaces HTTP URI, path, or string.
          - This parameter is only valid with the C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_uri) is replaced.
            type: str
            required: True
            choices:
              - request
              - proxy_connect
              - proxy_request
          type:
            description:
              - Specifies the part of the C(http_uri) to be replaced.
            type: str
            required: True
            choices:
              - path
              - query_string
              - full_string
          value:
            description:
              - The value of C(http_uri).
            type: str
            required: True
        version_added: "1.8.0"
  conditions:
    description:
      - A list of attributes that describe the condition.
      - See suboptions for details on how to construct each list entry.
      - The ordering of this list is important, the module ensures the order is
        kept when modifying the task.
      - The suboption options below are not required for all condition types,
        read the description for more details.
      - These conditions can be specified in any order. Despite the fact they are in a list,
        the order in the list does not matter to the BIG-IP.
    type: list
    elements: dict
    suboptions:
      type:
        description:
          - The condition type. This value controls which of the following options are required.
          - "When C(type) is C(http_uri), the valid choices are: C(path_begins_with_any), C(path_contains) or
            C(path_is_any)."
          - "When C(type) is C(http_host), the valid choices are: C(host_is_any), C(host_is_not_any),
            C(host_begins_with_any) or C(host_ends_with_any)."
          - "When C(type) is C(http_header), the C(header_name) parameter is mandatory and the valid choice is:
            C(header_is_any)."
          - "When C(type) is C(http_method), the valid choices are: C(method_matches_with_any)."
          - When C(type) is C(all_traffic), the system removes all existing conditions from
            this rule.
        type: str
        required: True
        choices:
          - http_uri
          - all_traffic
          - http_host
          - http_header
          - http_method
          - ssl_extension
          - tcp
      path_begins_with_any:
        description:
          - A list of strings of characters the HTTP URI should start with.
          - This parameter is only valid with the C(http_uri) type.
        type: list
        elements: str
      path_contains:
        description:
          - A list of strings of characters the HTTP URI should contain.
          - This parameter is only valid with the C(http_uri) type.
        type: list
        elements: str
        version_added: "1.8.0"
      path_is_any:
        description:
          - A list of strings of characters the HTTP URI should match.
          - This parameter is only valid with the C(http_uri) type.
        type: list
        elements: str
        version_added: "1.8.0"
      host_is_any:
        description:
          - A list of strings of characters the HTTP Host should match.
          - This parameter is only valid with the C(http_host) type.
        type: list
        elements: str
      host_is_not_any:
        description:
          - A list of strings of characters the HTTP Host should not match.
          - This parameter is only valid with the C(http_host) type.
        type: list
        elements: str
      host_begins_with_any:
        description:
          - A list of strings of characters the HTTP Host should start with.
          - This parameter is only valid with the C(http_host) type.
        type: list
        elements: str
      host_ends_with_any:
        description:
          - A list of strings of characters the HTTP Host should end with.
          - This parameter is only valid with the C(http_host) type.
        type: list
        elements: str
        version_added: "1.8.0"
      header_is_any:
        description:
          - A list of strings of characters the HTTP Header value should match.
          - This parameter is only valid with the C(http_header) type.
        type: list
        elements: str
        version_added: "1.8.0"
      header_name:
        description:
          - A name of C(http_header).
          - This parameter is only valid with the C(http_header) type.
        type: str
        version_added: "1.8.0"
      method_matches_with_any:
        description:
          - A list of strings of characters the HTTP Method value should match.
          - This parameter is only valid with the C(http_method) type.
        type: list
        elements: str
        version_added: "1.10.0"
      server_name_is_any:
        description:
          - A list of strings of characters the SSL Extension should match.
          - This parameter is only valid with the C(ssl_extension) type.
        type: list
        elements: str
      address_matches_with_any:
        description:
          - A list of IP Subnet address strings the IP address should match.
          - This parameter is only valid with the C(tcp) type.
        type: list
        elements: str
        version_added: "1.8.0"
      address_matches_with_datagroup:
        description:
          - A list of internal datagroup strings the IP address should match.
          - This parameter is only valid with the C(tcp) type.
        type: list
        elements: str
        version_added: "1.8.0"
      address_matches_with_external_datagroup:
        description:
          - A list of external datagroup strings the IP address should match.
          - This parameter is only valid with the C(tcp) type.
        type: list
        elements: str
        version_added: "1.10.0"
      event:
        description:
          - Events on which conditions type match rules can be triggered.
          - Supported only for C(http_header), C(http_method), C(ssl_extension) and C(tcp).
          - "Valid choices for C(http_header) condition types are: C(proxy_connect),
            C(proxy_request), C(proxy_response), C(request) and C(response)."
          - "Valid choices for C(http_method) condition types are: C(proxy_connect),
            C(proxy_request), C(proxy_response), C(request) and C(response)."
          - "Valid choices for C(tcp) condition types are: C(request), C(client_accepted),
            C(proxy_connect), C(proxy_request), C(proxy_response), C(ssl_client_hello), and
            C(ssl_client_server_hello_send)."
          - "Valid choices for C(ssl_extension) are: C(ssl_client_hello), and C(ssl_client_server_hello_send)."
        type: str
  state:
    description:
      - When C(present), ensures the key is uploaded to the device. When
        C(absent), ensures the key is removed from the device. If the key
        is currently in use, the module will not be able to remove the key.
    type: str
    choices:
      - present
      - absent
    default: present
  partition:
    description:
      - Device partition to manage resources on.
    type: str
    default: Common
extends_documentation_fragment: f5networks.f5_modules.f5
requirements:
  - BIG-IP >= v12.1.0
author:
  - <NAME> (@caphrim007)
  - <NAME> (@wojtek0806)
'''

EXAMPLES = r'''
- name: Create policies
  bigip_policy:
    name: Policy-Foo
    state: present
    provider:
      server: lb.mydomain.com
      user: admin
      password: <PASSWORD>
  delegate_to: localhost

- name: Add a rule to the new policy
  bigip_policy_rule:
    policy: Policy-Foo
    name: rule3
    conditions:
      - type: http_uri
        path_begins_with_any:
          - /ABC
    actions:
      - type: forward
        pool: pool-svrs
    provider:
      server: lb.mydomain.com
      user: admin
      password: <PASSWORD>
  delegate_to: localhost

- name: Add multiple rules to the new policy
  bigip_policy_rule:
    policy: Policy-Foo
    name: "{{ item.name }}"
    conditions: "{{ item.conditions }}"
    actions: "{{ item.actions }}"
    provider:
      server: lb.mydomain.com
      user: admin
      password: <PASSWORD>
  delegate_to: localhost
  loop:
    - name: rule1
      actions:
        - type: forward
          pool: pool-svrs
      conditions:
        - type: http_uri
          path_begins_with_any:
            - /euro
    - name: rule2
      actions:
        - type: forward
          pool: pool-svrs
      conditions:
        - type: http_uri
          path_begins_with_any:
            - /HomePage/
    - name: rule3
      actions:
        - type: set_variable
          variable_name: user-agent
          expression: tcl:[HTTP::header User-Agent]
          event: request
      conditions:
        - type: http_uri
          path_begins_with_any:
            - /HomePage/

- name: Remove all rules and conditions from the rule
  bigip_policy_rule:
    policy: Policy-Foo
    name: rule1
    conditions:
      - type: all_traffic
    actions:
      - type: ignore
    provider:
      server: lb.mydomain.com
      user: admin
      password: <PASSWORD>
  delegate_to: localhost
'''

RETURN = r'''
actions:
  description: The new list of actions applied to the rule.
  returned: changed
  type: complex
  contains:
    type:
      description: The action type.
      returned: changed
      type: str
      sample: forward
    pool:
      description: Pool for forwarding to.
      returned: changed
      type: str
      sample: foo-pool
  sample: hash/dictionary of values
conditions:
  description: The new list of conditions applied to the rule.
  returned: changed
  type: complex
  contains:
    type:
      description: The condition type.
      returned: changed
      type: str
      sample: http_uri
    path_begins_with_any:
      description: List of strings the URI begins with.
      returned: changed
      type: list
      sample: [foo, bar]
  sample: hash/dictionary of values
description:
  description: The new description of the rule.
  returned: changed
  type: str
  sample: My rule
rule_order:
  description: Specifies a number that indicates the order of this rule relative to other rules in the policy.
  returned: changed
  type: int
  sample: 10
'''

from datetime import datetime

from ansible.module_utils.basic import (
    AnsibleModule, env_fallback
)
from ansible.module_utils.six import iteritems

from ..module_utils.bigip import F5RestClient
from ..module_utils.common import (
    F5ModuleError, AnsibleF5Parameters, transform_name, f5_argument_spec, fq_name
)
from ..module_utils.compare import compare_complex_list
from ..module_utils.icontrol import tmos_version
from ..module_utils.teem import send_teem


class Parameters(AnsibleF5Parameters):
    api_map = {
        'actionsReference': 'actions',
        'conditionsReference': 'conditions',
        'ordinal': 'rule_order',
    }
    api_attributes = [
        'description',
        'actions',
        'conditions',
        'ordinal',
    ]

    updatables = [
        'actions',
        'conditions',
        'description',
        'rule_order',
    ]

    returnables = [
        'description',
        'action',
        'conditions',
        'rule_order',
    ]

    @property
    def name(self):
        return self._values.get('name', None)

    @property
    def description(self):
        return self._values.get('description', None)

    @property
    def policy(self):
        if self._values['policy'] is None:
            return None
        return self._values['policy']


class ApiParameters(Parameters):
    def _remove_internal_keywords(self, resource):
        items = [
            'kind', 'generation', 'selfLink', 'poolReference', 'offset', 'datagroupReference'
        ]
        for item in items:
            try:
                del resource[item]
            except KeyError:
                pass

    @property
    def actions(self):
        result = []
        if self._values['actions'] is None or 'items' not in self._values['actions']:
            return [dict(type='ignore')]
        for item in self._values['actions']['items']:
            action = dict()
            self._remove_internal_keywords(item)
            if 'forward' in item:
                action.update(item)
                action['type'] = 'forward'
                del action['forward']
            elif 'enable' in item:
                action.update(item)
                action['type'] = 'enable'
                del action['enable']
            elif 'disable' in item:
                action.update(item)
                action['type'] = 'disable'
                del action['disable']
            elif 'redirect' in item:
                action.update(item)
                action['type'] = 'redirect'
                del action['redirect']
            elif 'setVariable' in item:
                action.update(item)
                action['type'] = 'set_variable'
                del action['fullPath']
                del action['code']
                del action['expirySecs']
                del action['length']
                del action['port']
                del action['status']
                del action['vlanId']
                del action['timeout']
            elif 'shutdown' in item:
                action.update(item)
                action['type'] = 'reset'
                del action['shutdown']
            elif 'persist' in item:
                action.update(item)
                action['type'] = 'persist'
                del action['persist']
            elif 'remove' in item:
                action.update(item)
                action['type'] = 'remove'
                action.pop('fullPath', None)
                action.pop('code', None)
                action.pop('expirySecs', None)
                action.pop('length', None)
                action.pop('port', None)
                action.pop('status', None)
                action.pop('vlanId', None)
                action.pop('timeout', None)
                action.pop('offset', None)
                del action['remove']
            elif 'insert' in item:
                action.update(item)
                action['type'] = 'insert'
                action.pop('fullPath', None)
                action.pop('code', None)
                action.pop('expirySecs', None)
                action.pop('length', None)
                action.pop('port', None)
                action.pop('status', None)
                action.pop('vlanId', None)
                action.pop('timeout', None)
                action.pop('offset', None)
                del action['insert']
            elif 'replace' in item:
                action.update(item)
                action['type'] = 'replace'
                action.pop('fullPath', None)
                action.pop('code', None)
                action.pop('expirySecs', None)
                action.pop('length', None)
                action.pop('status', None)
                action.pop('vlanId', None)
                action.pop('timeout', None)
                action.pop('offset', None)
                if 'httpConnect' not in action:
                    action.pop('port', None)
                del action['replace']
            result.append(action)
        result = sorted(result, key=lambda x: x['name'])
        return result

    @property
    def conditions(self):
        result = []
        if self._values['conditions'] is None or 'items' not in self._values['conditions']:
            return [dict(type='all_traffic')]
        for item in self._values['conditions']['items']:
            action = dict()
            self._remove_internal_keywords(item)
            if 'httpUri' in item:
                action.update(item)
                action['type'] = 'http_uri'
                del action['httpUri']

                # Converts to common stringiness
                #
                # The tuple set "issubset" check that happens in the Difference
                # engine does not recognize that a u'foo' and 'foo' are equal "enough"
                # to consider them a subset. Therefore, we cast everything here to
                # whatever the common stringiness is.
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
            elif 'httpHost' in item:
                action.update(item)
                action['type'] = 'http_host'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
                del action['httpHost']
            elif 'httpMethod' in item:
                action.update(item)
                action['type'] = 'http_method'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
                del action['httpMethod']
            elif 'httpHeader' in item:
                action.update(item)
                action['type'] = 'http_header'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
                del action['httpHeader']
            elif 'sslExtension' in item:
                action.update(item)
                action['type'] = 'ssl_extension'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
                del action['sslExtension']
            elif 'tcp' in item:
                action.update(item)
                action['type'] = 'tcp'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
            result.append(action)
        # Names contains the index in which the rule is at.
        result = sorted(result, key=lambda x: x['name'])
        return result


class ModuleParameters(Parameters):
    @property
    def rule_order(self):
        if self._values['rule_order'] is None:
            return None
        if 0 < self._values['rule_order'] > 4294967295:
            raise F5ModuleError(
                "Specified number is out of valid range, correct range is between 0 and 4294967295."
            )
        return self._values['rule_order']

    @property
    def actions(self):
        result = []
        if self._values['actions'] is None:
            return None
        for idx, item in enumerate(self._values['actions']):
            action = dict()
            if 'name' in item:
                action['name'] = str(item['name'])
            else:
                action['name'] = str(idx)
            if item['type'] == 'forward':
                self._handle_forward_action(action, item)
            elif item['type'] == 'set_variable':
                self._handle_set_variable_action(action, item)
            elif item['type'] == 'enable':
                self._handle_enable_action(action, item)
            if item['type'] == 'disable':
                self._handle_disable_action(action, item)
            elif item['type'] == 'ignore':
                return [dict(type='ignore')]
            elif item['type'] == 'redirect':
                self._handle_redirect_action(action, item)
            elif item['type'] == 'reset':
                self._handle_reset_action(action, item)
                del action['shutdown']
            elif item['type'] == 'persist':
                self._handle_persist_action(action, item)
            elif item['type'] == 'remove':
                self._handle_remove_action(action, item)
            elif item['type'] == 'insert':
                self._handle_insert_action(action, item)
            elif item['type'] == 'replace':
                self._handle_replace_action(action, item)
            result.append(action)
        result = sorted(result, key=lambda x: x['name'])
        return result

    @property
    def conditions(self):
        result = []
        if self._values['conditions'] is None:
            return None
        for idx, item in enumerate(self._values['conditions']):
            action = dict()
            if 'name' in item:
                action['name'] = str(item['name'])
            else:
                action['name'] = str(idx)
            if item['type'] == 'http_uri':
                self._handle_http_uri_condition(action, item)
            elif item['type'] == 'http_method':
                self._handle_http_method_condition(action, item)
            elif item['type'] == 'http_host':
                self._handle_http_host_condition(action, item)
            elif item['type'] == 'http_header':
                self._handle_http_header_condition(action, item)
            elif item['type'] == 'ssl_extension':
                self._handle_ssl_extension_condition(action, item)
            elif item['type'] == 'tcp':
                self._handle_tcp_condition(action, item)
            elif item['type'] == 'all_traffic':
                return [dict(type='all_traffic')]
            result.append(action)
        result = sorted(result, key=lambda x: x['name'])
        return result

    def _handle_http_host_condition(self, action, item):
        options = ['host_begins_with_any', 'host_ends_with_any', 'host_is_any', 'host_is_not_any']
        action['type'] = 'http_host'

        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'host_begins_with_any', 'host_ends_with_any', host_is_any, or 'host_is_not_any' must be specified "
                "when the 'http_uri' type is used."
            )

        if 'host_begins_with_any' in item and item['host_begins_with_any'] is not None:
            if isinstance(item['host_begins_with_any'], list):
                values = item['host_begins_with_any']
            else:
                values = [item['host_begins_with_any']]
            action.update(dict(
                host=True,
                startsWith=True,
                values=values
            ))
        elif 'host_ends_with_any' in item and item['host_ends_with_any'] is not None:
            if isinstance(item['host_ends_with_any'], list):
                values = item['host_ends_with_any']
            else:
                values = [item['host_ends_with_any']]
            action.update(dict(
                host=True,
                endsWith=True,
                values=values
            ))
        elif 'host_is_any' in item and item['host_is_any'] is not None:
            if isinstance(item['host_is_any'], list):
                values = item['host_is_any']
            else:
                values = [item['host_is_any']]
            action.update(dict(
                equals=True,
                host=True,
                values=values
            ))
        elif 'host_is_not_any' in item and item['host_is_not_any'] is not None:
            if isinstance(item['host_is_not_any'], list):
                values = item['host_is_not_any']
            else:
                values = [item['host_is_not_any']]
            action.update({
                'equals': True,
                'host': True,
                'not': True,
                'values': values
            })

    def _handle_http_method_condition(self, action, item):
        options = ['method_matches_with_any']
        action['type'] = 'http_method'
        event_map = dict(
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            response='response',
        )

        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'method_matches_with_any' must be specified when the 'http_method' type is used."
            )

        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

        if 'method_matches_with_any' in item and item['method_matches_with_any'] is not None:
            if isinstance(item['method_matches_with_any'], list):
                values = item['method_matches_with_any']
            else:
                values = [item['method_matches_with_any']]
            action.update(dict(
                startsWith=True,
                values=values
            ))

    def _handle_http_uri_condition(self, action, item):
        action['type'] = 'http_uri'
        options = ['path_begins_with_any', 'path_contains', 'path_is_any']

        if all(k not in item for k in options):
            raise F5ModuleError(
                "A 'path_begins_with_any', 'path_contains' or 'path_is_any' must be specified "
                "when the 'http_uri' type is used."
            )

        if 'path_begins_with_any' in item and item['path_begins_with_any'] is not None:
            if isinstance(item['path_begins_with_any'], list):
                values = item['path_begins_with_any']
            else:
                values = [item['path_begins_with_any']]
            action.update(dict(
                path=True,
                startsWith=True,
                values=values
            ))
        elif 'path_contains' in item and item['path_contains'] is not None:
            if isinstance(item['path_contains'], list):
                values = item['path_contains']
            else:
                values = [item['path_contains']]
            action.update(dict(
                path=True,
                contains=True,
                values=values
            ))
        elif 'path_is_any' in item and item['path_is_any'] is not None:
            if isinstance(item['path_is_any'], list):
                values = item['path_is_any']
            else:
                values = [item['path_is_any']]
            action.update(dict(
                path=True,
                equals=True,
                values=values
            ))

    def _handle_tcp_condition(self, action, item):
        options = [
            'address_matches_with_any', 'address_matches_with_datagroup', 'address_matches_with_external_datagroup'
        ]
        event_map = dict(
            client_accepted='clientAccepted',
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            ssl_client_hello='sslClientHello',
            ssl_client_server_hello_send='sslClientServerhelloSend'
        )
        action['type'] = 'tcp'
        if all(k not in item for k in options):
            raise F5ModuleError(
                "A 'address_matches_with_any','address_matches_with_datagroup' or"
                "'address_matches_with_external_datagroup' must be specified when the 'tcp' type is used."
            )
        if 'address_matches_with_any' in item and item['address_matches_with_any'] is not None:
            if isinstance(item['address_matches_with_any'], list):
                values = item['address_matches_with_any']
            else:
                values = [item['address_matches_with_any']]
            action.update(dict(
                address=True,
                matches=True,
                values=values
            ))
        if 'address_matches_with_datagroup' in item and item['address_matches_with_datagroup'] is not None:
            if isinstance(item['address_matches_with_datagroup'], list):
                values = item['address_matches_with_datagroup']
            else:
                values = [item['address_matches_with_datagroup']]
            for value in values:
                action.update(dict(
                    address=True,
                    matches=True,
                    datagroup=fq_name(self.partition, value)
                )
                )
        if 'address_matches_with_external_datagroup' in item and \
                item['address_matches_with_external_datagroup'] is not None:
            if isinstance(item['address_matches_with_external_datagroup'], list):
                values = item['address_matches_with_external_datagroup']
            else:
                values = [item['address_matches_with_external_datagroup']]
            for value in values:
                action.update(dict(
                    address=True,
                    matches=True,
                    datagroup=fq_name(self.partition, value)
                )
                )
        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

    def _handle_ssl_extension_condition(self, action, item):
        action['type'] = 'ssl_extension'
        if 'server_name_is_any' in item:
            if isinstance(item['server_name_is_any'], list):
                values = item['server_name_is_any']
            else:
                values = [item['server_name_is_any']]
            action.update(dict(
                equals=True,
                serverName=True,
                values=values
            ))
        if 'event' not in item:
            raise F5ModuleError(
                "An 'event' must be specified when the 'ssl_extension' condition is used."
            )
        elif 'ssl_client_hello' in item['event']:
            action.update(dict(
                sslClientHello=True
            ))
        elif 'ssl_server_hello' in item['event']:
            action.update(dict(
                sslServerHello=True
            ))

    def _handle_http_header_condition(self, action, item):
        action['type'] = 'http_header'
        options = ['header_is_any']
        event_map = dict(
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            response='response',
        )
        if 'header_name' not in item:
            raise F5ModuleError(
                "An 'header_name' must be specified when the 'http_header' condition is used."
            )
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'header_is_any' must be specified when the 'http_header' type is used."
            )
        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

        if 'header_is_any' in item:
            if isinstance(item['header_is_any'], list):
                values = item['header_is_any']
            else:
                values = [item['header_is_any']]

            action.update(dict(
                equals=True,
                tmName=item['header_name'],
                values=values
            ))

    def _handle_forward_action(self, action, item):
        """Handle the nuances of the forwarding type

        Right now there is only a single type of forwarding that can be done. As that
        functionality expands, so-to will the behavior of this, and other, methods.
        Therefore, do not be surprised that the logic here is so rigid. It's deliberate.

        :param action:
        :param item:
        :return:
        """

        event_map = dict(
            client_accepted='clientAccepted',
            proxy_request='proxyRequest',
            request='request',
            ssl_client_hello='sslClientHello',
            ssl_client_server_hello_send='sslClientServerhelloSend'
        )

        action['type'] = 'forward'
        options = ['pool', 'virtual', 'node']
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'pool' or 'virtual' or 'node' must be specified when the 'forward' type is used."
            )
        if item.get('pool', None):
            action['pool'] = fq_name(self.partition, item['pool'])
        elif item.get('virtual', None):
            action['virtual'] = fq_name(self.partition, item['virtual'])
        elif item.get('node', None):
            action['node'] = item['node']

        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

    def _handle_set_variable_action(self, action, item):
        """Handle the nuances of the set_variable type

        :param action:
        :param item:
        :return:
        """
        if 'expression' not in item and 'variable_name' not in item:
            raise F5ModuleError(
                "A 'variable_name' and 'expression' must be specified when the 'set_variable' type is used."
            )

        if 'event' in item and item['event'] is not None:
            action[item['event']] = True
        else:
            action['request'] = True
        action.update(dict(
            type='set_variable',
            expression=item['expression'],
            tmName=item['variable_name'],
            setVariable=True,
            tcl=True
        ))

    def _handle_enable_action(self, action, item):
        """Handle the nuances of the enable type

        :param action:
        :param item:
        :return:
        """
        action['type'] = 'enable'
        if 'asm_policy' not in item:
            raise F5ModuleError(
                "An 'asm_policy' must be specified when the 'enable' type is used."
            )
        action.update(dict(
            policy=fq_name(self.partition, item['asm_policy']),
            asm=True
        ))

    def _handle_disable_action(self, action, item):
        """Handle the nuances of the disable type

        :param action:
        :param item:
        :return:
        """

        target_map = dict(
            server_ssl='serverSsl',
            persist='persist'
        )
        event_map = dict(
            client_accepted='clientAccepted',
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            server_connected='serverConnected',
        )

        action['type'] = 'disable'
        if 'disable_target' not in item:
            raise F5ModuleError(
                "An 'disable_target' must be specified when the 'enable' type is used."
            )
        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

        action[target_map[item['disable_target']]] = True

    def _handle_redirect_action(self, action, item):
        """Handle the nuances of the redirect type

        :param action:
        :param item:
        :return:
        """
        action['type'] = 'redirect'
        if 'location' not in item:
            raise F5ModuleError(
                "A 'location' must be specified when the 'redirect' type is used."
            )
        action.update(
            location=item['location'],
            httpReply=True,
        )

    def _handle_reset_action(self, action, item):
        """Handle the nuances of the reset type

        :param action:
        :param item:
        :return:
        """
        event_map = dict(
            client_accepted='clientAccepted',
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            response='response',
            server_connected='serverConnected',
            ssl_client_hello='sslClientHello',
            ssl_client_server_hello_send='sslClientServerhelloSend',
            ssl_server_handshake='sslServerHandshake',
            ssl_server_hello='sslServerHello',
            websocket_request='wsRequest',
            websocket_response='wsResponse'
        )

        action['type'] = 'reset'
        if 'event' not in item:
            raise F5ModuleError(
                "An 'event' must be specified when the 'reset' type is used."
            )
        event = event_map.get(item['event'], None)
        if not event:
            raise F5ModuleError(
                "Invalid event type specified for reset action: {0},"
                "check module documentation for valid event types.".format(item['event'])
            )

        action[event] = True
        action.update({
            'connection': True,
            'shutdown': True
        })

    def _handle_persist_action(self, action, item):
        """Handle the nuances of the persist type

        :param action:
        :param item:
        :return:
        """
        action['type'] = 'persist'
        if 'cookie_insert' not in item:
            raise F5ModuleError(
                "A 'cookie_insert' must be specified when the 'persist' type is used."
            )
        elif 'cookie_expiry' in item:
            action.update(
                cookieInsert=True,
                tmName=item['cookie_insert'],
                expiry=str(item['cookie_expiry'])
            )
        else:
            action.update(
                cookieInsert=True,
                tmName=item['cookie_insert']
            )

    def _handle_remove_action(self, action, item):
        """Handle the nuances of the remove type

        :param action:
        :param item:
        :return:
        """

        action['type'] = 'remove'
        options = ['http_header', 'http_referer', 'http_set_cookie', 'http_cookie']
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'http_header', 'http_referer', 'http_set_cookie' or 'http_cookie' must be specified when "
                "the 'remove' type is used."
            )
        if 'http_header' in item and item['http_header']:
            if item['http_header']['event'] == 'request':
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    request=True
                )
            elif item['http_header']['event'] == 'response':
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    response=True
                )
            else:
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name']
                )
        if 'http_referer' in item and item['http_referer']:
            if item['http_referer']['event'] == 'request':
                action.update(
                    httpReferer=True,
                    request=True
                )
            if item['http_referer']['event'] == 'proxy_connect':
                action.update(
                    httpReferer=True,
                    proxyConnect=True
                )
            if item['http_referer']['event'] == 'proxy_request':
                action.update(
                    httpReferer=True,
                    proxyRequest=True
                )
        if 'http_cookie' in item and item['http_cookie']:
            if item['http_cookie']['event'] == 'request':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    request=True
                )
            elif item['http_cookie']['event'] == 'proxy_connect':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    proxyConnect=True
                )
            elif item['http_cookie']['event'] == 'proxy_request':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    proxyRequest=True
                )
            else:
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name']
                )
        if 'http_set_cookie' in item and item['http_set_cookie']:
            action.update(
                httpSetCookie=True,
                tmName=item['http_set_cookie']['name'],
                response=True
            )

    def _handle_insert_action(self, action, item):
        """Handle the nuances of the insert type

        :param action:
        :param item:
        :return:
        """

        action['type'] = 'insert'
        options = ['http_header', 'http_referer', 'http_set_cookie', 'http_cookie']
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'http_header', 'http_referer', 'http_set_cookie' or 'http_cookie' must be specified when "
                "the 'insert' type is used."
            )

        if 'http_header' in item and item['http_header']:
            if item['http_header']['value'] is None:
                raise F5ModuleError(
                    "The http_header value key is required when action is of type 'insert'."
                )
            if item['http_header']['event'] == 'request':
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    value=item['http_header']['value'],
                    request=True
                )
            elif item['http_header']['event'] == 'response':
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    value=item['http_header']['value'],
                    response=True
                )
            else:
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    value=item['http_header']['value']
                )
        if 'http_referer' in item and item['http_referer']:
            if item['http_referer']['value'] is None:
                raise F5ModuleError(
                    "The http_referer value key is required when action is of type 'insert'."
                )
            if item['http_referer']['event'] == 'request':
                action.update(
                    httpReferer=True,
                    value=item['http_referer']['value'],
                    request=True
                )
            if item['http_referer']['event'] == 'proxy_connect':
                action.update(
                    httpReferer=True,
                    value=item['http_referer']['value'],
                    proxyConnect=True
                )
            if item['http_referer']['event'] == 'proxy_request':
                action.update(
                    httpReferer=True,
                    value=item['http_referer']['value'],
                    proxyRequest=True
                )
        if 'http_cookie' in item and item['http_cookie']:
            if item['http_cookie']['value'] is None:
                raise F5ModuleError(
                    "The http_cookie value key is required when action is of type 'insert'."
                )
            if item['http_cookie']['event'] == 'request':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    value=item['http_cookie']['value'],
                    request=True
                )
            elif item['http_cookie']['event'] == 'proxy_connect':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    value=item['http_cookie']['value'],
                    proxyConnect=True
                )
            elif item['http_cookie']['event'] == 'proxy_request':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    value=item['http_cookie']['value'],
                    proxyRequest=True
                )
            else:
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    value=item['http_cookie']['value']
                )
        if 'http_set_cookie' in item and item['http_set_cookie']:
            if item['http_set_cookie']['value'] is None:
                raise F5ModuleError(
                    "The http_set_cookie value key is required when action is of type 'insert'."
                )
            action.update(
                httpSetCookie=True,
                tmName=item['http_set_cookie']['name'],
                value=item['http_set_cookie']['value'],
                response=True
            )

    def _handle_replace_action(self, action, item):
        """Handle the nuances of the replace type

        :param action:
        :param item:
        :return:
        """

        action['type'] = 'replace'
        options = ['http_header', 'http_referer', 'http_host', 'http_connect', 'http_uri']
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'http_header', 'http_referer', 'http_host', 'http_connect' or 'http_uri' must be specified when "
                "the 'replace' type is used."
            )
        event_map = dict(
            client_accepted='clientAccepted',
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            response='response',
            server_connected='serverConnected',
            ssl_client_hello='sslClientHello'
        )
        type_map = dict(
            path='path',
            query_string='queryString',
            full_string='value'
        )
        if 'http_header' in item and item['http_header']:
            if item['http_header']['value'] is None:
                raise F5ModuleError(
                    "The http_header value key is required when action is of type 'replace'."
                )
            if item['http_header']['event'] is not None:
                action.update({
                    'httpHeader': True,
                    'tmName': item['http_header']['name'],
                    'value': item['http_header']['value'],
                    event_map[item['http_header']['event']]: True
                })
            else:
                action.update({
                    'httpHeader': True,
                    'tmName': item['http_header']['name'],
                    'value': item['http_header']['value']
                })
        if 'http_referer' in item and item['http_referer']:
            if item['http_referer']['value'] is not None:
                action.update({
                    'httpReferer': True,
                    'value': item['http_referer']['value'],
                    event_map[item['http_referer']['event']]: True
                })
            else:
                action.update({
                    'httpReferer': True,
                    event_map[item['http_referer']['event']]: True
                })
        if 'http_connect' in item and item['http_connect']:
            if item['http_connect']['port'] is None:
                action.update({
                    'httpConnect': True,
                    'host': item['http_connect']['value'],
                    'port': 0,
                    event_map[item['http_connect']['event']]: True
                })
            else:
                action.update({
                    'httpConnect': True,
                    'host': item['http_connect']['value'],
                    'port': item['http_connect']['port'],
                    event_map[item['http_connect']['event']]: True
                })
        if 'http_uri' in item and item['http_uri']:
            if item['http_uri']['event'] is not None:
                action.update({
                    'httpUri': True,
                    type_map[item['http_uri']['type']]: item['http_uri']['value'],
                    event_map[item['http_uri']['event']]: True
                })
            else:
                action.update({
                    'httpUri': True,
                    type_map[item['http_uri']['type']]: item['http_uri']['value'],
                })
        if 'http_host' in item and item['http_host']:
            if item['http_host']['event'] is not None:
                action.update({
                    'httpHost': True,
                    'value': item['http_host']['value'],
                    event_map[item['http_host']['event']]: True
                })
            else:
                action.update({
                    'httpHost': True,
                    'value': item['http_host']['value'],
                })


class Changes(Parameters):
    def to_return(self):
        result = {}
        for returnable in self.returnables:
            try:
                result[returnable] = getattr(self, returnable)
                result = self._filter_params(result)
            except Exception:
                raise
        return result


class ReportableChanges(Changes):
    event_map = dict(
        clientAccepted='client_accepted',
        proxyConnect='proxy_connect',
        proxyRequest='proxy_request',
        proxyResponse='proxy_response',
        request='request',
        response='response',
        serverConnected='server_connected',
        sslClientHello='ssl_client_hello'
    )
    uri_type_map = dict(
        path='path',
        queryString='query_string',
        value='full_string'
    )

    returnables = [
        'description', 'actions', 'conditions'
    ]

    def _map_value(self, item, t=False):
        if not t:
            for k in self.event_map.keys():
                if k in item:
                    return k
        else:
            for k in self.uri_type_map.keys():
                if k in item:
                    return k
        return None

    @property
    def actions(self):
        result = []
        if self._values['actions'] is None:
            return [dict(type='ignore')]
        for item in self._values['actions']:
            action = dict()
            if 'forward' in item:
                action.update(item)
                action['type'] = 'forward'
                del action['forward']
            elif 'replace' in item:
                action.update(item)
                action['type'] = 'replace'
                if 'httpHeader' in item:
                    event = self._map_value(item)
                    if event:
                        http_header = dict(event=self.event_map[event], name=action['tmName'], value=action['value'])
                        action['http_header'] = http_header
                        del action[event]
                    else:
                        http_header = dict(name=action['tmName'], value=action['value'])
                        action['http_header'] = http_header
                    del action['httpHeader']
                    del action['value']
                    del action['tmName']
                if 'httpReferer' in item:
                    event = self._map_value(item)
                    if event:
                        if 'value' in item:
                            http_ref = dict(event=self.event_map[event], value=action['value'])
                            action['http_referer'] = http_ref
                            del action['value']
                        else:
                            http_ref = dict(event=self.event_map[event])
                            action['http_referer'] = http_ref
                        del action[event]
                    else:
                        if 'value' in item:
                            http_ref = dict(event=self.event_map[event], value=action['value'])
                            action['http_referer'] = http_ref
                            del action['value']
                    del action['httpReferer']
                if 'httpConnect' in item:
                    event = self._map_value(item)
                    if event:
                        if 'value' in item and 'port' in item:
                            http_con = dict(event=self.event_map[event], value=action['value'], port=action['item'])
                            action['http_connect'] = http_con
                            del action['value']
                            del action['port']
                        elif 'value' in item and 'port' not in item:
                            http_con = dict(event=self.event_map[event], value=action['value'])
                            action['http_connect'] = http_con
                            del action['value']
                        elif 'value' not in item and 'port' in item:
                            http_con = dict(event=self.event_map[event], port=action['port'])
                            action['http_connect'] = http_con
                            del action['port']
                        else:
                            http_con = dict(event=self.event_map[event])
                            action['http_connect'] = http_con
                        del action[event]
                    else:
                        if 'value' in item and 'port' in item:
                            http_con = dict(value=action['value'], port=action['item'])
                            action['http_connect'] = http_con
                            del action['value']
                            del action['port']
                        elif 'value' in item and 'port' not in item:
                            http_con = dict(value=action['value'])
                            action['http_connect'] = http_con
                            del action['value']
                        elif 'value' not in item and 'port' in item:
                            http_con = dict(port=action['port'])
                            action['http_connect'] = http_con
                            del action['port']
                    del action['httpConnect']
                if 'httpUri' in item:
                    event = self._map_value(item)
                    kind = self._map_value(item, True)
                    if event:
                        http_uri = dict(event=self.event_map[event], type=self.uri_type_map[kind], value=action[kind])
                        action['http_uri'] = http_uri
                        del action[event]
                    else:
                        http_uri = dict(type=self.uri_type_map[kind], value=action[kind])
                        action['http_uri'] = http_uri
                    del action[kind]
                    del action['httpUri']
                if 'httpHost' in item:
                    event = self._map_value(item)
                    if event:
                        http_uri = dict(event=self.event_map[event], value=action['value'])
                        action['http_uri'] = http_uri
                        del action[event]
                    else:
                        http_uri = dict(value=action['value'])
                        action['http_uri'] = http_uri
                    del action['value']
                    del action['httpHost']
            elif 'insert' in item:
                action.update(item)
                action['type'] = 'insert'
                if 'httpHeader' in item:
                    if 'response' in item:
                        http_header = dict(event='response', name=action['tmName'], value=action['value'])
                        action['http_header'] = http_header
                        del action['response']
                    if 'request' in item:
                        http_header = dict(event='request', name=action['tmName'], value=action['value'])
                        action['http_header'] = http_header
                        del action['request']
                    del action['httpHeader']
                    del action['tmName']
                if 'httpReferer' in item:
                    if 'request' in item:
                        http_ref = dict(event='request', value=action['value'])
                        action['http_referer'] = http_ref
                        del action['request']
                    if 'proxyConnect' in item:
                        http_ref = dict(event='proxy_connect', value=action['value'])
                        action['http_referer'] = http_ref
                        del action['proxyConnect']
                    if 'proxyRequest' in item:
                        http_ref = dict(event='proxy_request', value=action['value'])
                        action['http_referer'] = http_ref
                        del action['proxyRequest']
                    del action['httpReferer']
                if 'httpCookie' in item:
                    if 'request' in item:
                        http_cookie = dict(event='request', name=action['tmName'], value=action['value'])
                        action['http_cookie'] = http_cookie
                        del action['request']
                    if 'proxyConnect' in item:
                        http_cookie = dict(event='proxy_connect', name=action['tmName'], value=action['value'])
                        action['http_cookie'] = http_cookie
                        del action['proxyConnect']
                    if 'proxyRequest' in item:
                        http_cookie = dict(event='proxy_request', name=action['tmName'], value=action['value'])
                        action['http_cookie'] = http_cookie
                        del action['proxyRequest']
                    del action['httpCookie']
                    del action['tmName']
                if 'httpSetCookie' in item:
                    http_set_cookie = dict(name=action['tmName'], value=action['value'])
                    action['http_set_cookie'] = http_set_cookie
                    del action['response']
                    del action['value']
                    del action['tmName']
                del action['insert']
                del action['value']
            elif 'remove' in item:
                action.update(item)
                action['type'] = 'remove'
                if 'httpHeader' in item:
                    if 'response' in item:
                        http_header = dict(event='response', name=action['tmName'])
                        action['http_header'] = http_header
                        del action['response']
                    if 'request' in item:
                        http_header = dict(event='request', name=action['tmName'])
                        action['http_header'] = http_header
                        del action['request']
                    del action['httpHeader']
                    del action['tmName']
                if 'httpReferer' in item:
                    if 'request' in item:
                        http_ref = dict(event='request')
                        action['http_referer'] = http_ref
                        del action['request']
                    if 'proxyConnect' in item:
                        http_ref = dict(event='proxy_connect')
                        action['http_referer'] = http_ref
                        del action['proxyConnect']
                    if 'proxyRequest' in item:
                        http_ref = dict(event='proxy_request')
                        action['http_referer'] = http_ref
                        del action['proxyRequest']
                    del action['httpReferer']
                if 'httpCookie' in item:
                    if 'request' in item:
                        http_cookie = dict(event='request', name=action['tmName'])
                        action['http_cookie'] = http_cookie
                        del action['request']
                    if 'proxyConnect' in item:
                        http_cookie = dict(event='proxy_connect', name=action['tmName'])
                        action['http_cookie'] = http_cookie
                        del action['proxyConnect']
                    if 'proxyRequest' in item:
                        http_cookie = dict(event='proxy_request', name=action['tmName'])
                        action['http_cookie'] = http_cookie
                        del action['proxyRequest']
                    del action['httpCookie']
                    del action['tmName']
                if 'httpSetCookie' in item:
                    action['http_set_cookie'] = dict(name=action['tmName'])
                    del action['response']
                    del action['tmName']
                del action['remove']
            elif 'set_variable' in item:
                action.update(item)
                action['type'] = 'set_variable'
                del action['set_variable']
            elif 'enable' in item:
                action.update(item)
                action['type'] = 'enable'
                del action['enable']
            elif 'disable' in item:
                action.update(item)
                action['type'] = 'disable'
                if 'serverSsl' in action and action['serverSsl']:
                    action['disable_target'] = 'server_ssl'
                    del action['serverSsl']
                if 'persist' in action and action['persist']:
                    action['disable_target'] = 'persist'
                    del action['persist']
                del action['enable']
            elif 'redirect' in item:
                action.update(item)
                action['type'] = 'redirect'
                del action['redirect']
                del action['httpReply']
            elif 'reset' in item:
                action.update(item)
                action['type'] = 'reset'
                del action['connection']
                del action['shutdown']
            elif 'persist' in item:
                action.update(item)
                action['type'] = 'persist'
                action['cookie_insert'] = action['tmName']
                if 'expiry' in item:
                    action['cookie_expiry'] = int(action['expiry'])
                    del action['expiry']
                del action['tmName']
                del action['persist']
                del action['cookieInsert']
            result.append(action)
        result = sorted(result, key=lambda x: x['name'])
        return result

    @property
    def conditions(self):
        result = []
        if self._values['conditions'] is None:
            return [dict(type='all_traffic')]
        for item in self._values['conditions']:
            action = dict()
            if 'httpUri' in item:
                action.update(item)
                action['type'] = 'http_uri'
                del action['httpUri']
            elif 'httpMethod' in item:
                action.update(item)
                action['type'] = 'http_method'
                del action['httpMethod']
            elif 'httpHost' in item:
                action.update(item)
                action['type'] = 'http_host'
                del action['httpHost']
            elif 'httpHeader' in item:
                action.update(item)
                action['type'] = 'http_header'
                action['header_name'] = action['tmName']
                del action['httpHeader']
                del action['tmName']
            elif 'tcp' in item:
                action.update(item)
                action['type'] = 'tcp'
                del action['tcp']
            elif 'sslExtension' in item:
                action.update(item)
                action['type'] = 'ssl_extension'
                del action['sslExtension']
            result.append(action)
        # Names contains the index in which the rule is at.
        result = sorted(result, key=lambda x: x['name'])
        return result


class UsableChanges(Changes):
    @property
    def actions(self):
        if self._values['actions'] is None:
            return None
        result = []
        for action in self._values['actions']:
            if 'type' not in action:
                continue
            if action['type'] == 'forward':
                action['forward'] = True
                del action['type']
            elif action['type'] == 'enable':
                action['enable'] = True
                del action['type']
            elif action['type'] == 'disable':
                action['disable'] = True
                del action['type']
            elif action['type'] == 'set_variable':
                action['setVariable'] = True
                action['tcl'] = True
                del action['type']
            elif action['type'] == 'ignore':
                result = []
                break
            elif action['type'] == 'redirect':
                action['httpReply'] = True
                action['redirect'] = True
                del action['type']
            elif action['type'] == 'reset':
                action['shutdown'] = True
                action['connection'] = True
                del action['type']
            elif action['type'] == 'persist':
                action['persist'] = True
                del action['type']
            elif action['type'] == 'remove':
                action['remove'] = True
                del action['type']
            elif action['type'] == 'insert':
                action['insert'] = True
                del action['type']
            elif action['type'] == 'replace':
                action['replace'] = True
                del action['type']
            result.append(action)
        return result

    @property
    def conditions(self):
        if self._values['conditions'] is None:
            return None
        result = []
        for condition in self._values['conditions']:
            if 'type' not in condition:
                continue
            if condition['type'] == 'http_uri':
                condition['httpUri'] = True
                del condition['type']
            elif condition['type'] == 'http_method':
                condition['httpMethod'] = True
                del condition['type']
            elif condition['type'] == 'http_host':
                condition['httpHost'] = True
                del condition['type']
            elif condition['type'] == 'http_header':
                condition['httpHeader'] = True
            elif condition['type'] == 'tcp':
                condition['tcp'] = True
                del condition['type']
            elif condition['type'] == 'ssl_extension':
                condition['sslExtension'] = True
                del condition['type']
            elif condition['type'] == 'all_traffic':
                result = []
                break
            result.append(condition)
        return result


class Difference(object):
    updatables = [
        'actions', 'conditions', 'description'
    ]

    def __init__(self, want, have=None):
        self.want = want
        self.have = have

    def compare(self, param):
        try:
            result = getattr(self, param)
            return result
        except AttributeError:
            return self.__default(param)

    def __default(self, param):
        attr1 = getattr(self.want, param)
        try:
            attr2 = getattr(self.have, param)
            if attr1 != attr2:
                return attr1
        except AttributeError:
            return attr1

    def to_tuple(self, items):
        result = []
        for x in items:
            tmp = [(str(k), str(v)) for k, v in iteritems(x)]
            result += tmp
        return result

    def _diff_complex_items(self, want, have):
        if want == [] and have is None:
            return None
        if want is None:
            return None
        w = self.to_tuple(want)
        h = self.to_tuple(have)
        if set(w).issubset(set(h)):
            return None
        else:
            return want

    @property
    def actions(self):
        if self.want.replace_with is True:
            return self.want.actions
        result = self._diff_complex_items(self.want.actions, self.have.actions)
        actioned = self._compare_complex_actions()
        if self._conditions_missing_default_rule_for_asm(result):
            raise F5ModuleError(
                "Valid options when using an ASM policy in a rule's 'enable' "
                "action include all_traffic, http_uri, or http_host."
            )
        if result is None and actioned is True:
            return self.want.actions
        return result

    @property
    def conditions(self):
        if self.want.replace_with is True:
            return self.want.conditions
        result = self._diff_complex_items(self.want.conditions, self.have.conditions)
        return result

    def _conditions_missing_default_rule_for_asm(self, want_actions):
        if want_actions is None:
            actions = self.have.actions
        else:
            actions = want_actions
        if actions is None:
            return False
        if any(x for x in actions if x['type'] == 'enable'):
            conditions = self._diff_complex_items(self.want.conditions, self.have.conditions)
            if conditions is None:
                return False
            if any(y for y in conditions if y['type'] not in ['all_traffic', 'http_uri', 'http_host', 'tcp']):
                return True
        return False

    def _compare_complex_actions(self):
        types = ['insert', 'remove', 'replace']
        if self.want.actions:
            want = [item for item in self.want.actions if item['type'] in types]
            have = [item for item in self.have.actions if item['type'] in types]
            result = compare_complex_list(want, have)
            if result:
                return True
        return False


class ModuleManager(object):
    def __init__(self, *args, **kwargs):
        self.module = kwargs.get('module', None)
        self.client = F5RestClient(**self.module.params)
        self.want = ModuleParameters(params=self.module.params)
        self.have = ApiParameters()
        self.changes = UsableChanges()

    def _update_changed_options(self):
        diff = Difference(self.want, self.have)
        updatables = Parameters.updatables
        changed = dict()
        for k in updatables:
            change = diff.compare(k)
            if change is None:
                continue
            else:
                if isinstance(change, dict):
                    changed.update(change)
                else:
                    changed[k] = change
        if changed:
            self.changes = UsableChanges(params=changed)
            return True
        return False

    def should_update(self):
        result = self._update_changed_options()
        if result:
            return True
        return False

    def exec_module(self):
        start = datetime.now().isoformat()
        version = tmos_version(self.client)
        changed = False
        result = dict()
        state = self.want.state

        if state == "present":
            changed = self.present()
        elif state == "absent":
            changed = self.absent()
        reportable = ReportableChanges(params=self.changes.to_return())
        changes = reportable.to_return()
        result.update(**changes)
        result.update(dict(changed=changed))
        self._announce_deprecations(result)
        send_teem(start, self.client, self.module, version)
        return result

    def _announce_deprecations(self, result):
        warnings = result.pop('__warnings', [])
        for warning in warnings:
            self.module.deprecate(
                msg=warning['msg'],
                version=warning['version']
            )

    def present(self):
        if self.exists():
            return self.update()
        else:
            return self.create()

    def absent(self):
        if self.exists():
            return self.remove()
        return False

    def update(self):
        self.have = self.read_current_from_device()
        if not self.should_update():
            return False
        if self.module.check_mode:
            return True
        if self.draft_exists():
            redraft = True
        else:
            redraft = False
            self._create_existing_policy_draft_on_device()
        self.update_on_device()
        if redraft is False:
            self.publish_on_device()
        return True

    def remove(self):
        if self.module.check_mode:
            return True
        if self.draft_exists():
            redraft = True
        else:
            redraft = False
            self._create_existing_policy_draft_on_device()
        self.remove_from_device()
        if self.exists():
            raise F5ModuleError("Failed to delete the resource.")
        if redraft is False:
            self.publish_on_device()
        return True

    def create(self):
        self.should_update()
        if self.module.check_mode:
            return True
        if self.draft_exists():
            redraft = True
        else:
            redraft = False
            self._create_existing_policy_draft_on_device()
        self.create_on_device()
        if redraft is False:
            self.publish_on_device()
        return True

    def exists(self):
        if self.draft_exists():
            uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
                self.client.provider['server'],
                self.client.provider['server_port'],
                transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
                self.want.name
            )
        else:
            uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
                self.client.provider['server'],
                self.client.provider['server_port'],
                transform_name(self.want.partition, self.want.policy),
                self.want.name
            )
        resp = self.client.api.get(uri)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status == 404 or 'code' in response and response['code'] == 404:
            return False
        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True

        errors = [401, 403, 409, 500, 501, 502, 503, 504]

        if resp.status in errors or 'code' in response and response['code'] in errors:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)

    def draft_exists(self):
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy, sub_path='Drafts')
        )
        resp = self.client.api.get(uri)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status == 404 or 'code' in response and response['code'] == 404:
            return False
        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True

        errors = [401, 403, 409, 500, 501, 502, 503, 504]

        if resp.status in errors or 'code' in response and response['code'] in errors:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)

    def _create_existing_policy_draft_on_device(self):
        params = dict(createDraft=True)
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy)
        )
        resp = self.client.api.patch(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def publish_on_device(self):
        params = dict(
            name=fq_name(self.want.partition,
                         self.want.policy,
                         sub_path='Drafts'
                         ),
            command="publish"

        )
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
        )
        resp = self.client.api.post(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def create_on_device(self):
        params = self.changes.api_params()
        params['name'] = self.want.name
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
        )
        resp = self.client.api.post(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def update_on_device(self):
        params = self.changes.api_params()
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
            self.want.name
        )
        resp = self.client.api.patch(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def remove_from_device(self):
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
            self.want.name
        )
        response = self.client.api.delete(uri)
        if response.status == 200:
            return True
        raise F5ModuleError(response.content)

    def read_current_from_device(self):
        if self.draft_exists():
            uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
                self.client.provider['server'],
                self.client.provider['server_port'],
                transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
                self.want.name
            )
        else:
            uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
                self.client.provider['server'],
                self.client.provider['server_port'],
                transform_name(self.want.partition, self.want.policy),
                self.want.name
            )
        query = "?expandSubcollections=true"
        resp = self.client.api.get(uri + query)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return ApiParameters(params=response)
        raise F5ModuleError(resp.content)


class ArgumentSpec(object):
    def __init__(self):
        self.supports_check_mode = True
        argument_spec = dict(
            description=dict(),
            actions=dict(
                type='list',
                elements='dict',
                options=dict(
                    type=dict(
                        choices=[
                            'forward',
                            'enable',
                            'ignore',
                            'redirect',
                            'reset',
                            'persist',
                            'set_variable',
                            'remove',
                            'insert',
                            'replace',
                            'disable',
                        ],
                        required=True
                    ),
                    pool=dict(),
                    node=dict(),
                    asm_policy=dict(),
                    virtual=dict(),
                    location=dict(),
                    event=dict(),
                    cookie_insert=dict(),
                    cookie_expiry=dict(type='int'),
                    expression=dict(),
                    variable_name=dict(),
                    disable_target=dict(
                        choices=['server_ssl', 'persist']
                    ),
                    http_header=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=[
                                    'request', 'response', 'proxy_connect',
                                    'proxy_request', 'proxy_response'
                                ],
                                required=True
                            ),
                            name=dict(required=True),
                            value=dict()
                        )
                    ),
                    http_referer=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=['request', 'proxy_connect', 'proxy_request'],
                                required=True
                            ),
                            value=dict()
                        )
                    ),
                    http_set_cookie=dict(
                        type='dict',
                        options=dict(
                            name=dict(required=True),
                            value=dict()
                        )
                    ),
                    http_cookie=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=['request', 'proxy_connect', 'proxy_request'],
                                required=True

                            ),
                            name=dict(required=True),
                            value=dict()
                        )
                    ),
                    http_connect=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=[
                                    'client_accepted', 'proxy_connect', 'proxy_request',
                                    'proxy_response', 'request', 'server_connected', 'ssl_client_hello'
                                ],
                                required=True
                            ),
                            value=dict(
                                required=True
                            ),
                            port=dict(type='int'),
                        )
                    ),
                    http_host=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=['request', 'proxy_connect', 'proxy_request'],
                                required=True
                            ),
                            value=dict(required=True)
                        )
                    ),
                    http_uri=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=['request', 'proxy_connect', 'proxy_request'],
                                required=True
                            ),
                            type=dict(
                                choices=['path', 'query_string', 'full_string'],
                                required=True
                            ),
                            value=dict(required=True)
                        ),

                    ),
                ),
                mutually_exclusive=[
                    ['pool', 'asm_policy', 'virtual', 'location', 'cookie_insert', 'node', 'http_header',
                     'http_referer', 'http_set_cookie', 'http_cookie', 'http_uri', 'http_host', 'http_connect',
                     'disable_target'
                     ]
                ]
            ),
            conditions=dict(
                type='list',
                elements='dict',
                options=dict(
                    type=dict(
                        choices=[
                            'http_uri',
                            'http_method',
                            'http_host',
                            'http_header',
                            'ssl_extension',
                            'all_traffic',
                            'tcp'
                        ],
                        required=True
                    ),
                    path_begins_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    path_contains=dict(
                        type='list',
                        elements='str',
                    ),
                    path_is_any=dict(
                        type='list',
                        elements='str',
                    ),
                    host_begins_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    host_ends_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    host_is_any=dict(
                        type='list',
                        elements='str',
                    ),
                    host_is_not_any=dict(
                        type='list',
                        elements='str',
                    ),
                    header_name=dict(),
                    header_is_any=dict(
                        type='list',
                        elements='str',
                    ),
                    method_matches_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    server_name_is_any=dict(
                        type='list',
                        elements='str',
                    ),
                    address_matches_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    address_matches_with_datagroup=dict(
                        type='list',
                        elements='str',
                    ),
                    address_matches_with_external_datagroup=dict(
                        type='list',
                        elements='str',
                    ),
                    event=dict()
                ),
            ),
            name=dict(required=True),
            policy=dict(required=True),
            rule_order=dict(type='int'),
            replace_with=dict(
                type='bool',
                default='no'
            ),
            state=dict(
                default='present',
                choices=['absent', 'present']
            ),
            partition=dict(
                default='Common',
                fallback=(env_fallback, ['F5_PARTITION'])
            )
        )
        self.argument_spec = {}
        self.argument_spec.update(f5_argument_spec)
        self.argument_spec.update(argument_spec)


def main():
    spec = ArgumentSpec()

    module = AnsibleModule(
        argument_spec=spec.argument_spec,
        supports_check_mode=spec.supports_check_mode
    )

    try:
        mm = ModuleManager(module=module)
        results = mm.exec_module()
        module.exit_json(**results)
    except F5ModuleError as ex:
        module.fail_json(msg=str(ex))


if __name__ == '__main__':
    main()

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2017, F5 Networks 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 = r'''
---
module: bigip_policy_rule
short_description: Manage LTM policy rules on a BIG-IP
description:
  - This module manages LTM policy rules on a BIG-IP.
version_added: "1.0.0"
options:
  name:
    description:
      - The name of the rule.
    type: str
    required: True
  policy:
    description:
      - The name of the policy you want to associate this rule with.
    type: str
    required: True
  replace_with:
    description:
      - Specifies if the C(conditions)/C(actions) given by the user should overwrite what exists on the device.
      - The option is useful when a subset of C(conditions)/C(actions) needs to be removed. This option is similar to the
        replace-all-with flag available in TMSH commands.
      - Using this option is not idempotent.
    type: bool
    default: no
  rule_order:
    description:
      - Specifies a number that indicates the order of this rule relative to other rules in the policy.
      - When not set, the device sets the parameter to 0.
      - If there are rules with the same rule order number, the device uses rule names
        to determine how the rules are ordered.
      - The lower the number, the lower the rule is in the general order, with the lowest number C(0) being the
        topmost one.
      - Valid range of values is between C(0) and C(4294967295) inclusive.
    type: int
    version_added: "1.10.0"
  description:
    description:
      - Description of the policy rule.
    type: str
  actions:
    description:
      - The actions you want the policy rule to perform.
      - The available attributes vary by the action, however, each action requires
        you specify a C(type).
      - These conditions can be specified in any order. Despite the fact they are in a list,
        the order in the list does not matter to the BIG-IP.
    type: list
    elements: dict
    suboptions:
      type:
        description:
          - The action type. This value controls which of the following options are required.
          - When C(type) is C(forward), the system associates a given C(pool), or C(virtual),
            or C(node) with this rule.
          - When C(type) is C(enable), the system associates a given C(asm_policy) with
            this rule.
          - When C(type) is C(ignore), the system removes all existing actions from this
            rule.
          - When C(type) is C(redirect), the system redirects an HTTP request to a different URL.
          - When C(type) is C(reset), the system resets the connection upon C(event).
          - When C(type) is C(persist), the system associates C(cookie_insert) and C(cookie_expiry) with this rule.
          - When C(type) is C(set_variable), the system sets a variable based on the evaluated Tcl C(expression) based on C(event).
          - When C(type) is C(remove), the system removes C(http_set_cookie), C(http_referer), C(http_header) or C(http_cookie) with this rule.
          - When C(type) is C(insert), the system inserts C(http_set_cookie), C(http_referer), C(http_header) or C(http_cookie) with this rule.
          - When C(type) is C(replace), the system replaces C(http_connect), C(http_referer), C(http_header), C(http_uri) or C(http_host) with this rule.
          - When C(type) is C(disable), the system disables C(disable_target) with this rule.
        type: str
        required: true
        choices:
          - forward
          - enable
          - ignore
          - redirect
          - reset
          - persist
          - set_variable
          - remove
          - insert
          - replace
          - disable
      pool:
        description:
          - Pool to which you want to forward traffic.
          - This parameter is only valid with the C(forward) type.
        type: str
      virtual:
        description:
          - Virtual server to which you want to forward traffic.
          - This parameter is only valid with the C(forward) type.
        type: str
      node:
        description:
          - Node to which you want to forward traffic.
          - This parameter is only valid with the C(forward) type.
        type: str
        version_added: "1.2.0"
      disable_target:
        description:
          - Target you want to disable.
          - This parameter is only valid with the C(disable) type.
        type: str
        version_added: "1.8.0"
        choices:
          - server_ssl
          - persist
      asm_policy:
        description:
          - ASM policy to enable.
          - This parameter is only valid with the C(enable) type.
        type: str
      location:
        description:
          - The new URL for which a redirect response is sent.
          - A Tcl command substitution can be used for this field.
        type: str
      event:
        description:
          - Events on which actions, such as reset and forward, can be triggered.
          - With the C(set_variable) action, it is used for specifying
            an action event, such as request or response.
          - "Valid event choices for C(forward) action type are: client_accepted, proxy_request
            request, ssl_client_hello and ssl_client_server_hello_send."
          - "Valid event choices for C(reset) acton type are: client_accepted, proxy_connect
            proxy_request, proxy_response, request, response, server_connected, ssl_client_hello,
            ssl_client_server_hello_send, ssl_server_handshake, ssl_server_hello, websocket_request,
            websocket_response."
          - "Valid event choices for C(disable) acton type are: client_accepted, proxy_connect
            proxy_request, proxy_response, request, server_connected."
        type: str
      expression:
        description:
          - A Tcl expression used with the C(set_variable) action.
        type: str
      variable_name:
        description:
           - Variable name used with the C(set_variable) action.
        type: str
      cookie_insert:
        description:
          - Cookie name on which you want to persist.
          - This parameter is only valid with the C(persist) type.
        type: str
        version_added: "1.1.0"
      cookie_expiry:
        description:
          - Optional argument, specifying the time for which the session is persisted.
          - This parameter is only valid with the C(persist) type.
        type: int
        version_added: "1.1.0"
      http_header:
        description:
          - HTTP Header that you want to remove or insert.
          - This parameter is only valid with the C(remove), C(insert) and C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_header) is removed, replaced, or inserted.
              - The C(request) and C(response) events are only choices with C(remove) and C(insert) type.
              - All of events are valid with C(replace) type action.
            type: str
            required: True
            choices:
              - request
              - response
              - proxy_connect
              - proxy_request
              - proxy_response
          name:
            description:
              - The name of C(http_header).
            type: str
            required: True
          value:
            description:
              - The value of C(http_header).
              - Mandatory parameter when configured with C(insert) or C(replace) type.
            type: str
        version_added: "1.8.0"
      http_referer:
        description:
          - HTTP Referer header you want to remove, replace, or insert.
          - This parameter is only valid with the C(remove), C(insert) and C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the c(http_referer) is removed, replaced, or inserted.
            required: True
            type: str
            choices:
              - request
              - proxy_connect
              - proxy_request
          value:
            description:
              - The value of C(http_referer).
              - This is a mandatory parameter when configured with C(insert) type action.
              - This parameter is ignored for the C(remove) type.
              - This parameter is optional for the C(replace) type.
            type: str
        version_added: "1.8.0"
      http_set_cookie:
        description:
          - HTTP Set-Cookie header you want to remove or insert.
          - This parameter is only valid with the C(remove) or c(insert) type.
        type: dict
        suboptions:
          name:
            description:
              - The name of C(http_set_cookie).
            type: str
            required: True
          value:
            description:
              - The value of C(http_set_cookie).
              - This is a mandatory parameter when configured with C(insert) type action.
            type: str
        version_added: "1.8.0"
      http_cookie:
        description:
          - HTTP Cookie header you want to remove or insert.
          - This parameter is only valid with the C(remove) and C(insert) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_cookie) is removed or inserted.
            type: str
            required: True
            choices:
              - request
              - proxy_connect
              - proxy_request
          name:
            description:
              - The name of C(http_cookie).
            type: str
            required: True
          value:
            description:
              - The value of C(http_cookie).
              - This is a mandatory parameter when configured with C(insert) type action.
            type: str
        version_added: "1.8.0"
      http_connect:
        description:
          - HTTP Connect header you want to replace.
          - This parameter is only valid with the C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_connect) header is replaced.
            required: True
            type: str
            choices:
              - client_accepted
              - proxy_connect
              - proxy_request
              - proxy_response
              - request
              - server_connected
              - ssl_client_hello
          value:
            description:
              - The value of C(http_connect).
            type: str
            required: True
          port:
            description:
              - The port number.
              - If a port number is not provided, the value is set to 0 by default.
              - Be explicit when defining rules, so the system does not override port values.
            type: int
        version_added: "1.8.0"
      http_host:
        description:
          - HTTP Host header you want to replace.
          - This parameter is only valid with the C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_host) is replaced.
            type: str
            required: True
            choices:
              - request
              - proxy_connect
              - proxy_request
          value:
            description:
              - The value of C(http_host).
            type: str
            required: True
        version_added: "1.8.0"
      http_uri:
        description:
          - Replaces HTTP URI, path, or string.
          - This parameter is only valid with the C(replace) type.
        type: dict
        suboptions:
          event:
            description:
              - Type of event when the C(http_uri) is replaced.
            type: str
            required: True
            choices:
              - request
              - proxy_connect
              - proxy_request
          type:
            description:
              - Specifies the part of the C(http_uri) to be replaced.
            type: str
            required: True
            choices:
              - path
              - query_string
              - full_string
          value:
            description:
              - The value of C(http_uri).
            type: str
            required: True
        version_added: "1.8.0"
  conditions:
    description:
      - A list of attributes that describe the condition.
      - See suboptions for details on how to construct each list entry.
      - The ordering of this list is important, the module ensures the order is
        kept when modifying the task.
      - The suboption options below are not required for all condition types,
        read the description for more details.
      - These conditions can be specified in any order. Despite the fact they are in a list,
        the order in the list does not matter to the BIG-IP.
    type: list
    elements: dict
    suboptions:
      type:
        description:
          - The condition type. This value controls which of the following options are required.
          - "When C(type) is C(http_uri), the valid choices are: C(path_begins_with_any), C(path_contains) or
            C(path_is_any)."
          - "When C(type) is C(http_host), the valid choices are: C(host_is_any), C(host_is_not_any),
            C(host_begins_with_any) or C(host_ends_with_any)."
          - "When C(type) is C(http_header), the C(header_name) parameter is mandatory and the valid choice is:
            C(header_is_any)."
          - "When C(type) is C(http_method), the valid choices are: C(method_matches_with_any)."
          - When C(type) is C(all_traffic), the system removes all existing conditions from
            this rule.
        type: str
        required: True
        choices:
          - http_uri
          - all_traffic
          - http_host
          - http_header
          - http_method
          - ssl_extension
          - tcp
      path_begins_with_any:
        description:
          - A list of strings of characters the HTTP URI should start with.
          - This parameter is only valid with the C(http_uri) type.
        type: list
        elements: str
      path_contains:
        description:
          - A list of strings of characters the HTTP URI should contain.
          - This parameter is only valid with the C(http_uri) type.
        type: list
        elements: str
        version_added: "1.8.0"
      path_is_any:
        description:
          - A list of strings of characters the HTTP URI should match.
          - This parameter is only valid with the C(http_uri) type.
        type: list
        elements: str
        version_added: "1.8.0"
      host_is_any:
        description:
          - A list of strings of characters the HTTP Host should match.
          - This parameter is only valid with the C(http_host) type.
        type: list
        elements: str
      host_is_not_any:
        description:
          - A list of strings of characters the HTTP Host should not match.
          - This parameter is only valid with the C(http_host) type.
        type: list
        elements: str
      host_begins_with_any:
        description:
          - A list of strings of characters the HTTP Host should start with.
          - This parameter is only valid with the C(http_host) type.
        type: list
        elements: str
      host_ends_with_any:
        description:
          - A list of strings of characters the HTTP Host should end with.
          - This parameter is only valid with the C(http_host) type.
        type: list
        elements: str
        version_added: "1.8.0"
      header_is_any:
        description:
          - A list of strings of characters the HTTP Header value should match.
          - This parameter is only valid with the C(http_header) type.
        type: list
        elements: str
        version_added: "1.8.0"
      header_name:
        description:
          - A name of C(http_header).
          - This parameter is only valid with the C(http_header) type.
        type: str
        version_added: "1.8.0"
      method_matches_with_any:
        description:
          - A list of strings of characters the HTTP Method value should match.
          - This parameter is only valid with the C(http_method) type.
        type: list
        elements: str
        version_added: "1.10.0"
      server_name_is_any:
        description:
          - A list of strings of characters the SSL Extension should match.
          - This parameter is only valid with the C(ssl_extension) type.
        type: list
        elements: str
      address_matches_with_any:
        description:
          - A list of IP Subnet address strings the IP address should match.
          - This parameter is only valid with the C(tcp) type.
        type: list
        elements: str
        version_added: "1.8.0"
      address_matches_with_datagroup:
        description:
          - A list of internal datagroup strings the IP address should match.
          - This parameter is only valid with the C(tcp) type.
        type: list
        elements: str
        version_added: "1.8.0"
      address_matches_with_external_datagroup:
        description:
          - A list of external datagroup strings the IP address should match.
          - This parameter is only valid with the C(tcp) type.
        type: list
        elements: str
        version_added: "1.10.0"
      event:
        description:
          - Events on which conditions type match rules can be triggered.
          - Supported only for C(http_header), C(http_method), C(ssl_extension) and C(tcp).
          - "Valid choices for C(http_header) condition types are: C(proxy_connect),
            C(proxy_request), C(proxy_response), C(request) and C(response)."
          - "Valid choices for C(http_method) condition types are: C(proxy_connect),
            C(proxy_request), C(proxy_response), C(request) and C(response)."
          - "Valid choices for C(tcp) condition types are: C(request), C(client_accepted),
            C(proxy_connect), C(proxy_request), C(proxy_response), C(ssl_client_hello), and
            C(ssl_client_server_hello_send)."
          - "Valid choices for C(ssl_extension) are: C(ssl_client_hello), and C(ssl_client_server_hello_send)."
        type: str
  state:
    description:
      - When C(present), ensures the key is uploaded to the device. When
        C(absent), ensures the key is removed from the device. If the key
        is currently in use, the module will not be able to remove the key.
    type: str
    choices:
      - present
      - absent
    default: present
  partition:
    description:
      - Device partition to manage resources on.
    type: str
    default: Common
extends_documentation_fragment: f5networks.f5_modules.f5
requirements:
  - BIG-IP >= v12.1.0
author:
  - <NAME> (@caphrim007)
  - <NAME> (@wojtek0806)
'''

EXAMPLES = r'''
- name: Create policies
  bigip_policy:
    name: Policy-Foo
    state: present
    provider:
      server: lb.mydomain.com
      user: admin
      password: <PASSWORD>
  delegate_to: localhost

- name: Add a rule to the new policy
  bigip_policy_rule:
    policy: Policy-Foo
    name: rule3
    conditions:
      - type: http_uri
        path_begins_with_any:
          - /ABC
    actions:
      - type: forward
        pool: pool-svrs
    provider:
      server: lb.mydomain.com
      user: admin
      password: <PASSWORD>
  delegate_to: localhost

- name: Add multiple rules to the new policy
  bigip_policy_rule:
    policy: Policy-Foo
    name: "{{ item.name }}"
    conditions: "{{ item.conditions }}"
    actions: "{{ item.actions }}"
    provider:
      server: lb.mydomain.com
      user: admin
      password: <PASSWORD>
  delegate_to: localhost
  loop:
    - name: rule1
      actions:
        - type: forward
          pool: pool-svrs
      conditions:
        - type: http_uri
          path_begins_with_any:
            - /euro
    - name: rule2
      actions:
        - type: forward
          pool: pool-svrs
      conditions:
        - type: http_uri
          path_begins_with_any:
            - /HomePage/
    - name: rule3
      actions:
        - type: set_variable
          variable_name: user-agent
          expression: tcl:[HTTP::header User-Agent]
          event: request
      conditions:
        - type: http_uri
          path_begins_with_any:
            - /HomePage/

- name: Remove all rules and conditions from the rule
  bigip_policy_rule:
    policy: Policy-Foo
    name: rule1
    conditions:
      - type: all_traffic
    actions:
      - type: ignore
    provider:
      server: lb.mydomain.com
      user: admin
      password: <PASSWORD>
  delegate_to: localhost
'''

RETURN = r'''
actions:
  description: The new list of actions applied to the rule.
  returned: changed
  type: complex
  contains:
    type:
      description: The action type.
      returned: changed
      type: str
      sample: forward
    pool:
      description: Pool for forwarding to.
      returned: changed
      type: str
      sample: foo-pool
  sample: hash/dictionary of values
conditions:
  description: The new list of conditions applied to the rule.
  returned: changed
  type: complex
  contains:
    type:
      description: The condition type.
      returned: changed
      type: str
      sample: http_uri
    path_begins_with_any:
      description: List of strings the URI begins with.
      returned: changed
      type: list
      sample: [foo, bar]
  sample: hash/dictionary of values
description:
  description: The new description of the rule.
  returned: changed
  type: str
  sample: My rule
rule_order:
  description: Specifies a number that indicates the order of this rule relative to other rules in the policy.
  returned: changed
  type: int
  sample: 10
'''

from datetime import datetime

from ansible.module_utils.basic import (
    AnsibleModule, env_fallback
)
from ansible.module_utils.six import iteritems

from ..module_utils.bigip import F5RestClient
from ..module_utils.common import (
    F5ModuleError, AnsibleF5Parameters, transform_name, f5_argument_spec, fq_name
)
from ..module_utils.compare import compare_complex_list
from ..module_utils.icontrol import tmos_version
from ..module_utils.teem import send_teem


class Parameters(AnsibleF5Parameters):
    api_map = {
        'actionsReference': 'actions',
        'conditionsReference': 'conditions',
        'ordinal': 'rule_order',
    }
    api_attributes = [
        'description',
        'actions',
        'conditions',
        'ordinal',
    ]

    updatables = [
        'actions',
        'conditions',
        'description',
        'rule_order',
    ]

    returnables = [
        'description',
        'action',
        'conditions',
        'rule_order',
    ]

    @property
    def name(self):
        return self._values.get('name', None)

    @property
    def description(self):
        return self._values.get('description', None)

    @property
    def policy(self):
        if self._values['policy'] is None:
            return None
        return self._values['policy']


class ApiParameters(Parameters):
    def _remove_internal_keywords(self, resource):
        items = [
            'kind', 'generation', 'selfLink', 'poolReference', 'offset', 'datagroupReference'
        ]
        for item in items:
            try:
                del resource[item]
            except KeyError:
                pass

    @property
    def actions(self):
        result = []
        if self._values['actions'] is None or 'items' not in self._values['actions']:
            return [dict(type='ignore')]
        for item in self._values['actions']['items']:
            action = dict()
            self._remove_internal_keywords(item)
            if 'forward' in item:
                action.update(item)
                action['type'] = 'forward'
                del action['forward']
            elif 'enable' in item:
                action.update(item)
                action['type'] = 'enable'
                del action['enable']
            elif 'disable' in item:
                action.update(item)
                action['type'] = 'disable'
                del action['disable']
            elif 'redirect' in item:
                action.update(item)
                action['type'] = 'redirect'
                del action['redirect']
            elif 'setVariable' in item:
                action.update(item)
                action['type'] = 'set_variable'
                del action['fullPath']
                del action['code']
                del action['expirySecs']
                del action['length']
                del action['port']
                del action['status']
                del action['vlanId']
                del action['timeout']
            elif 'shutdown' in item:
                action.update(item)
                action['type'] = 'reset'
                del action['shutdown']
            elif 'persist' in item:
                action.update(item)
                action['type'] = 'persist'
                del action['persist']
            elif 'remove' in item:
                action.update(item)
                action['type'] = 'remove'
                action.pop('fullPath', None)
                action.pop('code', None)
                action.pop('expirySecs', None)
                action.pop('length', None)
                action.pop('port', None)
                action.pop('status', None)
                action.pop('vlanId', None)
                action.pop('timeout', None)
                action.pop('offset', None)
                del action['remove']
            elif 'insert' in item:
                action.update(item)
                action['type'] = 'insert'
                action.pop('fullPath', None)
                action.pop('code', None)
                action.pop('expirySecs', None)
                action.pop('length', None)
                action.pop('port', None)
                action.pop('status', None)
                action.pop('vlanId', None)
                action.pop('timeout', None)
                action.pop('offset', None)
                del action['insert']
            elif 'replace' in item:
                action.update(item)
                action['type'] = 'replace'
                action.pop('fullPath', None)
                action.pop('code', None)
                action.pop('expirySecs', None)
                action.pop('length', None)
                action.pop('status', None)
                action.pop('vlanId', None)
                action.pop('timeout', None)
                action.pop('offset', None)
                if 'httpConnect' not in action:
                    action.pop('port', None)
                del action['replace']
            result.append(action)
        result = sorted(result, key=lambda x: x['name'])
        return result

    @property
    def conditions(self):
        result = []
        if self._values['conditions'] is None or 'items' not in self._values['conditions']:
            return [dict(type='all_traffic')]
        for item in self._values['conditions']['items']:
            action = dict()
            self._remove_internal_keywords(item)
            if 'httpUri' in item:
                action.update(item)
                action['type'] = 'http_uri'
                del action['httpUri']

                # Converts to common stringiness
                #
                # The tuple set "issubset" check that happens in the Difference
                # engine does not recognize that a u'foo' and 'foo' are equal "enough"
                # to consider them a subset. Therefore, we cast everything here to
                # whatever the common stringiness is.
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
            elif 'httpHost' in item:
                action.update(item)
                action['type'] = 'http_host'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
                del action['httpHost']
            elif 'httpMethod' in item:
                action.update(item)
                action['type'] = 'http_method'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
                del action['httpMethod']
            elif 'httpHeader' in item:
                action.update(item)
                action['type'] = 'http_header'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
                del action['httpHeader']
            elif 'sslExtension' in item:
                action.update(item)
                action['type'] = 'ssl_extension'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
                del action['sslExtension']
            elif 'tcp' in item:
                action.update(item)
                action['type'] = 'tcp'
                if 'values' in action:
                    action['values'] = [str(x) for x in action['values']]
            result.append(action)
        # Names contains the index in which the rule is at.
        result = sorted(result, key=lambda x: x['name'])
        return result


class ModuleParameters(Parameters):
    @property
    def rule_order(self):
        if self._values['rule_order'] is None:
            return None
        if 0 < self._values['rule_order'] > 4294967295:
            raise F5ModuleError(
                "Specified number is out of valid range, correct range is between 0 and 4294967295."
            )
        return self._values['rule_order']

    @property
    def actions(self):
        result = []
        if self._values['actions'] is None:
            return None
        for idx, item in enumerate(self._values['actions']):
            action = dict()
            if 'name' in item:
                action['name'] = str(item['name'])
            else:
                action['name'] = str(idx)
            if item['type'] == 'forward':
                self._handle_forward_action(action, item)
            elif item['type'] == 'set_variable':
                self._handle_set_variable_action(action, item)
            elif item['type'] == 'enable':
                self._handle_enable_action(action, item)
            if item['type'] == 'disable':
                self._handle_disable_action(action, item)
            elif item['type'] == 'ignore':
                return [dict(type='ignore')]
            elif item['type'] == 'redirect':
                self._handle_redirect_action(action, item)
            elif item['type'] == 'reset':
                self._handle_reset_action(action, item)
                del action['shutdown']
            elif item['type'] == 'persist':
                self._handle_persist_action(action, item)
            elif item['type'] == 'remove':
                self._handle_remove_action(action, item)
            elif item['type'] == 'insert':
                self._handle_insert_action(action, item)
            elif item['type'] == 'replace':
                self._handle_replace_action(action, item)
            result.append(action)
        result = sorted(result, key=lambda x: x['name'])
        return result

    @property
    def conditions(self):
        result = []
        if self._values['conditions'] is None:
            return None
        for idx, item in enumerate(self._values['conditions']):
            action = dict()
            if 'name' in item:
                action['name'] = str(item['name'])
            else:
                action['name'] = str(idx)
            if item['type'] == 'http_uri':
                self._handle_http_uri_condition(action, item)
            elif item['type'] == 'http_method':
                self._handle_http_method_condition(action, item)
            elif item['type'] == 'http_host':
                self._handle_http_host_condition(action, item)
            elif item['type'] == 'http_header':
                self._handle_http_header_condition(action, item)
            elif item['type'] == 'ssl_extension':
                self._handle_ssl_extension_condition(action, item)
            elif item['type'] == 'tcp':
                self._handle_tcp_condition(action, item)
            elif item['type'] == 'all_traffic':
                return [dict(type='all_traffic')]
            result.append(action)
        result = sorted(result, key=lambda x: x['name'])
        return result

    def _handle_http_host_condition(self, action, item):
        options = ['host_begins_with_any', 'host_ends_with_any', 'host_is_any', 'host_is_not_any']
        action['type'] = 'http_host'

        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'host_begins_with_any', 'host_ends_with_any', host_is_any, or 'host_is_not_any' must be specified "
                "when the 'http_uri' type is used."
            )

        if 'host_begins_with_any' in item and item['host_begins_with_any'] is not None:
            if isinstance(item['host_begins_with_any'], list):
                values = item['host_begins_with_any']
            else:
                values = [item['host_begins_with_any']]
            action.update(dict(
                host=True,
                startsWith=True,
                values=values
            ))
        elif 'host_ends_with_any' in item and item['host_ends_with_any'] is not None:
            if isinstance(item['host_ends_with_any'], list):
                values = item['host_ends_with_any']
            else:
                values = [item['host_ends_with_any']]
            action.update(dict(
                host=True,
                endsWith=True,
                values=values
            ))
        elif 'host_is_any' in item and item['host_is_any'] is not None:
            if isinstance(item['host_is_any'], list):
                values = item['host_is_any']
            else:
                values = [item['host_is_any']]
            action.update(dict(
                equals=True,
                host=True,
                values=values
            ))
        elif 'host_is_not_any' in item and item['host_is_not_any'] is not None:
            if isinstance(item['host_is_not_any'], list):
                values = item['host_is_not_any']
            else:
                values = [item['host_is_not_any']]
            action.update({
                'equals': True,
                'host': True,
                'not': True,
                'values': values
            })

    def _handle_http_method_condition(self, action, item):
        options = ['method_matches_with_any']
        action['type'] = 'http_method'
        event_map = dict(
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            response='response',
        )

        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'method_matches_with_any' must be specified when the 'http_method' type is used."
            )

        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

        if 'method_matches_with_any' in item and item['method_matches_with_any'] is not None:
            if isinstance(item['method_matches_with_any'], list):
                values = item['method_matches_with_any']
            else:
                values = [item['method_matches_with_any']]
            action.update(dict(
                startsWith=True,
                values=values
            ))

    def _handle_http_uri_condition(self, action, item):
        action['type'] = 'http_uri'
        options = ['path_begins_with_any', 'path_contains', 'path_is_any']

        if all(k not in item for k in options):
            raise F5ModuleError(
                "A 'path_begins_with_any', 'path_contains' or 'path_is_any' must be specified "
                "when the 'http_uri' type is used."
            )

        if 'path_begins_with_any' in item and item['path_begins_with_any'] is not None:
            if isinstance(item['path_begins_with_any'], list):
                values = item['path_begins_with_any']
            else:
                values = [item['path_begins_with_any']]
            action.update(dict(
                path=True,
                startsWith=True,
                values=values
            ))
        elif 'path_contains' in item and item['path_contains'] is not None:
            if isinstance(item['path_contains'], list):
                values = item['path_contains']
            else:
                values = [item['path_contains']]
            action.update(dict(
                path=True,
                contains=True,
                values=values
            ))
        elif 'path_is_any' in item and item['path_is_any'] is not None:
            if isinstance(item['path_is_any'], list):
                values = item['path_is_any']
            else:
                values = [item['path_is_any']]
            action.update(dict(
                path=True,
                equals=True,
                values=values
            ))

    def _handle_tcp_condition(self, action, item):
        options = [
            'address_matches_with_any', 'address_matches_with_datagroup', 'address_matches_with_external_datagroup'
        ]
        event_map = dict(
            client_accepted='clientAccepted',
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            ssl_client_hello='sslClientHello',
            ssl_client_server_hello_send='sslClientServerhelloSend'
        )
        action['type'] = 'tcp'
        if all(k not in item for k in options):
            raise F5ModuleError(
                "A 'address_matches_with_any','address_matches_with_datagroup' or"
                "'address_matches_with_external_datagroup' must be specified when the 'tcp' type is used."
            )
        if 'address_matches_with_any' in item and item['address_matches_with_any'] is not None:
            if isinstance(item['address_matches_with_any'], list):
                values = item['address_matches_with_any']
            else:
                values = [item['address_matches_with_any']]
            action.update(dict(
                address=True,
                matches=True,
                values=values
            ))
        if 'address_matches_with_datagroup' in item and item['address_matches_with_datagroup'] is not None:
            if isinstance(item['address_matches_with_datagroup'], list):
                values = item['address_matches_with_datagroup']
            else:
                values = [item['address_matches_with_datagroup']]
            for value in values:
                action.update(dict(
                    address=True,
                    matches=True,
                    datagroup=fq_name(self.partition, value)
                )
                )
        if 'address_matches_with_external_datagroup' in item and \
                item['address_matches_with_external_datagroup'] is not None:
            if isinstance(item['address_matches_with_external_datagroup'], list):
                values = item['address_matches_with_external_datagroup']
            else:
                values = [item['address_matches_with_external_datagroup']]
            for value in values:
                action.update(dict(
                    address=True,
                    matches=True,
                    datagroup=fq_name(self.partition, value)
                )
                )
        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

    def _handle_ssl_extension_condition(self, action, item):
        action['type'] = 'ssl_extension'
        if 'server_name_is_any' in item:
            if isinstance(item['server_name_is_any'], list):
                values = item['server_name_is_any']
            else:
                values = [item['server_name_is_any']]
            action.update(dict(
                equals=True,
                serverName=True,
                values=values
            ))
        if 'event' not in item:
            raise F5ModuleError(
                "An 'event' must be specified when the 'ssl_extension' condition is used."
            )
        elif 'ssl_client_hello' in item['event']:
            action.update(dict(
                sslClientHello=True
            ))
        elif 'ssl_server_hello' in item['event']:
            action.update(dict(
                sslServerHello=True
            ))

    def _handle_http_header_condition(self, action, item):
        action['type'] = 'http_header'
        options = ['header_is_any']
        event_map = dict(
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            response='response',
        )
        if 'header_name' not in item:
            raise F5ModuleError(
                "An 'header_name' must be specified when the 'http_header' condition is used."
            )
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'header_is_any' must be specified when the 'http_header' type is used."
            )
        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

        if 'header_is_any' in item:
            if isinstance(item['header_is_any'], list):
                values = item['header_is_any']
            else:
                values = [item['header_is_any']]

            action.update(dict(
                equals=True,
                tmName=item['header_name'],
                values=values
            ))

    def _handle_forward_action(self, action, item):
        """Handle the nuances of the forwarding type

        Right now there is only a single type of forwarding that can be done. As that
        functionality expands, so-to will the behavior of this, and other, methods.
        Therefore, do not be surprised that the logic here is so rigid. It's deliberate.

        :param action:
        :param item:
        :return:
        """

        event_map = dict(
            client_accepted='clientAccepted',
            proxy_request='proxyRequest',
            request='request',
            ssl_client_hello='sslClientHello',
            ssl_client_server_hello_send='sslClientServerhelloSend'
        )

        action['type'] = 'forward'
        options = ['pool', 'virtual', 'node']
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'pool' or 'virtual' or 'node' must be specified when the 'forward' type is used."
            )
        if item.get('pool', None):
            action['pool'] = fq_name(self.partition, item['pool'])
        elif item.get('virtual', None):
            action['virtual'] = fq_name(self.partition, item['virtual'])
        elif item.get('node', None):
            action['node'] = item['node']

        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

    def _handle_set_variable_action(self, action, item):
        """Handle the nuances of the set_variable type

        :param action:
        :param item:
        :return:
        """
        if 'expression' not in item and 'variable_name' not in item:
            raise F5ModuleError(
                "A 'variable_name' and 'expression' must be specified when the 'set_variable' type is used."
            )

        if 'event' in item and item['event'] is not None:
            action[item['event']] = True
        else:
            action['request'] = True
        action.update(dict(
            type='set_variable',
            expression=item['expression'],
            tmName=item['variable_name'],
            setVariable=True,
            tcl=True
        ))

    def _handle_enable_action(self, action, item):
        """Handle the nuances of the enable type

        :param action:
        :param item:
        :return:
        """
        action['type'] = 'enable'
        if 'asm_policy' not in item:
            raise F5ModuleError(
                "An 'asm_policy' must be specified when the 'enable' type is used."
            )
        action.update(dict(
            policy=fq_name(self.partition, item['asm_policy']),
            asm=True
        ))

    def _handle_disable_action(self, action, item):
        """Handle the nuances of the disable type

        :param action:
        :param item:
        :return:
        """

        target_map = dict(
            server_ssl='serverSsl',
            persist='persist'
        )
        event_map = dict(
            client_accepted='clientAccepted',
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            server_connected='serverConnected',
        )

        action['type'] = 'disable'
        if 'disable_target' not in item:
            raise F5ModuleError(
                "An 'disable_target' must be specified when the 'enable' type is used."
            )
        if 'event' in item and item['event'] is not None:
            event = event_map.get(item['event'], None)
            if event:
                action[event] = True

        action[target_map[item['disable_target']]] = True

    def _handle_redirect_action(self, action, item):
        """Handle the nuances of the redirect type

        :param action:
        :param item:
        :return:
        """
        action['type'] = 'redirect'
        if 'location' not in item:
            raise F5ModuleError(
                "A 'location' must be specified when the 'redirect' type is used."
            )
        action.update(
            location=item['location'],
            httpReply=True,
        )

    def _handle_reset_action(self, action, item):
        """Handle the nuances of the reset type

        :param action:
        :param item:
        :return:
        """
        event_map = dict(
            client_accepted='clientAccepted',
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            response='response',
            server_connected='serverConnected',
            ssl_client_hello='sslClientHello',
            ssl_client_server_hello_send='sslClientServerhelloSend',
            ssl_server_handshake='sslServerHandshake',
            ssl_server_hello='sslServerHello',
            websocket_request='wsRequest',
            websocket_response='wsResponse'
        )

        action['type'] = 'reset'
        if 'event' not in item:
            raise F5ModuleError(
                "An 'event' must be specified when the 'reset' type is used."
            )
        event = event_map.get(item['event'], None)
        if not event:
            raise F5ModuleError(
                "Invalid event type specified for reset action: {0},"
                "check module documentation for valid event types.".format(item['event'])
            )

        action[event] = True
        action.update({
            'connection': True,
            'shutdown': True
        })

    def _handle_persist_action(self, action, item):
        """Handle the nuances of the persist type

        :param action:
        :param item:
        :return:
        """
        action['type'] = 'persist'
        if 'cookie_insert' not in item:
            raise F5ModuleError(
                "A 'cookie_insert' must be specified when the 'persist' type is used."
            )
        elif 'cookie_expiry' in item:
            action.update(
                cookieInsert=True,
                tmName=item['cookie_insert'],
                expiry=str(item['cookie_expiry'])
            )
        else:
            action.update(
                cookieInsert=True,
                tmName=item['cookie_insert']
            )

    def _handle_remove_action(self, action, item):
        """Handle the nuances of the remove type

        :param action:
        :param item:
        :return:
        """

        action['type'] = 'remove'
        options = ['http_header', 'http_referer', 'http_set_cookie', 'http_cookie']
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'http_header', 'http_referer', 'http_set_cookie' or 'http_cookie' must be specified when "
                "the 'remove' type is used."
            )
        if 'http_header' in item and item['http_header']:
            if item['http_header']['event'] == 'request':
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    request=True
                )
            elif item['http_header']['event'] == 'response':
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    response=True
                )
            else:
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name']
                )
        if 'http_referer' in item and item['http_referer']:
            if item['http_referer']['event'] == 'request':
                action.update(
                    httpReferer=True,
                    request=True
                )
            if item['http_referer']['event'] == 'proxy_connect':
                action.update(
                    httpReferer=True,
                    proxyConnect=True
                )
            if item['http_referer']['event'] == 'proxy_request':
                action.update(
                    httpReferer=True,
                    proxyRequest=True
                )
        if 'http_cookie' in item and item['http_cookie']:
            if item['http_cookie']['event'] == 'request':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    request=True
                )
            elif item['http_cookie']['event'] == 'proxy_connect':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    proxyConnect=True
                )
            elif item['http_cookie']['event'] == 'proxy_request':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    proxyRequest=True
                )
            else:
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name']
                )
        if 'http_set_cookie' in item and item['http_set_cookie']:
            action.update(
                httpSetCookie=True,
                tmName=item['http_set_cookie']['name'],
                response=True
            )

    def _handle_insert_action(self, action, item):
        """Handle the nuances of the insert type

        :param action:
        :param item:
        :return:
        """

        action['type'] = 'insert'
        options = ['http_header', 'http_referer', 'http_set_cookie', 'http_cookie']
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'http_header', 'http_referer', 'http_set_cookie' or 'http_cookie' must be specified when "
                "the 'insert' type is used."
            )

        if 'http_header' in item and item['http_header']:
            if item['http_header']['value'] is None:
                raise F5ModuleError(
                    "The http_header value key is required when action is of type 'insert'."
                )
            if item['http_header']['event'] == 'request':
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    value=item['http_header']['value'],
                    request=True
                )
            elif item['http_header']['event'] == 'response':
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    value=item['http_header']['value'],
                    response=True
                )
            else:
                action.update(
                    httpHeader=True,
                    tmName=item['http_header']['name'],
                    value=item['http_header']['value']
                )
        if 'http_referer' in item and item['http_referer']:
            if item['http_referer']['value'] is None:
                raise F5ModuleError(
                    "The http_referer value key is required when action is of type 'insert'."
                )
            if item['http_referer']['event'] == 'request':
                action.update(
                    httpReferer=True,
                    value=item['http_referer']['value'],
                    request=True
                )
            if item['http_referer']['event'] == 'proxy_connect':
                action.update(
                    httpReferer=True,
                    value=item['http_referer']['value'],
                    proxyConnect=True
                )
            if item['http_referer']['event'] == 'proxy_request':
                action.update(
                    httpReferer=True,
                    value=item['http_referer']['value'],
                    proxyRequest=True
                )
        if 'http_cookie' in item and item['http_cookie']:
            if item['http_cookie']['value'] is None:
                raise F5ModuleError(
                    "The http_cookie value key is required when action is of type 'insert'."
                )
            if item['http_cookie']['event'] == 'request':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    value=item['http_cookie']['value'],
                    request=True
                )
            elif item['http_cookie']['event'] == 'proxy_connect':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    value=item['http_cookie']['value'],
                    proxyConnect=True
                )
            elif item['http_cookie']['event'] == 'proxy_request':
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    value=item['http_cookie']['value'],
                    proxyRequest=True
                )
            else:
                action.update(
                    httpCookie=True,
                    tmName=item['http_cookie']['name'],
                    value=item['http_cookie']['value']
                )
        if 'http_set_cookie' in item and item['http_set_cookie']:
            if item['http_set_cookie']['value'] is None:
                raise F5ModuleError(
                    "The http_set_cookie value key is required when action is of type 'insert'."
                )
            action.update(
                httpSetCookie=True,
                tmName=item['http_set_cookie']['name'],
                value=item['http_set_cookie']['value'],
                response=True
            )

    def _handle_replace_action(self, action, item):
        """Handle the nuances of the replace type

        :param action:
        :param item:
        :return:
        """

        action['type'] = 'replace'
        options = ['http_header', 'http_referer', 'http_host', 'http_connect', 'http_uri']
        if not any(x for x in options if x in item):
            raise F5ModuleError(
                "A 'http_header', 'http_referer', 'http_host', 'http_connect' or 'http_uri' must be specified when "
                "the 'replace' type is used."
            )
        event_map = dict(
            client_accepted='clientAccepted',
            proxy_connect='proxyConnect',
            proxy_request='proxyRequest',
            proxy_response='proxyResponse',
            request='request',
            response='response',
            server_connected='serverConnected',
            ssl_client_hello='sslClientHello'
        )
        type_map = dict(
            path='path',
            query_string='queryString',
            full_string='value'
        )
        if 'http_header' in item and item['http_header']:
            if item['http_header']['value'] is None:
                raise F5ModuleError(
                    "The http_header value key is required when action is of type 'replace'."
                )
            if item['http_header']['event'] is not None:
                action.update({
                    'httpHeader': True,
                    'tmName': item['http_header']['name'],
                    'value': item['http_header']['value'],
                    event_map[item['http_header']['event']]: True
                })
            else:
                action.update({
                    'httpHeader': True,
                    'tmName': item['http_header']['name'],
                    'value': item['http_header']['value']
                })
        if 'http_referer' in item and item['http_referer']:
            if item['http_referer']['value'] is not None:
                action.update({
                    'httpReferer': True,
                    'value': item['http_referer']['value'],
                    event_map[item['http_referer']['event']]: True
                })
            else:
                action.update({
                    'httpReferer': True,
                    event_map[item['http_referer']['event']]: True
                })
        if 'http_connect' in item and item['http_connect']:
            if item['http_connect']['port'] is None:
                action.update({
                    'httpConnect': True,
                    'host': item['http_connect']['value'],
                    'port': 0,
                    event_map[item['http_connect']['event']]: True
                })
            else:
                action.update({
                    'httpConnect': True,
                    'host': item['http_connect']['value'],
                    'port': item['http_connect']['port'],
                    event_map[item['http_connect']['event']]: True
                })
        if 'http_uri' in item and item['http_uri']:
            if item['http_uri']['event'] is not None:
                action.update({
                    'httpUri': True,
                    type_map[item['http_uri']['type']]: item['http_uri']['value'],
                    event_map[item['http_uri']['event']]: True
                })
            else:
                action.update({
                    'httpUri': True,
                    type_map[item['http_uri']['type']]: item['http_uri']['value'],
                })
        if 'http_host' in item and item['http_host']:
            if item['http_host']['event'] is not None:
                action.update({
                    'httpHost': True,
                    'value': item['http_host']['value'],
                    event_map[item['http_host']['event']]: True
                })
            else:
                action.update({
                    'httpHost': True,
                    'value': item['http_host']['value'],
                })


class Changes(Parameters):
    def to_return(self):
        result = {}
        for returnable in self.returnables:
            try:
                result[returnable] = getattr(self, returnable)
                result = self._filter_params(result)
            except Exception:
                raise
        return result


class ReportableChanges(Changes):
    event_map = dict(
        clientAccepted='client_accepted',
        proxyConnect='proxy_connect',
        proxyRequest='proxy_request',
        proxyResponse='proxy_response',
        request='request',
        response='response',
        serverConnected='server_connected',
        sslClientHello='ssl_client_hello'
    )
    uri_type_map = dict(
        path='path',
        queryString='query_string',
        value='full_string'
    )

    returnables = [
        'description', 'actions', 'conditions'
    ]

    def _map_value(self, item, t=False):
        if not t:
            for k in self.event_map.keys():
                if k in item:
                    return k
        else:
            for k in self.uri_type_map.keys():
                if k in item:
                    return k
        return None

    @property
    def actions(self):
        result = []
        if self._values['actions'] is None:
            return [dict(type='ignore')]
        for item in self._values['actions']:
            action = dict()
            if 'forward' in item:
                action.update(item)
                action['type'] = 'forward'
                del action['forward']
            elif 'replace' in item:
                action.update(item)
                action['type'] = 'replace'
                if 'httpHeader' in item:
                    event = self._map_value(item)
                    if event:
                        http_header = dict(event=self.event_map[event], name=action['tmName'], value=action['value'])
                        action['http_header'] = http_header
                        del action[event]
                    else:
                        http_header = dict(name=action['tmName'], value=action['value'])
                        action['http_header'] = http_header
                    del action['httpHeader']
                    del action['value']
                    del action['tmName']
                if 'httpReferer' in item:
                    event = self._map_value(item)
                    if event:
                        if 'value' in item:
                            http_ref = dict(event=self.event_map[event], value=action['value'])
                            action['http_referer'] = http_ref
                            del action['value']
                        else:
                            http_ref = dict(event=self.event_map[event])
                            action['http_referer'] = http_ref
                        del action[event]
                    else:
                        if 'value' in item:
                            http_ref = dict(event=self.event_map[event], value=action['value'])
                            action['http_referer'] = http_ref
                            del action['value']
                    del action['httpReferer']
                if 'httpConnect' in item:
                    event = self._map_value(item)
                    if event:
                        if 'value' in item and 'port' in item:
                            http_con = dict(event=self.event_map[event], value=action['value'], port=action['item'])
                            action['http_connect'] = http_con
                            del action['value']
                            del action['port']
                        elif 'value' in item and 'port' not in item:
                            http_con = dict(event=self.event_map[event], value=action['value'])
                            action['http_connect'] = http_con
                            del action['value']
                        elif 'value' not in item and 'port' in item:
                            http_con = dict(event=self.event_map[event], port=action['port'])
                            action['http_connect'] = http_con
                            del action['port']
                        else:
                            http_con = dict(event=self.event_map[event])
                            action['http_connect'] = http_con
                        del action[event]
                    else:
                        if 'value' in item and 'port' in item:
                            http_con = dict(value=action['value'], port=action['item'])
                            action['http_connect'] = http_con
                            del action['value']
                            del action['port']
                        elif 'value' in item and 'port' not in item:
                            http_con = dict(value=action['value'])
                            action['http_connect'] = http_con
                            del action['value']
                        elif 'value' not in item and 'port' in item:
                            http_con = dict(port=action['port'])
                            action['http_connect'] = http_con
                            del action['port']
                    del action['httpConnect']
                if 'httpUri' in item:
                    event = self._map_value(item)
                    kind = self._map_value(item, True)
                    if event:
                        http_uri = dict(event=self.event_map[event], type=self.uri_type_map[kind], value=action[kind])
                        action['http_uri'] = http_uri
                        del action[event]
                    else:
                        http_uri = dict(type=self.uri_type_map[kind], value=action[kind])
                        action['http_uri'] = http_uri
                    del action[kind]
                    del action['httpUri']
                if 'httpHost' in item:
                    event = self._map_value(item)
                    if event:
                        http_uri = dict(event=self.event_map[event], value=action['value'])
                        action['http_uri'] = http_uri
                        del action[event]
                    else:
                        http_uri = dict(value=action['value'])
                        action['http_uri'] = http_uri
                    del action['value']
                    del action['httpHost']
            elif 'insert' in item:
                action.update(item)
                action['type'] = 'insert'
                if 'httpHeader' in item:
                    if 'response' in item:
                        http_header = dict(event='response', name=action['tmName'], value=action['value'])
                        action['http_header'] = http_header
                        del action['response']
                    if 'request' in item:
                        http_header = dict(event='request', name=action['tmName'], value=action['value'])
                        action['http_header'] = http_header
                        del action['request']
                    del action['httpHeader']
                    del action['tmName']
                if 'httpReferer' in item:
                    if 'request' in item:
                        http_ref = dict(event='request', value=action['value'])
                        action['http_referer'] = http_ref
                        del action['request']
                    if 'proxyConnect' in item:
                        http_ref = dict(event='proxy_connect', value=action['value'])
                        action['http_referer'] = http_ref
                        del action['proxyConnect']
                    if 'proxyRequest' in item:
                        http_ref = dict(event='proxy_request', value=action['value'])
                        action['http_referer'] = http_ref
                        del action['proxyRequest']
                    del action['httpReferer']
                if 'httpCookie' in item:
                    if 'request' in item:
                        http_cookie = dict(event='request', name=action['tmName'], value=action['value'])
                        action['http_cookie'] = http_cookie
                        del action['request']
                    if 'proxyConnect' in item:
                        http_cookie = dict(event='proxy_connect', name=action['tmName'], value=action['value'])
                        action['http_cookie'] = http_cookie
                        del action['proxyConnect']
                    if 'proxyRequest' in item:
                        http_cookie = dict(event='proxy_request', name=action['tmName'], value=action['value'])
                        action['http_cookie'] = http_cookie
                        del action['proxyRequest']
                    del action['httpCookie']
                    del action['tmName']
                if 'httpSetCookie' in item:
                    http_set_cookie = dict(name=action['tmName'], value=action['value'])
                    action['http_set_cookie'] = http_set_cookie
                    del action['response']
                    del action['value']
                    del action['tmName']
                del action['insert']
                del action['value']
            elif 'remove' in item:
                action.update(item)
                action['type'] = 'remove'
                if 'httpHeader' in item:
                    if 'response' in item:
                        http_header = dict(event='response', name=action['tmName'])
                        action['http_header'] = http_header
                        del action['response']
                    if 'request' in item:
                        http_header = dict(event='request', name=action['tmName'])
                        action['http_header'] = http_header
                        del action['request']
                    del action['httpHeader']
                    del action['tmName']
                if 'httpReferer' in item:
                    if 'request' in item:
                        http_ref = dict(event='request')
                        action['http_referer'] = http_ref
                        del action['request']
                    if 'proxyConnect' in item:
                        http_ref = dict(event='proxy_connect')
                        action['http_referer'] = http_ref
                        del action['proxyConnect']
                    if 'proxyRequest' in item:
                        http_ref = dict(event='proxy_request')
                        action['http_referer'] = http_ref
                        del action['proxyRequest']
                    del action['httpReferer']
                if 'httpCookie' in item:
                    if 'request' in item:
                        http_cookie = dict(event='request', name=action['tmName'])
                        action['http_cookie'] = http_cookie
                        del action['request']
                    if 'proxyConnect' in item:
                        http_cookie = dict(event='proxy_connect', name=action['tmName'])
                        action['http_cookie'] = http_cookie
                        del action['proxyConnect']
                    if 'proxyRequest' in item:
                        http_cookie = dict(event='proxy_request', name=action['tmName'])
                        action['http_cookie'] = http_cookie
                        del action['proxyRequest']
                    del action['httpCookie']
                    del action['tmName']
                if 'httpSetCookie' in item:
                    action['http_set_cookie'] = dict(name=action['tmName'])
                    del action['response']
                    del action['tmName']
                del action['remove']
            elif 'set_variable' in item:
                action.update(item)
                action['type'] = 'set_variable'
                del action['set_variable']
            elif 'enable' in item:
                action.update(item)
                action['type'] = 'enable'
                del action['enable']
            elif 'disable' in item:
                action.update(item)
                action['type'] = 'disable'
                if 'serverSsl' in action and action['serverSsl']:
                    action['disable_target'] = 'server_ssl'
                    del action['serverSsl']
                if 'persist' in action and action['persist']:
                    action['disable_target'] = 'persist'
                    del action['persist']
                del action['enable']
            elif 'redirect' in item:
                action.update(item)
                action['type'] = 'redirect'
                del action['redirect']
                del action['httpReply']
            elif 'reset' in item:
                action.update(item)
                action['type'] = 'reset'
                del action['connection']
                del action['shutdown']
            elif 'persist' in item:
                action.update(item)
                action['type'] = 'persist'
                action['cookie_insert'] = action['tmName']
                if 'expiry' in item:
                    action['cookie_expiry'] = int(action['expiry'])
                    del action['expiry']
                del action['tmName']
                del action['persist']
                del action['cookieInsert']
            result.append(action)
        result = sorted(result, key=lambda x: x['name'])
        return result

    @property
    def conditions(self):
        result = []
        if self._values['conditions'] is None:
            return [dict(type='all_traffic')]
        for item in self._values['conditions']:
            action = dict()
            if 'httpUri' in item:
                action.update(item)
                action['type'] = 'http_uri'
                del action['httpUri']
            elif 'httpMethod' in item:
                action.update(item)
                action['type'] = 'http_method'
                del action['httpMethod']
            elif 'httpHost' in item:
                action.update(item)
                action['type'] = 'http_host'
                del action['httpHost']
            elif 'httpHeader' in item:
                action.update(item)
                action['type'] = 'http_header'
                action['header_name'] = action['tmName']
                del action['httpHeader']
                del action['tmName']
            elif 'tcp' in item:
                action.update(item)
                action['type'] = 'tcp'
                del action['tcp']
            elif 'sslExtension' in item:
                action.update(item)
                action['type'] = 'ssl_extension'
                del action['sslExtension']
            result.append(action)
        # Names contains the index in which the rule is at.
        result = sorted(result, key=lambda x: x['name'])
        return result


class UsableChanges(Changes):
    @property
    def actions(self):
        if self._values['actions'] is None:
            return None
        result = []
        for action in self._values['actions']:
            if 'type' not in action:
                continue
            if action['type'] == 'forward':
                action['forward'] = True
                del action['type']
            elif action['type'] == 'enable':
                action['enable'] = True
                del action['type']
            elif action['type'] == 'disable':
                action['disable'] = True
                del action['type']
            elif action['type'] == 'set_variable':
                action['setVariable'] = True
                action['tcl'] = True
                del action['type']
            elif action['type'] == 'ignore':
                result = []
                break
            elif action['type'] == 'redirect':
                action['httpReply'] = True
                action['redirect'] = True
                del action['type']
            elif action['type'] == 'reset':
                action['shutdown'] = True
                action['connection'] = True
                del action['type']
            elif action['type'] == 'persist':
                action['persist'] = True
                del action['type']
            elif action['type'] == 'remove':
                action['remove'] = True
                del action['type']
            elif action['type'] == 'insert':
                action['insert'] = True
                del action['type']
            elif action['type'] == 'replace':
                action['replace'] = True
                del action['type']
            result.append(action)
        return result

    @property
    def conditions(self):
        if self._values['conditions'] is None:
            return None
        result = []
        for condition in self._values['conditions']:
            if 'type' not in condition:
                continue
            if condition['type'] == 'http_uri':
                condition['httpUri'] = True
                del condition['type']
            elif condition['type'] == 'http_method':
                condition['httpMethod'] = True
                del condition['type']
            elif condition['type'] == 'http_host':
                condition['httpHost'] = True
                del condition['type']
            elif condition['type'] == 'http_header':
                condition['httpHeader'] = True
            elif condition['type'] == 'tcp':
                condition['tcp'] = True
                del condition['type']
            elif condition['type'] == 'ssl_extension':
                condition['sslExtension'] = True
                del condition['type']
            elif condition['type'] == 'all_traffic':
                result = []
                break
            result.append(condition)
        return result


class Difference(object):
    updatables = [
        'actions', 'conditions', 'description'
    ]

    def __init__(self, want, have=None):
        self.want = want
        self.have = have

    def compare(self, param):
        try:
            result = getattr(self, param)
            return result
        except AttributeError:
            return self.__default(param)

    def __default(self, param):
        attr1 = getattr(self.want, param)
        try:
            attr2 = getattr(self.have, param)
            if attr1 != attr2:
                return attr1
        except AttributeError:
            return attr1

    def to_tuple(self, items):
        result = []
        for x in items:
            tmp = [(str(k), str(v)) for k, v in iteritems(x)]
            result += tmp
        return result

    def _diff_complex_items(self, want, have):
        if want == [] and have is None:
            return None
        if want is None:
            return None
        w = self.to_tuple(want)
        h = self.to_tuple(have)
        if set(w).issubset(set(h)):
            return None
        else:
            return want

    @property
    def actions(self):
        if self.want.replace_with is True:
            return self.want.actions
        result = self._diff_complex_items(self.want.actions, self.have.actions)
        actioned = self._compare_complex_actions()
        if self._conditions_missing_default_rule_for_asm(result):
            raise F5ModuleError(
                "Valid options when using an ASM policy in a rule's 'enable' "
                "action include all_traffic, http_uri, or http_host."
            )
        if result is None and actioned is True:
            return self.want.actions
        return result

    @property
    def conditions(self):
        if self.want.replace_with is True:
            return self.want.conditions
        result = self._diff_complex_items(self.want.conditions, self.have.conditions)
        return result

    def _conditions_missing_default_rule_for_asm(self, want_actions):
        if want_actions is None:
            actions = self.have.actions
        else:
            actions = want_actions
        if actions is None:
            return False
        if any(x for x in actions if x['type'] == 'enable'):
            conditions = self._diff_complex_items(self.want.conditions, self.have.conditions)
            if conditions is None:
                return False
            if any(y for y in conditions if y['type'] not in ['all_traffic', 'http_uri', 'http_host', 'tcp']):
                return True
        return False

    def _compare_complex_actions(self):
        types = ['insert', 'remove', 'replace']
        if self.want.actions:
            want = [item for item in self.want.actions if item['type'] in types]
            have = [item for item in self.have.actions if item['type'] in types]
            result = compare_complex_list(want, have)
            if result:
                return True
        return False


class ModuleManager(object):
    def __init__(self, *args, **kwargs):
        self.module = kwargs.get('module', None)
        self.client = F5RestClient(**self.module.params)
        self.want = ModuleParameters(params=self.module.params)
        self.have = ApiParameters()
        self.changes = UsableChanges()

    def _update_changed_options(self):
        diff = Difference(self.want, self.have)
        updatables = Parameters.updatables
        changed = dict()
        for k in updatables:
            change = diff.compare(k)
            if change is None:
                continue
            else:
                if isinstance(change, dict):
                    changed.update(change)
                else:
                    changed[k] = change
        if changed:
            self.changes = UsableChanges(params=changed)
            return True
        return False

    def should_update(self):
        result = self._update_changed_options()
        if result:
            return True
        return False

    def exec_module(self):
        start = datetime.now().isoformat()
        version = tmos_version(self.client)
        changed = False
        result = dict()
        state = self.want.state

        if state == "present":
            changed = self.present()
        elif state == "absent":
            changed = self.absent()
        reportable = ReportableChanges(params=self.changes.to_return())
        changes = reportable.to_return()
        result.update(**changes)
        result.update(dict(changed=changed))
        self._announce_deprecations(result)
        send_teem(start, self.client, self.module, version)
        return result

    def _announce_deprecations(self, result):
        warnings = result.pop('__warnings', [])
        for warning in warnings:
            self.module.deprecate(
                msg=warning['msg'],
                version=warning['version']
            )

    def present(self):
        if self.exists():
            return self.update()
        else:
            return self.create()

    def absent(self):
        if self.exists():
            return self.remove()
        return False

    def update(self):
        self.have = self.read_current_from_device()
        if not self.should_update():
            return False
        if self.module.check_mode:
            return True
        if self.draft_exists():
            redraft = True
        else:
            redraft = False
            self._create_existing_policy_draft_on_device()
        self.update_on_device()
        if redraft is False:
            self.publish_on_device()
        return True

    def remove(self):
        if self.module.check_mode:
            return True
        if self.draft_exists():
            redraft = True
        else:
            redraft = False
            self._create_existing_policy_draft_on_device()
        self.remove_from_device()
        if self.exists():
            raise F5ModuleError("Failed to delete the resource.")
        if redraft is False:
            self.publish_on_device()
        return True

    def create(self):
        self.should_update()
        if self.module.check_mode:
            return True
        if self.draft_exists():
            redraft = True
        else:
            redraft = False
            self._create_existing_policy_draft_on_device()
        self.create_on_device()
        if redraft is False:
            self.publish_on_device()
        return True

    def exists(self):
        if self.draft_exists():
            uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
                self.client.provider['server'],
                self.client.provider['server_port'],
                transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
                self.want.name
            )
        else:
            uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
                self.client.provider['server'],
                self.client.provider['server_port'],
                transform_name(self.want.partition, self.want.policy),
                self.want.name
            )
        resp = self.client.api.get(uri)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status == 404 or 'code' in response and response['code'] == 404:
            return False
        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True

        errors = [401, 403, 409, 500, 501, 502, 503, 504]

        if resp.status in errors or 'code' in response and response['code'] in errors:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)

    def draft_exists(self):
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy, sub_path='Drafts')
        )
        resp = self.client.api.get(uri)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status == 404 or 'code' in response and response['code'] == 404:
            return False
        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True

        errors = [401, 403, 409, 500, 501, 502, 503, 504]

        if resp.status in errors or 'code' in response and response['code'] in errors:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)

    def _create_existing_policy_draft_on_device(self):
        params = dict(createDraft=True)
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy)
        )
        resp = self.client.api.patch(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def publish_on_device(self):
        params = dict(
            name=fq_name(self.want.partition,
                         self.want.policy,
                         sub_path='Drafts'
                         ),
            command="publish"

        )
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
        )
        resp = self.client.api.post(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def create_on_device(self):
        params = self.changes.api_params()
        params['name'] = self.want.name
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
        )
        resp = self.client.api.post(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def update_on_device(self):
        params = self.changes.api_params()
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
            self.want.name
        )
        resp = self.client.api.patch(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def remove_from_device(self):
        uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
            self.want.name
        )
        response = self.client.api.delete(uri)
        if response.status == 200:
            return True
        raise F5ModuleError(response.content)

    def read_current_from_device(self):
        if self.draft_exists():
            uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
                self.client.provider['server'],
                self.client.provider['server_port'],
                transform_name(self.want.partition, self.want.policy, sub_path='Drafts'),
                self.want.name
            )
        else:
            uri = "https://{0}:{1}/mgmt/tm/ltm/policy/{2}/rules/{3}".format(
                self.client.provider['server'],
                self.client.provider['server_port'],
                transform_name(self.want.partition, self.want.policy),
                self.want.name
            )
        query = "?expandSubcollections=true"
        resp = self.client.api.get(uri + query)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return ApiParameters(params=response)
        raise F5ModuleError(resp.content)


class ArgumentSpec(object):
    def __init__(self):
        self.supports_check_mode = True
        argument_spec = dict(
            description=dict(),
            actions=dict(
                type='list',
                elements='dict',
                options=dict(
                    type=dict(
                        choices=[
                            'forward',
                            'enable',
                            'ignore',
                            'redirect',
                            'reset',
                            'persist',
                            'set_variable',
                            'remove',
                            'insert',
                            'replace',
                            'disable',
                        ],
                        required=True
                    ),
                    pool=dict(),
                    node=dict(),
                    asm_policy=dict(),
                    virtual=dict(),
                    location=dict(),
                    event=dict(),
                    cookie_insert=dict(),
                    cookie_expiry=dict(type='int'),
                    expression=dict(),
                    variable_name=dict(),
                    disable_target=dict(
                        choices=['server_ssl', 'persist']
                    ),
                    http_header=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=[
                                    'request', 'response', 'proxy_connect',
                                    'proxy_request', 'proxy_response'
                                ],
                                required=True
                            ),
                            name=dict(required=True),
                            value=dict()
                        )
                    ),
                    http_referer=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=['request', 'proxy_connect', 'proxy_request'],
                                required=True
                            ),
                            value=dict()
                        )
                    ),
                    http_set_cookie=dict(
                        type='dict',
                        options=dict(
                            name=dict(required=True),
                            value=dict()
                        )
                    ),
                    http_cookie=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=['request', 'proxy_connect', 'proxy_request'],
                                required=True

                            ),
                            name=dict(required=True),
                            value=dict()
                        )
                    ),
                    http_connect=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=[
                                    'client_accepted', 'proxy_connect', 'proxy_request',
                                    'proxy_response', 'request', 'server_connected', 'ssl_client_hello'
                                ],
                                required=True
                            ),
                            value=dict(
                                required=True
                            ),
                            port=dict(type='int'),
                        )
                    ),
                    http_host=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=['request', 'proxy_connect', 'proxy_request'],
                                required=True
                            ),
                            value=dict(required=True)
                        )
                    ),
                    http_uri=dict(
                        type='dict',
                        options=dict(
                            event=dict(
                                choices=['request', 'proxy_connect', 'proxy_request'],
                                required=True
                            ),
                            type=dict(
                                choices=['path', 'query_string', 'full_string'],
                                required=True
                            ),
                            value=dict(required=True)
                        ),

                    ),
                ),
                mutually_exclusive=[
                    ['pool', 'asm_policy', 'virtual', 'location', 'cookie_insert', 'node', 'http_header',
                     'http_referer', 'http_set_cookie', 'http_cookie', 'http_uri', 'http_host', 'http_connect',
                     'disable_target'
                     ]
                ]
            ),
            conditions=dict(
                type='list',
                elements='dict',
                options=dict(
                    type=dict(
                        choices=[
                            'http_uri',
                            'http_method',
                            'http_host',
                            'http_header',
                            'ssl_extension',
                            'all_traffic',
                            'tcp'
                        ],
                        required=True
                    ),
                    path_begins_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    path_contains=dict(
                        type='list',
                        elements='str',
                    ),
                    path_is_any=dict(
                        type='list',
                        elements='str',
                    ),
                    host_begins_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    host_ends_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    host_is_any=dict(
                        type='list',
                        elements='str',
                    ),
                    host_is_not_any=dict(
                        type='list',
                        elements='str',
                    ),
                    header_name=dict(),
                    header_is_any=dict(
                        type='list',
                        elements='str',
                    ),
                    method_matches_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    server_name_is_any=dict(
                        type='list',
                        elements='str',
                    ),
                    address_matches_with_any=dict(
                        type='list',
                        elements='str',
                    ),
                    address_matches_with_datagroup=dict(
                        type='list',
                        elements='str',
                    ),
                    address_matches_with_external_datagroup=dict(
                        type='list',
                        elements='str',
                    ),
                    event=dict()
                ),
            ),
            name=dict(required=True),
            policy=dict(required=True),
            rule_order=dict(type='int'),
            replace_with=dict(
                type='bool',
                default='no'
            ),
            state=dict(
                default='present',
                choices=['absent', 'present']
            ),
            partition=dict(
                default='Common',
                fallback=(env_fallback, ['F5_PARTITION'])
            )
        )
        self.argument_spec = {}
        self.argument_spec.update(f5_argument_spec)
        self.argument_spec.update(argument_spec)


def main():
    spec = ArgumentSpec()

    module = AnsibleModule(
        argument_spec=spec.argument_spec,
        supports_check_mode=spec.supports_check_mode
    )

    try:
        mm = ModuleManager(module=module)
        results = mm.exec_module()
        module.exit_json(**results)
    except F5ModuleError as ex:
        module.fail_json(msg=str(ex))


if __name__ == '__main__':
    main()
#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright: (c) 2017, F5 Networks Inc. # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) --- module: bigip_policy_rule short_description: Manage LTM policy rules on a BIG-IP description:   - This module manages LTM policy rules on a BIG-IP. version_added: "1.0.0" options:   name:     description:       - The name of the rule.     type: str     required: True   policy:     description:       - The name of the policy you want to associate this rule with.     type: str     required: True   replace_with:     description:       - Specifies if the C(conditions)/C(actions) given by the user should overwrite what exists on the device.       - The option is useful when a subset of C(conditions)/C(actions) needs to be removed. This option is similar to the         replace-all-with flag available in TMSH commands.       - Using this option is not idempotent.     type: bool     default: no   rule_order:     description:       - Specifies a number that indicates the order of this rule relative to other rules in the policy.       - When not set, the device sets the parameter to 0.       - If there are rules with the same rule order number, the device uses rule names         to determine how the rules are ordered.       - The lower the number, the lower the rule is in the general order, with the lowest number C(0) being the         topmost one.       - Valid range of values is between C(0) and C(4294967295) inclusive.     type: int     version_added: "1.10.0"   description:     description:       - Description of the policy rule.     type: str   actions:     description:       - The actions you want the policy rule to perform.       - The available attributes vary by the action, however, each action requires         you specify a C(type).       - These conditions can be specified in any order. Despite the fact they are in a list,         the order in the list does not matter to the BIG-IP.     type: list     elements: dict     suboptions:       type:         description:           - The action type. This value controls which of the following options are required.           - When C(type) is C(forward), the system associates a given C(pool), or C(virtual),             or C(node) with this rule.           - When C(type) is C(enable), the system associates a given C(asm_policy) with             this rule.           - When C(type) is C(ignore), the system removes all existing actions from this             rule.           - When C(type) is C(redirect), the system redirects an HTTP request to a different URL.           - When C(type) is C(reset), the system resets the connection upon C(event).           - When C(type) is C(persist), the system associates C(cookie_insert) and C(cookie_expiry) with this rule.           - When C(type) is C(set_variable), the system sets a variable based on the evaluated Tcl C(expression) based on C(event).           - When C(type) is C(remove), the system removes C(http_set_cookie), C(http_referer), C(http_header) or C(http_cookie) with this rule.           - When C(type) is C(insert), the system inserts C(http_set_cookie), C(http_referer), C(http_header) or C(http_cookie) with this rule.           - When C(type) is C(replace), the system replaces C(http_connect), C(http_referer), C(http_header), C(http_uri) or C(http_host) with this rule.           - When C(type) is C(disable), the system disables C(disable_target) with this rule.         type: str         required: true         choices:           - forward           - enable           - ignore           - redirect           - reset           - persist           - set_variable           - remove           - insert           - replace           - disable       pool:         description:           - Pool to which you want to forward traffic.           - This parameter is only valid with the C(forward) type.         type: str       virtual:         description:           - Virtual server to which you want to forward traffic.           - This parameter is only valid with the C(forward) type.         type: str       node:         description:           - Node to which you want to forward traffic.           - This parameter is only valid with the C(forward) type.         type: str         version_added: "1.2.0"       disable_target:         description:           - Target you want to disable.           - This parameter is only valid with the C(disable) type.         type: str         version_added: "1.8.0"         choices:           - server_ssl           - persist       asm_policy:         description:           - ASM policy to enable.           - This parameter is only valid with the C(enable) type.         type: str       location:         description:           - The new URL for which a redirect response is sent.           - A Tcl command substitution can be used for this field.         type: str       event:         description:           - Events on which actions, such as reset and forward, can be triggered.           - With the C(set_variable) action, it is used for specifying             an action event, such as request or response.           - "Valid event choices for C(forward) action type are: client_accepted, proxy_request             request, ssl_client_hello and ssl_client_server_hello_send."           - "Valid event choices for C(reset) acton type are: client_accepted, proxy_connect             proxy_request, proxy_response, request, response, server_connected, ssl_client_hello,             ssl_client_server_hello_send, ssl_server_handshake, ssl_server_hello, websocket_request,             websocket_response."           - "Valid event choices for C(disable) acton type are: client_accepted, proxy_connect             proxy_request, proxy_response, request, server_connected."         type: str       expression:         description:           - A Tcl expression used with the C(set_variable) action.         type: str       variable_name:         description:            - Variable name used with the C(set_variable) action.         type: str       cookie_insert:         description:           - Cookie name on which you want to persist.           - This parameter is only valid with the C(persist) type.         type: str         version_added: "1.1.0"       cookie_expiry:         description:           - Optional argument, specifying the time for which the session is persisted.           - This parameter is only valid with the C(persist) type.         type: int         version_added: "1.1.0"       http_header:         description:           - HTTP Header that you want to remove or insert.           - This parameter is only valid with the C(remove), C(insert) and C(replace) type.         type: dict         suboptions:           event:             description:               - Type of event when the C(http_header) is removed, replaced, or inserted.               - The C(request) and C(response) events are only choices with C(remove) and C(insert) type.               - All of events are valid with C(replace) type action.             type: str             required: True             choices:               - request               - response               - proxy_connect               - proxy_request               - proxy_response           name:             description:               - The name of C(http_header).             type: str             required: True           value:             description:               - The value of C(http_header).               - Mandatory parameter when configured with C(insert) or C(replace) type.             type: str         version_added: "1.8.0"       http_referer:         description:           - HTTP Referer header you want to remove, replace, or insert.           - This parameter is only valid with the C(remove), C(insert) and C(replace) type.         type: dict         suboptions:           event:             description:               - Type of event when the c(http_referer) is removed, replaced, or inserted.             required: True             type: str             choices:               - request               - proxy_connect               - proxy_request           value:             description:               - The value of C(http_referer).               - This is a mandatory parameter when configured with C(insert) type action.               - This parameter is ignored for the C(remove) type.               - This parameter is optional for the C(replace) type.             type: str         version_added: "1.8.0"       http_set_cookie:         description:           - HTTP Set-Cookie header you want to remove or insert.           - This parameter is only valid with the C(remove) or c(insert) type.         type: dict         suboptions:           name:             description:               - The name of C(http_set_cookie).             type: str             required: True           value:             description:               - The value of C(http_set_cookie).               - This is a mandatory parameter when configured with C(insert) type action.             type: str         version_added: "1.8.0"       http_cookie:         description:           - HTTP Cookie header you want to remove or insert.           - This parameter is only valid with the C(remove) and C(insert) type.         type: dict         suboptions:           event:             description:               - Type of event when the C(http_cookie) is removed or inserted.             type: str             required: True             choices:               - request               - proxy_connect               - proxy_request           name:             description:               - The name of C(http_cookie).             type: str             required: True           value:             description:               - The value of C(http_cookie).               - This is a mandatory parameter when configured with C(insert) type action.             type: str         version_added: "1.8.0"       http_connect:         description:           - HTTP Connect header you want to replace.           - This parameter is only valid with the C(replace) type.         type: dict         suboptions:           event:             description:               - Type of event when the C(http_connect) header is replaced.             required: True             type: str             choices:               - client_accepted               - proxy_connect               - proxy_request               - proxy_response               - request               - server_connected               - ssl_client_hello           value:             description:               - The value of C(http_connect).             type: str             required: True           port:             description:               - The port number.               - If a port number is not provided, the value is set to 0 by default.               - Be explicit when defining rules, so the system does not override port values.             type: int         version_added: "1.8.0"       http_host:         description:           - HTTP Host header you want to replace.           - This parameter is only valid with the C(replace) type.         type: dict         suboptions:           event:             description:               - Type of event when the C(http_host) is replaced.             type: str             required: True             choices:               - request               - proxy_connect               - proxy_request           value:             description:               - The value of C(http_host).             type: str             required: True         version_added: "1.8.0"       http_uri:         description:           - Replaces HTTP URI, path, or string.           - This parameter is only valid with the C(replace) type.         type: dict         suboptions:           event:             description:               - Type of event when the C(http_uri) is replaced.             type: str             required: True             choices:               - request               - proxy_connect               - proxy_request           type:             description:               - Specifies the part of the C(http_uri) to be replaced.             type: str             required: True             choices:               - path               - query_string               - full_string           value:             description:               - The value of C(http_uri).             type: str             required: True         version_added: "1.8.0"   conditions:     description:       - A list of attributes that describe the condition.       - See suboptions for details on how to construct each list entry.       - The ordering of this list is important, the module ensures the order is         kept when modifying the task.       - The suboption options below are not required for all condition types,         read the description for more details.       - These conditions can be specified in any order. Despite the fact they are in a list,         the order in the list does not matter to the BIG-IP.     type: list     elements: dict     suboptions:       type:         description:           - The condition type. This value controls which of the following options are required.           - "When C(type) is C(http_uri), the valid choices are: C(path_begins_with_any), C(path_contains) or             C(path_is_any)."           - "When C(type) is C(http_host), the valid choices are: C(host_is_any), C(host_is_not_any),             C(host_begins_with_any) or C(host_ends_with_any)."           - "When C(type) is C(http_header), the C(header_name) parameter is mandatory and the valid choice is:             C(header_is_any)."           - "When C(type) is C(http_method), the valid choices are: C(method_matches_with_any)."           - When C(type) is C(all_traffic), the system removes all existing conditions from             this rule.         type: str         required: True         choices:           - http_uri           - all_traffic           - http_host           - http_header           - http_method           - ssl_extension           - tcp       path_begins_with_any:         description:           - A list of strings of characters the HTTP URI should start with.           - This parameter is only valid with the C(http_uri) type.         type: list         elements: str       path_contains:         description:           - A list of strings of characters the HTTP URI should contain.           - This parameter is only valid with the C(http_uri) type.         type: list         elements: str         version_added: "1.8.0"       path_is_any:         description:           - A list of strings of characters the HTTP URI should match.           - This parameter is only valid with the C(http_uri) type.         type: list         elements: str         version_added: "1.8.0"       host_is_any:         description:           - A list of strings of characters the HTTP Host should match.           - This parameter is only valid with the C(http_host) type.         type: list         elements: str       host_is_not_any:         description:           - A list of strings of characters the HTTP Host should not match.           - This parameter is only valid with the C(http_host) type.         type: list         elements: str       host_begins_with_any:         description:           - A list of strings of characters the HTTP Host should start with.           - This parameter is only valid with the C(http_host) type.         type: list         elements: str       host_ends_with_any:         description:           - A list of strings of characters the HTTP Host should end with.           - This parameter is only valid with the C(http_host) type.         type: list         elements: str         version_added: "1.8.0"       header_is_any:         description:           - A list of strings of characters the HTTP Header value should match.           - This parameter is only valid with the C(http_header) type.         type: list         elements: str         version_added: "1.8.0"       header_name:         description:           - A name of C(http_header).           - This parameter is only valid with the C(http_header) type.         type: str         version_added: "1.8.0"       method_matches_with_any:         description:           - A list of strings of characters the HTTP Method value should match.           - This parameter is only valid with the C(http_method) type.         type: list         elements: str         version_added: "1.10.0"       server_name_is_any:         description:           - A list of strings of characters the SSL Extension should match.           - This parameter is only valid with the C(ssl_extension) type.         type: list         elements: str       address_matches_with_any:         description:           - A list of IP Subnet address strings the IP address should match.           - This parameter is only valid with the C(tcp) type.         type: list         elements: str         version_added: "1.8.0"       address_matches_with_datagroup:         description:           - A list of internal datagroup strings the IP address should match.           - This parameter is only valid with the C(tcp) type.         type: list         elements: str         version_added: "1.8.0"       address_matches_with_external_datagroup:         description:           - A list of external datagroup strings the IP address should match.           - This parameter is only valid with the C(tcp) type.         type: list         elements: str         version_added: "1.10.0"       event:         description:           - Events on which conditions type match rules can be triggered.           - Supported only for C(http_header), C(http_method), C(ssl_extension) and C(tcp).           - "Valid choices for C(http_header) condition types are: C(proxy_connect),             C(proxy_request), C(proxy_response), C(request) and C(response)."           - "Valid choices for C(http_method) condition types are: C(proxy_connect),             C(proxy_request), C(proxy_response), C(request) and C(response)."           - "Valid choices for C(tcp) condition types are: C(request), C(client_accepted),             C(proxy_connect), C(proxy_request), C(proxy_response), C(ssl_client_hello), and             C(ssl_client_server_hello_send)."           - "Valid choices for C(ssl_extension) are: C(ssl_client_hello), and C(ssl_client_server_hello_send)."         type: str   state:     description:       - When C(present), ensures the key is uploaded to the device. When         C(absent), ensures the key is removed from the device. If the key         is currently in use, the module will not be able to remove the key.     type: str     choices:       - present       - absent     default: present   partition:     description:       - Device partition to manage resources on.     type: str     default: Common extends_documentation_fragment: f5networks.f5_modules.f5 requirements:   - BIG-IP >= v12.1.0 author:   - <NAME> (@caphrim007)   - <NAME> (@wojtek0806) - name: Create policies   bigip_policy:     name: Policy-Foo     state: present     provider:       server: lb.mydomain.com       user: admin       password: <PASSWORD>   delegate_to: localhost  - name: Add a rule to the new policy   bigip_policy_rule:     policy: Policy-Foo     name: rule3     conditions:       - type: http_uri         path_begins_with_any:           - /ABC     actions:       - type: forward         pool: pool-svrs     provider:       server: lb.mydomain.com       user: admin       password: <PASSWORD>   delegate_to: localhost  - name: Add multiple rules to the new policy   bigip_policy_rule:     policy: Policy-Foo     name: "{{ item.name }}"     conditions: "{{ item.conditions }}"     actions: "{{ item.actions }}"     provider:       server: lb.mydomain.com       user: admin       password: <PASSWORD>   delegate_to: localhost   loop:     - name: rule1       actions:         - type: forward           pool: pool-svrs       conditions:         - type: http_uri           path_begins_with_any:             - /euro     - name: rule2       actions:         - type: forward           pool: pool-svrs       conditions:         - type: http_uri           path_begins_with_any:             - /HomePage/     - name: rule3       actions:         - type: set_variable           variable_name: user-agent           expression: tcl:[HTTP::header User-Agent]           event: request       conditions:         - type: http_uri           path_begins_with_any:             - /HomePage/  - name: Remove all rules and conditions from the rule   bigip_policy_rule:     policy: Policy-Foo     name: rule1     conditions:       - type: all_traffic     actions:       - type: ignore     provider:       server: lb.mydomain.com       user: admin       password: <PASSWORD>   delegate_to: localhost actions:   description: The new list of actions applied to the rule.   returned: changed   type: complex   contains:     type:       description: The action type.       returned: changed       type: str       sample: forward     pool:       description: Pool for forwarding to.       returned: changed       type: str       sample: foo-pool   sample: hash/dictionary of values conditions:   description: The new list of conditions applied to the rule.   returned: changed   type: complex   contains:     type:       description: The condition type.       returned: changed       type: str       sample: http_uri     path_begins_with_any:       description: List of strings the URI begins with.       returned: changed       type: list       sample: [foo, bar]   sample: hash/dictionary of values description:   description: The new description of the rule.   returned: changed   type: str   sample: My rule rule_order:   description: Specifies a number that indicates the order of this rule relative to other rules in the policy.   returned: changed   type: int   sample: 10 # Converts to common stringiness # # The tuple set "issubset" check that happens in the Difference # engine does not recognize that a u'foo' and 'foo' are equal "enough" # to consider them a subset. Therefore, we cast everything here to # whatever the common stringiness is. # Names contains the index in which the rule is at. Handle the nuances of the forwarding type          Right now there is only a single type of forwarding that can be done. As that         functionality expands, so-to will the behavior of this, and other, methods.         Therefore, do not be surprised that the logic here is so rigid. It's deliberate.          :param action:         :param item:         :return: Handle the nuances of the set_variable type          :param action:         :param item:         :return: Handle the nuances of the enable type          :param action:         :param item:         :return: Handle the nuances of the disable type          :param action:         :param item:         :return: Handle the nuances of the redirect type          :param action:         :param item:         :return: Handle the nuances of the reset type          :param action:         :param item:         :return: Handle the nuances of the persist type          :param action:         :param item:         :return: Handle the nuances of the remove type          :param action:         :param item:         :return: Handle the nuances of the insert type          :param action:         :param item:         :return: Handle the nuances of the replace type          :param action:         :param item:         :return: # Names contains the index in which the rule is at.