-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Alina Buzachis <[email protected]> Co-authored-by: Abhijeet Kasurde <[email protected]>
- Loading branch information
1 parent
57099cd
commit 3a75a16
Showing
10 changed files
with
642 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright: Contributors to the Ansible project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
|
||
class ModuleDocFragment: | ||
|
||
AUTHS = """ | ||
options: | ||
controller_host: | ||
description: | ||
- The URL of the EDA controller. | ||
- If not set, the value of the C(CONTROLLER_URL) environment variable will be used. | ||
required: true | ||
type: str | ||
version_added: '2.0.0' | ||
controller_username: | ||
description: | ||
- Username used for authentication. | ||
- If not set, the value of the C(CONTROLLER_USERNAME) environment variable will be used. | ||
type: str | ||
version_added: '2.0.0' | ||
controller_password: | ||
description: | ||
- Password used for authentication. | ||
- If not set, the value of the C(CONTROLLER_PASSWORD) environment variable will be used. | ||
type: str | ||
version_added: '2.0.0' | ||
request_timeout: | ||
description: | ||
- Timeout in seconds for the connection with the EDA controller. | ||
- If not set, the value of the C(CONTROLLER_TIMEOUT) environment variable will be used. | ||
type: float | ||
default: 10 | ||
version_added: '2.0.0' | ||
validate_certs: | ||
description: | ||
- Whether to allow insecure connections to Ansible Automation Platform EDA | ||
Controller instance. | ||
- If C(no), SSL certificates will not be validated. | ||
- This should only be used on personally controlled sites using self-signed certificates. | ||
- If value not set, will try environment variable C(CONTROLLER_VERIFY_SSL) | ||
default: True | ||
type: bool | ||
version_added: '2.0.0' | ||
""" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright: Contributors to the Ansible project | ||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) | ||
|
||
from __future__ import absolute_import, division, print_function | ||
|
||
__metaclass__ = type | ||
|
||
from ansible.module_utils.basic import env_fallback | ||
|
||
AUTH_ARGSPEC = { | ||
"controller_host": { | ||
"fallback": (env_fallback, ["CONTROLLER_HOST"]), | ||
"required": True, | ||
}, | ||
"controller_username": { | ||
"fallback": (env_fallback, ["CONTROLLER_USERNAME"]), | ||
}, | ||
"controller_password": { | ||
"fallback": (env_fallback, ["CONTROLLER_PASSWORD"]), | ||
"no_log": True, | ||
}, | ||
"validate_certs": { | ||
"type": "bool", | ||
"default": True, | ||
"fallback": (env_fallback, ["CONTROLLER_VERIFY_SSL"]), | ||
}, | ||
"request_timeout": { | ||
"type": "float", | ||
"default": 10.0, | ||
"fallback": (env_fallback, ["CONTROLLER_REQUEST_TIMEOUT"]), | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright: Contributors to the Ansible project | ||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) | ||
|
||
from __future__ import absolute_import, division, print_function | ||
|
||
__metaclass__ = type | ||
|
||
import json | ||
from urllib.error import HTTPError, URLError | ||
from urllib.parse import urlencode, urlparse | ||
|
||
from ansible.module_utils.urls import Request | ||
|
||
from .errors import AuthError, EDAHTTPError | ||
|
||
|
||
class Response: | ||
def __init__(self, status, data, headers=None): | ||
self.status = status | ||
self.data = data | ||
# [('h1', 'v1'), ('H2', 'V2')] -> {'h1': 'v1', 'h2': 'V2'} | ||
self.headers = ( | ||
dict((k.lower(), v) for k, v in dict(headers).items()) if headers else {} | ||
) | ||
|
||
self._json = None | ||
|
||
@property | ||
def json(self): | ||
if self._json is None: | ||
try: | ||
self._json = json.loads(self.data) | ||
except ValueError as value_exp: | ||
raise EDAHTTPError( | ||
f"Received invalid JSON response: {self.data}" | ||
) from value_exp | ||
return self._json | ||
|
||
|
||
class Client: | ||
def __init__( | ||
self, | ||
host, | ||
username=None, | ||
password=None, | ||
timeout=None, | ||
validate_certs=None, | ||
): | ||
|
||
if not (host or "").startswith(("https://", "http://")): | ||
raise EDAHTTPError( | ||
f"Invalid instance host value: '{host}'. " | ||
"Value must start with 'https://' or 'http://'" | ||
) | ||
|
||
self.host = host | ||
self.username = username | ||
self.password = password | ||
self.timeout = timeout | ||
self.validate_certs = validate_certs | ||
|
||
# Try to parse the hostname as a url | ||
try: | ||
self.url = urlparse(self.host) | ||
# Store URL prefix for later use in build_url | ||
self.url_prefix = self.url.path | ||
except Exception as e: | ||
raise EDAHTTPError( | ||
f"Unable to parse eda_controller_host ({self.host}): {e}" | ||
) from e | ||
|
||
self.session = Request() | ||
|
||
def _request(self, method, path, data=None, headers=None): | ||
try: | ||
raw_resp = self.session.open( | ||
method, | ||
path, | ||
data=data, | ||
url_password=self.password, | ||
url_username=self.username, | ||
headers=headers, | ||
timeout=self.timeout, | ||
validate_certs=self.validate_certs, | ||
force_basic_auth=True, | ||
) | ||
|
||
except HTTPError as http_exp: | ||
# Wrong username/password, or expired access token | ||
if http_exp.code == 401: | ||
raise AuthError( | ||
f"Failed to authenticate with the instance: {http_exp.code} {http_exp.reason}" | ||
) from http_exp | ||
# Other HTTP error codes do not necessarily mean errors. | ||
# This is for the caller to decide. | ||
return Response(http_exp.code, http_exp.read(), http_exp.headers) | ||
except URLError as url_exp: | ||
raise EDAHTTPError(url_exp.reason) from url_exp | ||
|
||
return Response(raw_resp.status, raw_resp.read(), raw_resp.headers) | ||
|
||
def build_url(self, endpoint, query_params=None, identifier=None): | ||
# Make sure we start with /api/vX | ||
if not endpoint.startswith("/"): | ||
endpoint = f"/{endpoint}" | ||
prefix = self.url_prefix.rstrip("/") | ||
|
||
if not endpoint.startswith(prefix + "/api/"): | ||
endpoint = prefix + f"/api/eda/v1{endpoint}" | ||
if not endpoint.endswith("/") and "?" not in endpoint: | ||
endpoint = f"{endpoint}/" | ||
|
||
# Update the URL path with the endpoint | ||
url = self.url._replace(path=endpoint) | ||
|
||
if query_params: | ||
url = url._replace(query=urlencode(query_params)) | ||
if identifier: | ||
url = url._replace(path=url.path + str(identifier) + "/") | ||
|
||
return url | ||
|
||
def request(self, method, endpoint, **kwargs): | ||
# In case someone is calling us directly; make sure we were given a | ||
# method, let's not just assume a GET | ||
if not method: | ||
raise EDAHTTPError("The HTTP method must be defined") | ||
|
||
if method in ["POST"]: | ||
url = self.build_url(endpoint) | ||
elif method in ["DELETE", "PATCH", "PUT"]: | ||
url = self.build_url(endpoint, identifier=kwargs.get("id")) | ||
else: | ||
url = self.build_url(endpoint, query_params=kwargs.get("data")) | ||
|
||
# Extract the headers, this will be used in a couple of places | ||
headers = kwargs.get("headers", {}) | ||
|
||
if method in ["POST", "PUT", "PATCH"]: | ||
headers.setdefault("Content-Type", "application/json") | ||
kwargs["headers"] = headers | ||
|
||
data = ( | ||
None # Important, if content type is not JSON, this should not | ||
# be dict type | ||
) | ||
if headers.get("Content-Type", "") == "application/json": | ||
data = json.dumps(kwargs.get("data", {})) | ||
|
||
return self._request(method, url.geturl(), data=data, headers=headers) | ||
|
||
def get(self, path, **kwargs): | ||
resp = self.request("GET", path, **kwargs) | ||
if resp.status in (200, 404): | ||
return resp | ||
raise EDAHTTPError(f"HTTP error {resp.json}") | ||
|
||
def post(self, path, **kwargs): | ||
resp = self.request("POST", path, **kwargs) | ||
if resp.status == 201: | ||
return resp | ||
raise EDAHTTPError(f"HTTP error {resp.json}") | ||
|
||
def patch(self, path, **kwargs): | ||
resp = self.request("PATCH", path, **kwargs) | ||
if resp.status == 200: | ||
return resp | ||
raise EDAHTTPError(f"HTTP error {resp.json}") | ||
|
||
def delete(self, path, **kwargs): | ||
resp = self.request("DELETE", path, **kwargs) | ||
if resp.status == 204: | ||
return resp | ||
raise EDAHTTPError(f"HTTP error {resp.json}") |
Oops, something went wrong.