Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
74031fd
Added dependency checker and unit test case
mayankmani-sde Oct 17, 2025
9be3bab
pushed requested changes
mayankmani-sde Oct 21, 2025
5ab1feb
added header
mayankmani-sde Oct 21, 2025
799542d
added python range 3.12-3.13
mayankmani-sde Oct 21, 2025
0f2235d
updated mock test
mayankmani-sde Oct 21, 2025
e860507
updated version
mayankmani-sde Oct 21, 2025
80f5d28
added header in test
mayankmani-sde Oct 21, 2025
701d066
removed alias dc from test
mayankmani-sde Oct 21, 2025
f2718fd
added header in test
mayankmani-sde Oct 21, 2025
c550b0c
added comment in test
mayankmani-sde Oct 21, 2025
7aa1be3
fixed sanity
mayankmani-sde Oct 21, 2025
a19b2b7
FIXED SANITY
mayankmani-sde Oct 21, 2025
217012c
fixed sanity
mayankmani-sde Oct 21, 2025
74d7afd
changed error message
mayankmani-sde Oct 22, 2025
079ab14
checked for Ensure all versions are available and fixed error message
mayankmani-sde Oct 22, 2025
7b37786
removed code for range check for max versions of python and zos
mayankmani-sde Oct 22, 2025
90a2c07
added warning message
mayankmani-sde Oct 22, 2025
5be0f12
enhanced error message
mayankmani-sde Oct 22, 2025
14f0424
fixed sanity
mayankmani-sde Oct 22, 2025
6edc75d
added comment in test
mayankmani-sde Oct 22, 2025
519c0ef
fixed
mayankmani-sde Oct 24, 2025
bd1a5ee
typo error
mayankmani-sde Oct 24, 2025
d2b05e4
fixed
mayankmani-sde Oct 24, 2025
d8baed7
fixed
mayankmani-sde Oct 24, 2025
1348507
typo error
mayankmani-sde Oct 24, 2025
ddb9b7f
enhamced code
mayankmani-sde Nov 5, 2025
130963a
enhamced unit test case
mayankmani-sde Nov 5, 2025
ada03b2
fixed
mayankmani-sde Nov 5, 2025
a0d535e
fixed
mayankmani-sde Nov 5, 2025
8018bef
fixed
mayankmani-sde Nov 5, 2025
59a13d3
added integration of dependency_checker in zos_apf module
mayankmani-sde Nov 5, 2025
27d8cf8
added integration of dependency_checker in zos_archive
mayankmani-sde Nov 5, 2025
c1617c9
added integration of dependency_checker module in zos_backup_restore
mayankmani-sde Nov 5, 2025
f0a7062
added integration for dependency_checker in zos_blockinfile
mayankmani-sde Nov 5, 2025
8ccdb1f
added dependency_checker in all modules except zos_volume_init.py
mayankmani-sde Nov 5, 2025
47db734
added dependency-checker in zos_volume_init.py
mayankmani-sde Nov 5, 2025
2626457
Merge branch 'dev' into Enhancement/2133/Dependency_checker
mayankmani-sde Nov 5, 2025
d4d9fe8
FIXED
mayankmani-sde Nov 5, 2025
665ea6b
enhanced
mayankmani-sde Nov 5, 2025
874028b
updated dependency matrix from 1.3.6.0 to 1.4.0
mayankmani-sde Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions plugins/module_utils/dependency_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
# Copyright (c) IBM Corporation 2025
# 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 at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import, division, print_function
import sys
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import version

try:
from zoautil_py import zsystem
except ImportError:
zsystem = None

__metaclass__ = type

# ------------------------------------------------------------------------------
# Compatibility Matrix by Collection Version
# ------------------------------------------------------------------------------
COMPATIBILITY_MATRIX = {
"2.0.0": [
{"zoau_version": "1.4.0", "min_python_version": "3.12", "min_zos_version": 2.5},
{"zoau_version": "1.4.1", "min_python_version": "3.12", "min_zos_version": 2.5},
{"zoau_version": "1.4.2", "min_python_version": "3.12", "min_zos_version": 2.5},
],
"2.1.0": [
{"zoau_version": "1.4.1", "min_python_version": "3.12", "min_zos_version": 2.5},
{"zoau_version": "1.4.2", "min_python_version": "3.12", "min_zos_version": 2.5},
],
"2.2.0": [
{"zoau_version": "1.4.2", "min_python_version": "3.12", "min_zos_version": 2.5},
],
}

