From 309fe6d4d007933852706a2500331a184ce41dff Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 12 May 2025 11:54:27 +0330 Subject: [PATCH 01/51] initial nfs_exports_info module --- .github/BOTMETA.yml | 2 + plugins/modules/nfs_exports_info.py | 123 ++++++++++++++++++ .../plugins/modules/test_nfs_exports_info.py | 56 ++++++++ 3 files changed, 181 insertions(+) create mode 100644 plugins/modules/nfs_exports_info.py create mode 100644 tests/unit/plugins/modules/test_nfs_exports_info.py diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 4095986151..16fc49622f 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -986,6 +986,8 @@ files: ignore: mcodd $modules/nexmo.py: maintainers: sivel + $modules/nfs_exports_info.py: + maintainers: yousefenzhad $modules/nginx_status_info.py: maintainers: resmo $modules/nictagadm.py: diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py new file mode 100644 index 0000000000..0ebecc1b6a --- /dev/null +++ b/plugins/modules/nfs_exports_info.py @@ -0,0 +1,123 @@ +# Copyright: (c) 2025, Samaneh Yousefnezhad +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: nfs_exports_info + +short_description: Extract folders, IPs, and options from /etc/exports + +description: > + This module retrieves and processes the contents of the /etc/exports file from a remote server, + mapping folders to their corresponding IP addresses and access options. + +author: + - Samaneh Yousefnezhad (@yousefenzhad) + +options: + output_format: + description: + - The format of the returned mapping. + - If set to C(ips_per_share), output maps shared folders to IPs and options. + - If set to C(shares_per_ip), output maps IPs to shared folders and options. + required: true + type: str + choices: ['ips_per_share', 'shares_per_ip'] +""" + +EXAMPLES = r""" +- name: Get IPs and options per shared folder + fava.infra.nfs_exports_info: + output_format: 'ips_per_share' + +- name: Get shared folders and options per IP + fava.infra.nfs_exports_info: + output_format: 'shares_per_ip' +""" + +RETURN = r""" +exports_info: + description: A mapping of shared folders to IPs and their options, or the reverse. + type: dict + returned: always + +file_digest: + description: SHA1 hash of /etc/exports file for integrity verification. + type: str + returned: always +""" + +from ansible.module_utils.basic import AnsibleModule +import re + + +def get_exports(module, output_format, file_path="/etc/exports"): + try: + exports_file_digest = module.digest_from_file(file_path, 'sha1') + if exports_file_digest is None: + module.fail_json(msg=f"{file_path} file not found") + + with open(file_path, 'r') as f: + output_lines = f.readlines() + + exports = {} + pattern = r'\s*(\S+)\s+(.+)' + + for line in output_lines: + if line.strip() and not line.strip().startswith('#'): + match = re.match(pattern, line) + if not match: + continue + + folder = match.group(1) + rest = match.group(2) + + entries = re.findall(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)', rest) + for ip, options_str in entries: + options = options_str.split(',') + + if output_format == "ips_per_share": + entry = {"ip": ip, "options": options} + exports.setdefault(folder, []).append(entry) + + elif output_format == "shares_per_ip": + entry = {"folder": folder, "options": options} + exports.setdefault(ip, []).append(entry) + + return { + 'exports_info': exports, + 'file_digest': exports_file_digest + } + + except FileNotFoundError: + module.fail_json(msg=f"{file_path} file not found") + except Exception as e: + module.fail_json(msg=str(e)) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + output_format=dict(type='str', required=True, choices=['ips_per_share', 'shares_per_ip']) + ), + supports_check_mode=True + ) + + output_format = module.params['output_format'] + exports_info = get_exports(module, output_format) + + module.exit_json( + changed=False, + exports_info=exports_info['exports_info'], + file_digest=exports_info['file_digest'] + ) + + +if __name__ == '__main__': + main() + +__all__ = ['get_exports'] diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py new file mode 100644 index 0000000000..6ebe1fef99 --- /dev/null +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -0,0 +1,56 @@ +import pytest +from unittest.mock import mock_open, patch, MagicMock +from nfs_exports_info import get_exports + + +@pytest.fixture +def fake_exports_content(): + return """ +# Sample exports +/srv/nfs1 192.168.1.10(rw,sync) 192.168.1.20(ro,sync) +/srv/nfs2 192.168.1.30(rw,no_root_squash) +""" + + +def test_get_exports_ips_per_share(fake_exports_content): + mock_module = MagicMock() + mock_module.digest_from_file.return_value = "fake_sha1_digest" + + with patch("builtins.open", mock_open(read_data=fake_exports_content)): + result = get_exports(mock_module, "ips_per_share") + + expected = { + '/srv/nfs1': [ + {'ip': '192.168.1.10', 'options': ['rw', 'sync']}, + {'ip': '192.168.1.20', 'options': ['ro', 'sync']} + ], + '/srv/nfs2': [ + {'ip': '192.168.1.30', 'options': ['rw', 'no_root_squash']} + ] + } + + assert result['exports_info'] == expected + assert result['file_digest'] == "fake_sha1_digest" + + +def test_get_exports_shares_per_ip(fake_exports_content): + mock_module = MagicMock() + mock_module.digest_from_file.return_value = "fake_sha1_digest" + + with patch("builtins.open", mock_open(read_data=fake_exports_content)): + result = get_exports(mock_module, "shares_per_ip") + + expected = { + '192.168.1.10': [ + {'folder': '/srv/nfs1', 'options': ['rw', 'sync']} + ], + '192.168.1.20': [ + {'folder': '/srv/nfs1', 'options': ['ro', 'sync']} + ], + '192.168.1.30': [ + {'folder': '/srv/nfs2', 'options': ['rw', 'no_root_squash']} + ] + } + + assert result['exports_info'] == expected + assert result['file_digest'] == "fake_sha1_digest" From 05a326c5c7d6de6882cc5b13e190234afe7a3f75 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 12 May 2025 12:39:46 +0330 Subject: [PATCH 02/51] change nfs_exports_info --- plugins/modules/nfs_exports_info.py | 57 +++++++++++++++++------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 0ebecc1b6a..8a671f550c 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -59,44 +59,53 @@ def get_exports(module, output_format, file_path="/etc/exports"): try: exports_file_digest = module.digest_from_file(file_path, 'sha1') if exports_file_digest is None: - module.fail_json(msg=f"{file_path} file not found") + module.fail_json(msg="{} file not found".format(file_path)) - with open(file_path, 'r') as f: + try: + f = open(file_path, 'r') output_lines = f.readlines() + f.close() + except IOError: + module.fail_json(msg="Could not read {}".format(file_path)) exports = {} pattern = r'\s*(\S+)\s+(.+)' for line in output_lines: - if line.strip() and not line.strip().startswith('#'): - match = re.match(pattern, line) - if not match: - continue + line = line.strip() + if not line or line.startswith('#'): + continue - folder = match.group(1) - rest = match.group(2) + match = re.match(pattern, line) + if not match: + continue - entries = re.findall(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)', rest) - for ip, options_str in entries: - options = options_str.split(',') + folder = match.group(1) + rest = match.group(2) - if output_format == "ips_per_share": - entry = {"ip": ip, "options": options} - exports.setdefault(folder, []).append(entry) + entries = re.findall(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)', rest) + for ip, options_str in entries: + options = options_str.split(',') - elif output_format == "shares_per_ip": - entry = {"folder": folder, "options": options} - exports.setdefault(ip, []).append(entry) + if output_format == "ips_per_share": + entry = {"ip": ip, "options": options} + if folder not in exports: + exports[folder] = [] + exports[folder].append(entry) + + elif output_format == "shares_per_ip": + entry = {"folder": folder, "options": options} + if ip not in exports: + exports[ip] = [] + exports[ip].append(entry) return { 'exports_info': exports, 'file_digest': exports_file_digest } - except FileNotFoundError: - module.fail_json(msg=f"{file_path} file not found") except Exception as e: - module.fail_json(msg=str(e)) + module.fail_json(msg="Error while processing exports: {}".format(str(e))) def main(): @@ -108,16 +117,16 @@ def main(): ) output_format = module.params['output_format'] - exports_info = get_exports(module, output_format) + result = get_exports(module, output_format) module.exit_json( changed=False, - exports_info=exports_info['exports_info'], - file_digest=exports_info['file_digest'] + exports_info=result['exports_info'], + file_digest=result['file_digest'] ) if __name__ == '__main__': main() -__all__ = ['get_exports'] +__all__ = ['get_exports'] \ No newline at end of file From e0c4e3cdff061af9826339efebd0c8f80c92ae29 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 12 May 2025 12:56:32 +0330 Subject: [PATCH 03/51] add line at the end of file --- plugins/modules/nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 8a671f550c..b650780e2a 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -129,4 +129,4 @@ def main(): if __name__ == '__main__': main() -__all__ = ['get_exports'] \ No newline at end of file +__all__ = ['get_exports'] From 4aa341ae63a7037d16bdcf872d69539623aea1b6 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 12 May 2025 13:09:32 +0330 Subject: [PATCH 04/51] Fix: add __future__ imports to test for Python 2 compatibility --- tests/unit/plugins/modules/test_nfs_exports_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 6ebe1fef99..3f03d3e253 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import pytest from unittest.mock import mock_open, patch, MagicMock from nfs_exports_info import get_exports From 8949e2b598d4f16e5725c8e471e27bd4acb9d970 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 12 May 2025 13:35:13 +0330 Subject: [PATCH 05/51] Fix: add __metaclass__ for Python 2 compatibility in test --- tests/unit/plugins/modules/test_nfs_exports_info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 3f03d3e253..1bf6772aa5 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -1,9 +1,10 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + import pytest from unittest.mock import mock_open, patch, MagicMock from nfs_exports_info import get_exports - @pytest.fixture def fake_exports_content(): return """ From fef3a835cf194466080611cbb091efbcd6dd8217 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 12 May 2025 13:43:22 +0330 Subject: [PATCH 06/51] Fix E302: add 2 blank lines before top-level function --- tests/unit/plugins/modules/test_nfs_exports_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 1bf6772aa5..7eedca54ac 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -5,6 +5,7 @@ import pytest from unittest.mock import mock_open, patch, MagicMock from nfs_exports_info import get_exports + @pytest.fixture def fake_exports_content(): return """ From 2c75f3c29d16110f49628dec5b1da053d59ee2dc Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 07:33:23 +0330 Subject: [PATCH 07/51] correct ERROR collecting --- tests/unit/plugins/modules/test_nfs_exports_info.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 7eedca54ac..eab2720883 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -3,8 +3,7 @@ __metaclass__ = type import pytest from unittest.mock import mock_open, patch, MagicMock -from nfs_exports_info import get_exports - +from plugins.modules.nfs_exports_info import get_exports @pytest.fixture def fake_exports_content(): From e97bc684997af964454b3da834ff530815f7d51d Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 07:43:42 +0330 Subject: [PATCH 08/51] add blank --- tests/unit/plugins/modules/test_nfs_exports_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index eab2720883..4cd5c0a3ac 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -5,6 +5,7 @@ import pytest from unittest.mock import mock_open, patch, MagicMock from plugins.modules.nfs_exports_info import get_exports + @pytest.fixture def fake_exports_content(): return """ From 096f019aef3d029d731f3720b44a25024a061377 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 08:00:34 +0330 Subject: [PATCH 09/51] error mock module --- tests/unit/plugins/modules/test_nfs_exports_info.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 4cd5c0a3ac..6806b160ba 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -2,7 +2,13 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type import pytest -from unittest.mock import mock_open, patch, MagicMock + + +try: + from unittest.mock import mock_open, patch, MagicMock +except ImportError: + from mock import mock_open, patch, MagicMock + from plugins.modules.nfs_exports_info import get_exports @@ -57,3 +63,4 @@ def test_get_exports_shares_per_ip(fake_exports_content): assert result['exports_info'] == expected assert result['file_digest'] == "fake_sha1_digest" + \ No newline at end of file From 6367ff0226610c232821108a6e5f264176ae2737 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 08:06:10 +0330 Subject: [PATCH 10/51] new line --- tests/unit/plugins/modules/test_nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 6806b160ba..5a860367e7 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -63,4 +63,4 @@ def test_get_exports_shares_per_ip(fake_exports_content): assert result['exports_info'] == expected assert result['file_digest'] == "fake_sha1_digest" - \ No newline at end of file + From a0fae4c162353b285bf1742944442678f801d29e Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 08:11:58 +0330 Subject: [PATCH 11/51] new line end of file --- tests/unit/plugins/modules/test_nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 5a860367e7..ba2372b820 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -63,4 +63,4 @@ def test_get_exports_shares_per_ip(fake_exports_content): assert result['exports_info'] == expected assert result['file_digest'] == "fake_sha1_digest" - + From fc5073e341afeae4e1318618a8e531db53457b98 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 08:17:25 +0330 Subject: [PATCH 12/51] blank line at end of file --- tests/unit/plugins/modules/test_nfs_exports_info.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index ba2372b820..2acc5381f3 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -63,4 +63,3 @@ def test_get_exports_shares_per_ip(fake_exports_content): assert result['exports_info'] == expected assert result['file_digest'] == "fake_sha1_digest" - From 9c2dbfc5c1ad09b3a1980ac0af690133d0c76c52 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 08:25:43 +0330 Subject: [PATCH 13/51] FATAL: Command "pytest " --- .../plugins/modules/test_nfs_exports_info.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 2acc5381f3..80e61f9cbc 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -1,15 +1,19 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -import pytest - - try: from unittest.mock import mock_open, patch, MagicMock except ImportError: from mock import mock_open, patch, MagicMock -from plugins.modules.nfs_exports_info import get_exports +import pytest +import sys +import os + +# Add plugins/modules to path for direct import +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../plugins/modules'))) + +from nfs_exports_info import get_exports @pytest.fixture @@ -25,7 +29,7 @@ def test_get_exports_ips_per_share(fake_exports_content): mock_module = MagicMock() mock_module.digest_from_file.return_value = "fake_sha1_digest" - with patch("builtins.open", mock_open(read_data=fake_exports_content)): + with patch("builtins.open", mock_open(read_data=fake_exports_content)) if sys.version_info[0] == 3 else patch("__builtin__.open", mock_open(read_data=fake_exports_content)): result = get_exports(mock_module, "ips_per_share") expected = { @@ -46,7 +50,7 @@ def test_get_exports_shares_per_ip(fake_exports_content): mock_module = MagicMock() mock_module.digest_from_file.return_value = "fake_sha1_digest" - with patch("builtins.open", mock_open(read_data=fake_exports_content)): + with patch("builtins.open", mock_open(read_data=fake_exports_content)) if sys.version_info[0] == 3 else patch("__builtin__.open", mock_open(read_data=fake_exports_content)): result = get_exports(mock_module, "shares_per_ip") expected = { @@ -62,4 +66,4 @@ def test_get_exports_shares_per_ip(fake_exports_content): } assert result['exports_info'] == expected - assert result['file_digest'] == "fake_sha1_digest" + assert result['file_digest'] == "fake_sha1_digest" \ No newline at end of file From 128bb8b5c07e374f30304eba4cbf528a9250e962 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 08:31:38 +0330 Subject: [PATCH 14/51] Error Found 3 pep8 issue --- tests/unit/plugins/modules/test_nfs_exports_info.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 80e61f9cbc..1053226c48 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -11,7 +11,9 @@ import sys import os # Add plugins/modules to path for direct import -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../plugins/modules'))) +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '../../../../plugins/modules') +)) from nfs_exports_info import get_exports @@ -29,7 +31,8 @@ def test_get_exports_ips_per_share(fake_exports_content): mock_module = MagicMock() mock_module.digest_from_file.return_value = "fake_sha1_digest" - with patch("builtins.open", mock_open(read_data=fake_exports_content)) if sys.version_info[0] == 3 else patch("__builtin__.open", mock_open(read_data=fake_exports_content)): + patch_target = "builtins.open" if sys.version_info[0] == 3 else "__builtin__.open" + with patch(patch_target, mock_open(read_data=fake_exports_content)): result = get_exports(mock_module, "ips_per_share") expected = { @@ -50,7 +53,8 @@ def test_get_exports_shares_per_ip(fake_exports_content): mock_module = MagicMock() mock_module.digest_from_file.return_value = "fake_sha1_digest" - with patch("builtins.open", mock_open(read_data=fake_exports_content)) if sys.version_info[0] == 3 else patch("__builtin__.open", mock_open(read_data=fake_exports_content)): + patch_target = "builtins.open" if sys.version_info[0] == 3 else "__builtin__.open" + with patch(patch_target, mock_open(read_data=fake_exports_content)): result = get_exports(mock_module, "shares_per_ip") expected = { @@ -66,4 +70,4 @@ def test_get_exports_shares_per_ip(fake_exports_content): } assert result['exports_info'] == expected - assert result['file_digest'] == "fake_sha1_digest" \ No newline at end of file + assert result['file_digest'] == "fake_sha1_digest" From 7dd25442171d454c1a92732ecf5c75ddaa1cea02 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 13 May 2025 09:41:41 +0330 Subject: [PATCH 15/51] SPDX Format --- plugins/modules/nfs_exports_info.py | 1 + tests/unit/plugins/modules/test_nfs_exports_info.py | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index b650780e2a..9c7a1fe021 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-3.0-or-later # Copyright: (c) 2025, Samaneh Yousefnezhad # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 1053226c48..57c4eb7ac5 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function __metaclass__ = type From fafc2c0a641c25c4ccbd93ec6d570368ada1b4dd Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Sat, 17 May 2025 10:24:44 +0330 Subject: [PATCH 16/51] NOX Errors --- plugins/modules/nfs_exports_info.py | 22 ++++++++++++------- .../plugins/modules/test_nfs_exports_info.py | 5 +++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 9c7a1fe021..aa69c2f9c2 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -1,7 +1,10 @@ -# SPDX-License-Identifier: GPL-3.0-or-later +#!/usr/bin/python + # Copyright: (c) 2025, Samaneh Yousefnezhad -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function __metaclass__ = type @@ -10,11 +13,11 @@ DOCUMENTATION = r""" --- module: nfs_exports_info -short_description: Extract folders, IPs, and options from /etc/exports +short_description: Extract folders, IPs, and options from (/etc/exports) -description: > - This module retrieves and processes the contents of the /etc/exports file from a remote server, - mapping folders to their corresponding IP addresses and access options. +description: + - This module retrieves and processes the contents of the /etc/exports file from a remote server, + mapping folders to their corresponding IP addresses and access options. author: - Samaneh Yousefnezhad (@yousefenzhad) @@ -34,6 +37,7 @@ EXAMPLES = r""" - name: Get IPs and options per shared folder fava.infra.nfs_exports_info: output_format: 'ips_per_share' + register: result - name: Get shared folders and options per IP fava.infra.nfs_exports_info: @@ -42,7 +46,9 @@ EXAMPLES = r""" RETURN = r""" exports_info: - description: A mapping of shared folders to IPs and their options, or the reverse. + description: + - A mapping of shared folders to IPs and their options, or the reverse. + - What it is depends on O(output_format). type: dict returned: always diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 57c4eb7ac5..2cc59daed8 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -1,4 +1,9 @@ +# Copyright (c) 2025, Samaneh Yousefnezhad + +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) + # SPDX-License-Identifier: GPL-3.0-or-later + from __future__ import absolute_import, division, print_function __metaclass__ = type From 2c3bdff4420a35a7b8c9c7ca38c7423bc60622f2 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Sat, 17 May 2025 10:32:15 +0330 Subject: [PATCH 17/51] trailing whitespace --- plugins/modules/nfs_exports_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index aa69c2f9c2..090eeb3dea 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -15,7 +15,7 @@ module: nfs_exports_info short_description: Extract folders, IPs, and options from (/etc/exports) -description: +description: - This module retrieves and processes the contents of the /etc/exports file from a remote server, mapping folders to their corresponding IP addresses and access options. @@ -46,7 +46,7 @@ EXAMPLES = r""" RETURN = r""" exports_info: - description: + description: - A mapping of shared folders to IPs and their options, or the reverse. - What it is depends on O(output_format). type: dict From 5eda6b9a4f9253b4e2292534079094caff7a9c04 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 19 May 2025 08:27:43 +0330 Subject: [PATCH 18/51] SPDX-License --- plugins/modules/nfs_exports_info.py | 2 +- tests/unit/plugins/modules/test_nfs_exports_info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 090eeb3dea..80c514b2dd 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -1,6 +1,6 @@ #!/usr/bin/python -# Copyright: (c) 2025, Samaneh Yousefnezhad +# SPDX-FileCopyrightText: (c) 2025, Samaneh Yousefnezhad # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 2cc59daed8..f440ec1bf0 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Samaneh Yousefnezhad +# SPDX-FileCopyrightText: (c) 2025, Samaneh Yousefnezhad # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) From e77b24f6394de55c10d033133541baacf2cd1ebd Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 21 May 2025 09:41:10 +0330 Subject: [PATCH 19/51] Erorr community --- plugins/modules/nfs_exports_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 80c514b2dd..331417b50b 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -35,12 +35,12 @@ options: EXAMPLES = r""" - name: Get IPs and options per shared folder - fava.infra.nfs_exports_info: + community.general.nfs_exports_info: output_format: 'ips_per_share' register: result - name: Get shared folders and options per IP - fava.infra.nfs_exports_info: + community.general.nfs_exports_info: output_format: 'shares_per_ip' """ From edba62e9714dbb2f201516c3f2f1595db963460d Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 10:50:15 +0330 Subject: [PATCH 20/51] Format Error --- plugins/modules/nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 331417b50b..5411aa73c3 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -112,7 +112,7 @@ def get_exports(module, output_format, file_path="/etc/exports"): } except Exception as e: - module.fail_json(msg="Error while processing exports: {}".format(str(e))) + module.fail_json(msg="Error while processing exports: {}".format(e)) def main(): From 14cff08c7dce8a8a54941805979f6263267f0fa1 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 11:08:28 +0330 Subject: [PATCH 21/51] suggestion Error --- plugins/modules/nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 5411aa73c3..22ad91e1e8 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -13,7 +13,7 @@ DOCUMENTATION = r""" --- module: nfs_exports_info -short_description: Extract folders, IPs, and options from (/etc/exports) +short_description: Extract folders, IPs, and options from C(/etc/exports) description: - This module retrieves and processes the contents of the /etc/exports file from a remote server, From 37bd9a51dcf0ec05cc9a4b87ccef9cb59821196f Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 11:18:19 +0330 Subject: [PATCH 22/51] Error regular expression --- plugins/modules/nfs_exports_info.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 22ad91e1e8..14de7a96a8 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -63,6 +63,10 @@ import re def get_exports(module, output_format, file_path="/etc/exports"): + + IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') + + try: exports_file_digest = module.digest_from_file(file_path, 'sha1') if exports_file_digest is None: @@ -90,7 +94,10 @@ def get_exports(module, output_format, file_path="/etc/exports"): folder = match.group(1) rest = match.group(2) - entries = re.findall(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)', rest) + + entries = IP_ENTRY_PATTERN.findall(rest) + + for ip, options_str in entries: options = options_str.split(',') @@ -112,6 +119,7 @@ def get_exports(module, output_format, file_path="/etc/exports"): } except Exception as e: + module.fail_json(msg="Error while processing exports: {}".format(e)) @@ -136,4 +144,4 @@ def main(): if __name__ == '__main__': main() -__all__ = ['get_exports'] +__all__ = ['get_exports'] \ No newline at end of file From acfc096a2841479fc9deab4444bd1b1d5baae17f Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 11:22:27 +0330 Subject: [PATCH 23/51] regexp Error --- plugins/modules/nfs_exports_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 14de7a96a8..01e2ee7960 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -80,14 +80,14 @@ def get_exports(module, output_format, file_path="/etc/exports"): module.fail_json(msg="Could not read {}".format(file_path)) exports = {} - pattern = r'\s*(\S+)\s+(.+)' + pattern = re.compile(r'\s*(\S+)\s+(.+)') for line in output_lines: line = line.strip() if not line or line.startswith('#'): continue - match = re.match(pattern, line) + match = pattern.match(line) if not match: continue From c80e76f21bc3eb81f738560acb25f0d18cbcec9e Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 11:24:29 +0330 Subject: [PATCH 24/51] Syntax Error --- plugins/modules/nfs_exports_info.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 01e2ee7960..e97c4c8a58 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -1,9 +1,6 @@ #!/usr/bin/python - -# SPDX-FileCopyrightText: (c) 2025, Samaneh Yousefnezhad - -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - +# Copyright (c) 2025, Samaneh Yousefnezhad +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function From da148f6235a9d9e84be908977acf571d0a4a8512 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 11:36:03 +0330 Subject: [PATCH 25/51] space Error --- plugins/modules/nfs_exports_info.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index e97c4c8a58..1c6a971da5 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -59,11 +59,8 @@ from ansible.module_utils.basic import AnsibleModule import re -def get_exports(module, output_format, file_path="/etc/exports"): - +def get_exports(module, output_format, file_path="/etc/exports"): IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') - - try: exports_file_digest = module.digest_from_file(file_path, 'sha1') if exports_file_digest is None: @@ -83,18 +80,14 @@ def get_exports(module, output_format, file_path="/etc/exports"): line = line.strip() if not line or line.startswith('#'): continue - + match = pattern.match(line) if not match: continue - folder = match.group(1) rest = match.group(2) - entries = IP_ENTRY_PATTERN.findall(rest) - - for ip, options_str in entries: options = options_str.split(',') From e6b5d52c83ea17f5f1c68974f0a23535e5f46b87 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 11:40:54 +0330 Subject: [PATCH 26/51] trailing Error --- plugins/modules/nfs_exports_info.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 1c6a971da5..cbed4c6e53 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -57,8 +57,6 @@ file_digest: from ansible.module_utils.basic import AnsibleModule import re - - def get_exports(module, output_format, file_path="/etc/exports"): IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') try: @@ -80,7 +78,6 @@ def get_exports(module, output_format, file_path="/etc/exports"): line = line.strip() if not line or line.startswith('#'): continue - match = pattern.match(line) if not match: continue @@ -129,9 +126,6 @@ def main(): exports_info=result['exports_info'], file_digest=result['file_digest'] ) - - if __name__ == '__main__': main() - __all__ = ['get_exports'] \ No newline at end of file From 4004e07cd9cdd9e00af22473d3f327bbc0bb6a5f Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 11:53:23 +0330 Subject: [PATCH 27/51] blank Error --- plugins/modules/nfs_exports_info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index cbed4c6e53..f557989ab3 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -57,6 +57,8 @@ file_digest: from ansible.module_utils.basic import AnsibleModule import re + + def get_exports(module, output_format, file_path="/etc/exports"): IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') try: From 10e12a55d71037b6ba98af12e598dd00e378f19d Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 11:59:54 +0330 Subject: [PATCH 28/51] Blank Error --- plugins/modules/nfs_exports_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index f557989ab3..c88a043f1a 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -59,7 +59,7 @@ from ansible.module_utils.basic import AnsibleModule import re -def get_exports(module, output_format, file_path="/etc/exports"): +def get_exports(module, output_format, file_path="/etc/exports"): IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') try: exports_file_digest = module.digest_from_file(file_path, 'sha1') @@ -130,4 +130,4 @@ def main(): ) if __name__ == '__main__': main() -__all__ = ['get_exports'] \ No newline at end of file +__all__ = ['get_exports'] From 4cc0b7740f740d27d6375f104e8fd3ec286b1d89 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 12:04:33 +0330 Subject: [PATCH 29/51] 2 Blank Errors --- plugins/modules/nfs_exports_info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index c88a043f1a..222df87318 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -129,5 +129,7 @@ def main(): file_digest=result['file_digest'] ) if __name__ == '__main__': + + main() __all__ = ['get_exports'] From 1d65305eb15518f6b1ad7fab706a08bd36b709a8 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 12:10:02 +0330 Subject: [PATCH 30/51] again Blank --- plugins/modules/nfs_exports_info.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 222df87318..daf006d006 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -129,7 +129,6 @@ def main(): file_digest=result['file_digest'] ) if __name__ == '__main__': - - + main() __all__ = ['get_exports'] From 9944b60fc74b05c97d7e7ecd6393a40493ca0baa Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 12:15:34 +0330 Subject: [PATCH 31/51] 2 blank again --- plugins/modules/nfs_exports_info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index daf006d006..022962689f 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -112,6 +112,8 @@ def get_exports(module, output_format, file_path="/etc/exports"): module.fail_json(msg="Error while processing exports: {}".format(e)) + + def main(): module = AnsibleModule( argument_spec=dict( From 1d57e665d6ca94d38c12c7801f0254c9a834813e Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 12:27:01 +0330 Subject: [PATCH 32/51] Blanks --- plugins/modules/nfs_exports_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 022962689f..b6ae007780 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -112,8 +112,6 @@ def get_exports(module, output_format, file_path="/etc/exports"): module.fail_json(msg="Error while processing exports: {}".format(e)) - - def main(): module = AnsibleModule( argument_spec=dict( @@ -130,6 +128,8 @@ def main(): exports_info=result['exports_info'], file_digest=result['file_digest'] ) + + if __name__ == '__main__': main() From 09c3e3585a8568e90434be69a26a768deaa008f0 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 12:31:07 +0330 Subject: [PATCH 33/51] Blank Again --- plugins/modules/nfs_exports_info.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index b6ae007780..5894eb9b89 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -129,7 +129,6 @@ def main(): file_digest=result['file_digest'] ) - if __name__ == '__main__': main() From 7f328ec471045ef74f24cfe9aff3e7dae3e05be5 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Wed, 11 Jun 2025 12:48:31 +0330 Subject: [PATCH 34/51] Blank Error --- plugins/modules/nfs_exports_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 5894eb9b89..e77c106fcf 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -129,6 +129,7 @@ def main(): file_digest=result['file_digest'] ) + if __name__ == '__main__': main() From 04bf2be150fccf4527938db756c29867f5ad7bc3 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Sun, 15 Jun 2025 07:54:59 +0330 Subject: [PATCH 35/51] version_added --- plugins/modules/nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index e77c106fcf..e713baeae3 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -18,7 +18,7 @@ description: author: - Samaneh Yousefnezhad (@yousefenzhad) - +version_added: "11.1.0" options: output_format: description: From faf1a67d96f8a1b0223b151a1c086aeb9f3147f2 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Sun, 15 Jun 2025 08:11:34 +0330 Subject: [PATCH 36/51] paragraphs Error --- plugins/modules/nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index e713baeae3..43b232626b 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -13,7 +13,7 @@ module: nfs_exports_info short_description: Extract folders, IPs, and options from C(/etc/exports) description: - - This module retrieves and processes the contents of the /etc/exports file from a remote server, + - This module retrieves and processes the contents of the C(/etc/exports) file from a remote server, mapping folders to their corresponding IP addresses and access options. author: From db1ac68cbfa5fc5a707f4c69d061e94706f0889a Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Sun, 15 Jun 2025 08:46:50 +0330 Subject: [PATCH 37/51] SHA Changes --- plugins/modules/nfs_exports_info.py | 68 ++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 43b232626b..eba2f26ef3 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -1,5 +1,6 @@ #!/usr/bin/python -# Copyright (c) 2025, Samaneh Yousefnezhad + +# SPDX-FileCopyrightText: (c) 2025, Samaneh Yousefnezhad # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later @@ -13,12 +14,13 @@ module: nfs_exports_info short_description: Extract folders, IPs, and options from C(/etc/exports) description: - - This module retrieves and processes the contents of the C(/etc/exports) file from a remote server, + - This module retrieves and processes the contents of the /etc/exports file from a remote server, mapping folders to their corresponding IP addresses and access options. author: - Samaneh Yousefnezhad (@yousefenzhad) -version_added: "11.1.0" +version_added: "11.1.0" + options: output_format: description: @@ -50,39 +52,75 @@ exports_info: returned: always file_digest: - description: SHA1 hash of /etc/exports file for integrity verification. - type: str + description: + - A dictionary containing various hash values of the /etc/exports file for integrity verification. + - Keys are the hash algorithm names (e.g., 'sha256', 'sha1', 'md5'), and values are their corresponding hexadecimal digests. + - At least one hash value is guaranteed to be present if the file exists and is readable. + type: dict returned: always + sample: + sha256: "a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890" + sha1: "f7e8d9c0b1a23c4d5e6f7a8b9c0d1e2f3a4b5c6d" + md5: "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d" """ from ansible.module_utils.basic import AnsibleModule import re +import hashlib def get_exports(module, output_format, file_path="/etc/exports"): + + IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') + MAIN_LINE_PATTERN = re.compile(r'\s*(\S+)\s+(.+)') + + + file_digests = {} + hash_algorithms = ['sha256', 'sha1', 'md5'] + try: - exports_file_digest = module.digest_from_file(file_path, 'sha1') - if exports_file_digest is None: + + if not module.file_exists(file_path): module.fail_json(msg="{} file not found".format(file_path)) + file_content_bytes = None try: - f = open(file_path, 'r') - output_lines = f.readlines() - f.close() + with open(file_path, 'rb') as f: # Open in binary mode for hashing + file_content_bytes = f.read() except IOError: module.fail_json(msg="Could not read {}".format(file_path)) + if file_content_bytes: + for algo in hash_algorithms: + try: + hasher = hashlib.new(algo) + hasher.update(file_content_bytes) + file_digests[algo] = hasher.hexdigest() + except ValueError: + module.warn("Hash algorithm '{}' not available on this system. Skipping.".format(algo)) + except Exception as ex: + module.warn("Error calculating '{}' hash: {}".format(algo, ex)) + + exports = {} - pattern = re.compile(r'\s*(\S+)\s+(.+)') + + + output_lines = [] + if file_content_bytes: + output_lines = file_content_bytes.decode('utf-8', errors='ignore').splitlines() + for line in output_lines: line = line.strip() if not line or line.startswith('#'): continue - match = pattern.match(line) + + + match = MAIN_LINE_PATTERN.match(line) if not match: continue + folder = match.group(1) rest = match.group(2) @@ -104,14 +142,14 @@ def get_exports(module, output_format, file_path="/etc/exports"): return { 'exports_info': exports, - 'file_digest': exports_file_digest + 'file_digest': file_digests # <--- Returning the dictionary of hashes } except Exception as e: - module.fail_json(msg="Error while processing exports: {}".format(e)) + def main(): module = AnsibleModule( argument_spec=dict( @@ -131,6 +169,6 @@ def main(): if __name__ == '__main__': - main() + __all__ = ['get_exports'] From bccf55ba98d432601b31262e6a8716fcadc31cf1 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Sun, 15 Jun 2025 09:03:30 +0330 Subject: [PATCH 38/51] Blanks Errors --- plugins/modules/nfs_exports_info.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index eba2f26ef3..b0b76ea2b6 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -19,7 +19,7 @@ description: author: - Samaneh Yousefnezhad (@yousefenzhad) -version_added: "11.1.0" +version_added: "11.1.0" options: output_format: @@ -56,7 +56,7 @@ file_digest: - A dictionary containing various hash values of the /etc/exports file for integrity verification. - Keys are the hash algorithm names (e.g., 'sha256', 'sha1', 'md5'), and values are their corresponding hexadecimal digests. - At least one hash value is guaranteed to be present if the file exists and is readable. - type: dict + type: dict returned: always sample: sha256: "a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890" @@ -66,21 +66,19 @@ file_digest: from ansible.module_utils.basic import AnsibleModule import re -import hashlib +import hashlib def get_exports(module, output_format, file_path="/etc/exports"): - - + IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') MAIN_LINE_PATTERN = re.compile(r'\s*(\S+)\s+(.+)') - file_digests = {} hash_algorithms = ['sha256', 'sha1', 'md5'] try: - + if not module.file_exists(file_path): module.fail_json(msg="{} file not found".format(file_path)) @@ -101,10 +99,9 @@ def get_exports(module, output_format, file_path="/etc/exports"): module.warn("Hash algorithm '{}' not available on this system. Skipping.".format(algo)) except Exception as ex: module.warn("Error calculating '{}' hash: {}".format(algo, ex)) - - exports = {} - + + exports = {} output_lines = [] if file_content_bytes: @@ -147,9 +144,6 @@ def get_exports(module, output_format, file_path="/etc/exports"): except Exception as e: module.fail_json(msg="Error while processing exports: {}".format(e)) - - - def main(): module = AnsibleModule( argument_spec=dict( From 4f4c4bda34dbd79d212573ea0fcba3d1305f5a0e Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Sun, 15 Jun 2025 09:09:20 +0330 Subject: [PATCH 39/51] Blanks Again --- plugins/modules/nfs_exports_info.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index b0b76ea2b6..b15cf4ec08 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -70,12 +70,12 @@ import hashlib def get_exports(module, output_format, file_path="/etc/exports"): - + IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') - MAIN_LINE_PATTERN = re.compile(r'\s*(\S+)\s+(.+)') - + MAIN_LINE_PATTERN = re.compile(r'\s*(\S+)\s+(.+)') + file_digests = {} - hash_algorithms = ['sha256', 'sha1', 'md5'] + hash_algorithms = ['sha256', 'sha1', 'md5'] try: @@ -84,7 +84,7 @@ def get_exports(module, output_format, file_path="/etc/exports"): file_content_bytes = None try: - with open(file_path, 'rb') as f: # Open in binary mode for hashing + with open(file_path, 'rb') as f: file_content_bytes = f.read() except IOError: module.fail_json(msg="Could not read {}".format(file_path)) @@ -99,21 +99,15 @@ def get_exports(module, output_format, file_path="/etc/exports"): module.warn("Hash algorithm '{}' not available on this system. Skipping.".format(algo)) except Exception as ex: module.warn("Error calculating '{}' hash: {}".format(algo, ex)) - - - exports = {} + exports = {} output_lines = [] if file_content_bytes: output_lines = file_content_bytes.decode('utf-8', errors='ignore').splitlines() - - for line in output_lines: line = line.strip() if not line or line.startswith('#'): continue - - match = MAIN_LINE_PATTERN.match(line) if not match: continue From d448a638796088f2f258b3ddde15d547ade7bff8 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Sun, 15 Jun 2025 09:12:46 +0330 Subject: [PATCH 40/51] Space Error --- plugins/modules/nfs_exports_info.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index b15cf4ec08..eb20bc17cc 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -70,10 +70,9 @@ import hashlib def get_exports(module, output_format, file_path="/etc/exports"): - IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') MAIN_LINE_PATTERN = re.compile(r'\s*(\S+)\s+(.+)') - + file_digests = {} hash_algorithms = ['sha256', 'sha1', 'md5'] From 8d357942f9ebaf45f51b3043d130a706db9b928d Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 16 Jun 2025 07:56:00 +0330 Subject: [PATCH 41/51] Blanks --- plugins/modules/nfs_exports_info.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index eb20bc17cc..f9ded05a41 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -72,7 +72,7 @@ import hashlib def get_exports(module, output_format, file_path="/etc/exports"): IP_ENTRY_PATTERN = re.compile(r'(\d+\.\d+\.\d+\.\d+)\(([^)]+)\)') MAIN_LINE_PATTERN = re.compile(r'\s*(\S+)\s+(.+)') - + file_digests = {} hash_algorithms = ['sha256', 'sha1', 'md5'] @@ -99,7 +99,7 @@ def get_exports(module, output_format, file_path="/etc/exports"): except Exception as ex: module.warn("Error calculating '{}' hash: {}".format(algo, ex)) exports = {} - + output_lines = [] if file_content_bytes: output_lines = file_content_bytes.decode('utf-8', errors='ignore').splitlines() @@ -110,7 +110,7 @@ def get_exports(module, output_format, file_path="/etc/exports"): match = MAIN_LINE_PATTERN.match(line) if not match: continue - + folder = match.group(1) rest = match.group(2) @@ -132,11 +132,13 @@ def get_exports(module, output_format, file_path="/etc/exports"): return { 'exports_info': exports, - 'file_digest': file_digests # <--- Returning the dictionary of hashes + 'file_digest': file_digests } except Exception as e: module.fail_json(msg="Error while processing exports: {}".format(e)) + + def main(): module = AnsibleModule( argument_spec=dict( From ed5e9de2447d3c07d1e01e9f8a5277fc4c055cf3 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 16 Jun 2025 08:11:39 +0330 Subject: [PATCH 42/51] 1 Blank --- plugins/modules/nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index f9ded05a41..32b1e8b9eb 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -99,7 +99,7 @@ def get_exports(module, output_format, file_path="/etc/exports"): except Exception as ex: module.warn("Error calculating '{}' hash: {}".format(algo, ex)) exports = {} - + output_lines = [] if file_content_bytes: output_lines = file_content_bytes.decode('utf-8', errors='ignore').splitlines() From d088911ed8dabecd0be0708e54e5398442625747 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 16 Jun 2025 08:53:06 +0330 Subject: [PATCH 43/51] changes --- .../plugins/modules/test_nfs_exports_info.py | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index f440ec1bf0..dc18652a07 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -1,7 +1,5 @@ # SPDX-FileCopyrightText: (c) 2025, Samaneh Yousefnezhad - # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) - # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function @@ -10,13 +8,15 @@ __metaclass__ = type try: from unittest.mock import mock_open, patch, MagicMock except ImportError: + from mock import mock_open, patch, MagicMock import pytest import sys import os +import hashlib + -# Add plugins/modules to path for direct import sys.path.insert(0, os.path.abspath( os.path.join(os.path.dirname(__file__), '../../../../plugins/modules') )) @@ -33,15 +33,33 @@ def fake_exports_content(): """ +def calculate_expected_digests(content_string): + content_bytes = content_string.encode('utf-8') + digests = {} + hash_algorithms = ['sha256', 'sha1', 'md5'] + for algo in hash_algorithms: + try: + hasher = hashlib.new(algo) + hasher.update(content_bytes) + digests[algo] = hasher.hexdigest() + except ValueError: + pass + return digests + + def test_get_exports_ips_per_share(fake_exports_content): mock_module = MagicMock() - mock_module.digest_from_file.return_value = "fake_sha1_digest" + mock_module.file_exists.return_value = True + mock_module.warn.return_value = None + mock_module.fail_json.side_effect = Exception("fail_json called") + patch_target = "builtins.open" if sys.version_info[0] == 3 else "__builtin__.open" - with patch(patch_target, mock_open(read_data=fake_exports_content)): + + with patch(patch_target, mock_open(read_data=fake_exports_content.encode('utf-8'))): result = get_exports(mock_module, "ips_per_share") - expected = { + expected_exports_info = { '/srv/nfs1': [ {'ip': '192.168.1.10', 'options': ['rw', 'sync']}, {'ip': '192.168.1.20', 'options': ['ro', 'sync']} @@ -51,19 +69,28 @@ def test_get_exports_ips_per_share(fake_exports_content): ] } - assert result['exports_info'] == expected - assert result['file_digest'] == "fake_sha1_digest" + expected_file_digests = calculate_expected_digests(fake_exports_content) + + assert result['exports_info'] == expected_exports_info + assert result['file_digest'] == expected_file_digests def test_get_exports_shares_per_ip(fake_exports_content): mock_module = MagicMock() - mock_module.digest_from_file.return_value = "fake_sha1_digest" + + + + mock_module.file_exists.return_value = True + mock_module.warn.return_value = None + mock_module.fail_json.side_effect = Exception("fail_json called") + patch_target = "builtins.open" if sys.version_info[0] == 3 else "__builtin__.open" - with patch(patch_target, mock_open(read_data=fake_exports_content)): + + with patch(patch_target, mock_open(read_data=fake_exports_content.encode('utf-8'))): result = get_exports(mock_module, "shares_per_ip") - expected = { + expected_exports_info = { '192.168.1.10': [ {'folder': '/srv/nfs1', 'options': ['rw', 'sync']} ], @@ -75,5 +102,7 @@ def test_get_exports_shares_per_ip(fake_exports_content): ] } - assert result['exports_info'] == expected - assert result['file_digest'] == "fake_sha1_digest" + expected_file_digests = calculate_expected_digests(fake_exports_content) + + assert result['exports_info'] == expected_exports_info + assert result['file_digest'] == expected_file_digests From 07c0ac9390f81010f52a5ef18f86f8510db858b0 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Mon, 16 Jun 2025 09:25:10 +0330 Subject: [PATCH 44/51] Errors1 --- .../unit/plugins/modules/test_nfs_exports_info.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index dc18652a07..1a9529c44e 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -14,7 +14,7 @@ except ImportError: import pytest import sys import os -import hashlib +import hashlib sys.path.insert(0, os.path.abspath( @@ -36,14 +36,14 @@ def fake_exports_content(): def calculate_expected_digests(content_string): content_bytes = content_string.encode('utf-8') digests = {} - hash_algorithms = ['sha256', 'sha1', 'md5'] + hash_algorithms = ['sha256', 'sha1', 'md5'] for algo in hash_algorithms: try: hasher = hashlib.new(algo) hasher.update(content_bytes) digests[algo] = hasher.hexdigest() except ValueError: - pass + pass return digests @@ -51,9 +51,7 @@ def test_get_exports_ips_per_share(fake_exports_content): mock_module = MagicMock() mock_module.file_exists.return_value = True mock_module.warn.return_value = None - mock_module.fail_json.side_effect = Exception("fail_json called") - - + mock_module.fail_json.side_effect = Exception("fail_json called") patch_target = "builtins.open" if sys.version_info[0] == 3 else "__builtin__.open" with patch(patch_target, mock_open(read_data=fake_exports_content.encode('utf-8'))): @@ -77,14 +75,9 @@ def test_get_exports_ips_per_share(fake_exports_content): def test_get_exports_shares_per_ip(fake_exports_content): mock_module = MagicMock() - - - mock_module.file_exists.return_value = True mock_module.warn.return_value = None mock_module.fail_json.side_effect = Exception("fail_json called") - - patch_target = "builtins.open" if sys.version_info[0] == 3 else "__builtin__.open" with patch(patch_target, mock_open(read_data=fake_exports_content.encode('utf-8'))): From bce822a0c057f6035e2dd92a0e84cbd6696a9f62 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 17 Jun 2025 07:53:45 +0330 Subject: [PATCH 45/51] Suggested change --- plugins/modules/nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 32b1e8b9eb..700c7e3854 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -14,7 +14,7 @@ module: nfs_exports_info short_description: Extract folders, IPs, and options from C(/etc/exports) description: - - This module retrieves and processes the contents of the /etc/exports file from a remote server, + - This module retrieves and processes the contents of the C(/etc/exports) file from a remote server, mapping folders to their corresponding IP addresses and access options. author: From 66ff47251ad4469da8ed0cfb37748a37f4f6aee4 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 17 Jun 2025 08:08:23 +0330 Subject: [PATCH 46/51] Suggested change Error --- tests/unit/plugins/modules/test_nfs_exports_info.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 1a9529c44e..8516171ac7 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -5,11 +5,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -try: - from unittest.mock import mock_open, patch, MagicMock -except ImportError: - - from mock import mock_open, patch, MagicMock +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import mock_open, patch, MagicMock import pytest import sys From 138e65d3026c8544926b6d76f33deeeb04ec2690 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 17 Jun 2025 08:21:23 +0330 Subject: [PATCH 47/51] Changed --- tests/unit/plugins/modules/test_nfs_exports_info.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 8516171ac7..45faebcac2 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -12,13 +12,7 @@ import sys import os import hashlib - -sys.path.insert(0, os.path.abspath( - os.path.join(os.path.dirname(__file__), '../../../../plugins/modules') -)) - -from nfs_exports_info import get_exports - +from ansible_collections.community.general.plugins.modules.nfs_exports_info import get_exports @pytest.fixture def fake_exports_content(): From 4a1953031b60958e94d08474778d51890bf99e43 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 17 Jun 2025 08:27:40 +0330 Subject: [PATCH 48/51] Blank --- tests/unit/plugins/modules/test_nfs_exports_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 45faebcac2..4e09a75c66 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -14,6 +14,7 @@ import hashlib from ansible_collections.community.general.plugins.modules.nfs_exports_info import get_exports + @pytest.fixture def fake_exports_content(): return """ From ce0fcbbaef6a3949870f59256e5999de8fe250b5 Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 17 Jun 2025 08:34:26 +0330 Subject: [PATCH 49/51] os Error --- tests/unit/plugins/modules/test_nfs_exports_info.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index 4e09a75c66..d3f22bbd4a 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -9,7 +9,6 @@ from ansible_collections.community.internal_test_tools.tests.unit.compat.mock im import pytest import sys -import os import hashlib from ansible_collections.community.general.plugins.modules.nfs_exports_info import get_exports From a5bfc7e8a9424a4f2e32f9e896d351f242b3f35e Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 17 Jun 2025 08:52:13 +0330 Subject: [PATCH 50/51] Change again --- plugins/modules/nfs_exports_info.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/modules/nfs_exports_info.py b/plugins/modules/nfs_exports_info.py index 700c7e3854..771539d2a5 100644 --- a/plugins/modules/nfs_exports_info.py +++ b/plugins/modules/nfs_exports_info.py @@ -21,6 +21,10 @@ author: - Samaneh Yousefnezhad (@yousefenzhad) version_added: "11.1.0" +extends_documentation_fragment: + - community.general.attributes + - community.general.attributes.info_module + options: output_format: description: From 1a99b2ae3ad272549b99757ea5b87a32bd47022f Mon Sep 17 00:00:00 2001 From: Yousefnezhad Date: Tue, 17 Jun 2025 09:25:01 +0330 Subject: [PATCH 51/51] Changed1 --- tests/unit/plugins/modules/test_nfs_exports_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/plugins/modules/test_nfs_exports_info.py b/tests/unit/plugins/modules/test_nfs_exports_info.py index d3f22bbd4a..1313192dca 100644 --- a/tests/unit/plugins/modules/test_nfs_exports_info.py +++ b/tests/unit/plugins/modules/test_nfs_exports_info.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: (c) 2025, Samaneh Yousefnezhad +# Copyright (c) 2025, Samaneh Yousefnezhad # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later