Ensures that a security rule exists on the device. Also, ensure that all configurations are set appropriately.

    This method will create the rule if it does not exist. If the rule does exist, it will ensure that the
    configurations are set appropriately.

    If the rule does not exist and is created, any value that is not provided will be provided as the default.
    The action, to, from, source, destination, application, and service fields are mandatory and must be provided.

    This will enforce the exact match of the rule. For example, if the rule is currently configured with the log-end
    option, but this option is not specified in the state method, it will be removed and reset to the system default.

    It is strongly recommended to specify all options to ensure proper operation.

    When defining the profile group settings, the device can only support either a profile group or individual settings.
    If both are specified, the profile group will be preferred and the individual settings are ignored. If neither are
    specified, the value will be set to system default of none.

    name: The name of the module function to execute.

    rulename(str): The name of the security rule.  The name is case-sensitive and can have up to 31 characters, which
    can be letters, numbers, spaces, hyphens, and underscores. The name must be unique on a firewall and, on Panorama,
    unique within its device group and any ancestor or descendant device groups.

    vsys(str): The string representation of the VSYS ID. Defaults to VSYS 1.

    action(str): The action that the security rule will enforce. Valid options are: allow, deny, drop, reset-client,
    reset-server, reset-both.

    disabled(bool): Controls if the rule is disabled. Set 'True' to disable and 'False' to enable.

    sourcezone(str, list): The source zone(s). The value 'any' will match all zones.

    destinationzone(str, list): The destination zone(s). The value 'any' will match all zones.

    source(str, list): The source address(es). The value 'any' will match all addresses.

    destination(str, list): The destination address(es). The value 'any' will match all addresses.

    application(str, list): The application(s) matched. The value 'any' will match all applications.

    service(str, list): The service(s) matched. The value 'any' will match all services. The value
    'application-default' will match based upon the application defined ports.

    description(str): A description for the policy (up to 255 characters).

    logsetting(str): The name of a valid log forwarding profile.

    logstart(bool): Generates a traffic log entry for the start of a session (disabled by default).

    logend(bool): Generates a traffic log entry for the end of a session (enabled by default).

    negatesource(bool): Match all but the specified source addresses.

    negatedestination(bool): Match all but the specified destination addresses.

    profilegroup(str): A valid profile group name.

    datafilter(str): A valid data filter profile name. Ignored with the profilegroup option set.

    fileblock(str): A valid file blocking profile name. Ignored with the profilegroup option set.

    spyware(str): A valid spyware profile name. Ignored with the profilegroup option set.

    urlfilter(str): A valid URL filtering profile name. Ignored with the profilegroup option set.

    virus(str): A valid virus profile name. Ignored with the profilegroup option set.

    vulnerability(str): A valid vulnerability profile name. Ignored with the profilegroup option set.

    wildfire(str): A valid vulnerability profile name. Ignored with the profilegroup option set.

    move(str): An optional argument that ensure the rule is moved to a specific location. Valid options are 'top',
    'bottom', 'before', or 'after'. The 'before' and 'after' options require the use of the 'movetarget' argument
    to define the location of the move request.

    movetarget(str): An optional argument that defines the target of the move operation if the move argument is
    set to 'before' or 'after'.

    commit(bool): If true the firewall will commit the changes, if false do not commit changes.

    SLS Example:

    .. code-block:: yaml

        panos/rulebase/security/rule01:
            panos.security_rule_exists:
              - rulename: rule01
              - vsys: 1
              - action: allow
              - disabled: False
              - sourcezone: untrust
              - destinationzone: trust
              - source:
                - 10.10.10.0/24
                - 1.1.1.1
              - destination:
                - 2.2.2.2-2.2.2.4
              - application:
                - any
              - service:
                - tcp-25
              - description: My test security rule
              - logsetting: logprofile
              - logstart: False
              - logend: True
              - negatesource: False
              - negatedestination: False
              - profilegroup: myprofilegroup
              - move: top
              - commit: False

        panos/rulebase/security/rule01:
            panos.security_rule_exists:
              - rulename: rule01
              - vsys: 1
              - action: allow
              - disabled: False
              - sourcezone: untrust
              - destinationzone: trust
              - source:
                - 10.10.10.0/24
                - 1.1.1.1
              - destination:
                - 2.2.2.2-2.2.2.4
              - application:
                - any
              - service:
                - tcp-25
              - description: My test security rule
              - logsetting: logprofile
              - logstart: False
              - logend: False
              - datafilter: foobar
              - fileblock: foobar
              - spyware: foobar
              - urlfilter: foobar
              - virus: foobar
              - vulnerability: foobar
              - wildfire: foobar
              - move: after
              - movetarget: rule02
              - commit: False