# ------------------------------------------------------------------------------
# Version Fetchers
# ------------------------------------------------------------------------------


def get_zoau_version(module=None):
try:
from zoautil_py import ZOAU_API_VERSION
return ZOAU_API_VERSION
except ImportError:
if module:
module.fail_json(msg="Unable to import ZOAU. Please check PYTHONPATH, LIBPATH, ZOAU_HOME and PATH environment variables.")
return None


def get_python_version_info():
return sys.version_info.major, sys.version_info.minor


def get_python_version():
return f"{sys.version_info.major}.{sys.version_info.minor}.0"


def get_zos_version(module=None):
if zsystem is None:
if module:
module.warn("Unable to import ZOAU zsystem module.")
return None
try:
sys_info = zsystem.zinfo("sys", json_format=True)
sys_data = sys_info.get("data", {}).get("sys_info", {})
version = sys_data.get("product_version")
release = sys_data.get("product_release")
if version and release:
return f"{int(version)}.{int(release)}"
except Exception as e:
if module:
module.warn(f"Failed to fetch z/OS version: {e}")
return None

# ------------------------------------------------------------------------------
# Dependency Validation
# ------------------------------------------------------------------------------


def validate_dependencies(module):
zoau_version = get_zoau_version(module)
python_major, python_minor = get_python_version_info()
python_version_str = get_python_version()
zos_version_str = get_zos_version(module)
collection_version = version.__version__

# Ensure critical versions are available
if not all([zoau_version, zos_version_str, python_version_str, collection_version]):
module.fail_json(msg="Unable to fetch one or more required dependencies. Depedencies checked are ZOAU, Python, z/OS.")

# Convert z/OS version to float if available
zos_version = None
if zos_version_str:
try:
zos_version = float(zos_version_str)
except Exception:
if module:
module.warn(f"Unable to parse z/OS version: {zos_version_str}")

compat_list = COMPATIBILITY_MATRIX.get(collection_version, [])
if not compat_list:
module.fail_json(msg=f"No compatibility information for collection version: {collection_version}")

def parse_py(v):
return tuple(map(int, v.split(".")))

current_py = (python_major, python_minor)
min_py = None
min_zos = None

# Find matching ZOAU entry
for compat in compat_list:
if compat["zoau_version"] == zoau_version:
min_py = parse_py(compat["min_python_version"])
min_zos = compat["min_zos_version"]
break

if not min_py or not min_zos:
module.fail_json(msg=f"Incompatible ZOAU version: {zoau_version}")

# --- Validation logic ---
warnings = []
max_py = (3, 13)
max_zos = 3.1

# Too old = fail
if current_py < min_py:
module.fail_json(msg=f"Incompatible Python version: {python_version_str}. Minimum supported is {compat['min_python_version']}.")
if zos_version is not None and zos_version < min_zos:
module.fail_json(msg=f"Incompatible z/OS version: {zos_version_str}. Minimum supported is {min_zos}.")

# Too new = warn
if current_py > max_py:
msg = f"Python {python_version_str} exceeds the maximum tested version {max_py[0]}.{max_py[1]}."
warnings.append(msg)
module.warn(msg)

if zos_version is not None and zos_version > max_zos:
msg = f"z/OS {zos_version_str} exceeds the maximum tested version {max_zos}."
warnings.append(msg)
module.warn(msg)

# Exit module with warnings if any
if warnings:
module.exit_json(changed=False, msg="Dependency check passed with warnings.", warnings=warnings)
else:
module.exit_json(changed=False, msg="Dependency compatibility check passed.")
15 changes: 15 additions & 0 deletions plugins/module_utils/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-

# Copyright (c) IBM Corporation 2025
# 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
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Update this version with each new release of the collection

__version__ = "2.0.0"
119 changes: 119 additions & 0 deletions tests/unit/test_dependency_checker_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
# Copyright (c) IBM Corporation 2025
# 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
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest
from unittest.mock import MagicMock, patch
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import dependency_checker
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import version

class FakeModule:
def __init__(self):
self.warned = []
self.exited = None

def fail_json(self, **kwargs):
raise Exception(kwargs.get("msg", "fail_json called"))

def exit_json(self, **kwargs):
self.exited = kwargs
raise StopIteration(kwargs)

