Skip to content

Commit

Permalink
Fix module_utils code (#432)
Browse files Browse the repository at this point in the history
* Corrected spelling fixes
* Added missing method
* Simplify the code

Signed-off-by: Abhijeet Kasurde <[email protected]>
  • Loading branch information
Akasurde authored Sep 3, 2024
1 parent f2a57a1 commit 263cf74
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 113 deletions.
31 changes: 14 additions & 17 deletions plugins/module_utils/ah_api_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,16 @@
__metaclass__ = type

import base64
import re
import socket
import json
import time
import os
import socket
import time

from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.compat.version import LooseVersion as Version
from ansible.module_utils._text import to_bytes, to_text

from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode
from ansible.module_utils.six.moves.urllib.error import HTTPError

from ansible.module_utils.six.moves.urllib.parse import urlencode, urlparse
from ansible.module_utils.urls import Request, SSLValidationError


Expand Down Expand Up @@ -108,8 +105,8 @@ def __init__(self, argument_spec, direct_params=None, **kwargs):
setattr(self, short_param, direct_value)

# Perform some basic validation
if not re.match("^https{0,1}://", self.host):
self.host = "https://{host}".format(host=self.host)
if not self.host.startswith(("https://", "http://")):
self.host = "https://{0}".format(self.host)

# Try to parse the hostname as a url
try:
Expand Down Expand Up @@ -217,7 +214,7 @@ def make_request_raw_reponse(self, method, url, **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 Exception("The HTTP method must be defined")
raise AHAPIModuleError("The HTTP method must be defined")

# Extract the provided headers and data
headers = kwargs.get("headers", {})
Expand All @@ -239,22 +236,22 @@ def make_request_raw_reponse(self, method, url, **kwargs):
"The host sent back a server error: {path}: {error}. Please check the logs and try again later".format(path=url.path, error=he)
)
# Sanity check: Did we fail to authenticate properly? If so, fail out now; this is always a failure.
elif he.code == 401:
if he.code == 401:
raise AHAPIModuleError("Invalid authentication credentials for {path} (HTTP 401).".format(path=url.path))
# Sanity check: Did we get a forbidden response, which means that the user isn't allowed to do this? Report that.
elif he.code == 403:
if he.code == 403:
raise AHAPIModuleError("You do not have permission to {method} {path} (HTTP 403).".format(method=method, path=url.path))
# Sanity check: Did we get a 404 response?
# Requests with primary keys will return a 404 if there is no response, and we want to consistently trap these.
elif he.code == 404:
if he.code == 404:
raise AHAPIModuleError("The requested object could not be found at {path}.".format(path=url.path))
# Sanity check: Did we get a 405 response?
# A 405 means we used a method that isn't allowed. Usually this is a bad request, but it requires special treatment because the
# API sends it as a logic error in a few situations (e.g. trying to cancel a job that isn't running).
elif he.code == 405:
if he.code == 405:
raise AHAPIModuleError("Cannot make a {method} request to this endpoint {path}".format(method=method, path=url.path))
# Sanity check: Did we get some other kind of error? If so, write an appropriate error message.
elif he.code >= 400:
if he.code >= 400:
# We are going to return a 400 so the module can decide what to do with it
page_data = he.read()
try:
Expand Down Expand Up @@ -298,12 +295,12 @@ def make_request(self, method, url, wait_for_task=True, **kwargs):
except Exception as e:
if "non_field_errors" in response["json"]:
raise AHAPIModuleError("Errors occurred with request (HTTP 400). Errors: {errors}".format(errors=response["json"]["non_field_errors"]))
elif "errors" in response["json"]:
if "errors" in response["json"]:
def get_details(err):
return err["detail"]
raise AHAPIModuleError("Errors occurred with request (HTTP 400). Details: {errors}".format(
errors=", ".join(map(get_details, response["json"]["errors"]))))
elif "text" in response:
if "text" in response:
raise AHAPIModuleError("Errors occurred with request (HTTP 400). Errors: {errors}".format(errors=response["text"]))
raise AHAPIModuleError("Failed to read response body: {error}".format(error=e))

Expand Down
114 changes: 61 additions & 53 deletions plugins/module_utils/ah_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,23 @@

__metaclass__ = type

from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.urls import (
Request,
SSLValidationError,
ConnectionError,
fetch_file,
)
from ansible.module_utils.six import string_types
from ansible.module_utils.six import PY2, PY3
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode
from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.module_utils.six.moves.http_cookiejar import CookieJar
from ansible.module_utils._text import to_bytes, to_native, to_text
import os.path
from socket import gethostbyname
import re
from json import loads, dumps
import base64
import email.mime.application
import email.mime.multipart
import os
import os.path
import time
import email.mime.multipart
import email.mime.application
from json import dumps, loads
from socket import gethostbyname

from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.six import PY2, PY3, string_types
from ansible.module_utils.six.moves.http_cookiejar import CookieJar
from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.module_utils.six.moves.urllib.parse import urlencode, urlparse
from ansible.module_utils.urls import (ConnectionError, Request,
SSLValidationError, fetch_file)


class ItemNotDefined(Exception):
Expand Down Expand Up @@ -80,7 +75,6 @@ class AHModule(AnsibleModule):
"oauth_token": "ah_token",
}
IDENTITY_FIELDS = {}
ENCRYPTED_STRING = "$encrypted$"
host = "127.0.0.1"
path_prefix = "galaxy"
username = None
Expand All @@ -107,7 +101,7 @@ def __init__(self, argument_spec=None, direct_params=None, error_callback=None,

if direct_params is not None:
self.params = direct_params
# else:

super(AHModule, self).__init__(argument_spec=full_argspec, **kwargs)
self.session = Request(cookies=CookieJar(), validate_certs=self.verify_ssl, timeout=self.request_timeout)

Expand All @@ -132,7 +126,7 @@ def __init__(self, argument_spec=None, direct_params=None, error_callback=None,
self.fail_json(msg=error_msg)

# Perform some basic validation
if not re.match("^https{0,1}://", self.host):
if not self.host.startswith(("https://", "http://")):
self.host = "https://{0}".format(self.host)

# Try to parse the hostname as a url
Expand Down Expand Up @@ -340,8 +334,7 @@ def get_one(self, endpoint, name_or_id=None, allow_none=True, **kwargs):
if response["json"]["meta"]["count"] == 0:
if allow_none:
return None
else:
self.fail_wanted_one(response, endpoint, new_kwargs.get("data"))
self.fail_wanted_one(response, endpoint, new_kwargs.get("data"))
elif response["json"]["meta"]["count"] > 1:
if name_or_id:
# Since we did a name or ID search and got > 1 return something if the id matches
Expand Down Expand Up @@ -460,9 +453,8 @@ def delete_if_needed(self, existing_item, on_delete=None, auto_exit=True):
self.fail_json(msg="Unable to delete {0} {1}: {2}".format(item_type, item_name, response["status_code"]))

def get_item_name(self, item, allow_unknown=False):
if item:
if "name" in item:
return item["name"]
if item and "name" in item:
return item["name"]

if allow_unknown:
return "unknown"
Expand Down Expand Up @@ -503,16 +495,15 @@ def create_or_update_if_needed(
require_id=require_id,
fixed_url=fixed_url,
)
else:
return self.create_if_needed(
existing_item,
new_item,
endpoint,
on_create=on_create,
item_type=item_type,
auto_exit=auto_exit,
associations=associations,
)
return self.create_if_needed(
existing_item,
new_item,
endpoint,
on_create=on_create,
item_type=item_type,
auto_exit=auto_exit,
associations=associations,
)

def create_if_needed(
self,
Expand Down Expand Up @@ -598,18 +589,18 @@ def is_standalone(self):
def approve(self, endpoint, namespace=None, name=None, version=None, timeout=None, interval=10.0, auto_exit=True):

if self.is_standalone():
approvalEndpoint = "move/staging/published"
approval_endpoint = "move/staging/published"

if not endpoint:
self.fail_json(msg="Unable to approve due to missing endpoint")

response = self.post_endpoint("{0}/{1}".format(endpoint, approvalEndpoint), None, **{"return_none_on_404": True})
response = self.post_endpoint("{0}/{1}".format(endpoint, approval_endpoint), None, **{"return_none_on_404": True})

i = 0
while timeout is None or i < timeout:
if not response:
time.sleep(interval)
response = self.post_endpoint("{0}/{1}".format(endpoint, approvalEndpoint), None, **{"return_none_on_404": True})
response = self.post_endpoint("{0}/{1}".format(endpoint, approval_endpoint), None, **{"return_none_on_404": True})
i += interval
else:
break
Expand Down Expand Up @@ -669,7 +660,7 @@ def prepare_multipart(self, filename):
mime = "application/x-gzip"
m = email.mime.multipart.MIMEMultipart("form-data")

main_type, sep, sub_type = mime.partition("/")
main_type, dummy, sub_type = mime.partition("/")

with open(to_bytes(filename, errors="surrogate_or_strict"), "rb") as f:
part = email.mime.application.MIMEApplication(f.read())
Expand Down Expand Up @@ -705,7 +696,7 @@ def prepare_multipart(self, filename):
b_data = email.utils.fix_eols(fp.getvalue())
del m

headers, sep, b_content = b_data.partition(b"\r\n\r\n")
headers, dummy, b_content = b_data.partition(b"\r\n\r\n")
del b_data

if PY3:
Expand Down Expand Up @@ -739,21 +730,20 @@ def wait_for_complete(self, task_url):
self.fail_json(msg="Upload of collection failed: {0}".format(response["json"]["error"]["description"]))
else:
time.sleep(1)
return

def upload(self, path, endpoint, wait=True, repository="staging", item_type="unknown"):
if "://" in path:
tmppath = fetch_file(self, path)
path = path.split("/")[-1]
os.rename(tmppath, path)
self.add_cleanup_file(path)
ct, body = self.prepare_multipart(path)
content_type, body = self.prepare_multipart(path)
response = self.make_request(
"POST",
"{0}/content/{1}/v3/{2}/".format(self.path_prefix, repository, endpoint),
**{
"data": body,
"headers": {"Content-Type": str(ct)},
"headers": {"Content-Type": str(content_type)},
"binary": True,
"return_errors_on_404": True,
}
Expand All @@ -764,15 +754,14 @@ def upload(self, path, endpoint, wait=True, repository="staging", item_type="unk
if wait:
self.wait_for_complete(response["json"]["task"])
return
if "json" in response and "__all__" in response["json"]:
self.fail_json(msg="Unable to create {0} from {1}: {2}".format(item_type, path, response["json"]["__all__"][0]))
elif "json" in response and "errors" in response["json"] and "detail" in response["json"]["errors"][0]:
self.fail_json(msg="Unable to create {0} from {1}: {2}".format(item_type, path, response["json"]["errors"][0]["detail"]))
elif "json" in response:
self.fail_json(msg="Unable to create {0} from {1}: {2}".format(item_type, path, response["json"]))
else:
if "json" in response and "__all__" in response["json"]:
self.fail_json(msg="Unable to create {0} from {1}: {2}".format(item_type, path, response["json"]["__all__"][0]))
elif "json" in response and "errors" in response["json"] and "detail" in response["json"]["errors"][0]:
self.fail_json(msg="Unable to create {0} from {1}: {2}".format(item_type, path, response["json"]["errors"][0]["detail"]))
elif "json" in response:
self.fail_json(msg="Unable to create {0} from {1}: {2}".format(item_type, path, response["json"]))
else:
self.fail_json(msg="Unable to create {0} from {1}: {2}".format(item_type, path, response["status_code"]))
self.fail_json(msg="Unable to create {0} from {1}: {2}".format(item_type, path, response["status_code"]))

def update_if_needed(
self,
Expand Down Expand Up @@ -941,6 +930,25 @@ def get_exactly_one(self, endpoint, name_or_id=None, **kwargs):
def resolve_name_to_id(self, endpoint, name_or_id):
return self.get_exactly_one(endpoint, name_or_id)["id"]

@staticmethod
def fields_could_be_same(old_field, new_field):
"""Treating $encrypted$ as a wild card,
return False if the two values are KNOWN to be different
return True if the two values are the same, or could potentially be the same,
depending on the unknown $encrypted$ value or sub-values
"""
if isinstance(old_field, dict) and isinstance(new_field, dict):
if set(old_field.keys()) != set(new_field.keys()):
return False
for key in new_field.keys():
if not AHModule.fields_could_be_same(old_field[key], new_field[key]):
return False
return True # all sub-fields are either equal or could be equal
else:
if old_field == AHModule.ENCRYPTED_STRING:
return True
return bool(new_field == old_field)

def objects_could_be_different(self, old, new, field_set=None, warning=False):
if field_set is None:
field_set = set(fd for fd in new.keys() if fd not in ("modified", "related", "summary_fields"))
Expand Down
Loading

0 comments on commit 263cf74

Please sign in to comment.