Skip to content

Commit

Permalink
[v1.10.0] [zos_gather_facts] ZOAU 1.3 migration - zos_gather_facts (#…
Browse files Browse the repository at this point in the history
…1196)

* update module to leverage zoau python api for zinfo

Signed-off-by: Ketan Kelkar <[email protected]>

* add changelog fragment

Signed-off-by: Ketan Kelkar <[email protected]>

* address pep8 issues

Signed-off-by: Ketan Kelkar <[email protected]>

* update catch-all error message

Signed-off-by: Ketan Kelkar <[email protected]>

---------

Signed-off-by: Ketan Kelkar <[email protected]>
  • Loading branch information
ketankelkar authored Feb 1, 2024
1 parent 2109a5c commit f81108d
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 67 deletions.
4 changes: 4 additions & 0 deletions changelogs/fragments/1196-zoau-migration-zos_gather_facts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trivial:
- zos_gather_facts - Update module internally to leverage ZOAU python API
for zinfo.
(https://github.com/ansible-collections/ibm_zos_core/pull/1196).
87 changes: 36 additions & 51 deletions plugins/modules/zos_gather_facts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) IBM Corporation 2022, 2023
# Copyright (c) IBM Corporation 2022 - 2024
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand Down Expand Up @@ -108,30 +108,38 @@
"""

from fnmatch import fnmatch
import json
import traceback

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import (
zoau_version_checker
)

from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.import_handler import (
ZOAUImportError,
)

try:
from zoautil_py import zsystem
except ImportError:
zsystem = ZOAUImportError(traceback.format_exc())


def zinfo_cmd_string_builder(gather_subset):
"""Builds a command string for 'zinfo' based off the gather_subset list.
def zinfo_facts_list_builder(gather_subset):
"""Builds a list of strings to pass into 'zinfo' based off the
gather_subset list.
Arguments:
gather_subset {list} -- A list of subsets to pass in.
Returns:
[str] -- A string that contains a command line argument for calling
zinfo with the appropriate options.
[list[str]] -- A list of strings that contains sanitized subsets.
[None] -- An invalid value was received for the subsets.
"""
if gather_subset is None or 'all' in gather_subset:
return "zinfo -j -a"
return ["all"]

# base value
zinfo_arg_string = "zinfo -j"
subsets_list = []

# build full string
for subset in gather_subset:
# remove leading/trailing spaces
subset = subset.strip()
Expand All @@ -141,9 +149,9 @@ def zinfo_cmd_string_builder(gather_subset):
# sanitize subset against malicious (probably alphanumeric only?)
if not subset.isalnum():
return None
zinfo_arg_string += " -t " + subset
subsets_list.append(subset)

return zinfo_arg_string
return subsets_list


def flatten_zinfo_json(zinfo_dict):
Expand Down Expand Up @@ -214,59 +222,36 @@ def run_module():
if module.check_mode:
module.exit_json(**result)

