Skip to content

Commit

Permalink
Merge pull request #222 from titom73/cvprac-integration
Browse files Browse the repository at this point in the history
  • Loading branch information
titom73 authored Sep 4, 2020
2 parents 93745fc + 0ab5a1d commit 82e6cfb
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 34 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/ansible_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ on:
branches:
- 'devel'
- 'releases/**'
paths:
- 'ansible_collections/arista/cvp/**'
jobs:
'ansible-test':
name: Run ansible-test validation
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

# Ansible Modules for Arista CloudVision Platform

> All the CV communication are now managed by [__cvprac library__](https://github.com/aristanetworks/cvprac). So a new [requirements](#dependencies) __MUST__ be installed first before any code execution.
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->

<!-- code_chunk_output -->
Expand Down Expand Up @@ -136,8 +138,9 @@ ansible_httpapi_port=443

This collection requires the following to be installed on the Ansible control machine:

- python `3.7`
- python `3.6` and higher
- ansible >= `2.9.0`
- [cvprac](https://github.com/aristanetworks/cvprac) version `1.0.4`
- requests >= `2.22.0`
- treelib version `1.5.5`

Expand Down Expand Up @@ -213,4 +216,4 @@ Support for this `arista.cvp` collection is provided by the community directly i

## License

Project is published under [Apache 2.0 License](LICENSE)
Project is published under [Apache 2.0 License](LICENSE)
6 changes: 5 additions & 1 deletion ansible_collections/arista/cvp/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Ansible Modules for Arista CloudVision Platform

> All the CV communication are now managed by [__cvprac library__](https://github.com/aristanetworks/cvprac). So a new [requirements](#dependencies) __MUST__ be installed first before any code execution.
## About

[Arista Networks](https://www.arista.com/) supports Ansible for managing devices running the EOS operating system through [CloudVision platform (CVP)](https://www.arista.com/en/products/eos/eos-cloudvision). This roles includes a set of ansible modules that perform specific configuration tasks on CVP server. These tasks include: collecting facts, managing configlets, containers, build provisionning topology and running tasks.
Expand All @@ -18,6 +20,7 @@ __Python:__

__Additional Python Libraries required:__

- [cvprac](https://github.com/aristanetworks/cvprac) version `1.0.4`
- requests >= `2.22.0`
- treelib version `1.5.5` or later

Expand All @@ -30,6 +33,7 @@ ansible 2.9 or later
```shell
pip install requests>=2.22.0
pip install treelib>=1.5.5
pip install cvprac==1.0.4
```

Ansible galaxy hosts all stable version of this collection. Installation from ansible-galaxy is the most convenient approach for consuming `arista.cvp` content
Expand All @@ -38,7 +42,7 @@ Ansible galaxy hosts all stable version of this collection. Installation from an
$ ansible-galaxy collection install arista.cvp
Process install dependency map
Starting collection install process
Installing 'arista.cvp:1.0.3' to '~/.ansible/collections/ansible_collections/arista/cvp'
Installing 'arista.cvp:1.1.0' to '~/.ansible/collections/ansible_collections/arista/cvp'
```

## Modules overview
Expand Down
41 changes: 41 additions & 0 deletions ansible_collections/arista/cvp/plugins/module_utils/cv_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,44 @@ def match_filter(input, filter, default_always='all'):
return True
LOGGER.debug(" * is_in_filter - NOT matched")
return False

def cv_update_configlets_on_device(module, device_facts, add_configlets, del_configlets):
response = dict()
device_deletion = None
device_addition = None
# Initial Logging
LOGGER.debug(' * cv_update_configlets_on_device - add_configlets: %s', str(add_configlets))
LOGGER.debug(' * cv_update_configlets_on_device - del_configlets: %s', str(del_configlets))
# Work on delete configlet scenario
LOGGER.info(" * cv_update_configlets_on_device - start device deletion process")
if len(del_configlets) > 0:
try:
device_deletion = module.client.api.remove_configlets_from_device(
app_name="Ansible",
dev=device_facts,
del_configlets=del_configlets,
create_task=True
)
response = device_deletion
except Exception as error:
errorMessage = str(error)
LOGGER.error('OK, something wrong happens, raise an exception: %s', str(errorMessage))
LOGGER.info(" * cv_update_configlets_on_device - device_deletion result: %s", str(device_deletion))
# Work on Add configlet scenario
LOGGER.debug(" * cv_update_configlets_on_device - start device addition process")
if len(add_configlets) > 0:
LOGGER.debug(' * cv_update_configlets_on_device - ADD configlets: %s', str(add_configlets))
try:
device_addition = module.client.api.apply_configlets_to_device(
app_name="Ansible",
dev=device_facts,
new_configlets=add_configlets,
create_task=True
)
response.update(device_addition)
except Exception as error:
errorMessage = str(error)
LOGGER.error('OK, something wrong happens, raise an exception: %s', str(errorMessage))
LOGGER.info(" * cv_update_configlets_on_device - device_addition result: %s", str(device_addition))
LOGGER.info(" * cv_update_configlets_on_device - final result: %s", str(response))
return response
15 changes: 13 additions & 2 deletions ansible_collections/arista/cvp/plugins/modules/cv_configlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,16 @@
import traceback
import logging
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.arista.cvp.plugins.module_utils.cv_client import CvpClient
from ansible_collections.arista.cvp.plugins.module_utils.cv_client_errors import CvpLoginError
# from ansible_collections.arista.cvp.plugins.module_utils.cv_client import CvpClient
# from ansible_collections.arista.cvp.plugins.module_utils.cv_client_errors import CvpLoginError
try:
from cvprac.cvp_client import CvpClient
from cvprac.cvp_client_errors import CvpLoginError
HAS_CVPRAC = True
except ImportError:
HAS_CVPRAC = False
CVPRAC_IMP_ERR = traceback.format_exc()

from ansible.module_utils.connection import Connection
import ansible_collections.arista.cvp.plugins.module_utils.logger # noqa # pylint: disable=unused-import
DIFFLIB_IMP_ERR = None
Expand Down Expand Up @@ -697,6 +705,9 @@ def main():
if not HAS_DIFFLIB:
module.fail_json(msg='difflib required for this module')

if not HAS_CVPRAC:
module.fail_json(msg='cvprac required for this module')

result = dict(changed=False, data={})
# messages = dict(issues=False)
# Connect to CVP instance
Expand Down
27 changes: 22 additions & 5 deletions ansible_collections/arista/cvp/plugins/modules/cv_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,16 @@
import ansible_collections.arista.cvp.plugins.module_utils.logger # noqa # pylint: disable=unused-import
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import Connection
from ansible_collections.arista.cvp.plugins.module_utils.cv_client import CvpClient
from ansible_collections.arista.cvp.plugins.module_utils.cv_client_errors import CvpLoginError, CvpApiError
# from ansible_collections.arista.cvp.plugins.module_utils.cv_client import CvpClient
# from ansible_collections.arista.cvp.plugins.module_utils.cv_client_errors import CvpLoginError
try:
from cvprac.cvp_client import CvpClient
from cvprac.cvp_client_errors import CvpLoginError, CvpApiError
HAS_CVPRAC = True
except ImportError:
HAS_CVPRAC = False
CVPRAC_IMP_ERR = traceback.format_exc()

from ansible.module_utils.six import string_types
TREELIB_IMP_ERR = None
try:
Expand Down Expand Up @@ -860,6 +868,8 @@ def configure_configlet_to_container(module, intended, facts):
# Define wether we want to save topology or not
# Force to True as per issue115
save_topology = True
# Structure to save CVP result for configlet changes
configlet_action = dict()
# Configlet filter
configlet_filter = module.params['configlet_filter']
# Read complete intended topology to locate devices
Expand Down Expand Up @@ -896,6 +906,7 @@ def configure_configlet_to_container(module, intended, facts):
new_configlets=configlet_list_attach,
container=container_info_cvp,
create_task=save_topology)
MODULE_LOGGER.debug('Get following response from cvprac for addition: %s', str(configlet_action))
# Release list of configlet to configure (#165)
configlet_list_attach = list()
if 'data' in configlet_action and configlet_action['data']['status'] == 'success':
Expand All @@ -914,15 +925,15 @@ def configure_configlet_to_container(module, intended, facts):
container_info_cvp), str(container_name))
if container_info_cvp is not None and 'configlets' in container_info_cvp:
for configlet in container_info_cvp['configlets']:
# If configlet matchs filter, we just remove attachement.
# If configlet matchs filter, we just remove attachment.
match_filter = cv_tools.match_filter(
input=configlet, filter=configlet_filter, default_always='none')
MODULE_LOGGER.info('Filter test has returned: %s - Filter is %s - input is %s', str(match_filter), str(configlet_filter), str(configlet))
# If configlet is not in intended and does not match filter, ignore it
# If filter is set to ['none'], we consider to NOT touch attachment in any situation.
if (match_filter is False
and container_factinfo(container_name=container, facts=facts) is not None
and configlet not in container_factinfo(container_name=container, facts=facts)['configlets']):
and container_factinfo(container_name=container, facts=facts) is not None
and configlet not in container_factinfo(container_name=container, facts=facts)['configlets']):
MODULE_LOGGER.warning('configlet does not match filter (%s) and is not in intended topology (%s), skipped', str(
configlet_filter), str(container_info_cvp['configlets']))
continue
Expand Down Expand Up @@ -955,6 +966,7 @@ def configure_configlet_to_container(module, intended, facts):
del_configlets=configlet_list_detach,
container=container_info_cvp,
create_task=save_topology)
MODULE_LOGGER.debug('Get following response from cvprac for deletion: %s', str(configlet_action))
# Release list of configlet to configure (#165)
configlet_list_detach = list()
if 'data' in configlet_action and configlet_action['data']['status'] == 'success':
Expand All @@ -971,6 +983,7 @@ def configure_configlet_to_container(module, intended, facts):
result['changed'] = True
result['attached_configlet'] = attached
result['detached_configlet'] = detached
MODULE_LOGGER.debug('configure_configlet_to_container returns %s', str(result))
return result


Expand Down Expand Up @@ -1116,6 +1129,10 @@ def main():

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

if not HAS_CVPRAC:
module.fail_json(msg='cvprac required for this module')

result = dict(changed=False, data={})
result['data']['taskIds'] = list()
result['data']['tasks'] = list()
Expand Down
58 changes: 42 additions & 16 deletions ansible_collections/arista/cvp/plugins/modules/cv_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,20 @@
}

import logging
import traceback
import ansible_collections.arista.cvp.plugins.module_utils.logger # noqa # pylint: disable=unused-import
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.arista.cvp.plugins.module_utils.cv_client import CvpClient
from ansible_collections.arista.cvp.plugins.module_utils.cv_client_errors import (
CvpLoginError
)
from ansible_collections.arista.cvp.plugins.module_utils.cv_tools import cv_update_configlets_on_device
# from ansible_collections.arista.cvp.plugins.module_utils.cv_client import CvpClient
# from ansible_collections.arista.cvp.plugins.module_utils.cv_client_errors import CvpLoginError
try:
from cvprac.cvp_client import CvpClient
from cvprac.cvp_client_errors import CvpLoginError
HAS_CVPRAC = True
except ImportError:
HAS_CVPRAC = False
CVPRAC_IMP_ERR = traceback.format_exc()

from ansible.module_utils.connection import Connection

DOCUMENTATION = r"""
Expand Down Expand Up @@ -557,7 +565,7 @@ def build_new_devices_list(module):
module=module, device_name=ansible_device_hostname
)
if cvp_device is None:
module.fail_json(msg="Device not available on Cloudvision ("+ansible_device_hostname+")")
module.fail_json(msg="Device not available on Cloudvision (" + ansible_device_hostname + ")")
if len(cvp_device) >= 0:
if is_in_container(device=cvp_device, container="undefined_container"):
device_info = {
Expand Down Expand Up @@ -703,12 +711,13 @@ def devices_new(module):

# Execute configlet update on device
try:
device_action = module.client.api.provision_device(
MODULE_LOGGER.info('provision device using cvprac.api.deploy_device')
device_action = module.client.api.deploy_device(
app_name="Ansible",
device=device_facts,
container=container_facts,
container=container_facts['name'],
configlets=configlets_add,
imageBundle=imageBundle_attached,
# imageBundle=imageBundle_attached,
create_task=action_save_topology,
)
except Exception as error:
Expand All @@ -717,6 +726,7 @@ def devices_new(module):
device_update["name"],
errorMessage,
)
MODULE_LOGGER.debug('OK, something wrong happens, raise an exception: %s', str(message))
result_update.append({device_update["name"]: message})
else:
# Capture and report error message sent by CV during update
Expand Down Expand Up @@ -902,10 +912,9 @@ def devices_update(module, mode="override"):
MODULE_LOGGER.debug(" * device_update - device facts: %s", str(device_facts))

MODULE_LOGGER.debug(" * device_update - var status for %s: add: %s / del: %s",
str(device_update["name"]),
str(configlets_add),
str(configlets_delete)
)
str(device_update["name"]),
str(configlets_add),
str(configlets_delete))
# # Structure to list configlets to delete
configlets_delete = list()
# # Structure to list configlets to configure on device.
Expand Down Expand Up @@ -962,14 +971,26 @@ def devices_update(module, mode="override"):
module.fail_json("Error - device does not exists on CV side.")

# Execute configlet update on device
MODULE_LOGGER.debug(' * device_update - device_update configlets: %s', str(device_update["configlets"]))
MODULE_LOGGER.debug(' * device_update - cv_configlets configlets: %s', str(device_update["cv_configlets"]))
if is_list_diff(device_update["configlets"], device_update["cv_configlets"]):
MODULE_LOGGER.debug(' * device_update - call cv_update_configlets_on_device')
try:
device_action = module.client.api.update_configlets_on_device(
app_name="Ansible",
device=device_facts,
MODULE_LOGGER.debug(' * device_update - cv_configlets configlets: %s')
# device_action = module.client.api.update_configlets_on_device(
# app_name="Ansible",
# device=device_facts,
# add_configlets=configlets_add,
# del_configlets=configlets_delete,
# )
MODULE_LOGGER.debug("", str(configlets_add))
device_action = cv_update_configlets_on_device(
module=module,
device_facts=device_facts,
add_configlets=configlets_add,
del_configlets=configlets_delete,
del_configlets=configlets_delete
)
MODULE_LOGGER.debug(' * device_update - get response from cv_update_configlets_on_device: %s', str(device_action))
except Exception as error:
errorMessage = str(error)
message = "Device %s Configlets cannot be updated - %s" % (
Expand All @@ -987,6 +1008,7 @@ def devices_update(module, mode="override"):
result_update.append({device_update["name"]: message})
else:
changed = True # noqa # pylint: disable=unused-variable
MODULE_LOGGER.debug(' * device_update - looking for taskIds in %s', str(device_action))
if "taskIds" in str(device_action):
devices_updated += 1
for taskId in device_action["data"]["taskIds"]:
Expand Down Expand Up @@ -1209,6 +1231,10 @@ def main():
choices=['merge', 'override', 'delete']))

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

if not HAS_CVPRAC:
module.fail_json(msg='cvprac required for this module')

# Connect to CVP instance
module.client = connect(module)

Expand Down
Loading

0 comments on commit 82e6cfb

Please sign in to comment.