Skip to content

Commit 8c56429

Browse files
mayankmani-sdesurendrababuravellafernandofloresg
authored
[zos_lineinfile]Robust JSON Parsing and Cmd Cleanup (#2080)
* fixed bugs * added fragments * fixed sanity * Suggested fragment * found deafult set to 0 * thrown the actual errorwhile parsing command output * added test cases * added fragments for adding test case * suggested fragments added * Update 2080-zos_lineinfile-fixed-json-parsing.yml * added assertion * CHANGED CODE LOGIC TO MAINTAIN STANDARD * REMOVED UNWANTED IMPORT RE --------- Co-authored-by: surendrababuravella <[email protected]> Co-authored-by: Fernando Flores <[email protected]>
1 parent f209a26 commit 8c56429

File tree

3 files changed

+92
-9
lines changed

3 files changed

+92
-9
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
bugfixes:
2+
- zos_lineinfile - The module would report a false negative when certain special characters
3+
where present in the `line` option. Fix now reports the successful operation.
4+
(https://github.com/ansible-collections/ibm_zos_core/pull/2080).
5+
trivial:
6+
- test_zos_lineinfile_func - added test case to verify insertbefore functionality with regex pattern before Ansible block line.
7+
(https://github.com/ansible-collections/ibm_zos_core/pull/2080).

plugins/modules/zos_lineinfile.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -757,16 +757,39 @@ def main():
757757
if ins_bef:
758758
stdout = stdout.replace(ins_bef, quotedString(ins_bef))
759759
try:
760+
# Attempt to parse stdout content directly as JSON
760761
ret = json.loads(stdout)
761-
except Exception:
762-
messageDict = dict(msg="dsed return content is NOT in json format", stdout=str(stdout), stderr=str(stderr), rc=rc)
763-
if result.get('backup_name'):
764-
messageDict['backup_name'] = result['backup_name']
765-
module.fail_json(**messageDict)
766-
767-
result['cmd'] = ret['cmd']
768-
result['changed'] = ret['changed']
769-
result['found'] = ret['found']
762+
except json.JSONDecodeError:
763+
try:
764+
# If parsing fails, clean up possible wrapping quotes
765+
if (stdout.startswith("'") and stdout.endswith("'")) or (stdout.startswith('"') and stdout.endswith('"')):
766+
cleaned_stdout = stdout[1:-1]
767+
else:
768+
cleaned_stdout = stdout
769+
# Replace escaped backslashes with single backslashes
770+
cleaned_stdout = cleaned_stdout.replace('\\\\', '\\')
771+
# Further clean up: escape any single backslashes not followed by a valid JSON escape character
772+
import re
773+
cleaned_stdout = re.sub(r'\\([^"\\/bfnrtu])', r'\\\\\1', cleaned_stdout)
774+
# Try parsing the cleaned string as JSON
775+
ret = json.loads(cleaned_stdout)
776+
except Exception as e:
777+
# If still failing, report failure with useful debug info
778+
messageDict = dict(
779+
msg=f"Failed while parsing command output {str(e)} ",
780+
stdout=str(stdout),
781+
stderr=str(stderr),
782+
rc=rc
783+
)
784+
if result.get('backup_name'):
785+
messageDict['backup_name'] = result['backup_name']
786+
module.fail_json(**messageDict)
787+
# If the parsed JSON has a 'cmd' key, clean its string for safe use
788+
if 'cmd' in ret:
789+
ret['cmd'] = ret['cmd'].replace('\\"', '"').replace('\\\\', '\\')
790+
result['cmd'] = ret['cmd']
791+
result['changed'] = ret.get('changed', False)
792+
result['found'] = ret.get('found', 0)
770793
# Only return 'rc' if stderr is not empty to not fail the playbook run in a nomatch case
771794
# That information will be given with 'changed' and 'found'
772795
if len(stderr):

tests/functional/modules/test_zos_lineinfile_func.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,31 @@
6161
export ZOAU_ROOT
6262
export _BPXK_AUTOCVT"""
6363

64+
TEST_PARSING_CONTENT = """if [ -z STEPLIB ] && tty -s;
65+
then
66+
export STEPLIB=none
67+
exec -a 0 SHELL
68+
fi
69+
PATH=/usr/lpp/zoautil/v100/bin:/usr/lpp/rsusr/ported/bin:/bin:/var/bin
70+
export PATH
71+
ZOAU_ROOT=/usr/lpp/zoautil/v100
72+
export ZOAU_ROOT
73+
export _BPXK_AUTOCVT
74+
/* End Ansible Block Insert */"""
75+
76+
EXPECTED_TEST_PARSING_CONTENT = """if [ -z STEPLIB ] && tty -s;
77+
then
78+
export STEPLIB=none
79+
exec -a 0 SHELL
80+
fi
81+
PATH=/usr/lpp/zoautil/v100/bin:/usr/lpp/rsusr/ported/bin:/bin:/var/bin
82+
export PATH
83+
ZOAU_ROOT=/usr/lpp/zoautil/v100
84+
export ZOAU_ROOT
85+
export _BPXK_AUTOCVT
86+
SYMDEF(&IPVSRV1='IPL')
87+
/* End Ansible Block Insert */"""
88+
6489
TEST_CONTENT_ADVANCED_REGULAR_EXPRESSION="""if [ -z STEPLIB ] && tty -s;
6590
then
6691
D160882
@@ -681,6 +706,34 @@ def test_ds_line_insertafter_regex(ansible_zos_module, dstype):
681706
finally:
682707
remove_ds_environment(ansible_zos_module, ds_name)
683708

709+
@pytest.mark.ds
710+
@pytest.mark.parametrize("dstype", ds_type)
711+
def test_ds_line_insert_before_ansible_block(ansible_zos_module, dstype):
712+
hosts = ansible_zos_module
713+
ds_type = dstype
714+
715+
params = {
716+
"insertbefore": "/\* End Ansible Block Insert \*/",
717+
"line": "SYMDEF(&IPVSRV1='IPL')",
718+
"state": "present"
719+
}
720+
721+
ds_name = get_tmp_ds_name()
722+
temp_file = get_random_file_name(dir=TMP_DIRECTORY)
723+
content = TEST_PARSING_CONTENT
724+
725+
try:
726+
ds_full_name = set_ds_environment(ansible_zos_module, temp_file, ds_name, ds_type, content)
727+
params["path"] = ds_full_name
728+
729+
results = hosts.all.zos_lineinfile(**params)
730+
for result in results.contacted.values():
731+
assert result.get("changed") == 1
732+
results = hosts.all.shell(cmd="cat \"//'{0}'\" ".format(params["path"]))
733+
for result in results.contacted.values():
734+
assert result.get("stdout") == EXPECTED_TEST_PARSING_CONTENT
735+
finally:
736+
remove_ds_environment(ansible_zos_module, ds_name)
684737

685738
@pytest.mark.ds
686739
@pytest.mark.parametrize("dstype", ds_type)

0 commit comments

Comments
 (0)