diff --git a/MANIFEST.in b/MANIFEST.in
index 374457c..f9f7ef8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -6,6 +6,13 @@ include LICENSE
include VERSION
include .pylintrc
recursive-include docs *.rst
+recursive-include docs *.cfg
+recursive-include docs *.csv
+recursive-include docs *.md
+recursive-include docs *.png
+recursive-include docs *.py
+recursive-include docs *.tok
+recursive-include docs *.txt
recursive-include test *.py
recursive-include test *.yaml
recursive-include test *.swix
diff --git a/VERSION b/VERSION
index af0b7dd..238d6e8 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.6
+1.0.7
diff --git a/cvprac/__init__.py b/cvprac/__init__.py
index e56ef8a..4828605 100644
--- a/cvprac/__init__.py
+++ b/cvprac/__init__.py
@@ -32,5 +32,5 @@
''' RESTful API Client class for Cloudvision(R) Portal
'''
-__version__ = '1.0.6'
+__version__ = '1.0.7'
__author__ = 'Arista Networks, Inc.'
diff --git a/cvprac/cvp_api.py b/cvprac/cvp_api.py
index 1c42ddd..df1e097 100644
--- a/cvprac/cvp_api.py
+++ b/cvprac/cvp_api.py
@@ -810,11 +810,15 @@ def get_devices_in_container(self, name):
devices.append(device)
return devices
- def get_device_by_name(self, fqdn):
+ def get_device_by_name(self, fqdn, search_by_hostname=False):
''' Returns the net element device dict for the devices fqdn name.
Args:
- fqdn (str): Fully qualified domain name of the device.
+ fqdn (str): Fully qualified domain name or hostname of the
+ device.
+ search_by_hostname (boolean): if set True will attempt to split
+ the fqdn string to match on the hostname portion
+ specifically which should be the first component
Returns:
device (dict): The net element device dict for the device if
@@ -826,9 +830,14 @@ def get_device_by_name(self, fqdn):
device = {}
if 'netElementList' in data:
for netelem in data['netElementList']:
- if netelem['fqdn'] == fqdn:
- device = netelem
- break
+ if not search_by_hostname:
+ if netelem['fqdn'] == fqdn:
+ device = netelem
+ break
+ else:
+ if netelem['fqdn'].split('.')[0] == fqdn:
+ device = netelem
+ break
return device
def get_device_by_mac(self, device_mac):
@@ -852,6 +861,27 @@ def get_device_by_mac(self, device_mac):
break
return device
+ def get_device_by_serial(self, device_serial):
+ ''' Returns the net element device dict for the devices serial number.
+
+ Args:
+ device_serial (str): Serial number of the device.
+
+ Returns:
+ device (dict): The net element device dict for the device if
+ otherwise returns an empty hash.
+ '''
+ self.log.debug('get_device_by_serial: Serial Number: %s'
+ % device_serial)
+ data = self.search_topology(device_serial)
+ device = {}
+ if 'netElementList' in data:
+ for netelem in data['netElementList']:
+ if netelem['serialNumber'] == device_serial:
+ device = netelem
+ break
+ return device
+
def get_device_configuration(self, device_mac):
''' Returns the running configuration for the device provided.
@@ -1009,21 +1039,24 @@ def add_configlet_builder(self, name, config, draft=False, form=None):
draft (bool): If builder is a draft
form (list): Array/list of form data
Parameters:
- fieldId (str):
- fieldLabel (str):
- value (str):
- type (str): {
- 'Text box',
+ fieldId (str): "",
+ fieldLabel (str): "",
+ value (str): "",
+ type (str): "", (Options below)
+ ('Text box',
'Text area',
'Drop down',
'Check box',
'Radio button',
'IP address',
- 'Password'
- }
- validation:
- mandatory (boolean):
- helpText (str)
+ 'Password')
+ validation: {
+ mandatory (boolean): true,
+ },
+ helpText (str): "",
+ depends (str): "",
+ dataValidationErrorExist (boolean): true,
+ dataValidation (string): ""
Returns:
key (str): The key for the configlet
@@ -1102,7 +1135,7 @@ def update_configlet(self, config, key, name, wait_task_ids=False):
timeout=self.request_timeout)
def update_configlet_builder(self, name, key, config, draft=False,
- wait_for_task=False):
+ wait_for_task=False, form=None):
''' Update an existing configlet builder.
Args:
config (str): Contents of the configlet builder configuration
@@ -1110,19 +1143,43 @@ def update_configlet_builder(self, name, key, config, draft=False,
name: (str): name of the configlet builder
draft (boolean): is update a draft
wait_for_task (boolean): wait for task IDs to be generated
+ form (list): Array/list of form data
+ Parameters:
+ fieldId (str): "",
+ fieldLabel (str): "",
+ value (str): "",
+ type (str): "", (Options below)
+ ('Text box',
+ 'Text area',
+ 'Drop down',
+ 'Check box',
+ 'Radio button',
+ 'IP address',
+ 'Password')
+ validation: {
+ mandatory (boolean): true,
+ },
+ helpText (str): "",
+ depends (str): "",
+ dataValidationErrorExist (boolean): true,
+ dataValidation (string): ""
'''
+ if not form:
+ form = []
+
data = {
"name": name,
"waitForTaskIds": wait_for_task,
"data": {
+ "formList": form,
"main_script": {
"data": config
}
}
}
debug_str = 'update_configlet_builder:' \
- ' config: {} key: {} name: {} '
- self.log.debug(debug_str.format(config, key, name))
+ ' config: {} key: {} name: {} form: {}'
+ self.log.debug(debug_str.format(config, key, name, form))
# Update the configlet builder
url_string = '/configlet/updateConfigletBuilder.do?' \
'isDraft={}&id={}&action=save'
@@ -1262,7 +1319,7 @@ def _save_topology_v2(self, data):
return self.clnt.post(url, data=data, timeout=self.request_timeout)
def apply_configlets_to_device(self, app_name, dev, new_configlets,
- create_task=True):
+ create_task=True, reorder_configlets=False):
''' Apply the configlets to the device.
Args:
@@ -1271,6 +1328,18 @@ def apply_configlets_to_device(self, app_name, dev, new_configlets,
new_configlets (list): List of configlet name and key pairs
create_task (bool): Determines whether or not to execute a save
and create the tasks (if any)
+ reorder_configlets (bool): Defaults to False. To use this
+ parameter you must first get the full list of configlets
+ applied to the device (for example via the
+ get_configlets_by_device_id function) and provide the
+ full list of configlets (in addition to any new configlets
+ being applied) in the desired order as the new_configlets
+ parameter. It is also important to keep in mind configlets
+ that are applied to parent containers because they will
+ be applied before configlets applied to the device
+ directly. Set this parameter to True only with the full
+ list of configlets being applied to the device provided
+ via the new_configlets parameter.
Returns:
response (dict): A dict that contains a status and a list of
@@ -1280,15 +1349,17 @@ def apply_configlets_to_device(self, app_name, dev, new_configlets,
'''
self.log.debug('apply_configlets_to_device: dev: %s names: %s' %
(dev, new_configlets))
- # Get all the configlets assigned to the device.
- configlets = self.get_configlets_by_device_id(dev['systemMacAddress'])
-
# Get a list of the names and keys of the configlets
cnames = []
ckeys = []
- for configlet in configlets:
- cnames.append(configlet['name'])
- ckeys.append(configlet['key'])
+
+ if not reorder_configlets:
+ # Get all the configlets assigned to the device.
+ configlets = self.get_configlets_by_device_id(
+ dev['systemMacAddress'])
+ for configlet in configlets:
+ cnames.append(configlet['name'])
+ ckeys.append(configlet['key'])
# Add the new configlets to the end of the arrays
for entry in new_configlets:
diff --git a/docs/labs/README.md b/docs/labs/README.md
new file mode 100644
index 0000000..43652fb
--- /dev/null
+++ b/docs/labs/README.md
@@ -0,0 +1,53 @@
+# cvprac labs
+
+The following lab examples will walk through the most commonly used REST API calls using cvprac
+to help users interact with Arista CloudVision easily and automate the provisioning of network devices.
+
+## Authentication
+
+There are two ways to authenticate using the REST APIs:
+- user/password (on-prem only)
+- service account token (available on CVP 2020.3.0+ and CVaaS)
+
+### User/password authentication
+
+```python
+from cvprac.cvp_client import CvpClient
+clnt = CvpClient()
+clnt.connect(['10.83.13.33'],'cvpadmin', 'arastra')
+```
+
+### Service account token authentication
+
+To access the CloudVision as-a-Service and send API requests, “Service Account Token” is needed.
+After obtaining the service account token, it can be used for authentication when sending API requests.
+
+Service accounts can be created from the Settings page where a service token can be generated as seen below:
+
+![serviceaccount1](./static/serviceaccount1.png)
+![serviceaccount2](./static/serviceaccount2.png)
+![serviceaccount3](./static/serviceaccount3.png)
+
+The token should be copied and saved to a file that can later be referred to.
+
+```python
+from cvprac.cvp_client import CvpClient
+clnt = CvpClient()
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token)
+```
+
+>NOTE In case of CVaaS the `is_cvaas` parameters has to be set to `True`
+
+Service accounts are supported on CVP on-prem starting from `2020.3.0`. More details in the [TOI](https://eos.arista.com/toi/cvp-2020-3-0/service-accounts/) and the [CV config guide](https://www.arista.com/en/cg-cv/cv-service-accounts).
+
+```python
+from cvprac.cvp_client import CvpClient
+
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['10.83.13.33'], username='',password='',api_token=token)
+```
\ No newline at end of file
diff --git a/docs/labs/lab01-cvp-info/get_cvp_info.py b/docs/labs/lab01-cvp-info/get_cvp_info.py
new file mode 100644
index 0000000..0e002d0
--- /dev/null
+++ b/docs/labs/lab01-cvp-info/get_cvp_info.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+result = clnt.api.get_cvp_info()
+print(result)
diff --git a/docs/labs/lab02-inventory-operations/compliance_check.py b/docs/labs/lab02-inventory-operations/compliance_check.py
new file mode 100644
index 0000000..306b407
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/compliance_check.py
@@ -0,0 +1,57 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+### Compliance Code description
+compliance = {"0000":"Configuration is in sync",
+ "0001": "Config is out of sync",
+ "0002": "Image is out of sync",
+ "0003": "Config & image out of sync",
+ "0004": "Config, Image and Device time are in sync",
+ "0005": "Device is not reachable",
+ "0006": "The current EOS version on this device is not supported by CVP. Upgrade the device to manage.",
+ "0007": "Extensions are out of sync",
+ "0008": "Config, Image and Extensions are out of sync",
+ "0009": "Config and Extensions are out of sync",
+ "0010": "Image and Extensions are out of sync",
+ "0011": "Unauthorized User",
+ "0012": "Config, Image, Extension and Device time are out of sync",
+ "0013": "Config, Image and Device time are out of sync",
+ "0014": "Config, Extensions and Device time are out of sync",
+ "0015": "Image, Extensions and Device time are out of sync",
+ "0016": "Config and Device time are out of sync",
+ "0017": "Image and Device time are out of sync",
+ "0018": "Extensions and Device time are out of sync",
+ "0019": "Device time is out of sync"
+}
+
+# Create connection to CloudVision using Service account token
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='',password='',api_token=token)
+
+def check_devices_under_container(client, container):
+ ''' container is the container ID '''
+
+ nodeId = container['key']
+ nodeName = container['name']
+ api = '/ztp/getAllNetElementList.do?'
+ queryParams = "nodeId={}&queryParam=&nodeName={}&startIndex=0&endIndex=0&contextQueryParam=&ignoreAdd=false&useCache=true".format(nodeId, nodeName)
+ return client.get(api + queryParams)
+
+
+container = clnt.api.get_container_by_name('TP_LEAFS')
+
+devices = (check_devices_under_container(clnt,container))
+
+for device in devices['netElementList']:
+ code = device['complianceCode']
+ print(device['fqdn'], ' ', code,' ', compliance[code])
diff --git a/docs/labs/lab02-inventory-operations/remove_all_devices.py b/docs/labs/lab02-inventory-operations/remove_all_devices.py
new file mode 100644
index 0000000..e49b804
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/remove_all_devices.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+inventory = clnt.api.get_inventory()
+
+devices = []
+for netelement in inventory:
+ devices.append(netelement['systemMacAddress'])
+
+clnt.api.delete_devices(devices)
diff --git a/docs/labs/lab02-inventory-operations/remove_devices.py b/docs/labs/lab02-inventory-operations/remove_devices.py
new file mode 100644
index 0000000..3cde0b3
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/remove_devices.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision using Service account token
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='', password='', api_token=token)
+
+devices = ["50:08:00:a7:ca:c3","50:08:00:b1:5b:0b","50:08:00:60:c6:76",
+ "50:08:00:25:9d:36","50:08:00:8b:ee:b1","50:08:00:8c:22:49"]
+
+clnt.api.delete_devices(devices)
diff --git a/docs/labs/lab02-inventory-operations/remove_devices_from_container.py b/docs/labs/lab02-inventory-operations/remove_devices_from_container.py
new file mode 100644
index 0000000..c432fe8
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/remove_devices_from_container.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+# Get devices in a specific container
+inventory = clnt.api.get_devices_in_container("Undefined")
+
+devices = []
+for netelement in inventory:
+ devices.append(netelement['systemMacAddress'])
+
+clnt.api.delete_devices(devices)
diff --git a/docs/labs/lab03-configlet-management/assign_configlet_to_device.py b/docs/labs/lab03-configlet-management/assign_configlet_to_device.py
new file mode 100644
index 0000000..2973408
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/assign_configlet_to_device.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+configletName = 'cvprac_example2'
+
+device_name = "tp-avd-leaf1"
+device = clnt.api.get_device_by_name(device_name)
+
+configlet = clnt.api.get_configlet_by_name(configletName)
+
+clnt.api.apply_configlets_to_device("", device, [configlet])
diff --git a/docs/labs/lab03-configlet-management/backup_configlets.py b/docs/labs/lab03-configlet-management/backup_configlets.py
new file mode 100644
index 0000000..6543a6b
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/backup_configlets.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# Get configlets and save them to individual files
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+configlets = clnt.api.get_configlets(start=0,end=0)
+
+for configlet in configlets['data']:
+ with open(configlet['name'],'w') as f:
+ f.write(configlet['config'])
diff --git a/docs/labs/lab03-configlet-management/backup_configletsV2.py b/docs/labs/lab03-configlet-management/backup_configletsV2.py
new file mode 100644
index 0000000..8dff0b2
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/backup_configletsV2.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# Get configlets and save them to individual files using multi threading
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from concurrent.futures import ThreadPoolExecutor
+from functools import wraps
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+total = clnt.api.get_configlets(start=0,end=1)['total']
+
+def get_list_of_configlets():
+ """
+ Create a thread pool and download specified urls
+ """
+
+ futures_list = []
+ results = []
+
+ with ThreadPoolExecutor(max_workers=40) as executor:
+ for i in range(0,total+1,10):
+ futures = executor.submit(clnt.api.get_configlets, start=i,end=i+10)
+ futures_list.append(futures)
+
+ for future in futures_list:
+ try:
+ result = future.result(timeout=60)
+ results.append(result)
+ except Exception:
+ results.append(None)
+ print(future.result())
+ return results
+
+if __name__ == "__main__":
+
+ results = get_list_of_configlets()
+ for configlet in results[0]['data']:
+ with open(configlet['name'],'w') as f:
+ f.write(configlet['config'])
diff --git a/docs/labs/lab03-configlet-management/common.cfg b/docs/labs/lab03-configlet-management/common.cfg
new file mode 100644
index 0000000..7620ae1
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/common.cfg
@@ -0,0 +1,6 @@
+!
+ip name-server vrf management 1.1.1.1
+ip name-server vrf management 8.8.8.8
+!
+ntp server vrf management time.google.com
+!
diff --git a/docs/labs/lab03-configlet-management/configlet_list.txt b/docs/labs/lab03-configlet-management/configlet_list.txt
new file mode 100644
index 0000000..645252c
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/configlet_list.txt
@@ -0,0 +1,6 @@
+tp-avd_tp-avd-leaf1
+tp-avd_tp-avd-leaf2
+tp-avd_tp-avd-leaf3
+tp-avd_tp-avd-leaf4
+tp-avd_tp-avd-spine1
+tp-avd_tp-avd-spine2
\ No newline at end of file
diff --git a/docs/labs/lab03-configlet-management/create_configlet.py b/docs/labs/lab03-configlet-management/create_configlet.py
new file mode 100644
index 0000000..8579fbf
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/create_configlet.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+configletName = "cvprac_example"
+
+configlet = """!
+interface Ethernet10
+ description test
+ ip address 10.144.144.1/24
+!
+"""
+
+clnt.api.add_configlet(configletName,configlet)
diff --git a/docs/labs/lab03-configlet-management/create_configlet_from_file.py b/docs/labs/lab03-configlet-management/create_configlet_from_file.py
new file mode 100644
index 0000000..d6d07c6
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/create_configlet_from_file.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+configletName = "cvprac_example2"
+
+with open("common.cfg") as file:
+ configlet = file.read()
+clnt.api.add_configlet(configletName, configlet)
diff --git a/docs/labs/lab03-configlet-management/get_configlets.py b/docs/labs/lab03-configlet-management/get_configlets.py
new file mode 100644
index 0000000..fc3dc2d
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/get_configlets.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# Get list of configlets in parallel
+
+from cvprac.cvp_client import CvpClient
+import ssl
+from concurrent.futures import ThreadPoolExecutor
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+import time
+from functools import wraps
+
+def get_list_of_configlets(configlets):
+ """
+ Create a thread pool and download specified urls
+ """
+
+ futures_list = []
+ results = []
+
+ with ThreadPoolExecutor(max_workers=40) as executor:
+ for configlet in configlets:
+ futures = executor.submit(clnt.api.get_configlet_by_name, configlet)
+ futures_list.append(futures)
+
+ for future in futures_list:
+ try:
+ result = future.result(timeout=60)
+ results.append(result)
+ except Exception:
+ results.append(None)
+ return results
+
+if __name__ == "__main__":
+ # Example with pre-defined list
+ configlets = ["tp-avd_tp-avd-leaf1","tp-avd_tp-avd-leaf2","tp-avd_tp-avd-leaf3","tp-avd_tp-avd-leaf4"]
+
+ # Example with loading list of configlets from a file
+ # with open("configlet_list.txt") as f:
+ # configlets = f.read().splitlines()
+
+ results = get_list_of_configlets(configlets)
+ for result in results:
+ print(result)
diff --git a/docs/labs/lab03-configlet-management/reorder_configlet_on_device.py b/docs/labs/lab03-configlet-management/reorder_configlet_on_device.py
new file mode 100644
index 0000000..3a5bb6f
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/reorder_configlet_on_device.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+configletNames = ['tp-avd_tp-avd-leaf1','vlan144','api_models']
+
+device_name = "tp-avd-leaf1"
+device = clnt.api.get_device_by_name(device_name)
+
+configlets = []
+
+for name in configletNames:
+ configlets.append(clnt.api.get_configlet_by_name(name))
+
+# Apply configlets in the order specified in the list
+clnt.api.apply_configlets_to_device("", device, configlets, reorder_configlets=True)
diff --git a/docs/labs/lab03-configlet-management/update_configlet.py b/docs/labs/lab03-configlet-management/update_configlet.py
new file mode 100644
index 0000000..b3eb9c0
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/update_configlet.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+# Modify existing configlet
+
+configletName = "cvprac_example"
+
+configlet = """!
+interface Ethernet10
+ description DUB_R04
+ ip address 10.144.144.2/24
+!
+"""
+
+configletID = clnt.api.get_configlet_by_name(configletName)['key']
+
+clnt.api.update_configlet( configlet, configletID, configletName)
diff --git a/docs/labs/lab04-container-management/add_image_to_container.py b/docs/labs/lab04-container-management/add_image_to_container.py
new file mode 100644
index 0000000..99fc05e
--- /dev/null
+++ b/docs/labs/lab04-container-management/add_image_to_container.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+container_name = "TP_FABRIC"
+container = clnt.api.get_container_by_name(container_name)
+
+clnt.api.apply_image_to_container(image, container)
diff --git a/docs/labs/lab04-container-management/assign_configlet_to_container.py b/docs/labs/lab04-container-management/assign_configlet_to_container.py
new file mode 100644
index 0000000..a6e8828
--- /dev/null
+++ b/docs/labs/lab04-container-management/assign_configlet_to_container.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+container_name = "TP_LEAFS"
+
+configletName = 'cvprac_example2'
+
+container = clnt.api.get_container_by_name(container_name)
+
+configlet = clnt.api.get_configlet_by_name(configletName)
+
+clnt.api.apply_configlets_to_container("", container, [configlet])
diff --git a/docs/labs/lab04-container-management/create_container.py b/docs/labs/lab04-container-management/create_container.py
new file mode 100644
index 0000000..6e5b8dc
--- /dev/null
+++ b/docs/labs/lab04-container-management/create_container.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+# Get parent container information
+parent = clnt.api.get_container_by_name("ContainerA")
+
+# Create new container ContainerB under ContainerA
+
+clnt.api.add_container("ContainerB",parent["name"],parent["key"])
diff --git a/docs/labs/lab04-container-management/remove_image_from_container.py b/docs/labs/lab04-container-management/remove_image_from_container.py
new file mode 100644
index 0000000..8a48be8
--- /dev/null
+++ b/docs/labs/lab04-container-management/remove_image_from_container.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+container_name = "TP_FABRIC"
+container = clnt.api.get_container_by_name(container_name)
+
+clnt.api.remove_image_from_container(image, container)
diff --git a/docs/labs/lab04-container-management/rename_container.py b/docs/labs/lab04-container-management/rename_container.py
new file mode 100644
index 0000000..74f4562
--- /dev/null
+++ b/docs/labs/lab04-container-management/rename_container.py
@@ -0,0 +1,32 @@
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+oldName = "test"
+newName = "test121"
+
+container_id = clnt.api.get_container_by_name(oldName)['key']
+
+data = {"data":[{"info": "Container {} renamed from {}".format(newName, oldName),
+ "infoPreview": "Container {} renamed from {}".format(newName, oldName),
+ "action": "update",
+ "nodeType": "container",
+ "nodeId": container_id,
+ "toId":"",
+ "fromId":"",
+ "nodeName": newName,
+ "fromName": "",
+ "toName": "",
+ "toIdType": "container",
+ "oldNodeName": oldName
+ }
+ ]
+ }
+
+clnt.api._add_temp_action(data)
+clnt.api._save_topology_v2([])
diff --git a/docs/labs/lab05-device-management/add_image_to_devices.py b/docs/labs/lab05-device-management/add_image_to_devices.py
new file mode 100644
index 0000000..9150a3c
--- /dev/null
+++ b/docs/labs/lab05-device-management/add_image_to_devices.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+device_name = "tp-avd-leaf2"
+device = clnt.api.get_device_by_name(device_name)
+
+clnt.api.apply_image_to_device(image, device)
diff --git a/docs/labs/lab05-device-management/add_image_wo_tempaction.py b/docs/labs/lab05-device-management/add_image_wo_tempaction.py
new file mode 100644
index 0000000..1100a5a
--- /dev/null
+++ b/docs/labs/lab05-device-management/add_image_wo_tempaction.py
@@ -0,0 +1,100 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import json
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+device_name = "tp-avd-leaf2"
+device = clnt.api.get_device_by_name(device_name)
+
+def apply_image_to_element_no_temp(image, element, name, id_type, create_task=True):
+ ''' Apply an image bundle to a device or container
+ A copy of the appl_image_to_element() function without creating a tempAction.
+ Useful in situations where we need to call saveTopology on a per tempAction basis,
+ which is only possible if the addTempAction function is not used and the data
+ that we would've passed in the addTempAction call is passed in the
+ saveTopology call.
+ Args:
+ image (dict): The image info.
+ element (dict): Info about the element to apply an image to. Dict
+ can contain device info or container info.
+ name (str): Name of the element the image is being applied to.
+ id_type (str): - Id type of the element the image is being applied to
+ - can be 'netelement' or 'container'
+ create_task (bool): Determines whether or not to execute a save
+ and create the tasks (if any)
+ Returns:
+ response (list): A list that contains the tempAction data
+ Ex: [{'NetworkRollbackTask': False,
+ 'taskJson': '[{
+ "info": "Apply image: vEOS-4.26.0.1F to netelement tp-avd-leaf2",
+ "infoPreview": "Apply image: vEOS-4.26.0.1F to netelement tp-avd-leaf2",
+ "note": "",
+ "action": "associate", "nodeType":
+ "imagebundle",
+ "nodeId": "imagebundle_1622072231719691917",
+ "toId": "50:08:00:b1:5b:0b",
+ "toIdType": "netelement",
+ "fromId": "",
+ "nodeName": "vEOS-4.26.0.1F",
+ "fromName": "", "
+ toName": "tp-avd-leaf2",
+ "childTasks": [],
+ "parentTask": ""}]'}]
+ '''
+
+ print('Attempt to apply %s to %s %s' % (image['name'],
+ id_type, name))
+ info = 'Apply image: %s to %s %s' % (image['name'], id_type, name)
+ node_id = ''
+ if 'imageBundleKeys' in image:
+ if image['imageBundleKeys']:
+ node_id = image['imageBundleKeys'][0]
+ print('Provided image is an image object.'
+ ' Using first value from imageBundleKeys - %s'
+ % node_id)
+ if 'id' in image:
+ node_id = image['id']
+ print('Provided image is an image bundle object.'
+ ' Found v1 API id field - %s' % node_id)
+ elif 'key' in image:
+ node_id = image['key']
+ print('Provided image is an image bundle object.'
+ ' Found v2 API key field - %s' % node_id)
+ data = [
+ {
+ "NetworkRollbackTask": False,
+ "taskJson": json.dumps([{'info': info,
+ 'infoPreview': info,
+ 'note': '',
+ 'action': 'associate',
+ 'nodeType': 'imagebundle',
+ 'nodeId': node_id,
+ 'toId': element['key'],
+ 'toIdType': id_type,
+ 'fromId': '',
+ 'nodeName': image['name'],
+ 'fromName': '',
+ 'toName': name,
+ 'childTasks': [],
+ 'parentTask': ''}])
+ }
+ ]
+ return data
+
+create_task = False
+tempAction = apply_image_to_element_no_temp(image, device, device['fqdn'], 'netelement', create_task)
+
+clnt.api._save_topology_v2(tempAction)
\ No newline at end of file
diff --git a/docs/labs/lab05-device-management/remove_image_from_device.py b/docs/labs/lab05-device-management/remove_image_from_device.py
new file mode 100644
index 0000000..5e9910c
--- /dev/null
+++ b/docs/labs/lab05-device-management/remove_image_from_device.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+device_name = "tp-avd-leaf2"
+device = clnt.api.get_device_by_name(device_name)
+
+clnt.api.remove_image_from_device(image, device)
diff --git a/docs/labs/lab05-device-management/set_mgmt_ip.py b/docs/labs/lab05-device-management/set_mgmt_ip.py
new file mode 100644
index 0000000..f0c33f1
--- /dev/null
+++ b/docs/labs/lab05-device-management/set_mgmt_ip.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+
+data = {"data":[{"info":"Device IP Address Changed",
+ "infoPreview":" Device IP Address Changed to 10.83.13.214",
+ "action":"associate",
+ "nodeType":"ipaddress",
+ "nodeId":"",
+ "toId":"50:08:00:a7:ca:c3", # MAC of the device
+ "fromId":"",
+ "nodeName":"",
+ "fromName":"",
+ "toName":"tp-avd-leaf1", # hostname
+ "toIdType":"netelement",
+ "nodeIpAddress":"10.83.13.219", # the temporary management IP Address
+ "nodeTargetIpAddress":"10.83.13.214" # the final management IP address
+ }
+ ]
+ }
+clnt.api._add_temp_action(data)
+
+clnt.api._save_topology_v2([])
diff --git a/docs/labs/lab06-provisioning/change_control_workflow.py b/docs/labs/lab06-provisioning/change_control_workflow.py
new file mode 100644
index 0000000..af1ee4b
--- /dev/null
+++ b/docs/labs/lab06-provisioning/change_control_workflow.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from datetime import datetime
+
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+ccid = 'cvprac0904211418'
+name = "cvprac CC test"
+tlist = ['1021','1020','1019','1018']
+
+### Create Change control with the list of tasks
+clnt.api.create_change_control_v3(ccid, name, tlist)
+
+### Approve CC
+clnt.api.approve_change_control('cvprac0904211418', timestamp=datetime.utcnow().isoformat() + 'Z')
+
+### Execute CC
+clnt.api.execute_change_controls(['cvprac0904211418'])
diff --git a/docs/labs/lab06-provisioning/gen_builder.py b/docs/labs/lab06-provisioning/gen_builder.py
new file mode 100644
index 0000000..8cd389d
--- /dev/null
+++ b/docs/labs/lab06-provisioning/gen_builder.py
@@ -0,0 +1,63 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+container_id = clnt.api.get_container_by_name("TP_LEAFS")['key']
+builder_name = 'SYS_TelemetryBuilderV3'
+configletBuilderID = clnt.api.get_configlet_by_name(builder_name)['key']
+
+payload = {"previewValues":[{
+ "fieldId":"vrf",
+ "value":"red"}],
+ "configletBuilderId":configletBuilderID,
+ "netElementIds":[],
+ "pageType":"container",
+ "containerId":container_id,
+ "containerToId":"",
+ "mode":"assign"}
+
+preview = clnt.post('/configlet/configletBuilderPreview.do', data=payload)
+
+generated_names_list = []
+generated_keys_list = []
+
+for i in preview['data']:
+ generated_names_list.append(i['configlet']['name'])
+ generated_keys_list.append(i['configlet']['key'])
+
+clnt.get("/configlet/searchConfiglets.do?objectId={}&objectType=container&type=ignoreDraft&queryparam={}&startIndex=0&endIndex=22&sortByColumn=&sortOrder=".format(container_id, builder_name.lower()))
+
+tempData = {"data":[{
+ "info":"Configlet Assign: to container TP_LEAFS",
+ "infoPreview":"Configlet Assign: to container TP_LEAFS",
+ "action":"associate",
+ "nodeType":"configlet",
+ "nodeId":"",
+ "toId":container_id,
+ "fromId":"","nodeName":"","fromName":"",
+ "toName":"TP_LEAFS",
+ "toIdType":"container",
+ "configletList":generated_keys_list,
+ "configletNamesList":generated_names_list,
+ "ignoreConfigletList":[],
+ "ignoreConfigletNamesList":[],
+ "configletBuilderList":[configletBuilderID],
+ "configletBuilderNamesList":[builder_name],
+ "ignoreConfigletBuilderList":[],
+ "ignoreConfigletBuilderNamesList":[]
+ }
+ ]
+ }
+
+clnt.api._add_temp_action(tempData)
+clnt.api._save_topology_v2([])
diff --git a/docs/labs/lab06-provisioning/move_device.py b/docs/labs/lab06-provisioning/move_device.py
new file mode 100644
index 0000000..5257f79
--- /dev/null
+++ b/docs/labs/lab06-provisioning/move_device.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='',password='',api_token=token)
+
+container = clnt.api.get_container_by_name('TP_LEAFS') # container object
+
+app_name = "my app" # can be any string
+
+device = {"key":"00:1c:73:c5:4c:87", "fqdn":"co633.ire.aristanetworks.com"}
+
+move_device_to_container(app_name, device, container)
diff --git a/docs/labs/lab06-provisioning/vc_task_retrigger.py b/docs/labs/lab06-provisioning/vc_task_retrigger.py
new file mode 100644
index 0000000..512adaa
--- /dev/null
+++ b/docs/labs/lab06-provisioning/vc_task_retrigger.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+# Example on how to re-trigger task creation if a config push task was previously
+# cancelled and the device is still config out of sync
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+# Trigger tasks after they were cancelled
+
+devices = ["tp-avd-leaf1", "tp-avd-leaf2", "tp-avd-leaf3", "tp-avd-leaf4", "tp-avd-spine1", "tp-avd-spine2"]
+
+compliance = {"0001": "Config is out of sync",
+ "0003": "Config & image out of sync",
+ "0004": "Config, Image and Device time are in sync",
+ "0005": "Device is not reachable",
+ "0008": "Config, Image and Extensions are out of sync",
+ "0009": "Config and Extensions are out of sync",
+ "0012": "Config, Image, Extension and Device time are out of sync",
+ "0013": "Config, Image and Device time are out of sync",
+ "0014": "Config, Extensions and Device time are out of sync",
+ "0016": "Config and Device time are out of sync",
+}
+
+for dev in devices:
+ # generate device object
+ device = clnt.api.get_device_by_name(dev)
+ # generate configlet list
+ cl = clnt.api.get_configlets_by_device_id(device['systemMacAddress'])
+ # generate a task if config is out of sync
+ if device['complianceCode'] in compliance.keys():
+ print(clnt.api.apply_configlets_to_device("", device, cl))
diff --git a/docs/labs/lab07-aaa/aaa_users.csv b/docs/labs/lab07-aaa/aaa_users.csv
new file mode 100644
index 0000000..14b3706
--- /dev/null
+++ b/docs/labs/lab07-aaa/aaa_users.csv
@@ -0,0 +1,5 @@
+username,first_name,last_name,email,user_type,role,status
+alice,,,alice@abc.xyz,SSO,network-admin,Enabled
+bob,,,bob@abc.xyz,SSO,network-admin,Enabled
+jane,Jane,Smith,jane@abc.xyz,SSO,network-admin,Enabled
+john,John,Smith,john@abc.xyz,SSO,network-admin,Enabled
\ No newline at end of file
diff --git a/docs/labs/lab07-aaa/add_new_user_cvaas.py b/docs/labs/lab07-aaa/add_new_user_cvaas.py
new file mode 100644
index 0000000..af2d48e
--- /dev/null
+++ b/docs/labs/lab07-aaa/add_new_user_cvaas.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from cvprac.cvp_client import CvpClient
+
+# Create connection to CloudVision using Service Account token
+with open("cvaas.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token)
+
+username = "john"
+password = ""
+role = "network-admin"
+status = "Enabled"
+first_name = "John"
+last_name = "Smith"
+email = "john.smith@abc.xyz"
+utype = "SSO"
+
+try:
+ clnt.api.add_user(username,password,role,status,first_name,last_name,email,utype)
+except CvpApiError as e:
+ print(e)
diff --git a/docs/labs/lab07-aaa/add_new_user_onprem.py b/docs/labs/lab07-aaa/add_new_user_onprem.py
new file mode 100644
index 0000000..218c9fc
--- /dev/null
+++ b/docs/labs/lab07-aaa/add_new_user_onprem.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from getpass import getpass
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+username = "cvpuser2"
+password = getpass()
+role = "network-admin"
+status = "Enabled"
+first_name = "Cloud"
+last_name = "Vision"
+email = "cvp@arista.com"
+utype = "TACACS"
+
+try:
+ clnt.api.add_user(username,password,role,status,first_name,last_name,email,utype)
+except CvpApiError as e:
+ print(e)
diff --git a/docs/labs/lab07-aaa/add_users_from_csv_cvaas.py b/docs/labs/lab07-aaa/add_users_from_csv_cvaas.py
new file mode 100644
index 0000000..c5cdda5
--- /dev/null
+++ b/docs/labs/lab07-aaa/add_users_from_csv_cvaas.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from cvprac.cvp_client import CvpClient
+import csv
+
+# Create connection to CloudVision using Service Account token
+with open("cvaas.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token)
+
+
+with open("aaa_users.csv") as csvfile:
+ for i in csv.DictReader(csvfile):
+ data = dict(i)
+ try:
+ clnt.api.add_user(data['username'], "", data['role'], data['status'], data['first_name'], data['last_name'], data['email'], data['user_type'])
+ except CvpApiError as e:
+ print(e)
+ print ("Adding user {} to CVaaS".format(data['username']))
diff --git a/docs/labs/lab07-aaa/cvaas.tok b/docs/labs/lab07-aaa/cvaas.tok
new file mode 100644
index 0000000..9d0234c
--- /dev/null
+++ b/docs/labs/lab07-aaa/cvaas.tok
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/labs/lab07-aaa/get_user_info.py b/docs/labs/lab07-aaa/get_user_info.py
new file mode 100644
index 0000000..5e5a193
--- /dev/null
+++ b/docs/labs/lab07-aaa/get_user_info.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from cvprac.cvp_client import CvpClient
+
+with open("cvaas.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token)
+
+user_info = clnt.api.get_user('kishore')
+print (user_info)
diff --git a/docs/labs/lab08-resource-apis/resource_cvprac.py b/docs/labs/lab08-resource-apis/resource_cvprac.py
new file mode 100644
index 0000000..e454fc9
--- /dev/null
+++ b/docs/labs/lab08-resource-apis/resource_cvprac.py
@@ -0,0 +1,187 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from pprint import pprint as pp
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Reading the service account token from a file
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='',password='',api_token=token)
+
+def get_events_all(client):
+ ''' Get All events '''
+ event_url = '/api/resources/event/v1/Event/all'
+ response = client.get(event_url)
+ return response['data']
+
+def get_event(client, key, ts):
+ event_url = '/api/resources/event/v1/Event?'
+ url = event_url + 'key.key=' + key + "&key.timestamp=" + ts
+ response = client.get(url)
+ return response
+
+def get_events_t1_t2(client, t1, t2):
+ event_url = '/api/resources/event/v1/Event/all?'
+ url = event_url + 'time.start=' + t1 + "&time.end=" + t2
+ response = client.get(url)
+ return response['data']
+
+def get_events_by_severity(client, severity):
+ payload = {"partialEqFilter": [{"severity": severity }]}
+ event_url = '/api/resources/event/v1/Event/all'
+ response = client.post(event_url, data=payload)
+ if 'data' in response.keys():
+ return response['data']
+ else:
+ return response
+
+def get_events_by_type(client, etype):
+ payload = {"partialEqFilter": [{"eventType": etype }]}
+ event_url = '/api/resources/event/v1/Event/all'
+ response = client.post(event_url, data=payload)
+ if 'data' in response.keys():
+ return response['data']
+ else:
+ return response
+
+def get_active_devices(client):
+ ''' Get active devices '''
+ dev_url = '/api/resources/inventory/v1/Device/all'
+ devices_data = client.get(dev_url)
+ devices = []
+ for device in devices_data['data']:
+ try:
+ if device['result']['value']['streamingStatus'] == "STREAMING_STATUS_ACTIVE":
+ devices.append(device['result']['value']['hostname'])
+ # pass on archived datasets
+ except KeyError as e:
+ continue
+ return devices
+
+def get_all_device_tags(client):
+ tag_url = '/api/resources/tag/v1/DeviceTag/all'
+ tag_data = client.get(tag_url)
+ tags = []
+ for tag in tag_data['data']:
+ tags.append({tag['result']['value']['key']['label']:tag['result']['value']['key']['value']})
+ return tags
+
+def get_all_interface_tags(client):
+ tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig/all'
+ tags = client.get(tag_url)
+ return tags['data']
+
+def filter_interface_tag(client, dId=None, ifId=None, label=None, value=None):
+ tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig/all'
+ payload = {
+ "partialEqFilter": [
+ {"key": {"deviceId": dId, "interfaceId": ifId, "label": label, "value": value}}
+ ]
+ }
+ response = client.post(tag_url, data=payload)
+ return response
+
+def create_itag(client, label, value):
+ tag_url = '/api/resources/tag/v1/InterfaceTagConfig'
+ payload = {"key":{"label":label,"value":value}}
+ response = client.post(tag_url, data=payload)
+ return response
+
+def assign_itag(client, dId, ifId, label, value):
+ tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig'
+ payload = {"key":{"label":label, "value":value, "deviceId": dId, "interfaceId": ifId}}
+ response = client.post(tag_url, data=payload)
+ return response
+
+def create_dtag(client, label, value):
+ tag_url = '/api/resources/tag/v1/DeviceTagConfig'
+ payload = {"key":{"label":label,"value":value}}
+ response = client.post(tag_url, data=payload)
+ return response
+
+def assign_dtag(client, dId, label, value):
+ tag_url = '/api/resources/tag/v1/DeviceTagAssignmentConfig'
+ payload = {"key":{"label":label, "value":value, "deviceId": dId}}
+ response = client.post(tag_url, data=payload)
+ return response
+
+### Uncomment the below functions/print statement to test
+
+# ### Get all active events
+# print ('=== All active events ===')
+# cvpevents = get_events_all(clnt)
+# for event in cvpevents:
+# print(event)
+
+# ### Get a specific event
+# key = "6098ae39e4c8a9d7"
+# ts ="2021-04-06T21:53:00Z"
+# get_event(clnt, key, ts)
+
+# ### Get events between two dates
+# t1 = "2021-04-06T09:00:00Z"
+# t2 = "2021-04-06T14:00:00Z"
+# events = get_events_t1_t2(clnt, t1, t2)
+# print(f"=== Events between {t1} and {t2} ===")
+# pp(events)
+
+# ### Get all INFO severity events ###
+# # EVENT_SEVERITY_UNSPECIFIED = 0
+# # EVENT_SEVERITY_INFO = 1
+# # EVENT_SEVERITY_WARNING = 2
+# # EVENT_SEVERITY_ERROR = 3
+# # EVENT_SEVERITY_CRITICAL = 4
+# ####################################
+
+# severity = 1 ## Severity INFO
+# info = get_events_by_severity(clnt, severity)
+# print('=== Get all INFO severity events ===')
+# pp(info)
+
+# ### Get specific event types
+
+# etype = "LOW_DEVICE_DISK_SPACE"
+# event = get_events_by_type(clnt, etype)
+# print('=== Get all Low Disk Space events ===')
+# pp(event)
+
+# ### Get the inventory
+# print ('=== Inventory ===')
+# print(get_active_devices(clnt))
+
+# ### Get all devie tags
+# print('=== Device Tags ===' )
+# for tag in get_all_device_tags(clnt):
+# print (tag)
+
+# ### Get all interface tag assignments
+# print(get_all_interface_tags(clnt))
+
+# ### Get all interfaces that have a tag with a specific value on a device
+# print(filter_interface_tag(clnt, dId="JPE14070534", value="speed40Gbps"))
+
+# ### Get all tags for an interface of a device
+# print(filter_interface_tag(clnt, dId="JPE14070534", ifId="Ethernet1"))
+
+# ### Get all interfaces that have a specific tag assigned
+# print(filter_interface_tag(clnt, dId="JPE14070534", label="lldp_hostname"))
+
+# ### Create an interface tag
+# create_itag(clnt, "lldp_chassis", "50:08:00:0d:00:48")
+
+# ### Assign an interface tag
+# assign_itag(clnt, "JPE14070534", "Ethernet4", "lldp_chassis", "50:08:00:0d:00:38")
+
+# ### Create a device tag
+# create_dtag(clnt, "topology_hint_pod", "ire-pod11")
+
+# ### Assign an interface tag
+# assign_dtag(clnt, "JPE14070534", "topology_hint_pod", "ire-pod11" )
diff --git a/docs/labs/static/serviceaccount1.png b/docs/labs/static/serviceaccount1.png
new file mode 100644
index 0000000..b6de68b
Binary files /dev/null and b/docs/labs/static/serviceaccount1.png differ
diff --git a/docs/labs/static/serviceaccount2.png b/docs/labs/static/serviceaccount2.png
new file mode 100644
index 0000000..3b49542
Binary files /dev/null and b/docs/labs/static/serviceaccount2.png differ
diff --git a/docs/labs/static/serviceaccount3.png b/docs/labs/static/serviceaccount3.png
new file mode 100644
index 0000000..2eca99a
Binary files /dev/null and b/docs/labs/static/serviceaccount3.png differ
diff --git a/docs/release-notes-1.0.6.rst b/docs/release-notes-1.0.6.rst
index 170647f..de52e0a 100644
--- a/docs/release-notes-1.0.6.rst
+++ b/docs/release-notes-1.0.6.rst
@@ -7,7 +7,7 @@ v1.0.6
New Modules
^^^^^^^^^^^
-* Started to add api method update_configlet_builder and add test.. (`a32dd7a `_) [`dbm79 `_]
+* Added API method update_configlet_builder and test. (`a32dd7a `_) [`dbm79 `_]
* Added function and test for API endpoint updateReconcileConfiglet.do. (`7e90de9 `_) [`mharista `_]
Enhancements
@@ -19,7 +19,7 @@ Fixed
^^^^^
* Fix client logout function to use cvprac client post function instead of session post function. (`abaf257 `_) [`mharista `_]
-* Mask localhost/127.0.0.1 with node ip for cb scripts. (`d45ac6e `_) [`Rajat Bajaj `_]
+* Mask localhost/127.0.0.1 with node IP for configlet builder scripts. (`d45ac6e `_) [`Rajat Bajaj `_]
* Updating info string to tackle backend inconsistent state when moving devices from the Undefined container. (`82ea8b9 `_) [`noredistribution `_]
* Remove CVaaS un/pw login. Only API tokens for CVaaS now. (`f9fd6b5 `_) [`mharista `_]
* Update redundant functions to self reference. (`0095b00 `_) [`mharista `_]
diff --git a/docs/release-notes-1.0.7.rst b/docs/release-notes-1.0.7.rst
new file mode 100644
index 0000000..137ad10
--- /dev/null
+++ b/docs/release-notes-1.0.7.rst
@@ -0,0 +1,19 @@
+######
+v1.0.7
+######
+
+2021-7-1
+
+New Modules
+^^^^^^^^^^^
+
+* Added new method for searching for a device by serial_number. (`f23e154 `_) [`titom73 `_]
+
+Enhancements
+^^^^^^^^^^^^
+
+* Added form parameter to update_configlet_builder function. (`e5e3719 `_) [`mharista `_]
+* Added parameter to apply_configlets_to_device for reordering existing configlets. (`6681800 `_) [`mharista `_]
+* Added documentation examples of cvprac usage in docs/labs. (`b1c3443 `_) [`noredistribution `_]
+* Added support for searching by hostname to get_device_by_name. (`1eb08cb `_) [`titom73 `_]
+* Added lab example for saving topology with specific tempaction data. (`f3282a2 `_) [`noredistribution `_]
diff --git a/test/system/test_cvp_api.py b/test/system/test_cvp_api.py
index 5a53e9f..0f57b34 100644
--- a/test/system/test_cvp_api.py
+++ b/test/system/test_cvp_api.py
@@ -694,6 +694,20 @@ def test_api_get_device_by_name_substring(self):
self.assertIsNotNone(result)
self.assertEqual(result, {})
+ def test_api_get_device_by_name_search_by_hostname(self):
+ ''' Verify get_device_by_name with hostname portion of fqdn is
+ successful with search_by_hostname parameter set to True
+ '''
+ if 'hostname' in self.device:
+ hostname = self.device['hostname']
+ else:
+ hostname = self.device['hostname'].split('.')[0]
+ result = self.api.get_device_by_name(hostname,
+ search_by_hostname=True)
+ self.assertIsNotNone(result)
+ self.assertEqual(result['fqdn'], self.device['fqdn'])
+ self.assertEqual(result['fqdn'].split('.')[0], hostname)
+
def test_api_get_device_by_mac(self):
''' Verify get_device_by_mac with partial fqdn returns nothing
'''
@@ -709,6 +723,20 @@ def test_api_get_device_by_mac_bad(self):
self.assertIsNotNone(result)
self.assertEqual(result, {})
+ def test_api_get_device_by_serial(self):
+ ''' Verify get_device_by_serial
+ '''
+ result = self.api.get_device_by_serial(self.device['serialNumber'])
+ self.assertIsNotNone(result)
+ self.assertEqual(result['serialNumber'], self.device['serialNumber'])
+
+ def test_api_get_device_by_serial_bad(self):
+ ''' Verify get_device_by_mac with bad serial
+ '''
+ result = self.api.get_device_by_serial('bogus_serial')
+ self.assertIsNotNone(result)
+ self.assertEqual(result, {})
+
def _create_configlet_builder(self, name, config, draft, form=None):
# Delete configlet builder in case it was left by a previous test run
try:
@@ -1137,8 +1165,10 @@ def test_api_container_url_encode_name(self):
def test_api_configlets_to_device(self):
''' Verify apply_configlets_to_device and
- remove_configlets_from_device
+ remove_configlets_from_device. Also test apply_configlets_to_device
+ with reorder_configlets parameter set to True
'''
+ # pylint: disable=too-many-statements
# Create a new configlet
name = 'test_device_configlet'
config = 'alias srie show running-config interface ethernet 1'
@@ -1170,10 +1200,50 @@ def test_api_configlets_to_device(self):
# Execute Task
self._execute_long_running_task(task_id)
+ # Get current configlets order with new configlet before reordering
+ configlets_order = self.api.get_configlets_by_device_id(
+ self.device['systemMacAddress'])
+ # Swap order of last two configlets
+ last_configlet = configlets_order[-1]
+ second_to_last = configlets_order[-2]
+ configlets_order[-1] = second_to_last
+ configlets_order[-2] = last_configlet
+
# Get the next task ID
task_id = self._get_next_task_id()
- # Remove configlet from device
+ # reorder configlets
+ self.api.apply_configlets_to_device(label, self.device,
+ configlets_order, create_task=True,
+ reorder_configlets=True)
+
+ # Validate task was created to remove the configlet to device
+ # Wait 30 seconds for task to get created
+ cnt = 30
+ while cnt > 0:
+ time.sleep(1)
+ result = self.api.get_task_by_id(task_id)
+ if result is not None:
+ break
+ cnt -= 1
+ self.assertIsNotNone(result)
+ self.assertEqual(result['workOrderId'], task_id)
+ self.assertIn(label, result['description'])
+
+ # Execute Task
+ self._execute_long_running_task(task_id)
+
+ # Get reordered configlets
+ configlets_order = self.api.get_configlets_by_device_id(
+ self.device['systemMacAddress'])
+ # Verify order of last two configlets swapped
+ self.assertEqual(configlets_order[-1]['name'], second_to_last['name'])
+ self.assertEqual(configlets_order[-2]['name'], last_configlet['name'])
+
+ # Get the next task ID
+ task_id = self._get_next_task_id()
+
+ # Remove new configlet from device
self.api.remove_configlets_from_device(label, self.device, [param])
# Validate task was created to remove the configlet to device
@@ -1192,7 +1262,7 @@ def test_api_configlets_to_device(self):
# Execute Task
self._execute_long_running_task(task_id)
- # Delete the configlet
+ # Delete the new configlet
self.api.delete_configlet(name, key)
# Check compliance