#!/usr/bin/python
#
# Copyright (c) 2014, Arista Networks, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#   Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
#   Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
#   Neither the name of Arista Networks nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
DOCUMENTATION = """
module: eos_switchport
short_description: Manage switchport (layer 2) interface resources in EOS
description:
  - Provides active state management of switchport (layer 2) interface
    configuration in Arista EOS.  All configuration of resources is
    idempotent unless otherwise specified.
author: Arista EOS+
notes:
requirements:
  - Arista EOS 4.12.0 or later
  - EOS command API enabled
  - pyeapi 0.1.1 or later
options:
  name:
    description:
      - The unique interface identifier name.  The interface name must use
        the full interface name (no abbreviated names).  For example,
        interfaces should be specified as Ethernet1 not Et1
    required: true
   aliases: ['intfid']
  mode:
    description:
      - Identifies the mode of operation for the interface.  Switchport
        interfaces can act as trunk interfaces (carrying multiple VLANs)
        or as access interfaces (attached to a single VLAN)
    required: false
    choices: ['trunk', 'access']
  access_vlan:
    description:
      - Configures the VLAN associated with a switchport that is
        configured to use 'access' mode.  This parameter only takes
        effect if mode is equal to 'access'
      - The access vlan must be in the range of 1 to 4094
    required: false
    default: null
    alias: []
  trunk_native_vlan:
    description:
      - Configures the native VLAN on a trunk interface for untagged
        packets entering the switchport.  This parameter only takes
        effect if mode is equal to 'trunk'
      - The trunk native vlan must be in the range of 1 to 4094
    required: false
    default: null
    alias: []
  trunk_allowed_vlans:
    description:
      - Configures the set of VLANs that are allowed to traverse this
        switchport interface.  This parameter only takes effect if
        the mode is configured to 'trunk'
      - The set of VLAN identifiers all be within the range of 1 to 4094
    required: false
    default: null
    alias: []
  state:
    description:
      - The state of the switchport related to the current running
        configuration.
    required: false
    default: 'present'
    choices: ['present', 'absent']
  config:
    description:
      - Configures the path to the pyeapi config file
  connection:
    description:
      - Configures the name of the connection to use from the eapi.conf file
    default: 'localhost'
  null_as_default:
    description:
      - Instructs the module how to handle null values.  If this flag is set
        then null values trigger default configuration otherwise do nothing
    default: false
    choices: BOOLEANS
"""
import syslog

from ansible.module_utils.basic import *

import pyeapi


def log(entry):
    """ Writes an entry to the local syslog

    Args:
        entry (str): The text string to write to syslog

    """
    syslog.openlog('ansible-eos')
    syslog.syslog(syslog.LOG_NOTICE, str(entry))

def get_instance(node, name):
    """ Returns switchport instance object properties

    Response

    .. code-block:: json

        {
            "name": <string>,
            "state": [absent, present],
            "mode": [trunk, access],
            "access_vlan": <string>,
            "trunk_native_vlan": <string>,
            "trunk_allowed_vlans": <string>
        }

    Args:
        node (Node): An instance of pyeapi.client.Node
        name (str): The name of the interface to return the instance of
            switchport for from the running-config

    Returns:
        A Python dictionary object that represents the interface resource

    """
    result = node.api('switchports').get(name)
    instance = dict(name=name, state='absent')
    if result:
        instance['state'] = 'present'
        instance['mode'] = result['mode']
        instance['access_vlan'] = result['access_vlan']
        instance['trunk_native_vlan'] = result['trunk_native_vlan']
        instance['trunk_allowed_vlans'] = result['trunk_allowed_vlans']
    return instance

def create_instance(node, name):
    """Creates a new instance of switchport on the node

    Args:
        node (Node): An instance of pyepai.client.Node
        name (str): The name of the interface create the switchport for

    Returns:
        A Python dictionary object that represents the interface resource from
            the node's running configuration

    """
    node.api('switchports').create(name)
    return get_instance(node, name)

