cvprac 2.0.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cvprac-2.0.0 → cvprac-2.2.0}/PKG-INFO +25 -7
- cvprac-2.2.0/VERSION +1 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac/__init__.py +1 -1
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac/cvp_api.py +124 -15
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac.egg-info/PKG-INFO +25 -7
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac.egg-info/SOURCES.txt +2 -0
- cvprac-2.2.0/docs/release-notes-2.1.0.rst +10 -0
- cvprac-2.2.0/docs/release-notes-2.2.0.rst +10 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/test/fixtures/cvp_nodes.yaml +1 -1
- {cvprac-2.0.0 → cvprac-2.2.0}/test/system/test_cvp_base.py +66 -9
- {cvprac-2.0.0 → cvprac-2.2.0}/test/system/test_cvp_change_control_api.py +123 -12
- {cvprac-2.0.0 → cvprac-2.2.0}/test/system/test_cvp_client_api.py +8 -9
- {cvprac-2.0.0 → cvprac-2.2.0}/test/unit/test_api.py +156 -0
- cvprac-2.0.0/VERSION +0 -1
- {cvprac-2.0.0 → cvprac-2.2.0}/.pylintrc +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/LICENSE +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/MANIFEST.in +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/Makefile +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/README.md +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac/cvp_client.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac/cvp_client_errors.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac.egg-info/dependency_links.txt +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac.egg-info/requires.txt +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac.egg-info/top_level.txt +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/cvprac.spec +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/dev-requirements.txt +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/README.md +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab01-cvp-info/get_cvp_info.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab02-inventory-operations/compliance_check.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab02-inventory-operations/get_running_configs.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab02-inventory-operations/get_running_configs_by_time.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab02-inventory-operations/remove_all_devices_legacy.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab02-inventory-operations/remove_and_decommission_device.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab02-inventory-operations/remove_devices_from_container_legacy.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab02-inventory-operations/remove_devices_legacy.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/assign_configlet_to_device.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/backup_configlets.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/backup_configletsV2.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/common.cfg +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/config_search.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/configlet_list.txt +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/create_configlet.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/create_configlet_from_file.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/get_applied_netelements.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/get_configlets.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/reorder_configlet_on_device.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab03-configlet-management/update_configlet.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab04-container-management/add_image_to_container.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab04-container-management/assign_configlet_to_container.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab04-container-management/create_container.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab04-container-management/remove_image_from_container.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab04-container-management/rename_container.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab05-device-management/add_image_to_devices.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab05-device-management/add_image_wo_tempaction.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab05-device-management/remove_image_from_device.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab05-device-management/set_mgmt_ip.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/atd_e2e_provisioning_workflow.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/auto_reconcile_on_rc_change.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/change_control_custom_rapi.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/change_control_workflow_rapi.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/configlets/AVD_leaf1.cfg +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/configlets/AVD_leaf2.cfg +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/configlets/AVD_leaf3.cfg +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/configlets/AVD_leaf4.cfg +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/configlets/AVD_spine1.cfg +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/configlets/AVD_spine2.cfg +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/gen_builder.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/mlag_issu.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/move_device.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab06-provisioning/vc_task_retrigger.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/aaa_users.csv +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/add_new_user_cvaas.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/add_new_user_onprem.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/add_users_from_csv_cvaas.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/create_svc_account.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/create_svc_account_token.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/create_terminattr_tokens.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/cvaas.tok +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/delete_all_expired_svc_account_tokens.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/delete_svc_account.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/delete_svc_account_created_by_user.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/get_user_info.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab07-aaa/svc_account_misc.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab08-resource-apis/resource_cvprac.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/lab08-resource-apis/topology_tag_assignment.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/static/serviceaccount1.png +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/static/serviceaccount2.png +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/labs/static/serviceaccount3.png +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-0.7.0.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-0.8.0.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-0.9.0.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.0.0.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.0.1.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.0.2.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.0.3.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.0.4.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.0.5.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.0.6.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.0.7.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.2.0.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.2.2.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.3.0.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.3.1.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.3.2.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.4.0.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.4.1.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-1.4.2.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/docs/release-notes-2.0.0.rst +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/requirements.txt +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/setup.cfg +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/setup.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/test/fixtures/image-file.swix +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/test/lib/systestlib.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/test/system/test_cvp_client.py +0 -0
- {cvprac-2.0.0 → cvprac-2.2.0}/test/unit/test_client.py +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cvprac
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Arista Cloudvision(R) Portal Rest API Client written in python
|
|
5
5
|
Home-page: https://github.com/aristanetworks/cvprac
|
|
6
|
+
Download-URL: https://github.com/aristanetworks/cvprac/tarball/2.2.0
|
|
6
7
|
Author: Arista Networks, Inc.
|
|
7
8
|
Author-email: eosplus-dev@arista.com
|
|
8
9
|
License: BSD-3
|
|
9
|
-
Download-URL: https://github.com/aristanetworks/cvprac/tarball/2.0.0
|
|
10
10
|
Keywords: networking CloudVision development rest api
|
|
11
|
-
Platform: UNKNOWN
|
|
12
11
|
Classifier: Development Status :: 4 - Beta
|
|
13
12
|
Classifier: Intended Audience :: Developers
|
|
14
13
|
Classifier: Intended Audience :: System Administrators
|
|
@@ -24,8 +23,29 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
25
|
Description-Content-Type: text/markdown
|
|
27
|
-
Provides-Extra: dev
|
|
28
26
|
License-File: LICENSE
|
|
27
|
+
Requires-Dist: requests[socks]>=2.27.0
|
|
28
|
+
Requires-Dist: packaging>=23.2
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: check-manifest; extra == "dev"
|
|
31
|
+
Requires-Dist: pep8; extra == "dev"
|
|
32
|
+
Requires-Dist: pyflakes; extra == "dev"
|
|
33
|
+
Requires-Dist: pylint; extra == "dev"
|
|
34
|
+
Requires-Dist: coverage; extra == "dev"
|
|
35
|
+
Requires-Dist: pyyaml; extra == "dev"
|
|
36
|
+
Dynamic: author
|
|
37
|
+
Dynamic: author-email
|
|
38
|
+
Dynamic: classifier
|
|
39
|
+
Dynamic: description
|
|
40
|
+
Dynamic: description-content-type
|
|
41
|
+
Dynamic: download-url
|
|
42
|
+
Dynamic: home-page
|
|
43
|
+
Dynamic: keywords
|
|
44
|
+
Dynamic: license
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
Dynamic: provides-extra
|
|
47
|
+
Dynamic: requires-dist
|
|
48
|
+
Dynamic: summary
|
|
29
49
|
|
|
30
50
|
# Arista Cloudvision® Portal RESTful API Client
|
|
31
51
|
|
|
@@ -478,5 +498,3 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
478
498
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
479
499
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
480
500
|
THE POSSIBILITY OF SUCH DAMAGE.
|
|
481
|
-
|
|
482
|
-
|
cvprac-2.2.0/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.2.0
|
|
@@ -471,6 +471,22 @@ class CvpApi():
|
|
|
471
471
|
return self.clnt.post('/task/cancelTask.do', data=data,
|
|
472
472
|
timeout=self.request_timeout)
|
|
473
473
|
|
|
474
|
+
def get_config_diff_for_task(self, task_id):
|
|
475
|
+
''' Get the config diff and validation errors for a specific task.
|
|
476
|
+
Uses the compliance check API to compare designed vs running config.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
task_id (str): The task ID to check.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
response (list): A list of dicts containing sources, diff, and
|
|
483
|
+
error entries. Error entries have error_code, error_msg,
|
|
484
|
+
line_num, configlet_name, and config_line_num fields.
|
|
485
|
+
'''
|
|
486
|
+
diff_url = '/api/v3/services/compliancecheck.Compliance/GetConfigDiffForTask'
|
|
487
|
+
return self.clnt.post(diff_url, data={"task_id": str(task_id)},
|
|
488
|
+
timeout=self.request_timeout)
|
|
489
|
+
|
|
474
490
|
def get_configlets(self, start=0, end=0):
|
|
475
491
|
''' Returns a list of all defined configlets.
|
|
476
492
|
|
|
@@ -685,6 +701,7 @@ class CvpApi():
|
|
|
685
701
|
return data
|
|
686
702
|
|
|
687
703
|
# pylint: disable=too-many-locals
|
|
704
|
+
# pylint: disable=too-many-branches
|
|
688
705
|
def add_devices_to_inventory(self, device_list, wait=False, move_to_container=True):
|
|
689
706
|
''' Add list of devices to inventory and optionally move them to specified parent container.
|
|
690
707
|
|
|
@@ -714,6 +731,10 @@ class CvpApi():
|
|
|
714
731
|
parent_key: 'container-id-1234'
|
|
715
732
|
}
|
|
716
733
|
]
|
|
734
|
+
Returns: Dict of devices that failed to onboard in below format. None if all devices
|
|
735
|
+
onboarded successfully.
|
|
736
|
+
|
|
737
|
+
{'192.168.0.11': "ERROR"}
|
|
717
738
|
'''
|
|
718
739
|
|
|
719
740
|
self.log.debug('add_devices_to_inventory: called')
|
|
@@ -735,17 +756,27 @@ class CvpApi():
|
|
|
735
756
|
self.clnt.post('/inventory/add/addToInventory.do?'
|
|
736
757
|
'startIndex=0&endIndex=0', data=data,
|
|
737
758
|
timeout=self.request_timeout)
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
# Create a list of device IPs
|
|
742
|
-
device_ips = [dev['device_ip'] for dev in device_list]
|
|
743
|
-
|
|
744
|
-
# First add the devices to the inventory in a single call
|
|
745
|
-
data = {'hosts': device_ips}
|
|
746
|
-
self.clnt.post('/inventory/devices', data=data,
|
|
747
|
-
timeout=self.request_timeout)
|
|
759
|
+
return None
|
|
760
|
+
self.log.debug('v2 Inventory API Call')
|
|
748
761
|
|
|
762
|
+
# Create a list of device IPs
|
|
763
|
+
device_ips = [dev['device_ip'] for dev in device_list]
|
|
764
|
+
|
|
765
|
+
# First add the devices to the inventory in a single call
|
|
766
|
+
data = {'hosts': device_ips}
|
|
767
|
+
failed_onboard = None
|
|
768
|
+
resp = self.clnt.post('/inventory/devices', data=data, timeout=self.request_timeout)
|
|
769
|
+
if resp and 'failed' in resp['data']:
|
|
770
|
+
failed_onboard = resp['data']['failed']
|
|
771
|
+
for failed_dev_ip in failed_onboard:
|
|
772
|
+
self.log.warning(
|
|
773
|
+
'Found error onboarding device %s - %s',
|
|
774
|
+
failed_dev_ip,
|
|
775
|
+
failed_onboard[failed_dev_ip]
|
|
776
|
+
)
|
|
777
|
+
device_ips.remove(failed_dev_ip)
|
|
778
|
+
|
|
779
|
+
if device_ips:
|
|
749
780
|
# Get the inventory list
|
|
750
781
|
inv = self.get_inventory()
|
|
751
782
|
|
|
@@ -771,11 +802,14 @@ class CvpApi():
|
|
|
771
802
|
for device in device_list:
|
|
772
803
|
devs = [dev for dev in inv if 'ipAddress' in dev and
|
|
773
804
|
device['device_ip'] in dev['ipAddress']]
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
805
|
+
# Only attempt to move device to another container if it is in inventory
|
|
806
|
+
if devs:
|
|
807
|
+
dev = devs[0]
|
|
808
|
+
container = {'key': device['parent_key'],
|
|
809
|
+
'name': device['parent_name']}
|
|
810
|
+
self.move_device_to_container('add_devices_to_inventory API v2',
|
|
811
|
+
dev, container, False)
|
|
812
|
+
return failed_onboard
|
|
779
813
|
|
|
780
814
|
def add_device_to_inventory(self, device_ip, parent_name,
|
|
781
815
|
parent_key, wait=False, move_to_container=True):
|
|
@@ -3040,6 +3074,24 @@ class CvpApi():
|
|
|
3040
3074
|
return self.clnt.get(cc_url, timeout=self.request_timeout)
|
|
3041
3075
|
return None
|
|
3042
3076
|
|
|
3077
|
+
def change_control_get_task_errors(self, cc_id):
|
|
3078
|
+
''' Get task configuration errors for a change control.
|
|
3079
|
+
Supported versions: CVP 2021.2.0 or newer and CVaaS.
|
|
3080
|
+
|
|
3081
|
+
Args:
|
|
3082
|
+
cc_id (str): The ID of the change control.
|
|
3083
|
+
|
|
3084
|
+
Returns:
|
|
3085
|
+
errors (list): A list of (task_id, error_dict) tuples for any tasks
|
|
3086
|
+
with DEVICEERROR entries. Empty list if no errors found.
|
|
3087
|
+
Returns None if the change control does not exist or the API
|
|
3088
|
+
is unsupported.
|
|
3089
|
+
'''
|
|
3090
|
+
cc_status = self.change_control_get_one(cc_id)
|
|
3091
|
+
if cc_status is None:
|
|
3092
|
+
return None
|
|
3093
|
+
return self._parse_task_errors_from_cc_data(cc_status)
|
|
3094
|
+
|
|
3043
3095
|
def change_control_approval_get_one(self, cc_id, cc_time=None):
|
|
3044
3096
|
''' Get the state of a specific Change Control's approve config using Resource APIs.
|
|
3045
3097
|
Supported versions: CVP 2021.2.0 or newer and CVaaS.
|
|
@@ -3093,6 +3145,10 @@ class CvpApi():
|
|
|
3093
3145
|
''' Approve/Unapprove a change control using Resource APIs.
|
|
3094
3146
|
Supported versions: CVP 2021.2.0 or newer and CVaaS.
|
|
3095
3147
|
|
|
3148
|
+
When approving (approve=True), validates that no tasks in the
|
|
3149
|
+
change control have configuration errors. Raises CvpApiError
|
|
3150
|
+
if any task has DEVICEERROR entries from the compliance check.
|
|
3151
|
+
|
|
3096
3152
|
Args:
|
|
3097
3153
|
cc_id (str): The ID of the change control.
|
|
3098
3154
|
notes (str): An optional approval note.
|
|
@@ -3111,6 +3167,19 @@ class CvpApi():
|
|
|
3111
3167
|
else:
|
|
3112
3168
|
self.log.error('The version timestamp was not found in the CC status.')
|
|
3113
3169
|
return None
|
|
3170
|
+
|
|
3171
|
+
if approve:
|
|
3172
|
+
task_errors = self._parse_task_errors_from_cc_data(cc_status)
|
|
3173
|
+
if task_errors:
|
|
3174
|
+
error_details = "; ".join(
|
|
3175
|
+
f"Task {tid}: {err['error_msg']}"
|
|
3176
|
+
for tid, err in task_errors
|
|
3177
|
+
)
|
|
3178
|
+
raise CvpApiError(
|
|
3179
|
+
f"Cannot approve change control {cc_id}. "
|
|
3180
|
+
f"Tasks have configuration errors: {error_details}"
|
|
3181
|
+
)
|
|
3182
|
+
|
|
3114
3183
|
payload = {
|
|
3115
3184
|
"key": {
|
|
3116
3185
|
"id": cc_id
|
|
@@ -3123,6 +3192,46 @@ class CvpApi():
|
|
|
3123
3192
|
}
|
|
3124
3193
|
return self.clnt.post(cc_url, data=payload, timeout=self.request_timeout)
|
|
3125
3194
|
|
|
3195
|
+
def _parse_task_errors_from_cc_data(self, cc_data):
|
|
3196
|
+
''' Extract task IDs from a change control data and check each for config errors.
|
|
3197
|
+
Make an API call on each task and parse the config diff for errors.
|
|
3198
|
+
|
|
3199
|
+
Args:
|
|
3200
|
+
cc_data (dict): The change control dict from change_control_get_one.
|
|
3201
|
+
|
|
3202
|
+
Returns:
|
|
3203
|
+
errors (list): A list of (task_id, error_dict) tuples for any tasks
|
|
3204
|
+
with DEVICEERROR entries. Empty list if no errors found.
|
|
3205
|
+
error_dict example: {
|
|
3206
|
+
"error_code": "DEVICEERROR",
|
|
3207
|
+
"error_msg": "> typocommand test % Invalid input (at token 0: 'typocommand')",
|
|
3208
|
+
"line_num": 50,
|
|
3209
|
+
"configlet_name": "device-name",
|
|
3210
|
+
"config_line_num": 54
|
|
3211
|
+
}
|
|
3212
|
+
'''
|
|
3213
|
+
errors = []
|
|
3214
|
+
stages = (cc_data.get('value', {}).get('change', {})
|
|
3215
|
+
.get('stages', {}).get('values', {}))
|
|
3216
|
+
for stage in stages.values():
|
|
3217
|
+
action = stage.get('action', {})
|
|
3218
|
+
if action.get('name') == 'task':
|
|
3219
|
+
task_id = (action.get('args', {}).get('values', {})
|
|
3220
|
+
.get('TaskID'))
|
|
3221
|
+
if task_id:
|
|
3222
|
+
try:
|
|
3223
|
+
resp = self.get_config_diff_for_task(task_id)
|
|
3224
|
+
except Exception as error:
|
|
3225
|
+
self.log.error(
|
|
3226
|
+
f"Failed to get config diff for task {task_id}: {error}")
|
|
3227
|
+
continue
|
|
3228
|
+
if isinstance(resp, list):
|
|
3229
|
+
for entry in resp:
|
|
3230
|
+
err = entry.get('error') if isinstance(entry, dict) else None
|
|
3231
|
+
if err and err.get('error_code') == 'DEVICEERROR':
|
|
3232
|
+
errors.append((task_id, err))
|
|
3233
|
+
return errors
|
|
3234
|
+
|
|
3126
3235
|
def change_control_delete(self, cc_id):
|
|
3127
3236
|
''' Delete a pending Change Control using Resource APIs.
|
|
3128
3237
|
Supported versions: CVP 2021.2.0 or newer and CVaaS.
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cvprac
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Arista Cloudvision(R) Portal Rest API Client written in python
|
|
5
5
|
Home-page: https://github.com/aristanetworks/cvprac
|
|
6
|
+
Download-URL: https://github.com/aristanetworks/cvprac/tarball/2.2.0
|
|
6
7
|
Author: Arista Networks, Inc.
|
|
7
8
|
Author-email: eosplus-dev@arista.com
|
|
8
9
|
License: BSD-3
|
|
9
|
-
Download-URL: https://github.com/aristanetworks/cvprac/tarball/2.0.0
|
|
10
10
|
Keywords: networking CloudVision development rest api
|
|
11
|
-
Platform: UNKNOWN
|
|
12
11
|
Classifier: Development Status :: 4 - Beta
|
|
13
12
|
Classifier: Intended Audience :: Developers
|
|
14
13
|
Classifier: Intended Audience :: System Administrators
|
|
@@ -24,8 +23,29 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
25
|
Description-Content-Type: text/markdown
|
|
27
|
-
Provides-Extra: dev
|
|
28
26
|
License-File: LICENSE
|
|
27
|
+
Requires-Dist: requests[socks]>=2.27.0
|
|
28
|
+
Requires-Dist: packaging>=23.2
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: check-manifest; extra == "dev"
|
|
31
|
+
Requires-Dist: pep8; extra == "dev"
|
|
32
|
+
Requires-Dist: pyflakes; extra == "dev"
|
|
33
|
+
Requires-Dist: pylint; extra == "dev"
|
|
34
|
+
Requires-Dist: coverage; extra == "dev"
|
|
35
|
+
Requires-Dist: pyyaml; extra == "dev"
|
|
36
|
+
Dynamic: author
|
|
37
|
+
Dynamic: author-email
|
|
38
|
+
Dynamic: classifier
|
|
39
|
+
Dynamic: description
|
|
40
|
+
Dynamic: description-content-type
|
|
41
|
+
Dynamic: download-url
|
|
42
|
+
Dynamic: home-page
|
|
43
|
+
Dynamic: keywords
|
|
44
|
+
Dynamic: license
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
Dynamic: provides-extra
|
|
47
|
+
Dynamic: requires-dist
|
|
48
|
+
Dynamic: summary
|
|
29
49
|
|
|
30
50
|
# Arista Cloudvision® Portal RESTful API Client
|
|
31
51
|
|
|
@@ -478,5 +498,3 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
478
498
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
479
499
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
480
500
|
THE POSSIBILITY OF SUCH DAMAGE.
|
|
481
|
-
|
|
482
|
-
|
|
@@ -37,6 +37,8 @@ docs/release-notes-1.4.0.rst
|
|
|
37
37
|
docs/release-notes-1.4.1.rst
|
|
38
38
|
docs/release-notes-1.4.2.rst
|
|
39
39
|
docs/release-notes-2.0.0.rst
|
|
40
|
+
docs/release-notes-2.1.0.rst
|
|
41
|
+
docs/release-notes-2.2.0.rst
|
|
40
42
|
docs/labs/README.md
|
|
41
43
|
docs/labs/lab01-cvp-info/get_cvp_info.py
|
|
42
44
|
docs/labs/lab02-inventory-operations/compliance_check.py
|
|
@@ -13,6 +13,7 @@ import urllib3
|
|
|
13
13
|
sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))
|
|
14
14
|
from systestlib import DutSystemTest
|
|
15
15
|
from cvprac.cvp_client import CvpClient
|
|
16
|
+
from cvprac.cvp_client_errors import CvpApiError
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
@@ -101,7 +102,6 @@ class TestCvpClientBase(DutSystemTest):
|
|
|
101
102
|
'''
|
|
102
103
|
self.task_id = None
|
|
103
104
|
self.cc_id = str(uuid.uuid4())
|
|
104
|
-
# self.cc_name = 'test_api_%d %s' % time.time()
|
|
105
105
|
self.cc_name = f'test_api_{time.time()}'
|
|
106
106
|
|
|
107
107
|
def tearDown(self):
|
|
@@ -113,11 +113,25 @@ class TestCvpClientBase(DutSystemTest):
|
|
|
113
113
|
if chg_ctrl_get_one:
|
|
114
114
|
# Delete CC
|
|
115
115
|
self.delete_change_control(self.cc_id)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
self.cc_id = None
|
|
117
|
+
# Cancel task
|
|
118
|
+
if self.task_id:
|
|
119
|
+
# Do not attempt to cancel tasks with taskStatus
|
|
120
|
+
# in following list.
|
|
121
|
+
ignore_task_status = ["COMPLETED"]
|
|
122
|
+
# Do not attempt to cancel tasks with currentTaskName
|
|
123
|
+
# in following list.
|
|
124
|
+
ignore_current_task_name = ["Cancelled"]
|
|
125
|
+
task = self.api.get_task_by_id(self.task_id)
|
|
126
|
+
if task:
|
|
127
|
+
if task["currentTaskName"] not in ignore_current_task_name:
|
|
128
|
+
if task["taskStatus"] not in ignore_task_status:
|
|
129
|
+
try:
|
|
130
|
+
self.cancel_task(self.task_id)
|
|
131
|
+
except CvpApiError as err:
|
|
132
|
+
print(f"Error while trying to cancel task {self.task_id} - {err}")
|
|
133
|
+
print(f"Task info - {task}")
|
|
134
|
+
self.task_id = None
|
|
121
135
|
|
|
122
136
|
def _get_next_task_id(self):
|
|
123
137
|
''' Return the next task id.
|
|
@@ -146,10 +160,10 @@ class TestCvpClientBase(DutSystemTest):
|
|
|
146
160
|
configlet = None
|
|
147
161
|
for conf in self.dev_configlets:
|
|
148
162
|
if conf['netElementCount'] == 1:
|
|
149
|
-
configlet = conf
|
|
163
|
+
configlet = conf.copy()
|
|
150
164
|
break
|
|
151
165
|
if configlet is None:
|
|
152
|
-
configlet = self.dev_configlets[0]
|
|
166
|
+
configlet = self.dev_configlets[0].copy()
|
|
153
167
|
|
|
154
168
|
config = configlet['config']
|
|
155
169
|
org_config = config
|
|
@@ -184,7 +198,50 @@ class TestCvpClientBase(DutSystemTest):
|
|
|
184
198
|
err_msg = f'Timeout waiting for task id {task_id} to be created'
|
|
185
199
|
self.assertGreater(cnt, 0, msg=err_msg)
|
|
186
200
|
self.task_id = task_id
|
|
187
|
-
return task_id, org_config
|
|
201
|
+
return task_id, org_config, configlet
|
|
202
|
+
|
|
203
|
+
def _create_task_with_invalid_config(self):
|
|
204
|
+
''' Create a task by adding an invalid EOS command to a configlet.
|
|
205
|
+
The invalid command will cause a DEVICEERROR in the config diff
|
|
206
|
+
compliance check, which is used to test the task error validation
|
|
207
|
+
in change_control_approve.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
(task_id, org_config, configlet)
|
|
211
|
+
task_id (str): Task ID
|
|
212
|
+
org_config (str): Original configlet contents for cleanup
|
|
213
|
+
configlet (dict): The configlet dict for cleanup
|
|
214
|
+
'''
|
|
215
|
+
task_id = self._get_next_task_id()
|
|
216
|
+
configlet = None
|
|
217
|
+
for conf in self.dev_configlets:
|
|
218
|
+
if conf['netElementCount'] == 1:
|
|
219
|
+
configlet = conf.copy()
|
|
220
|
+
break
|
|
221
|
+
if configlet is None:
|
|
222
|
+
configlet = self.dev_configlets[0].copy()
|
|
223
|
+
|
|
224
|
+
org_config = configlet['config']
|
|
225
|
+
config = org_config + '\ntypocommand test\n'
|
|
226
|
+
configlet['config'] = config
|
|
227
|
+
|
|
228
|
+
self.api.update_configlet(config, configlet['key'], configlet['name'])
|
|
229
|
+
|
|
230
|
+
cnt = 30
|
|
231
|
+
if self.clnt.apiversion is None:
|
|
232
|
+
self.api.get_cvp_info()
|
|
233
|
+
if self.clnt.apiversion >= 2.0:
|
|
234
|
+
cnt += 30
|
|
235
|
+
while cnt > 0:
|
|
236
|
+
time.sleep(1)
|
|
237
|
+
result = self.api.get_task_by_id(task_id)
|
|
238
|
+
if result is not None:
|
|
239
|
+
break
|
|
240
|
+
cnt -= 1
|
|
241
|
+
err_msg = f'Timeout waiting for task id {task_id} to be created'
|
|
242
|
+
self.assertGreater(cnt, 0, msg=err_msg)
|
|
243
|
+
self.task_id = task_id
|
|
244
|
+
return task_id, org_config, configlet
|
|
188
245
|
|
|
189
246
|
def delete_change_control(self, cc_id):
|
|
190
247
|
""" Delete change control
|