1
0
Fork 0

Merging upstream version 1.4.0+dfsg.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-05 14:18:51 +01:00
parent 295cee048d
commit c306a1dcc2
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
10 changed files with 509 additions and 495 deletions

View file

@ -49,7 +49,9 @@ coverage_report:
pep8: pep8:
-pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE) cvprac/ -pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE) cvprac/
-pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE),E402 test/ -pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE),E402 test/lib/
-pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE),E402 test/system/
-pep8 -r --ignore=$(PEP8_IGNORE),E402,E501 test/unit/
pyflakes: pyflakes:
pyflakes cvprac/ test/ pyflakes cvprac/ test/

View file

@ -1 +1 @@
1.3.2 1.4.0

View file

@ -32,5 +32,5 @@
''' RESTful API Client class for Cloudvision(R) Portal ''' RESTful API Client class for Cloudvision(R) Portal
''' '''
__version__ = '1.3.2' __version__ = '1.4.0'
__author__ = 'Arista Networks, Inc.' __author__ = 'Arista Networks, Inc.'

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,9 @@
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# #
# pylint: disable=too-many-branches,too-many-statements,too-many-locals,too-many-lines
''' RESTful API Client class for Cloudvision(R) Portal ''' RESTful API Client class for Cloudvision(R) Portal
This module provides a RESTful API client for Cloudvision(R) Portal (CVP) This module provides a RESTful API client for Cloudvision(R) Portal (CVP)
@ -96,18 +99,24 @@ import json
import logging import logging
from logging.handlers import SysLogHandler from logging.handlers import SysLogHandler
from itertools import cycle from itertools import cycle
from pkg_resources import parse_version from packaging.version import parse
import requests import requests
from requests.exceptions import ConnectionError, HTTPError, Timeout, \ from requests.exceptions import ( # pylint: disable=redefined-builtin
ReadTimeout, TooManyRedirects, JSONDecodeError ConnectionError,
HTTPError,
Timeout,
ReadTimeout,
TooManyRedirects,
JSONDecodeError
)
from cvprac.cvp_api import CvpApi from cvprac.cvp_api import CvpApi
from cvprac.cvp_client_errors import CvpApiError, CvpLoginError, \ from cvprac.cvp_client_errors import CvpApiError, CvpLoginError, \
CvpRequestError, CvpSessionLogOutError CvpRequestError, CvpSessionLogOutError
class CvpClient(object): class CvpClient():
''' Use this class to create a persistent connection to CVP. ''' Use this class to create a persistent connection to CVP.
''' '''
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
@ -233,28 +242,37 @@ class CvpClient(object):
' Appending 0. Updated Version String - %s', ' Appending 0. Updated Version String - %s',
".".join(version_components)) ".".join(version_components))
full_version = ".".join(version_components) full_version = ".".join(version_components)
if parse_version(full_version) >= parse_version('2023.1.0'): if parse(full_version) >= parse('2024.1.0'):
self.log.info('Setting API version to v12')
self.apiversion = 12.0
elif parse(full_version) >= parse('2023.3.0'):
self.log.info('Setting API version to v11')
self.apiversion = 11.0
elif parse(full_version) >= parse('2023.2.0'):
self.log.info('Setting API version to v10')
self.apiversion = 10.0
elif parse(full_version) >= parse('2023.1.0'):
self.log.info('Setting API version to v9') self.log.info('Setting API version to v9')
self.apiversion = 9.0 self.apiversion = 9.0
elif parse_version(full_version) >= parse_version('2022.1.0'): elif parse(full_version) >= parse('2022.1.0'):
self.log.info('Setting API version to v8') self.log.info('Setting API version to v8')
self.apiversion = 8.0 self.apiversion = 8.0
elif parse_version(full_version) >= parse_version('2021.3.0'): elif parse(full_version) >= parse('2021.3.0'):
self.log.info('Setting API version to v7') self.log.info('Setting API version to v7')
self.apiversion = 7.0 self.apiversion = 7.0
elif parse_version(full_version) >= parse_version('2021.2.0'): elif parse(full_version) >= parse('2021.2.0'):
self.log.info('Setting API version to v6') self.log.info('Setting API version to v6')
self.apiversion = 6.0 self.apiversion = 6.0
elif parse_version(full_version) >= parse_version('2020.2.4'): elif parse(full_version) >= parse('2020.2.4'):
self.log.info('Setting API version to v5') self.log.info('Setting API version to v5')
self.apiversion = 5.0 self.apiversion = 5.0
elif parse_version(full_version) >= parse_version('2020.1.1'): elif parse(full_version) >= parse('2020.1.1'):
self.log.info('Setting API version to v4') self.log.info('Setting API version to v4')
self.apiversion = 4.0 self.apiversion = 4.0
elif parse_version(full_version) >= parse_version('2019.0.0'): elif parse(full_version) >= parse('2019.0.0'):
self.log.info('Setting API version to v3') self.log.info('Setting API version to v3')
self.apiversion = 3.0 self.apiversion = 3.0
elif parse_version(full_version) >= parse_version('2018.2.0'): elif parse(full_version) >= parse('2018.2.0'):
self.log.info('Setting API version to v2') self.log.info('Setting API version to v2')
self.apiversion = 2.0 self.apiversion = 2.0
else: else:
@ -378,13 +396,12 @@ class CvpClient(object):
self.error_msg = '\n' self.error_msg = '\n'
for _ in range(0, num_nodes): for _ in range(0, num_nodes):
host = next(self.node_pool) host = next(self.node_pool)
self.url_prefix = ('https://%s:%d/web' % (host, self.port or 443)) self.url_prefix = f"https://{host}:{self.port or 443}/web"
self.url_prefix_short = ('https://%s:%d' self.url_prefix_short = f"https://{host}:{self.port or 443}"
% (host, self.port or 443))
error = self._reset_session() error = self._reset_session()
if error is None: if error is None:
break break
self.error_msg += '%s: %s\n' % (host, error) self.error_msg += f"{host}: {error}\n"
def _reset_session(self): def _reset_session(self):
''' Get a new request session and try logging into the current ''' Get a new request session and try logging into the current
@ -428,23 +445,20 @@ class CvpClient(object):
if 'Unauthorized' in response.reason: if 'Unauthorized' in response.reason:
# Check for 'Unauthorized' User error because this is how # Check for 'Unauthorized' User error because this is how
# CVP responds to a logged out users requests in 2018.x. # CVP responds to a logged out users requests in 2018.x.
msg = '%s: Request Error: %s' % (prefix, response.reason) msg = f"{prefix}: Request Error: {response.reason}"
self.log.error(msg) self.log.error(msg)
raise CvpApiError(msg) raise CvpApiError(msg)
if 'User is unauthorized' in response.text: if 'User is unauthorized' in response.text:
# Check for 'User is unauthorized' response text because this # Check for 'User is unauthorized' response text because this
# is how CVP responds to a logged out users requests in 2019.x. # is how CVP responds to a logged out users requests in 2019.x.
msg = '%s: Request Error: User is unauthorized' % prefix msg = f"{prefix}: Request Error: User is unauthorized"
self.log.error(msg) self.log.error(msg)
raise CvpApiError(msg) raise CvpApiError(msg)
else: msg = f"{prefix}: Request Error: {response.reason} - {response.text}"
msg = '%s: Request Error: %s - %s' % (prefix, response.reason, raise CvpRequestError(msg)
response.text)
self.log.error(msg)
raise CvpRequestError(msg)
if 'LOG OUT MESSAGE' in response.text: if 'LOG OUT MESSAGE' in response.text:
msg = ('%s: Request Error: session logged out' % prefix) msg = f"{prefix}: Request Error: session logged out"
raise CvpSessionLogOutError(msg) raise CvpSessionLogOutError(msg)
joutput = json_decoder(response.text) joutput = json_decoder(response.text)
@ -460,9 +474,9 @@ class CvpClient(object):
# Build the error message from all the errors. # Build the error message from all the errors.
err_msg = error_list[0] err_msg = error_list[0]
for idx in range(1, len(error_list)): for idx in range(1, len(error_list)):
err_msg = '%s\n%s' % (err_msg, error_list[idx]) err_msg = f"{err_msg}\n{error_list[idx]}"
msg = ('%s: Request Error: %s' % (prefix, err_msg)) msg = f"{prefix}: Request Error: {err_msg}"
self.log.error(msg) self.log.error(msg)
raise CvpApiError(msg) raise CvpApiError(msg)
@ -477,8 +491,7 @@ class CvpClient(object):
response status is not OK. response status is not OK.
''' '''
if not response.ok: if not response.ok:
msg = '%s: Request Error: %s - %s' % (prefix, response.reason, msg = f"{prefix}: Request Error: {response.reason} - {response.text}"
response.text)
self.log.error(msg) self.log.error(msg)
raise CvpRequestError(msg) raise CvpRequestError(msg)
@ -512,7 +525,7 @@ class CvpClient(object):
self.headers.pop('APP_SESSION_ID', None) self.headers.pop('APP_SESSION_ID', None)
if self.api_token is not None: if self.api_token is not None:
return self._set_headers_api_token() return self._set_headers_api_token()
elif self.is_cvaas: if self.is_cvaas:
raise CvpLoginError('CVaaS only supports API token authentication.' raise CvpLoginError('CVaaS only supports API token authentication.'
' Please create an API token and provide it' ' Please create an API token and provide it'
' via the api_token parameter in combination' ' via the api_token parameter in combination'
@ -551,7 +564,7 @@ class CvpClient(object):
headers=self.headers, headers=self.headers,
timeout=self.connect_timeout, timeout=self.connect_timeout,
verify=self.cert) verify=self.cert)
self._is_good_response(response, 'Authenticate: %s' % url) self._is_good_response(response, f"Authenticate: {url}")
self.cookies = response.cookies self.cookies = response.cookies
self.headers['APP_SESSION_ID'] = response.json()['sessionId'] self.headers['APP_SESSION_ID'] = response.json()['sessionId']
@ -561,18 +574,20 @@ class CvpClient(object):
''' '''
# If using an API token there is no need to run a Login API. # If using an API token there is no need to run a Login API.
# Simply add the token into the headers or cookies # Simply add the token into the headers or cookies
self.headers['Authorization'] = 'Bearer %s' % self.api_token self.headers['Authorization'] = f"Bearer {self.api_token}"
# Alternative to adding token to headers it can be added to # Alternative to adding token to headers it can be added to
# cookies as shown below. # cookies as shown below.
# self.cookies = {'access_token': self.api_token} # self.cookies = {'access_token': self.api_token}
url = self.url_prefix_short + '/api/v1/rest/' url = self.url_prefix_short + '/api/v1/rest/'
response = self.session.get(url, response = self.session.get(
cookies=self.cookies, url,
headers=self.headers, cookies=self.cookies,
timeout=self.connect_timeout, headers=self.headers,
verify=self.cert) timeout=self.connect_timeout,
verify=self.cert
)
# Verify that the generic request was successful # Verify that the generic request was successful
self._is_good_response(response, 'Authenticate: %s' % url) self._is_good_response(response, f"Authenticate: {url}")
def logout(self): def logout(self):
''' '''
@ -584,7 +599,7 @@ class CvpClient(object):
self.log.info('User logged out.') self.log.info('User logged out.')
self.session = None self.session = None
else: else:
err = 'Error trying to logout %s' % response err = f"Error trying to logout {response}"
self.log.error(err) self.log.error(err)
def _make_request(self, req_type, url, timeout, data=None, def _make_request(self, req_type, url, timeout, data=None,
@ -700,8 +715,8 @@ class CvpClient(object):
try: try:
resp_data = response.json() resp_data = response.json()
if (resp_data is not None and 'result' in resp_data if (resp_data is not None and 'result' in resp_data and
and '/resources/' in full_url): '/resources/' in full_url):
# Resource APIs use JSON streaming and will return # Resource APIs use JSON streaming and will return
# multiple JSON objects during GetAll type API # multiple JSON objects during GetAll type API
# calls. We are wrapping the multiple objects into # calls. We are wrapping the multiple objects into
@ -725,10 +740,8 @@ class CvpClient(object):
' response data. Attempt to decode') ' response data. Attempt to decode')
decoded_data = json_decoder(response.text) decoded_data = json_decoder(response.text)
return {'data': decoded_data} return {'data': decoded_data}
else: self.log.error("Unknown format for JSONDecodeError - %s", err_str)
self.log.error('Unknown format for JSONDecodeError - %s', raise error
err_str)
raise error
def _send_request(self, req_type, full_url, timeout, data=None, def _send_request(self, req_type, full_url, timeout, data=None,
files=None): files=None):
@ -795,7 +808,7 @@ class CvpClient(object):
timeout=timeout, timeout=timeout,
verify=self.cert) verify=self.cert)
else: else:
fhs = dict() fhs = {}
fhs['Accept'] = self.headers['Accept'] fhs['Accept'] = self.headers['Accept']
if 'APP_SESSION_ID' in self.headers: if 'APP_SESSION_ID' in self.headers:
fhs['APP_SESSION_ID'] = self.headers[ fhs['APP_SESSION_ID'] = self.headers[
@ -830,8 +843,7 @@ class CvpClient(object):
continue continue
try: try:
self._is_good_response(response, '%s: %s ' % self._is_good_response(response, f"{req_type}: {full_url} ")
(req_type, full_url))
except CvpSessionLogOutError as error: except CvpSessionLogOutError as error:
self.log.debug(error) self.log.debug(error)
# Retry the request to the same node if there was a CVP session # Retry the request to the same node if there was a CVP session
@ -840,11 +852,10 @@ class CvpClient(object):
# be retried on the same node. # be retried on the same node.
if req_try + 1 == self.NUM_RETRY_REQUESTS: if req_try + 1 == self.NUM_RETRY_REQUESTS:
raise error raise error
else: self._reset_session()
self._reset_session() if not self.session:
if not self.session: raise error
raise error continue
continue
except CvpApiError as error: except CvpApiError as error:
self.log.debug(error) self.log.debug(error)
if ('Unauthorized' in error.msg or if ('Unauthorized' in error.msg or
@ -859,14 +870,12 @@ class CvpClient(object):
# will be retried on the same node. # will be retried on the same node.
if req_try + 1 == self.NUM_RETRY_REQUESTS: if req_try + 1 == self.NUM_RETRY_REQUESTS:
raise error raise error
else: self._reset_session()
self._reset_session() if not self.session:
if not self.session: raise error
raise error continue
continue # pylint: disable=raising-bad-type
else: raise error
# pylint: disable=raising-bad-type
raise error
return response return response
def get(self, url, timeout=30): def get(self, url, timeout=30):

View file

@ -1,6 +1,5 @@
check-manifest check-manifest
coverage coverage
mock
pdoc pdoc
pep8 pep8
pyflakes pyflakes

View file

@ -7,7 +7,7 @@
import argparse import argparse
import ssl import ssl
import sys import sys
from pkg_resources import parse_version from packaging.version import parse
from getpass import getpass from getpass import getpass
from cvprac.cvp_client import CvpClient from cvprac.cvp_client import CvpClient
import requests.packages.urllib3 import requests.packages.urllib3
@ -56,7 +56,7 @@ def main():
# Get the current CVP version # Get the current CVP version
cvp_release = clnt.api.get_cvp_info()['version'] cvp_release = clnt.api.get_cvp_info()['version']
if parse_version(cvp_release) < parse_version('2020.3.0'): if parse(cvp_release) < parse('2020.3.0'):
# For older CVP, we manually trigger a compliance check # For older CVP, we manually trigger a compliance check
try: try:
clnt.api.check_compliance('root', 'container') clnt.api.check_compliance('root', 'container')

View file

@ -0,0 +1,20 @@
######
v1.4.0
######
2024-5-6
Enhancements
^^^^^^^^^^^^
* Move from pkg_resources to packaging for Python 3.12 support. (`271 <https://github.com/aristanetworks/cvprac/pull/271>`_) [`mharista <https://github.com/mharista>`_]
* Add support for searchTopology V3 endpoint. (`275 <https://github.com/aristanetworks/cvprac/pull/275>`_) [`mharista <https://github.com/mharista>`_]
Fixed
^^^^^
* Add missing url encoding for get_user. (`264 <https://github.com/aristanetworks/cvprac/pull/264>`_) [`noredistribution <https://github.com/noredistribution>`_]
* Updated the enrollment endpoint for CVaaS. (`269 <https://github.com/aristanetworks/cvprac/pull/269>`_) [`noredistribution <https://github.com/noredistribution>`_]
* Add timeout to get_configlets_and_mappers(). (`270 <https://github.com/aristanetworks/cvprac/pull/270>`_) [`noredistribution <https://github.com/noredistribution>`_]
* Update setup.py to reference python3 only. (`272 <https://github.com/aristanetworks/cvprac/pull/272>`_) [`mharista <https://github.com/mharista>`_]
* Python3 lint/format fixes. (`273 <https://github.com/aristanetworks/cvprac/pull/273>`_) [`mharista <https://github.com/mharista>`_]

View file

@ -1 +1,2 @@
requests[socks]>=2.27.0 requests[socks]>=2.27.0
packaging>=23.2

View file

@ -100,8 +100,13 @@ setup(
# Specify the Python versions you support here. In particular, ensure # Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both. # that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
], ],
# What does your project relate to? # What does your project relate to?
@ -111,7 +116,7 @@ setup(
# your project is installed. For an analysis of "install_requires" vs pip's # your project is installed. For an analysis of "install_requires" vs pip's
# requirements files see: # requirements files see:
# https://packaging.python.org/en/latest/requirements.html # https://packaging.python.org/en/latest/requirements.html
install_requires=['requests[socks]>=2.27.0'], install_requires=['requests[socks]>=2.27.0', 'packaging>=23.2'],
# List additional groups of dependencies here (e.g. development # List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax, # dependencies). You can install these using the following syntax,