def set_attribute(node, method_name, name, value, default):
    log('Invoked %s for switchport %s with value %s and default %s'\
        % (method_name, name, value, default))
    method = getattr(node.api('switchports'), method_name)
    return method(name, value, default)

def set_mode(node, name, **kwargs):
    """Configures the mode attribute for the switchport

    Args:
        node (Node): An instance of pyeapi.client.Node used for sending and
            receiveing eAPI messages
        name (str): The interface to configure the value for
        kwargs: Optional keyword arguments

    Returns:
        True if the command was completed successfully otherwise False is
            returned

    """
    default = kwargs.get('default', False)
    value = kwargs['mode']
    return set_attribute(node, 'set_mode', name, value, default)

def set_access_vlan(node, name, **kwargs):
    """Configures the access vlan attribute for the switchport

    Args:
        node (Node): An instance of pyeapi.client.Node used for sending and
            receiveing eAPI messages
        name (str): The interface to configure the value for
        kwargs: Optional keyword arguments

    Returns:
        True if the command was completed successfully otherwise False is
            returned

    """
    default = kwargs.get('default', False)
    value = kwargs['access_vlan']
    return set_attribute(node, 'set_access_vlan', name, value, default)

def set_trunk_native_vlan(node, name, **kwargs):
    """Configures the trunk native vlan attribute for the switchport

    Args:
        node (Node): An instance of pyeapi.client.Node used for sending and
            receiveing eAPI messages
        name (str): The interface to configure the value for
        kwargs: Optional keyword arguments

    Returns:
        True if the command was completed successfully otherwise False is
            returned

    """
    default = kwargs.get('default', False)
    value = kwargs['trunk_native_vlan']
    return set_attribute(node, 'set_trunk_native_vlan', name, value, default)

def set_trunk_allowed_vlans(node, name, **kwargs):
    """Configures the trunk allowed vlans attribute for the switchport

    Args:
        node (Node): An instance of pyeapi.client.Node used for sending and
            receiveing eAPI messages
        name (str): The interface to configure the value for
        kwargs: Optional keyword arguments

    Returns:
        True if the command was completed successfully otherwise False is
            returned

    """
    default = kwargs.get('default', False)
    value = kwargs['trunk_allowed_vlans']
    return set_attribute(node, 'set_trunk_allowed_vlans', name, value, default)

def main():
    """ The main module routine called when the module is run by Ansible
    """

    argument_spec = dict(
        name=dict(required=True),
        mode=dict(choices=['access', 'trunk']),
        access_vlan=dict(),
        trunk_native_vlan=dict(),
        trunk_allowed_vlans=dict(),
        config=dict(),
        connection=dict(default='localhost'),
        null_as_default=dict(type='bool', default=False),
        state=dict(default='present', choices=['present', 'absent'])
    )

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


    if module.params['config']:
        pyeapi.load_config(module.params['config'])
    node = pyeapi.connect_to(module.params['connection'])

    result = dict(changed=False, created=False, removed=False, changes=dict())
    state = module.params['state']
    null_as_default = module.params['null_as_default']

    name = module.params['name']

    attributes = {
        'name': name,
        'mode': module.params['mode'],
        'access_vlan': module.params['access_vlan'],
        'trunk_native_vlan': module.params['trunk_native_vlan'],
        'trunk_allowed_vlans': module.params['trunk_allowed_vlans'],
    }

    instance = get_instance(node, name)

    if state == 'present':
        if instance['state'] == 'absent':
            if not module.check_mode:
                instance = create_instance(node, name)
            result['created'] = True

        changeset = attributes.viewitems() - instance.viewitems()
        for key, value in changeset:
            func = 'set_%s' % key
            if func in globals():
                command = globals().get(func)
                default = value is None and null_as_default
                if value is not None or default:
                    if not result['created']:
                        result['changes'][key] = value
                        result['changed'] = True
                    if not module.check_mode:
                        command(node, **attributes)

    elif instance['state'] == 'present' and state == 'absent':
        result['removed'] = True
        if not module.check_mode:
            node.api('switchports').delete(name)

    result['instance'] = get_instance(node, name)
    module.exit_json(**result)

main()