def security_rule_exists(name,
                         rulename=None,
                         vsys='1',
                         action=None,
                         disabled=None,
                         sourcezone=None,
                         destinationzone=None,
                         source=None,
                         destination=None,
                         application=None,
                         service=None,
                         description=None,
                         logsetting=None,
                         logstart=None,
                         logend=None,
                         negatesource=None,
                         negatedestination=None,
                         profilegroup=None,
                         datafilter=None,
                         fileblock=None,
                         spyware=None,
                         urlfilter=None,
                         virus=None,
                         vulnerability=None,
                         wildfire=None,
                         move=None,
                         movetarget=None,
                         commit=False):
    '''
    Ensures that a security rule exists on the device. Also, ensure that all configurations are set appropriately.

    This method will create the rule if it does not exist. If the rule does exist, it will ensure that the
    configurations are set appropriately.

    If the rule does not exist and is created, any value that is not provided will be provided as the default.
    The action, to, from, source, destination, application, and service fields are mandatory and must be provided.

    This will enforce the exact match of the rule. For example, if the rule is currently configured with the log-end
    option, but this option is not specified in the state method, it will be removed and reset to the system default.

    It is strongly recommended to specify all options to ensure proper operation.

    When defining the profile group settings, the device can only support either a profile group or individual settings.
    If both are specified, the profile group will be preferred and the individual settings are ignored. If neither are
    specified, the value will be set to system default of none.

    name: The name of the module function to execute.

    rulename(str): The name of the security rule.  The name is case-sensitive and can have up to 31 characters, which
    can be letters, numbers, spaces, hyphens, and underscores. The name must be unique on a firewall and, on Panorama,
    unique within its device group and any ancestor or descendant device groups.

    vsys(str): The string representation of the VSYS ID. Defaults to VSYS 1.

    action(str): The action that the security rule will enforce. Valid options are: allow, deny, drop, reset-client,
    reset-server, reset-both.

    disabled(bool): Controls if the rule is disabled. Set 'True' to disable and 'False' to enable.

    sourcezone(str, list): The source zone(s). The value 'any' will match all zones.

    destinationzone(str, list): The destination zone(s). The value 'any' will match all zones.

    source(str, list): The source address(es). The value 'any' will match all addresses.

    destination(str, list): The destination address(es). The value 'any' will match all addresses.

    application(str, list): The application(s) matched. The value 'any' will match all applications.

    service(str, list): The service(s) matched. The value 'any' will match all services. The value
    'application-default' will match based upon the application defined ports.

    description(str): A description for the policy (up to 255 characters).

    logsetting(str): The name of a valid log forwarding profile.

    logstart(bool): Generates a traffic log entry for the start of a session (disabled by default).

    logend(bool): Generates a traffic log entry for the end of a session (enabled by default).

    negatesource(bool): Match all but the specified source addresses.

    negatedestination(bool): Match all but the specified destination addresses.

    profilegroup(str): A valid profile group name.

    datafilter(str): A valid data filter profile name. Ignored with the profilegroup option set.

    fileblock(str): A valid file blocking profile name. Ignored with the profilegroup option set.

    spyware(str): A valid spyware profile name. Ignored with the profilegroup option set.

    urlfilter(str): A valid URL filtering profile name. Ignored with the profilegroup option set.

    virus(str): A valid virus profile name. Ignored with the profilegroup option set.

    vulnerability(str): A valid vulnerability profile name. Ignored with the profilegroup option set.

    wildfire(str): A valid vulnerability profile name. Ignored with the profilegroup option set.

    move(str): An optional argument that ensure the rule is moved to a specific location. Valid options are 'top',
    'bottom', 'before', or 'after'. The 'before' and 'after' options require the use of the 'movetarget' argument
    to define the location of the move request.

    movetarget(str): An optional argument that defines the target of the move operation if the move argument is
    set to 'before' or 'after'.

    commit(bool): If true the firewall will commit the changes, if false do not commit changes.

    SLS Example:

    .. code-block:: yaml

        panos/rulebase/security/rule01:
            panos.security_rule_exists:
              - rulename: rule01
              - vsys: 1
              - action: allow
              - disabled: False
              - sourcezone: untrust
              - destinationzone: trust
              - source:
                - 10.10.10.0/24
                - 1.1.1.1
              - destination:
                - 2.2.2.2-2.2.2.4
              - application:
                - any
              - service:
                - tcp-25
              - description: My test security rule
              - logsetting: logprofile
              - logstart: False
              - logend: True
              - negatesource: False
              - negatedestination: False
              - profilegroup: myprofilegroup
              - move: top
              - commit: False

        panos/rulebase/security/rule01:
            panos.security_rule_exists:
              - rulename: rule01
              - vsys: 1
              - action: allow
              - disabled: False
              - sourcezone: untrust
              - destinationzone: trust
              - source:
                - 10.10.10.0/24
                - 1.1.1.1
              - destination:
                - 2.2.2.2-2.2.2.4
              - application:
                - any
              - service:
                - tcp-25
              - description: My test security rule
              - logsetting: logprofile
              - logstart: False
              - logend: False
              - datafilter: foobar
              - fileblock: foobar
              - spyware: foobar
              - urlfilter: foobar
              - virus: foobar
              - vulnerability: foobar
              - wildfire: foobar
              - move: after
              - movetarget: rule02
              - commit: False
    '''
    ret = _default_ret(name)

    if not rulename:
        return ret

    # Check if rule currently exists
    rule = __salt__['panos.get_security_rule'](rulename, vsys)['result']

    if rule and 'entry' in rule:
        rule = rule['entry']
    else:
        rule = {}

    # Build the rule element
    element = ""
    if sourcezone:
        element += "<from>{0}</from>".format(_build_members(sourcezone, True))
    else:
        ret.update({'comment': "The sourcezone field must be provided."})
        return ret

    if destinationzone:
        element += "<to>{0}</to>".format(_build_members(destinationzone, True))
    else:
        ret.update({'comment': "The destinationzone field must be provided."})
        return ret

    if source:
        element += "<source>{0}</source>".format(_build_members(source, True))
    else:
        ret.update({'comment': "The source field must be provided."})
        return

    if destination:
        element += "<destination>{0}</destination>".format(_build_members(destination, True))
    else:
        ret.update({'comment': "The destination field must be provided."})
        return ret

    if application:
        element += "<application>{0}</application>".format(_build_members(application, True))
    else:
        ret.update({'comment': "The application field must be provided."})
        return ret

    if service:
        element += "<service>{0}</service>".format(_build_members(service, True))
    else:
        ret.update({'comment': "The service field must be provided."})
        return ret

    if action:
        element += "<action>{0}</action>".format(action)
    else:
        ret.update({'comment': "The action field must be provided."})
        return ret

    if disabled is not None:
        if disabled:
            element += "<disabled>yes</disabled>"
        else:
            element += "<disabled>no</disabled>"

    if description:
        element += "<description>{0}</description>".format(description)

    if logsetting:
        element += "<log-setting>{0}</log-setting>".format(logsetting)

    if logstart is not None:
        if logstart:
            element += "<log-start>yes</log-start>"
        else:
            element += "<log-start>no</log-start>"

    if logend is not None:
        if logend:
            element += "<log-end>yes</log-end>"
        else:
            element += "<log-end>no</log-end>"

    if negatesource is not None:
        if negatesource:
            element += "<negate-source>yes</negate-source>"
        else:
            element += "<negate-source>no</negate-source>"

    if negatedestination is not None:
        if negatedestination:
            element += "<negate-destination>yes</negate-destination>"
        else:
            element += "<negate-destination>no</negate-destination>"

    # Build the profile settings
    profile_string = None
    if profilegroup:
        profile_string = "<group><member>{0}</member></group>".format(profilegroup)
    else:
        member_string = ""
        if datafilter:
            member_string += "<data-filtering><member>{0}</member></data-filtering>".format(datafilter)
        if fileblock:
            member_string += "<file-blocking><member>{0}</member></file-blocking>".format(fileblock)
        if spyware:
            member_string += "<spyware><member>{0}</member></spyware>".format(spyware)
        if urlfilter:
            member_string += "<url-filtering><member>{0}</member></url-filtering>".format(urlfilter)
        if virus:
            member_string += "<virus><member>{0}</member></virus>".format(virus)
        if vulnerability:
            member_string += "<vulnerability><member>{0}</member></vulnerability>".format(vulnerability)
        if wildfire:
            member_string += "<wildfire-analysis><member>{0}</member></wildfire-analysis>".format(wildfire)
        if member_string != "":
            profile_string = "<profiles>{0}</profiles>".format(member_string)

    if profile_string:
        element += "<profile-setting>{0}</profile-setting>".format(profile_string)

    full_element = "<entry name='{0}'>{1}</entry>".format(rulename, element)

    new_rule = xml.to_dict(ET.fromstring(full_element), True)

    config_change = False

    if rule == new_rule:
        ret.update({
            'comment': 'Security rule already exists. No changes required.'
        })
    else:
        config_change = True
        xpath = "/config/devices/entry[@name=\'localhost.localdomain\']/vsys/entry[@name=\'vsys{0}\']/rulebase/" \
                "security/rules/entry[@name=\'{1}\']".format(vsys, rulename)

        result, msg = _edit_config(xpath, full_element)

        if not result:
            ret.update({
                'comment': msg
            })
            return ret

        ret.update({
            'changes': {'before': rule, 'after': new_rule},
            'comment': 'Security rule verified successfully.'
        })

    if move:
        movepath = "/config/devices/entry[@name=\'localhost.localdomain\']/vsys/entry[@name=\'vsys{0}\']/rulebase/" \
                   "security/rules/entry[@name=\'{1}\']".format(vsys, rulename)
        move_result = False
        move_msg = ''
        if move == "before" and movetarget:
            move_result, move_msg = _move_before(movepath, movetarget)
        elif move == "after":
            move_result, move_msg = _move_after(movepath, movetarget)
        elif move == "top":
            move_result, move_msg = _move_top(movepath)
        elif move == "bottom":
            move_result, move_msg = _move_bottom(movepath)

        if config_change:
            ret.update({
                'changes': {'before': rule, 'after': new_rule, 'move': move_msg}
            })
        else:
            ret.update({
                'changes': {'move': move_msg}
            })

        if not move_result:
            ret.update({
                'comment': move_msg
            })
            return ret

    if commit is True:
        ret.update({
            'commit': __salt__['panos.commit'](),
            'result': True
        })
    else:
        ret.update({
            'result': True
        })

    return ret