Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for XML file upload pre-change (DCNE-150) #77

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
17 changes: 13 additions & 4 deletions plugins/httpapi/nd.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,12 @@ def send_request(self, method, path, data=None):
raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None)))
return self._verify_response(response, method, full_path, rdata)

def send_file_request(self, method, path, file=None, data=None, remote_path=None, file_key="file"):
def send_file_request(self, method, path, file=None, data=None, remote_path=None, file_key="file", file_ext=None):
"""This method handles file download and upload operations
:arg method (str): Method can be GET or POST
:arg path (str): Path should be the resource path
:arg file (str): The absolute file path of the target file
:arg file_ext (str): The file extension, with leading dot, to be used for the file. If file has already an extension, it will be replaced
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a check that the file extension leads with a dot? possibly error or inject when this is not done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, added

:arg data (dict): Data should be the dictionary object
:arg remote_path (str): Remote directory path to download/upload the file object

Expand Down Expand Up @@ -255,6 +256,14 @@ def send_file_request(self, method, path, file=None, data=None, remote_path=None
if method is not None:
self.method = method

# If file_ext is provided, replace the file extension (if present) or add it
if file_ext is not None:
if not file_ext.startswith(".") or file_ext not in set(mimetypes.types_map.keys()):
raise ValueError("Invalid file extension provided. Please provide a valid file extension, with leading dot")
akinross marked this conversation as resolved.
Show resolved Hide resolved
filename = os.path.splitext(os.path.basename(file))[0] + file_ext
else:
filename = os.path.basename(file)

try:
# create data field
data["uploadedFileName"] = os.path.basename(file)
Expand All @@ -267,11 +276,11 @@ def send_file_request(self, method, path, file=None, data=None, remote_path=None
try:
# create fields for MultipartEncoder
if remote_path:
fields = dict(rdir=remote_path, name=(os.path.basename(file), open(file, "rb"), mimetypes.guess_type(file)))
fields = dict(rdir=remote_path, name=(filename, open(file, "rb"), mimetypes.guess_type(filename)))
elif file_key == "importfile":
fields = dict(spec=(json.dumps(data)), importfile=(os.path.basename(file), open(file, "rb"), mimetypes.guess_type(file)))
fields = dict(spec=(json.dumps(data)), importfile=(filename, open(file, "rb"), mimetypes.guess_type(filename)))
else:
fields = dict(data=("data.json", data_str, "application/json"), file=(os.path.basename(file), open(file, "rb"), mimetypes.guess_type(file)))
fields = dict(data=("data.json", data_str, "application/json"), file=(filename, open(file, "rb"), mimetypes.guess_type(filename)))

if not HAS_MULTIPART_ENCODER:
if sys.version_info.major == 2:
Expand Down
6 changes: 4 additions & 2 deletions plugins/module_utils/nd.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,9 @@ def __init__(self, module):
self.module.warn("Enable debug output because ANSIBLE_DEBUG was set.")
self.params["output_level"] = "debug"

def request(self, path, method=None, data=None, file=None, qs=None, prefix="", file_key="file", output_format="json", ignore_not_found_error=False):
def request(
self, path, method=None, data=None, file=None, qs=None, prefix="", file_key="file", output_format="json", ignore_not_found_error=False, file_ext=None
):
"""Generic HTTP method for ND requests."""
self.path = path

Expand All @@ -238,7 +240,7 @@ def request(self, path, method=None, data=None, file=None, qs=None, prefix="", f
uri = uri + update_qs(qs)
try:
if file is not None:
info = conn.send_file_request(method, uri, file, data, None, file_key)
info = conn.send_file_request(method, uri, file, data, None, file_key, file_ext)
else:
if data:
info = conn.send_request(method, uri, json.dumps(data))
Expand Down
11 changes: 11 additions & 0 deletions plugins/module_utils/ndi.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,17 @@ def is_json(self, myjson):
return False
return True

def is_xml(self, myxml):
try:
from lxml import etree

etree.parse(myxml)
except ImportError:
self.nd.fail_json(msg="Cannot use lxml etree because lxml module is not available")
except etree.XMLSyntaxError:
return False
return True

def load(self, fh, chunk_size=1024):
depth = 0
in_str = False
Expand Down
30 changes: 21 additions & 9 deletions plugins/modules/nd_pcv.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
file:
description:
- Optional parameter if creating new pre-change analysis from file.
- XML and JSON files are supported. If no file extension is provided, the file is assumed to be JSON.
type: str
manual:
description:
Expand Down Expand Up @@ -216,17 +217,28 @@ def main():
if file:
if not os.path.exists(file):
nd.fail_json(msg="File not found : {0}".format(file))
# check whether file content is a valid json
if ndi.is_json(open(file, "rb").read()) is False:
extract_data = ndi.load(open(file))
# Check whether the file is a valid XML file. If it's not, check if it's a valid JSON or else process it as a file from cisco.aci modules.
if ndi.is_xml(open(file, "rb")):
file_ext = ".xml"
else:
extract_data = json.loads(open(file, "rb").read())
if isinstance(extract_data, list):
ndi.cmap = {}
tree = ndi.construct_tree(extract_data)
ndi.create_structured_data(tree, file)
if ndi.is_json(open(file, "rb").read()):
file_ext = ".json"
extract_data = json.loads(open(file, "rb").read())
else:
try:
file_ext = ".json"
extract_data = ndi.load(open(file))
except BaseException:
nd.fail_json(msg="Error processing the file. Check if file content is valid.")

if isinstance(extract_data, list):
ndi.cmap = {}
tree = ndi.construct_tree(extract_data)
ndi.create_structured_data(tree, file)

# Send REST API request to create a new PCV job
create_pcv_path = "{0}/{1}/fabric/{2}/prechangeAnalysis/fileChanges".format(path, insights_group, site_name)
file_resp = nd.request(create_pcv_path, method="POST", file=os.path.abspath(file), data=data, prefix=ndi.prefix)
file_resp = nd.request(create_pcv_path, method="POST", file=os.path.abspath(file), file_ext=file_ext, data=data, prefix=ndi.prefix)
if file_resp.get("success") is True:
nd.existing = file_resp.get("value")["data"]
elif manual:
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
requests_toolbelt
jsonpath-ng
jsonpath-ng
lxml
3 changes: 2 additions & 1 deletion tests/integration/network-integration.requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
requests_toolbelt
jsonpath-ng
jsonpath-ng
samiib marked this conversation as resolved.
Show resolved Hide resolved
lxml
Loading
Loading