From d76392ed2a1b5b08097e1f5ae780d031f9d1adf3 Mon Sep 17 00:00:00 2001 From: Yuhua Zou <41054978+ZouYuhua@users.noreply.github.com> Date: Tue, 11 Oct 2022 04:41:04 +0800 Subject: [PATCH] New module: iso_customize (#5190) * add ansible module iso_customize.py * rerun CI testing due to "Failed to send request to https://api.github.com/repos/ansible/ansible/issues/23642: HTTP Error 403: rate limit exceeded" * Rerun CI testing due to "Failed to send request to https://api.github....." * rerun CI testing due to failure "Unknown error when attempting to call Galaxy at 'https://galaxy.ansible.com/api/v2/collections/netbox/netbox/versions/3.1.0/': The read operation timed out" * change document part as felixfontein's careful review * modify test file as russoz's comments * modify comment part of module * add comment for the example * add more tests: check the files are deleted / added in customized ISO * fix it: failed to run ansible.posix.mount in ubuntu * fix it: ansible.posix.mount is not working well in some OS. * change DOCUMENTATION part * change files according to the comment from code review * fix issue: E231: missing whitespace after ':' * modify the description of Document * modify code for code review * delete extra blank line in yml file * Try to fix CI testing issue: "Caught \"'foo' is undefined. 'foo' is undefined\" while evaluating 'b' with item == {'a': 1}" * delete extra blank line in the end of file * change code as the comment from code review * change code from code review * change type: str to type: path * change type:str to type:path * delete unused variable * fix CI testing error: return-syntax-error: RETURN.dest_iso.type: not a valid value for dictionary value @ data['dest_iso']['type']. Got 'path' * add testcase: test add files / delete files separately * add more testcases: test if we can catch exception from error input of users * change code from code review * fix issue: E231: missing whitespace after ',' * change code from code review * add notes to document * modify notes in document part * /rebuild_failed /rebuild_failed * Try to support running testcases not only in MAC but also in other OS. * modify document * change mount to ansible.posix.mount * skip the test platform which report "Error mounting" * fix mount failed: Operation not permitted * change code from code review * change document from code review * fix CI testing issue in some platforms * Update plugins/modules/files/iso_customize.py * change code from code review 1) change testcase 2) try to fix "mount: not permitted" * modify aliases file * change document and rerun CI testing * add skip/docker as suggested * add debug task * fix issue in redhat 7.9: occurred while running the lookup plugin 'file'. ..could not locate file in lookup.. * change code from the code review * modify function "iso_rr_check_file_exist" to "iso_check_file_exists" to make it works in all types of ISO 1. modify function "iso_rr_check_file_exist" to "iso_check_file_exists" to make it works in all types of ISO 2. run main.yml with newer python 3.10.6 ansible [core 2.13.4] config file = None configured module search path = ['/Users/zouy/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/local/Cellar/ansible/6.4.0/libexec/lib/python3.10/site-packages/ansible ansible collection location = /Users/zouy/.ansible/collections:/usr/share/ansible/collections executable location = /usr/local/bin/ansible python version = 3.10.6 (main, Aug 30 2022, 05:12:36) [Clang 13.1.6 (clang-1316.0.21.2.5)] jinja version = 3.1.2 libyaml = True * delete blank * simply the code as suggested. * Two small docs updates. Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> Co-authored-by: Felix Fontein --- .github/BOTMETA.yml | 2 + meta/runtime.yml | 2 + plugins/modules/files/iso_customize.py | 343 ++++++++++++++++++ .../integration/targets/iso_customize/aliases | 14 + .../targets/iso_customize/meta/main.yml | 7 + .../iso_customize/tasks/iso_customize.yml | 75 ++++ .../tasks/iso_customize_add_files.yml | 34 ++ .../tasks/iso_customize_delete_files.yml | 34 ++ .../tasks/iso_customize_exception.yml | 81 +++++ .../targets/iso_customize/tasks/iso_mount.yml | 39 ++ .../targets/iso_customize/tasks/main.yml | 94 +++++ .../targets/iso_customize/tasks/prepare.yml | 39 ++ 12 files changed, 764 insertions(+) create mode 100644 plugins/modules/files/iso_customize.py create mode 100644 tests/integration/targets/iso_customize/aliases create mode 100644 tests/integration/targets/iso_customize/meta/main.yml create mode 100644 tests/integration/targets/iso_customize/tasks/iso_customize.yml create mode 100644 tests/integration/targets/iso_customize/tasks/iso_customize_add_files.yml create mode 100644 tests/integration/targets/iso_customize/tasks/iso_customize_delete_files.yml create mode 100644 tests/integration/targets/iso_customize/tasks/iso_customize_exception.yml create mode 100644 tests/integration/targets/iso_customize/tasks/iso_mount.yml create mode 100644 tests/integration/targets/iso_customize/tasks/main.yml create mode 100644 tests/integration/targets/iso_customize/tasks/prepare.yml diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 0d35e1b5fd..408ec38e03 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -560,6 +560,8 @@ files: maintainers: jpmens noseka1 $modules/files/iso_create.py: maintainers: Tomorrow9 + $modules/files/iso_customize.py: + maintainers: ZouYuhua $modules/files/iso_extract.py: maintainers: dagwieers jhoekx ribbons $modules/files/read_csv.py: diff --git a/meta/runtime.yml b/meta/runtime.yml index 2685df53a7..eb6d226fec 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -560,6 +560,8 @@ plugin_routing: redirect: community.general.files.iso_create iso_extract: redirect: community.general.files.iso_extract + iso_customize: + redirect: community.general.files.iso_customize jabber: redirect: community.general.notification.jabber java_cert: diff --git a/plugins/modules/files/iso_customize.py b/plugins/modules/files/iso_customize.py new file mode 100644 index 0000000000..a6136cdd8f --- /dev/null +++ b/plugins/modules/files/iso_customize.py @@ -0,0 +1,343 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +DOCUMENTATION = r''' +--- +module: iso_customize +short_description: Add/remove/change files in ISO file +description: + - This module is used to add/remove/change files in ISO file. + - The file inside ISO will be overwritten if it exists by option I(add_files). +author: + - Yuhua Zou (@ZouYuhua) +requirements: + - "pycdlib" + - "python >= 2.7" +version_added: '5.8.0' + +options: + src_iso: + description: + - This is the path of source ISO file. + type: path + required: true + dest_iso: + description: + - The path of the customized ISO file. + type: path + required: true + delete_files: + description: + - Absolute paths for files inside the ISO file that should be removed. + type: list + required: false + elements: str + add_files: + description: + - Allows to add and replace files in the ISO file. + - Will create intermediate folders inside the ISO file when they do not exist. + type: list + required: false + elements: dict + suboptions: + src_file: + description: + - The path with file name on the machine the module is executed on. + type: path + required: true + dest_file: + description: + - The absolute path of the file inside the ISO file. + type: str + required: true +notes: +- The C(pycdlib) library states it supports Python 2.7 and 3.4 only. +- > + The function I(add_file) in pycdlib will overwrite the existing file in ISO with type ISO9660 / Rock Ridge 1.12 / Joliet / UDF. + But it will not overwrite the existing file in ISO with Rock Ridge 1.09 / 1.10. + So we take workaround "delete the existing file and then add file for ISO with Rock Ridge". +''' + +EXAMPLES = r''' +- name: "Customize ISO file" + community.general.iso_customize: + src_iso: "/path/to/ubuntu-22.04-desktop-amd64.iso" + dest_iso: "/path/to/ubuntu-22.04-desktop-amd64-customized.iso" + delete_files: + - "/boot.catalog" + add_files: + - src_file: "/path/to/grub.cfg" + dest_file: "/boot/grub/grub.cfg" + - src_file: "/path/to/ubuntu.seed" + dest_file: "/preseed/ubuntu.seed" + register: customize_iso_result +''' + +RETURN = r''' +src_iso: + description: Path of source ISO file. + returned: on success + type: str + sample: "/path/to/file.iso" +dest_iso: + description: Path of the customized ISO file. + returned: on success + type: str + sample: "/path/to/customized.iso" +''' + +import os +import traceback + +PYCDLIB_IMP_ERR = None +try: + import pycdlib + HAS_PYCDLIB = True +except ImportError: + PYCDLIB_IMP_ERR = traceback.format_exc() + HAS_PYCDLIB = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.common.text.converters import to_native + + +# The upper dir exist, we only add subdirectoy +def iso_add_dir(module, opened_iso, iso_type, dir_path): + parent_dir, check_dirname = dir_path.rsplit("/", 1) + if not parent_dir.strip(): + parent_dir = "/" + check_dirname = check_dirname.strip() + + for dirname, dirlist, dummy_filelist in opened_iso.walk(iso_path=parent_dir.upper()): + if dirname == parent_dir.upper(): + if check_dirname.upper() in dirlist: + return + + if parent_dir == "/": + current_dirpath = "/%s" % check_dirname + else: + current_dirpath = "%s/%s" % (parent_dir, check_dirname) + + current_dirpath_upper = current_dirpath.upper() + try: + if iso_type == "iso9660": + opened_iso.add_directory(current_dirpath_upper) + elif iso_type == "rr": + opened_iso.add_directory(current_dirpath_upper, rr_name=check_dirname) + elif iso_type == "joliet": + opened_iso.add_directory(current_dirpath_upper, joliet_path=current_dirpath) + elif iso_type == "udf": + opened_iso.add_directory(current_dirpath_upper, udf_path=current_dirpath) + except Exception as err: + msg = "Failed to create dir %s with error: %s" % (current_dirpath, to_native(err)) + module.fail_json(msg=msg) + + +def iso_add_dirs(module, opened_iso, iso_type, dir_path): + dirnames = dir_path.strip().split("/") + + current_dirpath = "/" + for item in dirnames: + if not item.strip(): + continue + if current_dirpath == "/": + current_dirpath = "/%s" % item + else: + current_dirpath = "%s/%s" % (current_dirpath, item) + + iso_add_dir(module, opened_iso, iso_type, current_dirpath) + + +def iso_check_file_exists(opened_iso, dest_file): + file_dir = os.path.dirname(dest_file).strip() + file_name = os.path.basename(dest_file) + dirnames = file_dir.strip().split("/") + + parent_dir = "/" + for item in dirnames: + if not item.strip(): + continue + + for dirname, dirlist, dummy_filelist in opened_iso.walk(iso_path=parent_dir.upper()): + if dirname != parent_dir.upper(): + break + + if item.upper() not in dirlist: + return False + + if parent_dir == "/": + parent_dir = "/%s" % item + else: + parent_dir = "%s/%s" % (parent_dir, item) + + if '.' not in file_name: + file_in_iso_path = file_name.upper() + '.;1' + else: + file_in_iso_path = file_name.upper() + ';1' + + for dirname, dummy_dirlist, filelist in opened_iso.walk(iso_path=parent_dir.upper()): + if dirname != parent_dir.upper(): + return False + + return file_name.upper() in filelist or file_in_iso_path in filelist + + +def iso_add_file(module, opened_iso, iso_type, src_file, dest_file): + dest_file = dest_file.strip() + if dest_file[0] != "/": + dest_file = "/%s" % dest_file + + file_local = src_file.strip() + + file_dir = os.path.dirname(dest_file).strip() + file_name = os.path.basename(dest_file) + if '.' not in file_name: + file_in_iso_path = dest_file.upper() + '.;1' + else: + file_in_iso_path = dest_file.upper() + ';1' + + if file_dir and file_dir != "/": + iso_add_dirs(module, opened_iso, iso_type, file_dir) + + try: + if iso_type == "iso9660": + opened_iso.add_file(file_local, iso_path=file_in_iso_path) + elif iso_type == "rr": + # For ISO with Rock Ridge 1.09 / 1.10, it won't overwrite the existing file + # So we take workaround here: delete the existing file and then add file + if iso_check_file_exists(opened_iso, dest_file): + opened_iso.rm_file(iso_path=file_in_iso_path) + opened_iso.add_file(file_local, iso_path=file_in_iso_path, rr_name=file_name) + elif iso_type == "joliet": + opened_iso.add_file(file_local, iso_path=file_in_iso_path, joliet_path=dest_file) + elif iso_type == "udf": + # For ISO with UDF, it won't always succeed to overwrite the existing file + # So we take workaround here: delete the existing file and then add file + if iso_check_file_exists(opened_iso, dest_file): + opened_iso.rm_file(udf_path=dest_file) + opened_iso.add_file(file_local, iso_path=file_in_iso_path, udf_path=dest_file) + except Exception as err: + msg = "Failed to add local file %s to ISO with error: %s" % (file_local, to_native(err)) + module.fail_json(msg=msg) + + +def iso_delete_file(module, opened_iso, iso_type, dest_file): + dest_file = dest_file.strip() + if dest_file[0] != "/": + dest_file = "/%s" % dest_file + file_name = os.path.basename(dest_file) + + if not iso_check_file_exists(opened_iso, dest_file): + module.fail_json(msg="The file %s does not exist." % dest_file) + + if '.' not in file_name: + file_in_iso_path = dest_file.upper() + '.;1' + else: + file_in_iso_path = dest_file.upper() + ';1' + + try: + if iso_type == "iso9660": + opened_iso.rm_file(iso_path=file_in_iso_path) + elif iso_type == "rr": + opened_iso.rm_file(iso_path=file_in_iso_path) + elif iso_type == "joliet": + opened_iso.rm_file(joliet_path=dest_file) + elif iso_type == "udf": + opened_iso.rm_file(udf_path=dest_file) + except Exception as err: + msg = "Failed to delete iso file %s with error: %s" % (dest_file, to_native(err)) + module.fail_json(msg=msg) + + +def iso_rebuild(module, src_iso, dest_iso, delete_files_list, add_files_list): + iso = None + iso_type = "iso9660" + + try: + iso = pycdlib.PyCdlib(always_consistent=True) + iso.open(src_iso) + if iso.has_rock_ridge(): + iso_type = "rr" + elif iso.has_joliet(): + iso_type = "joliet" + elif iso.has_udf(): + iso_type = "udf" + + for item in delete_files_list: + iso_delete_file(module, iso, iso_type, item) + + for item in add_files_list: + iso_add_file(module, iso, iso_type, item['src_file'], item['dest_file']) + + iso.write(dest_iso) + except Exception as err: + msg = "Failed to rebuild ISO %s with error: %s" % (src_iso, to_native(err)) + module.fail_json(msg=msg) + finally: + if iso: + iso.close() + + +def main(): + argument_spec = dict( + src_iso=dict(type='path', required=True), + dest_iso=dict(type='path', required=True), + delete_files=dict(type='list', elements='str', default=[]), + add_files=dict( + type='list', elements='dict', default=[], + options=dict( + src_file=dict(type='path', required=True), + dest_file=dict(type='str', required=True), + ), + ), + ) + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=[('delete_files', 'add_files'), ], + supports_check_mode=True, + ) + if not HAS_PYCDLIB: + module.fail_json( + missing_required_lib('pycdlib'), exception=PYCDLIB_IMP_ERR) + + src_iso = module.params['src_iso'] + if not os.path.exists(src_iso): + module.fail_json(msg="ISO file %s does not exist." % src_iso) + + dest_iso = module.params['dest_iso'] + dest_iso_dir = os.path.dirname(dest_iso) + if dest_iso_dir and not os.path.exists(dest_iso_dir): + module.fail_json(msg="The dest directory %s does not exist" % dest_iso_dir) + + delete_files_list = [s.strip() for s in module.params['delete_files']] + add_files_list = module.params['add_files'] + if add_files_list: + for item in add_files_list: + if not os.path.exists(item['src_file']): + module.fail_json(msg="The file %s does not exist." % item['src_file']) + + result = dict( + src_iso=src_iso, + customized_iso=dest_iso, + delete_files=delete_files_list, + add_files=add_files_list, + changed=True, + ) + + if not module.check_mode: + iso_rebuild(module, src_iso, dest_iso, delete_files_list, add_files_list) + + result['changed'] = True + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/iso_customize/aliases b/tests/integration/targets/iso_customize/aliases new file mode 100644 index 0000000000..bc011eaf8d --- /dev/null +++ b/tests/integration/targets/iso_customize/aliases @@ -0,0 +1,14 @@ +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +shippable/posix/group1 +destructive +skip/aix +skip/freebsd +skip/alpine +skip/python2.6 +skip/docker +needs/root + diff --git a/tests/integration/targets/iso_customize/meta/main.yml b/tests/integration/targets/iso_customize/meta/main.yml new file mode 100644 index 0000000000..982de6eb03 --- /dev/null +++ b/tests/integration/targets/iso_customize/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# 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 + +dependencies: + - setup_remote_tmp_dir diff --git a/tests/integration/targets/iso_customize/tasks/iso_customize.yml b/tests/integration/targets/iso_customize/tasks/iso_customize.yml new file mode 100644 index 0000000000..f7d7bffd10 --- /dev/null +++ b/tests/integration/targets/iso_customize/tasks/iso_customize.yml @@ -0,0 +1,75 @@ +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +- name: Add a line to the file test02.cfg and make sure it succeed + ansible.builtin.lineinfile: + path: "{{ test_dir }}/test02.cfg" + regexp: "^test" + line: "test" + +- name: "Customize ISO file: add file, delete file and change file" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test.iso" + dest_iso: "{{ test_dir }}/{{ dest_iso_name }}" + delete_files: + - "/test01.cfg" + add_files: + - src_file: "{{ test_dir }}/test01.cfg" + dest_file: "/preseed/ubuntu.seed" + - src_file: "{{ test_dir }}/test02.cfg" + dest_file: "/test02.cfg" + +- include_tasks: iso_mount.yml + vars: + iso_name: "{{ dest_iso_name }}" + +- debug: var=mount_root_dir + +- name: Check the file test01.cfg is deleted + stat: + path: "{{ mount_root_dir }}/test01.cfg" + register: check_file + +- assert: + that: + - check_file.stat.exists == False + +- name: Check the file /preseed/ubuntu.seed is added + stat: + path: "{{ mount_root_dir }}/preseed/ubuntu.seed" + register: check_file + +- assert: + that: + - check_file.stat.exists == True + +- block: + - name: Get the content of file test02.cfg + command: "cat {{ mount_root_dir }}/test02.cfg" + register: get_file_content + + - set_fact: + file_contents: "{{ get_file_content.stdout }}" + when: ansible_distribution == 'RedHat' and ansible_distribution_version is version('7.9', '==') + +- name: Get the content of file test02.cfg + set_fact: + file_contents: "{{ lookup('file', mount_root_dir + '/test02.cfg') }}" + when: not (ansible_distribution == 'RedHat' and ansible_distribution_version is version('7.9', '==')) + +- fail: msg="Failed to replace the file test02.cfg" + when: file_contents != "test" + +- name: Umount ISO + mount: + path: "{{ mount_root_dir }}" + fstab: "{{ test_dir }}/temp.fstab" + state: unmounted + +- name: Delete line of file test02.cfg + ansible.builtin.lineinfile: + path: "{{ test_dir }}/test02.cfg" + regexp: "test" + state: absent diff --git a/tests/integration/targets/iso_customize/tasks/iso_customize_add_files.yml b/tests/integration/targets/iso_customize/tasks/iso_customize_add_files.yml new file mode 100644 index 0000000000..210767707b --- /dev/null +++ b/tests/integration/targets/iso_customize/tasks/iso_customize_add_files.yml @@ -0,0 +1,34 @@ +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +- name: "Customize ISO file: add file" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test1.iso" + dest_iso: "{{ test_dir }}/{{ dest_iso_name }}" + add_files: + - src_file: "{{ test_dir }}/test01.cfg" + dest_file: "preseed/ubuntu.seed" + + +- include_tasks: iso_mount.yml + vars: + iso_name: "{{ dest_iso_name }}" + +- debug: var=mount_root_dir + +- name: Check the file /preseed/ubuntu.seed is added + stat: + path: "{{ mount_root_dir }}/preseed/ubuntu.seed" + register: check_file + +- assert: + that: + - check_file.stat.exists == True + +- name: Umount ISO + mount: + path: "{{ mount_root_dir }}" + fstab: "{{ test_dir }}/temp.fstab" + state: unmounted diff --git a/tests/integration/targets/iso_customize/tasks/iso_customize_delete_files.yml b/tests/integration/targets/iso_customize/tasks/iso_customize_delete_files.yml new file mode 100644 index 0000000000..bceeeb53a9 --- /dev/null +++ b/tests/integration/targets/iso_customize/tasks/iso_customize_delete_files.yml @@ -0,0 +1,34 @@ +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +- name: "Customize ISO file: delete file" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test1.iso" + dest_iso: "{{ test_dir }}/{{ dest_iso_name }}" + delete_files: + - "test01.cfg" + +- debug: var=ansible_distribution + +- include_tasks: iso_mount.yml + vars: + iso_name: "{{ dest_iso_name }}" + +- debug: var=mount_root_dir + +- name: Check the file test01.cfg is deleted + stat: + path: "{{ mount_root_dir }}/test01.cfg" + register: check_file + +- assert: + that: + - check_file.stat.exists == False + +- name: Umount ISO + mount: + path: "{{ mount_root_dir }}" + fstab: "{{ test_dir }}/temp.fstab" + state: unmounted diff --git a/tests/integration/targets/iso_customize/tasks/iso_customize_exception.yml b/tests/integration/targets/iso_customize/tasks/iso_customize_exception.yml new file mode 100644 index 0000000000..6716f9e1b4 --- /dev/null +++ b/tests/integration/targets/iso_customize/tasks/iso_customize_exception.yml @@ -0,0 +1,81 @@ +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +- name: "Testcase: local resource ISO does not exists" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test11.iso" + dest_iso: "{{ test_dir }}/{{ dest_iso_name }}" + register: customized_result + failed_when: customized_result.msg.find('does not exist') == -1 + +- name: "Testcase:: dest dir does not exists" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test1.iso" + dest_iso: "/aaa/{{ dest_iso_name }}" + register: customized_result + failed_when: customized_result.msg.find('does not exist') == -1 + +# Test: Get MODULE FAILURE when no add files data and no delete files data +- name: "Testcase:: no add files data and no delete files data" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test1.iso" + dest_iso: "{{ test_dir }}/iso_customize_nodata.iso" + delete_files: + add_files: + register: customized_result + failed_when: customized_result.msg.find("MODULE FAILURE") == -1 + +# Test: nothing is changed when no options "add files" and "delete files" +- block: + - name: "Testcase: no options 'add files' and 'delete files'" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test1.iso" + dest_iso: "{{ test_dir }}/iso_customize_nochanged.iso" + + - name: Get stats of a file test1.iso + ansible.builtin.stat: + path: "{{ test_dir }}/test1.iso" + register: iso_orginal + + - name: Get stats of a file iso_customize_nochanged.iso + ansible.builtin.stat: + path: "{{ test_dir }}/iso_customize_nochanged.iso" + register: iso_customized + + - name: compare size + fail: msg="Check we have nothing changed for customized ISO" + when: iso_orginal.stat.size != iso_customized.stat.size + +- name: "Testcase: delete the non-existing file in ISO" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test1.iso" + dest_iso: "{{ test_dir }}/{{ dest_iso_name }}" + delete_files: + - "/test03.cfg" + register: customized_result + failed_when: customized_result.msg.find("does not exist") == -1 + +# Test: failed when local src file does not exists +- name: "Testcase: local src file does not exists" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test.iso" + dest_iso: "{{ test_dir }}/{{ dest_iso_name }}" + add_files: + - src_file: "{{ test_dir }}/test03.cfg" + dest_file: "/preseed/ubuntu.seed" + register: customized_result + failed_when: customized_result.msg.find("does not exist") == -1 + +# Test: filenames with whitespaces +# We report error: the user should be reponsible for the it +- name: "Testcase: filenames with whitespaces" + community.general.iso_customize: + src_iso: "{{ test_dir }}/test.iso" + dest_iso: "{{ test_dir }}/{{ dest_iso_name }}" + add_files: + - src_file: " {{ test_dir }}/test01.cfg " + dest_file: "/preseed/ubuntu.seed" + register: customized_result + failed_when: customized_result.msg.find("does not exist") == -1 diff --git a/tests/integration/targets/iso_customize/tasks/iso_mount.yml b/tests/integration/targets/iso_customize/tasks/iso_mount.yml new file mode 100644 index 0000000000..cf4ab81996 --- /dev/null +++ b/tests/integration/targets/iso_customize/tasks/iso_mount.yml @@ -0,0 +1,39 @@ +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +- debug: var=ansible_distribution + +- block: + - name: "Mount customized ISO on MAC" + command: "hdiutil attach {{ test_dir }}/{{ iso_name }} -mountroot {{ test_dir }}/iso_mount" + + # For MAC, we have different root directory for different type of ISO + - set_fact: + mount_root_dir: "{{ test_dir }}/iso_mount/disk_image" + + - set_fact: + mount_root_dir: "{{ test_dir }}/iso_mount/AUTOINSTALL" + when: iso_name.find('joliet') != -1 + + - set_fact: + mount_root_dir: "{{ test_dir }}/iso_mount/CDROM" + when: iso_name.find('udf') != -1 + when: ansible_distribution == "MacOSX" + +- block: + - name: "Mount {{ iso_name }} to {{ test_dir }}/iso_mount on localhost" + become: true + ansible.posix.mount: + path: "{{ test_dir }}/iso_mount" + src: "{{ test_dir }}/{{ iso_name }}" + opts: "ro,noauto" + fstab: "{{ test_dir }}/temp.fstab" + fstype: "iso9660" + state: mounted + + - set_fact: + mount_root_dir: "{{ test_dir }}/iso_mount" + when: + - ansible_distribution != "MacOSX" diff --git a/tests/integration/targets/iso_customize/tasks/main.yml b/tests/integration/targets/iso_customize/tasks/main.yml new file mode 100644 index 0000000000..5745b1a8ad --- /dev/null +++ b/tests/integration/targets/iso_customize/tasks/main.yml @@ -0,0 +1,94 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +- name: Skip some platforms which does not support ansible.posix.mount + meta: end_play + when: ansible_distribution in ['Alpine'] + +- set_fact: + test_dir: '{{ remote_tmp_dir }}/test_iso_customize' + +- include_tasks: prepare.yml + +- name: Create iso file with a specified file and directory + community.general.iso_create: + src_files: + - "{{ test_dir }}/test01.cfg" + - "{{ test_dir }}/test02.cfg" + dest_iso: "{{ test_dir }}/test.iso" + interchange_level: 3 + +- include_tasks: iso_customize.yml + vars: + dest_iso_name: "iso_customize.iso" + +- name: Create an ISO file with Rock Ridge extension + community.general.iso_create: + src_files: + - "{{ test_dir }}/test01.cfg" + - "{{ test_dir }}/test02.cfg" + dest_iso: "{{ test_dir }}/test.iso" + rock_ridge: "1.09" + +- include_tasks: iso_customize.yml + vars: + dest_iso_name: "iso_customize_rr.iso" + +- name: Create an ISO file with Joliet support + community.general.iso_create: + src_files: + - "{{ test_dir }}/test01.cfg" + - "{{ test_dir }}/test02.cfg" + dest_iso: "{{ test_dir }}/test.iso" + interchange_level: 3 + joliet: 3 + vol_ident: AUTOINSTALL + +- include_tasks: iso_customize.yml + vars: + dest_iso_name: "iso_customize_joliet.iso" + +- name: Create iso file with UDF enabled + community.general.iso_create: + src_files: + - "{{ test_dir }}/test01.cfg" + - "{{ test_dir }}/test02.cfg" + dest_iso: "{{ test_dir }}/test.iso" + udf: True + +- include_tasks: iso_customize.yml + vars: + dest_iso_name: "iso_customize_udf.iso" + +# Create initial iso for customzing with only option add_files/delete_files +- name: Create iso file with a specified file and directory + community.general.iso_create: + src_files: + - "{{ test_dir }}/test01.cfg" + dest_iso: "{{ test_dir }}/test1.iso" + interchange_level: 3 + +- include_tasks: iso_customize_add_files.yml + vars: + dest_iso_name: "iso_customize_add.iso" + +- include_tasks: iso_customize_delete_files.yml + vars: + dest_iso_name: "iso_customize_delete.iso" + +# Test: misc exception +- include_tasks: iso_customize_exception.yml + vars: + dest_iso_name: "iso_customize_exception.iso" + +- name: Delete testing sub-directory + ansible.builtin.file: + path: '{{ test_dir }}' + state: absent diff --git a/tests/integration/targets/iso_customize/tasks/prepare.yml b/tests/integration/targets/iso_customize/tasks/prepare.yml new file mode 100644 index 0000000000..0db78553d0 --- /dev/null +++ b/tests/integration/targets/iso_customize/tasks/prepare.yml @@ -0,0 +1,39 @@ +# Copyright (c) 2022, Ansible Project +# Copyright (c) 2022, VMware, Inc. All Rights Reserved. +# 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 + +- name: install pycdlib + ansible.builtin.pip: + name: pycdlib + +- name: Make sure the previous testing sub-directory is deleted + ansible.builtin.file: + path: '{{ test_dir }}' + state: absent + +- name: Create our testing sub-directory + ansible.builtin.file: + path: '{{ test_dir }}' + state: directory + +- name: Create sub directory to mount customized ISO + ansible.builtin.file: + path: '{{ test_dir }}/iso_mount' + state: directory + +- name: Create temporary file test01.cfg for testing + ansible.builtin.file: + path: "{{ test_dir }}/test01.cfg" + state: touch + +- name: Add a line to the file test01.cfg and make sure it succeed + ansible.builtin.lineinfile: + path: "{{ test_dir }}/test01.cfg" + regexp: "^aaa" + line: "aaa" + +- name: Create temporary file test02.cfg for testing + ansible.builtin.file: + path: "{{ test_dir }}/test02.cfg" + state: touch