Merging upstream version 0.12.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
f45bc3d463
commit
8d2f70e3c7
77 changed files with 23610 additions and 2331 deletions
272
eos_downloader/logics/arista_server.py
Normal file
272
eos_downloader/logics/arista_server.py
Normal file
|
@ -0,0 +1,272 @@
|
|||
#!/usr/bin/python
|
||||
# coding: utf-8 -*-
|
||||
# pylint: disable=too-many-positional-arguments
|
||||
# pylint: disable=dangerous-default-value
|
||||
|
||||
"""Server module for handling interactions with Arista software download portal.
|
||||
|
||||
This module provides the AristaServer class which manages authentication and
|
||||
file retrieval operations with the Arista software download portal. It handles
|
||||
session management, XML data retrieval, and download URL generation.
|
||||
|
||||
Classes
|
||||
-------
|
||||
AristaServer
|
||||
Main class for interacting with the Arista software portal.
|
||||
|
||||
Dependencies
|
||||
-----------
|
||||
- base64: For encoding authentication tokens
|
||||
- json: For handling JSON data in requests
|
||||
- xml.etree.ElementTree: For parsing XML responses
|
||||
- loguru: For logging
|
||||
- requests: For making HTTP requests
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> from eos_downloader.logics.server import AristaServer
|
||||
>>> server = AristaServer(token='my_auth_token')
|
||||
>>> server.authenticate()
|
||||
>>> xml_data = server.get_xml_data()
|
||||
>>> download_url = server.get_url('/path/to/file')
|
||||
|
||||
Notes
|
||||
-----
|
||||
The module requires valid authentication credentials to interact with the Arista portal.
|
||||
All server interactions are performed over HTTPS and follow Arista's API specifications.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import logging
|
||||
import json
|
||||
from typing import Dict, Union, Any
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from loguru import logger
|
||||
import requests
|
||||
|
||||
import eos_downloader.exceptions
|
||||
import eos_downloader.defaults
|
||||
|
||||
|
||||
class AristaServer:
|
||||
"""AristaServer class to handle authentication and interactions with Arista software download portal.
|
||||
|
||||
This class provides methods to authenticate with the Arista software portal,
|
||||
retrieve XML data containing available software packages, and generate download URLs
|
||||
for specific files.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
token : str, optional
|
||||
Authentication token for Arista portal access
|
||||
timeout : int, default=5
|
||||
Timeout in seconds for HTTP requests
|
||||
session_server : str
|
||||
URL of the authentication server
|
||||
headers : Dict[str, any]
|
||||
HTTP headers to use in requests
|
||||
xml_url : str
|
||||
URL to retrieve software package XML data
|
||||
download_server : str
|
||||
Base URL for file downloads
|
||||
_session_id : str
|
||||
Session ID obtained after authentication
|
||||
|
||||
Methods
|
||||
-------
|
||||
authenticate(token: Union[bool, None] = None) -> bool
|
||||
Authenticates with the Arista portal using provided or stored token
|
||||
get_xml_data() -> ET.ElementTree
|
||||
Retrieves XML data containing available software packages
|
||||
get_url(remote_file_path: str) -> Union[str, None]
|
||||
Generates download URL for a specific file path
|
||||
|
||||
Raises
|
||||
------
|
||||
eos_downloader.exceptions.AuthenticationError
|
||||
When authentication fails due to invalid or expired token
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
token: Union[str, None] = None,
|
||||
timeout: int = 5,
|
||||
session_server: str = eos_downloader.defaults.DEFAULT_SERVER_SESSION,
|
||||
headers: Dict[str, Any] = eos_downloader.defaults.DEFAULT_REQUEST_HEADERS,
|
||||
xml_url: str = eos_downloader.defaults.DEFAULT_SOFTWARE_FOLDER_TREE,
|
||||
download_server: str = eos_downloader.defaults.DEFAULT_DOWNLOAD_URL,
|
||||
) -> None:
|
||||
"""Initialize the Server class with optional parameters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
token : Union[str, None], optional
|
||||
Authentication token. Defaults to None.
|
||||
timeout : int, optional
|
||||
Request timeout in seconds. Defaults to 5.
|
||||
session_server : str, optional
|
||||
URL of the session server. Defaults to DEFAULT_SERVER_SESSION.
|
||||
headers : Dict[str, any], optional
|
||||
HTTP headers for requests. Defaults to DEFAULT_REQUEST_HEADERS.
|
||||
xml_url : str, optional
|
||||
URL of the software folder tree XML. Defaults to DEFAULT_SOFTWARE_FOLDER_TREE.
|
||||
download_server : str, optional
|
||||
Base URL for downloads. Defaults to DEFAULT_DOWNLOAD_URL.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
self.token: Union[str, None] = token
|
||||
self._session_server = session_server
|
||||
self._headers = headers
|
||||
self._timeout = timeout
|
||||
self._xml_url = xml_url
|
||||
self._download_server = download_server
|
||||
self._session_id = None
|
||||
|
||||
logging.info(f"Initialized AristaServer with headers: {self._headers}")
|
||||
|
||||
def authenticate(self, token: Union[str, None] = None) -> bool:
|
||||
"""Authenticate to the API server using access token.
|
||||
|
||||
The token is encoded in base64 and sent to the server for authentication.
|
||||
A session ID is retrieved from the server response if authentication is successful.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
token : Union[str, None], optional
|
||||
Access token for authentication. If None, uses existing token stored in instance. Defaults to None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if authentication successful, False otherwise
|
||||
|
||||
Raises
|
||||
------
|
||||
eos_downloader.exceptions.AuthenticationError
|
||||
If access token is invalid or expired
|
||||
"""
|
||||
|
||||
if token is not None:
|
||||
self.token = token
|
||||
if self.token is None:
|
||||
logger.error("No token provided for authentication")
|
||||
return False
|
||||
credentials = (base64.b64encode(self.token.encode())).decode("utf-8")
|
||||
jsonpost = {"accessToken": credentials}
|
||||
result = requests.post(
|
||||
self._session_server,
|
||||
data=json.dumps(jsonpost),
|
||||
timeout=self._timeout,
|
||||
headers=self._headers,
|
||||
)
|
||||
if result.json()["status"]["message"] in [
|
||||
"Access token expired",
|
||||
"Invalid access token",
|
||||
]:
|
||||
logging.critical(
|
||||
f"Authentication failed: {result.json()['status']['message']}"
|
||||
)
|
||||
raise eos_downloader.exceptions.AuthenticationError
|
||||
# return False
|
||||
try:
|
||||
if "data" in result.json():
|
||||
self._session_id = result.json()["data"]["session_code"]
|
||||
logging.info(f"Authenticated with session ID: {self._session_id}")
|
||||
return True
|
||||
except KeyError as error:
|
||||
logger.error(
|
||||
f"Key Error in parsing server response ({result.json()}): {error}"
|
||||
)
|
||||
return False
|
||||
return False
|
||||
|
||||
def get_xml_data(self) -> Union[ET.ElementTree, None]:
|
||||
"""Retrieves XML data from the server.
|
||||
|
||||
This method fetches XML data by making a POST request to the server's XML endpoint.
|
||||
If not already authenticated, it will initiate the authentication process first.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ET.ElementTree
|
||||
An ElementTree object containing the parsed XML data from the server response.
|
||||
|
||||
Raises
|
||||
------
|
||||
KeyError
|
||||
If the server response doesn't contain the expected data structure.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The method requires a valid session ID which is obtained through authentication.
|
||||
The XML data is expected to be in the response JSON under data.xml path.
|
||||
"""
|
||||
|
||||
logging.info(f"Getting XML data from server {self._session_server}")
|
||||
if self._session_id is None:
|
||||
logging.debug("Not authenticated to server, start authentication process")
|
||||
self.authenticate()
|
||||
jsonpost = {"sessionCode": self._session_id}
|
||||
result = requests.post(
|
||||
self._xml_url,
|
||||
data=json.dumps(jsonpost),
|
||||
timeout=self._timeout,
|
||||
headers=self._headers,
|
||||
)
|
||||
try:
|
||||
folder_tree = result.json()["data"]["xml"]
|
||||
logging.debug("XML data received from Arista server")
|
||||
return ET.ElementTree(ET.fromstring(folder_tree))
|
||||
except KeyError as error:
|
||||
logger.error(f"Unkown key in server response: {error}")
|
||||
return None
|
||||
|
||||
def get_url(self, remote_file_path: str) -> Union[str, None]:
|
||||
"""Get download URL for a remote file from server.
|
||||
|
||||
This method retrieves the download URL for a specified remote file by making a POST request
|
||||
to the server. If not authenticated, it will first authenticate before making the request.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
remote_file_path : str
|
||||
Path to the remote file on server to get download URL for
|
||||
|
||||
Returns
|
||||
-------
|
||||
Union[str, None]
|
||||
The download URL if successful, None if request fails or URL not found in response
|
||||
|
||||
Raises
|
||||
------
|
||||
requests.exceptions.RequestException
|
||||
If the request to server fails
|
||||
json.JSONDecodeError
|
||||
If server response is not valid JSON
|
||||
requests.exceptions.Timeout
|
||||
If server request times out
|
||||
"""
|
||||
|
||||
logging.info(f"Getting download URL for {remote_file_path}")
|
||||
if self._session_id is None:
|
||||
logging.debug("Not authenticated to server, start authentication process")
|
||||
self.authenticate()
|
||||
jsonpost = {"sessionCode": self._session_id, "filePath": remote_file_path}
|
||||
result = requests.post(
|
||||
self._download_server,
|
||||
data=json.dumps(jsonpost),
|
||||
timeout=self._timeout,
|
||||
headers=self._headers,
|
||||
)
|
||||
if "data" in result.json() and "url" in result.json()["data"]:
|
||||
# logger.debug('URL to download file is: {}', result.json())
|
||||
logging.info("Download URL received from server")
|
||||
logging.debug(f'URL to download file is: {result.json()["data"]["url"]}')
|
||||
return result.json()["data"]["url"]
|
||||
return None
|
Loading…
Add table
Add a link
Reference in a new issue