def warn(self, msg):
self.warned.append(msg)


# ------------------------------
# Test: Python above max triggers warning
# ------------------------------
def test_python_above_max(monkeypatch):
# This approach is preferred over using simple dictionaries because:
# 1. The validate_dependencies function relies on real functions to fetch versions.
# 2. Using monkeypatch allows us to simulate different runtime environments.
# 3. We can precisely test success, failure, and warning scenarios without affecting
# the real system or requiring specific Python/ZOAU/zOS versions.
monkeypatch.setattr(dependency_checker, "get_zoau_version", lambda mod=None: "1.4.2")
monkeypatch.setattr(dependency_checker, "get_python_version_info", lambda: (3, 14))
monkeypatch.setattr(dependency_checker, "get_python_version", lambda: "3.14.0")
monkeypatch.setattr(dependency_checker, "get_zos_version", lambda mod=None: "2.6")
monkeypatch.setattr(version, "__version__", "2.0.0")

mod = FakeModule()
with pytest.raises(StopIteration):
dependency_checker.validate_dependencies(mod)
assert any("Python 3.14.0 exceeds the maximum tested version" in w for w in mod.warned)
assert mod.exited["msg"] == "Dependency check passed with warnings."


# ------------------------------
# Test: z/OS above max triggers warning
# ------------------------------
def test_zos_above_max(monkeypatch):
monkeypatch.setattr(dependency_checker, "get_zoau_version", lambda mod=None: "1.4.2")
monkeypatch.setattr(dependency_checker, "get_python_version_info", lambda: (3, 12))
monkeypatch.setattr(dependency_checker, "get_python_version", lambda: "3.12.0")
monkeypatch.setattr(dependency_checker, "get_zos_version", lambda mod=None: "3.2")
monkeypatch.setattr(version, "__version__", "2.0.0")

mod = FakeModule()
with pytest.raises(StopIteration):
dependency_checker.validate_dependencies(mod)
assert any("z/OS 3.2 exceeds the maximum tested version" in w for w in mod.warned)
assert mod.exited["msg"] == "Dependency check passed with warnings."


# ------------------------------
# Test: versions within range pass without warning
# ------------------------------
def test_versions_within_range(monkeypatch):
monkeypatch.setattr(dependency_checker, "get_zoau_version", lambda mod=None: "1.4.2")
monkeypatch.setattr(dependency_checker, "get_python_version_info", lambda: (3, 12))
monkeypatch.setattr(dependency_checker, "get_python_version", lambda: "3.12.0")
monkeypatch.setattr(dependency_checker, "get_zos_version", lambda mod=None: "2.6")
monkeypatch.setattr(version, "__version__", "2.0.0")

mod = FakeModule()
with pytest.raises(StopIteration):
dependency_checker.validate_dependencies(mod)
assert mod.warned == []
assert mod.exited["msg"] == "Dependency compatibility check passed."


# ------------------------------
# Test: Python below min fails
# ------------------------------
def test_python_below_min(monkeypatch):
monkeypatch.setattr(dependency_checker, "get_zoau_version", lambda mod=None: "1.4.2")
monkeypatch.setattr(dependency_checker, "get_python_version_info", lambda: (3, 11))
monkeypatch.setattr(dependency_checker, "get_python_version", lambda: "3.11.0")
monkeypatch.setattr(dependency_checker, "get_zos_version", lambda mod=None: "2.6")
monkeypatch.setattr(version, "__version__", "2.0.0")

mod = FakeModule()
with pytest.raises(Exception) as exc:
dependency_checker.validate_dependencies(mod)
assert "Incompatible Python version" in str(exc.value)


# ------------------------------
# Test: z/OS below min fails
# ------------------------------
def test_zos_below_min(monkeypatch):
monkeypatch.setattr(dependency_checker, "get_zoau_version", lambda mod=None: "1.4.2")
monkeypatch.setattr(dependency_checker, "get_python_version_info", lambda: (3, 12))
monkeypatch.setattr(dependency_checker, "get_python_version", lambda: "3.12.0")
monkeypatch.setattr(dependency_checker, "get_zos_version", lambda mod=None: "2.4")
monkeypatch.setattr(version, "__version__", "2.0.0")

mod = FakeModule()
with pytest.raises(Exception) as exc:
dependency_checker.validate_dependencies(mod)
assert "Incompatible z/OS version" in str(exc.value)