if not zoau_version_checker.is_zoau_version_higher_than("1.2.1"):
if not zoau_version_checker.is_zoau_version_higher_than("1.3.0"):
module.fail_json(
("The zos_gather_facts module requires ZOAU >= 1.2.1. Please "
("The zos_gather_facts module requires ZOAU >= 1.3.0. Please "
"upgrade the ZOAU version on the target node.")
)

gather_subset = module.params['gather_subset']

# build out zinfo command with correct options
# build out list of strings to pass to zinfo python api.
# call this whether or not gather_subsets list is empty/valid/etc
# rely on the function to report back errors. Note the function only
# rely on the helper function to report back errors. Note the function only
# returns None if there's malicious or improperly formatted subsets.
# Invalid subsets are caught when the actual zinfo command is run.
cmd = zinfo_cmd_string_builder(gather_subset)
if not cmd:
# Invalid subsets are caught when the actual zinfo function is run.
facts_list = zinfo_facts_list_builder(gather_subset)
if not facts_list:
module.fail_json(msg="An invalid subset was passed to Ansible.")

rc, fcinfo_out, err = module.run_command(cmd, encoding=None)

decode_str = fcinfo_out.decode('utf-8')

# We DO NOT return a partial list. Instead we FAIL FAST since we are
# targeting automation -- quiet but well-intended error messages may easily
# be skipped
if rc != 0:
# there are 3 known error messages in zinfo, if neither gets
# triggered then we send out this generic zinfo error message.
err_msg = ('An exception has occurred in Z Open Automation Utilities '
'(ZOAU) utility \'zinfo\'. See \'zinfo_err_msg\' for '
'additional details.')
# triggered by invalid optarg eg "zinfo -q"
if 'BGYSC5201E' in err.decode('utf-8'):
err_msg = ('Invalid call to zinfo. See \'zinfo_err_msg\' for '
'additional details.')
# triggered when optarg does not get expected arg eg "zinfo -t"
elif 'BGYSC5202E' in err.decode('utf-8'):
err_msg = ('Invalid call to zinfo. Possibly missing a valid subset'
' See \'zinfo_err_msg\' for additional details.')
# triggered by illegal subset eg "zinfo -t abc"
elif 'BGYSC5203E' in err.decode('utf-8'):
err_msg = ('An invalid subset was detected. See \'zinfo_err_msg\' '
'for additional details.')

module.fail_json(msg=err_msg, zinfo_err_msg=err)

zinfo_dict = {} # to track parsed zinfo facts.

try:
zinfo_dict = json.loads(decode_str)
except json.JSONDecodeError:
# tell user something else for this error? This error is thrown when
# Python doesn't like the json string it parsed from zinfo.
module.fail_json(msg="Unsupported JSON format for the output.")
zinfo_dict = zsystem.zinfo(json=True, facts=facts_list)
except ValueError:
err_msg = 'An invalid subset was detected.'
module.fail_json(msg=err_msg)
except Exception as e:
err_msg = (
'An exception has occurred. Unable to gather facts. '
'See stderr for more details.'
)
module.fail_json(msg=err_msg, stderr=str(e))

# remove zinfo subsets from parsed zinfo result, flatten by one level
flattened_d = flatten_zinfo_json(zinfo_dict)
Expand Down
3 changes: 1 addition & 2 deletions tests/functional/modules/test_zos_gather_facts_func.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

# Copyright (c) IBM Corporation 2022
# Copyright (c) IBM Corporation 2022 - 2024
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand Down Expand Up @@ -120,7 +120,6 @@ def test_with_gather_subset_bad(ansible_zos_module, gather_subset):

for result in results.contacted.values():
assert result is not None
assert re.match(r'^BGYSC5203E', result.get('zinfo_err_msg'))
assert re.match(r'^An invalid subset', result.get('msg'))


Expand Down
29 changes: 15 additions & 14 deletions tests/unit/test_zos_gather_facts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

# Copyright (c) IBM Corporation 2022
# Copyright (c) IBM Corporation 2022 - 2024
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand All @@ -18,38 +18,39 @@
__metaclass__ = type

import pytest
from mock import call

# Used my some mock modules, should match import directly below
IMPORT_NAME = "ibm_zos_core.plugins.modules.zos_gather_facts"

# Tests for zos_father_facts helper functions

test_data = [
(["ipl"], "zinfo -j -t ipl"),
(["ipl "], "zinfo -j -t ipl"),
([" ipl"], "zinfo -j -t ipl"),
(["ipl", "sys"], "zinfo -j -t ipl -t sys"),
(["all"], "zinfo -j -a"),
(None, "zinfo -j -a"),
(["ipl", "all", "sys"], "zinfo -j -a"),
(["ipl"], ["ipl"]),
(["ipl "], ["ipl"]),
([" ipl"], ["ipl"]),
(["ipl", "sys"], ["ipl", "sys"]),
(["all"], ["all"]),
(None, ["all"]),
(["ipl", "all", "sys"], ["all"]),
# function does not validate legal vs illegal subsets
(["asdf"], "zinfo -j -t asdf"),
([""], None), # attemtped injection
(["asdf"], ["asdf"]),
([""], None),
(["ipl; cat /.bashrc"], None), # attemtped injection
# for now, 'all' with some other invalid subset resolves to 'all'
(["ipl", "all", "ipl; cat /.ssh/id_rsa"], ["all"]),
]


@pytest.mark.parametrize("args,expected", test_data)
def test_zos_gather_facts_zinfo_cmd_string_builder(
def test_zos_gather_facts_zinfo_facts_list_builder(
zos_import_mocker, args, expected):

mocker, importer = zos_import_mocker
zos_gather_facts = importer(IMPORT_NAME)

try:
result = zos_gather_facts.zinfo_cmd_string_builder(args)
# # add more logic here as the function evolves.
result = zos_gather_facts.zinfo_facts_list_builder(args)
# add more logic here as the function evolves.
except Exception:
result = None
assert result == expected
Expand Down

0 comments on commit f81108d

Please sign in to comment.