5.6.0
This commit is contained in:
@@ -28,15 +28,19 @@ Requirements:
|
||||
- DECORT cloud platform version 3.8.6 or higher
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
import json
|
||||
from typing import Callable, Iterable
|
||||
import re
|
||||
from typing import Any, Callable, Iterable
|
||||
import time
|
||||
|
||||
import jwt
|
||||
import netaddr
|
||||
import time
|
||||
import requests
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||
|
||||
|
||||
class DecortController(object):
|
||||
"""DecortController is a utility class that holds target controller context and handles API requests formatting
|
||||
based on the requested authentication type.
|
||||
@@ -161,7 +165,38 @@ class DecortController(object):
|
||||
VM_RESIZE_DOWN = 1
|
||||
VM_RESIZE_UP = 2
|
||||
|
||||
class AccountStatus(Enum):
|
||||
CONFIRMED = 'CONFIRMED'
|
||||
DELETED = 'DELETED'
|
||||
DESTROYED = 'DESTROYED'
|
||||
DESTROYING = 'DESTROYING'
|
||||
DISABLED = 'DISABLED'
|
||||
|
||||
class AccountSortableField(Enum):
|
||||
createdTime = 'createdTime'
|
||||
deletedTime = 'deletedTime'
|
||||
id = 'id'
|
||||
name = 'name'
|
||||
status = 'status'
|
||||
updatedTime = 'updatedTime'
|
||||
|
||||
class AccountUserRights(Enum):
|
||||
R = 'R'
|
||||
RCX = 'RCX'
|
||||
ARCXDU = 'ARCXDU'
|
||||
CXDRAU = 'CXDRAU'
|
||||
|
||||
class MESSAGES:
|
||||
@staticmethod
|
||||
def ssl_error(url: None | str = None):
|
||||
url_text = f' while connecting to {url}' if url else ''
|
||||
return (
|
||||
f'An SSL error occurred{url_text}.'
|
||||
f' If your platform is using a self-signed'
|
||||
f' SSL certificate, see description for'
|
||||
f' parameter `verify_ssl` in the modules docs.'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def obj_not_found(obj: str, id: None | int = None) -> str:
|
||||
with_id = f' with ID={id}' if id else ''
|
||||
@@ -251,6 +286,10 @@ class DecortController(object):
|
||||
f' for the {obj} with ID={id}.'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def str_not_parsed(string: str):
|
||||
return f'The string "{string}" cannot be parsed.'
|
||||
|
||||
@staticmethod
|
||||
def method_in_check_mode(method_name: str, method_args: tuple,
|
||||
method_kwargs: dict) -> str:
|
||||
@@ -408,8 +447,36 @@ class DecortController(object):
|
||||
new_f.__name__ = orig_f.__name__
|
||||
return new_f
|
||||
|
||||
@staticmethod
|
||||
def dt_str_to_sec(dt_str) -> None | int:
|
||||
"""
|
||||
Convert the datetime string to the Unix-time int.
|
||||
The format of `dt_str`: `yyyymmddhhmmss` with the ability
|
||||
to use any delimiter between digit groups (for example:
|
||||
`yyyy-mm-dd hh:mm:ss`).
|
||||
"""
|
||||
|
||||
re_pattern = re.compile(
|
||||
r'^(?P<year>\d{4}).?(?P<month>\d{2})'
|
||||
r'.?(?P<day>\d{2}).?(?P<hour>\d{2})'
|
||||
r'.?(?P<minute>\d{2}).?(?P<second>\d{2})$'
|
||||
)
|
||||
|
||||
re_match = re_pattern.match(dt_str)
|
||||
if not re_match:
|
||||
return
|
||||
|
||||
datetime_args = {k: int(v) for k, v in re_match.groupdict().items()}
|
||||
|
||||
return int(datetime(**datetime_args).timestamp())
|
||||
|
||||
@staticmethod
|
||||
def sec_to_dt_str(sec: int, str_format: str = '%Y-%m-%d_%H-%M-%S') -> str:
|
||||
"""
|
||||
Convert the Unix-time int to the datetime string of the format
|
||||
from `str_format`.
|
||||
"""
|
||||
|
||||
if sec:
|
||||
return time.strftime(str_format, time.gmtime(sec))
|
||||
return ''
|
||||
@@ -556,6 +623,9 @@ class DecortController(object):
|
||||
# catch requests.exceptions.ConnectionError to handle incorrect oauth2_url case
|
||||
try:
|
||||
token_get_resp = requests.post(token_get_url, data=req_data, verify=self.verify_ssl)
|
||||
except requests.exceptions.SSLError:
|
||||
self.message(self.MESSAGES.ssl_error(url=token_get_url))
|
||||
self.exit(fail=True)
|
||||
except requests.exceptions.ConnectionError as errco:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "Failed to connect to '{}' to obtain JWT access token: {}".format(token_get_url, errco)
|
||||
@@ -620,6 +690,9 @@ class DecortController(object):
|
||||
|
||||
try:
|
||||
api_resp = requests.post(req_url, headers=req_header, verify=self.verify_ssl)
|
||||
except requests.exceptions.SSLError:
|
||||
self.message(self.MESSAGES.ssl_error(url=req_url))
|
||||
self.exit(fail=True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "Failed to connect to '{}' while validating JWT".format(req_url)
|
||||
@@ -665,6 +738,9 @@ class DecortController(object):
|
||||
|
||||
try:
|
||||
api_resp = requests.post(req_url, data=req_data, verify=self.verify_ssl)
|
||||
except requests.exceptions.SSLError:
|
||||
self.message(self.MESSAGES.ssl_error(url=req_url))
|
||||
self.exit(fail=True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "Failed to connect to '{}' while validating legacy user".format(req_url)
|
||||
@@ -746,6 +822,9 @@ class DecortController(object):
|
||||
params=arg_params,
|
||||
headers=http_headers,
|
||||
verify=self.verify_ssl)
|
||||
except requests.exceptions.SSLError:
|
||||
self.message(self.MESSAGES.ssl_error(url=req_url))
|
||||
self.exit(fail=True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "Failed to connect to '{}' when calling DECORT API.".format(api_resp.url)
|
||||
@@ -784,6 +863,193 @@ class DecortController(object):
|
||||
self.amodule.fail_json(**self.result)
|
||||
return None
|
||||
|
||||
@waypoint
|
||||
def user_whoami(self) -> dict:
|
||||
"""
|
||||
Implementation of functionality of the API method
|
||||
`/system/usermanager/whoami`.
|
||||
"""
|
||||
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name='/restmachine/system/usermanager/whoami',
|
||||
arg_params={},
|
||||
)
|
||||
|
||||
return api_resp.json()
|
||||
|
||||
@waypoint
|
||||
def user_get(self, id: str) -> dict:
|
||||
"""
|
||||
Implementation of functionality of the API method
|
||||
`/cloudapi/user/get`.
|
||||
"""
|
||||
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name='/restmachine/cloudapi/user/get',
|
||||
arg_params={
|
||||
'username': id,
|
||||
},
|
||||
)
|
||||
|
||||
return api_resp.json()
|
||||
|
||||
@waypoint
|
||||
def user_accounts(
|
||||
self,
|
||||
account_id: None | int = None,
|
||||
account_name: None | str = None,
|
||||
account_status: None | AccountStatus = None,
|
||||
account_user_rights: None | AccountUserRights = None,
|
||||
deleted: bool = False,
|
||||
page_number: int = 1,
|
||||
page_size: None | int = None,
|
||||
resource_consumption: bool = False,
|
||||
sort_by_asc=True,
|
||||
sort_by_field: None | AccountSortableField = None,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Implementation of the functionality of API methods
|
||||
`/cloudapi/account/list`,
|
||||
`/cloudapi/account/listDeleted` and
|
||||
`/cloudapi/account/listResourceConsumption`.
|
||||
"""
|
||||
|
||||
sort_by = None
|
||||
if sort_by_field:
|
||||
sort_by_prefix = '+' if sort_by_asc else '-'
|
||||
sort_by = f'{sort_by_prefix}{sort_by_field.value}'
|
||||
|
||||
api_params = {
|
||||
'by_id': account_id,
|
||||
'name': account_name,
|
||||
'acl': account_user_rights.value if account_user_rights else None,
|
||||
'page': page_number if page_size else None,
|
||||
'size': page_size,
|
||||
'sortBy': sort_by,
|
||||
'status': account_status.value if account_status else None,
|
||||
}
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name={
|
||||
False: '/restmachine/cloudapi/account/list',
|
||||
True: '/restmachine/cloudapi/account/listDeleted',
|
||||
}[deleted],
|
||||
arg_params=api_params,
|
||||
)
|
||||
|
||||
accounts = api_resp.json()['data']
|
||||
|
||||
if resource_consumption and not deleted:
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name=(
|
||||
'/restmachine/cloudapi/account/listResourceConsumption'
|
||||
),
|
||||
arg_params={},
|
||||
)
|
||||
accounts_rc_list = api_resp.json()['data']
|
||||
accounts_rc_dict = {a['id']: a for a in accounts_rc_list}
|
||||
for a in accounts:
|
||||
a_id = a['id']
|
||||
a['resource_consumed'] = accounts_rc_dict[a_id]['Consumed']
|
||||
a['resource_reserved'] = accounts_rc_dict[a_id]['Reserved']
|
||||
|
||||
for a in accounts:
|
||||
a['createdTime_readable'] = self.sec_to_dt_str(a['createdTime'])
|
||||
a['deletedTime_readable'] = self.sec_to_dt_str(a['deletedTime'])
|
||||
a['updatedTime_readable'] = self.sec_to_dt_str(a['updatedTime'])
|
||||
|
||||
return accounts
|
||||
|
||||
@waypoint
|
||||
def user_resource_consumption(self) -> dict[str, dict]:
|
||||
"""
|
||||
Implementation of the functionality of API method
|
||||
`/cloudapi/user/getResourceConsumption`.
|
||||
"""
|
||||
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name='/restmachine/cloudapi/user/getResourceConsumption',
|
||||
arg_params={},
|
||||
)
|
||||
api_resp_json = api_resp.json()
|
||||
|
||||
return {
|
||||
'resource_consumed': api_resp_json['Consumed'],
|
||||
'resource_reserved': api_resp_json['Reserved'],
|
||||
}
|
||||
|
||||
@waypoint
|
||||
def user_audits(self,
|
||||
api_method: None | str = None,
|
||||
http_status_code: None | int = None,
|
||||
start_unix_time: None | int = None,
|
||||
end_unix_time: None | int = None,
|
||||
page_number: int = 1,
|
||||
page_size: None | int = None) -> dict[str, Any]:
|
||||
"""
|
||||
Implementation of the functionality of API method
|
||||
`/cloudapi/user/getAudit`.
|
||||
"""
|
||||
|
||||
api_params = {
|
||||
'call': api_method,
|
||||
'statuscode': http_status_code,
|
||||
'timestampAt': start_unix_time,
|
||||
'timestampTo': end_unix_time,
|
||||
'page': page_number if page_size else None,
|
||||
'size': page_size,
|
||||
}
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name='/restmachine/cloudapi/user/getAudit',
|
||||
arg_params=api_params,
|
||||
)
|
||||
|
||||
audits = api_resp.json()['data']
|
||||
|
||||
for a in audits:
|
||||
a['Time_readable'] = self.sec_to_dt_str(a['Time'])
|
||||
|
||||
return audits
|
||||
|
||||
@waypoint
|
||||
def user_api_methods(self, id: str) -> dict:
|
||||
"""
|
||||
Implementation of the functionality of API method
|
||||
`/cloudapi/user/apiList`.
|
||||
"""
|
||||
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name='/restmachine/cloudapi/user/apiList',
|
||||
arg_params={
|
||||
'userId': id
|
||||
},
|
||||
)
|
||||
|
||||
return api_resp.json()
|
||||
|
||||
@waypoint
|
||||
def user_objects_search(self, search_string: str) -> list[dict]:
|
||||
"""
|
||||
Implementation of the functionality of API method
|
||||
`/cloudapi/user/search`.
|
||||
"""
|
||||
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name='/restmachine/cloudapi/user/search',
|
||||
arg_params={
|
||||
'text': search_string,
|
||||
},
|
||||
)
|
||||
|
||||
return api_resp.json()
|
||||
|
||||
###################################
|
||||
# Compute and KVM VM resource manipulation methods
|
||||
###################################
|
||||
|
||||
Reference in New Issue
Block a user