diff --git a/changelogs/fragments/316-update-zos-blockinfile-force.yml b/changelogs/fragments/316-update-zos-blockinfile-force.yml new file mode 100644 index 000000000..ebdc89c16 --- /dev/null +++ b/changelogs/fragments/316-update-zos-blockinfile-force.yml @@ -0,0 +1,7 @@ +minor_changes: + - > + zos_blockinfile - updates the module with a new option named force. + This allows for a user to specifiy that the data set can be shared with + others during an update which results in the data set you are updating to + be simultaneously updated by others. + (https://github.com/ansible-collections/ibm_zos_core/pull/316). diff --git a/plugins/modules/zos_blockinfile.py b/plugins/modules/zos_blockinfile.py index bdd4aa023..0ac3c2a17 100644 --- a/plugins/modules/zos_blockinfile.py +++ b/plugins/modules/zos_blockinfile.py @@ -141,6 +141,18 @@ required: false type: str default: IBM-1047 + force: + description: + - Specifies that the data set can be shared with others during an update + which results in the data set you are updating to be simultaneously + updated by others. + - This is helpful when a data set is being used in a long running process + such as a started task and you are wanting to update or read. + - The C(-f) option enables sharing of data sets through the disposition + I(DISP=SHR). + required: false + type: bool + default: false indentation: description: - Defines the number of spaces needed to prepend in every line of the block. @@ -242,7 +254,7 @@ description: Constructed ZOAU dmod shell command based on the parameters returned: success type: str - sample: dmodhelper -d -b -c IBM-1047 -m "BEGIN\nEND\n# {mark} ANSIBLE MANAGED BLOCK" -e "$ a\\PATH=/dir/bin:$PATH" /etc/profile + sample: dmod -d -b -c IBM-1047 -m "BEGIN\nEND\n# {mark} ANSIBLE MANAGED BLOCK" -e "$ a\\PATH=/dir/bin:$PATH" /etc/profile msg: description: The module messages returned: failure @@ -319,7 +331,7 @@ def transformBlock(block, indentation_char, indentation_spaces): return block -def present(src, block, marker, ins_aft, ins_bef, encoding): +def present(src, block, marker, ins_aft, ins_bef, encoding, force): """Replace a block with the matching regex pattern Insert a block before/after the matching pattern Insert a block at BOF/EOF @@ -337,6 +349,7 @@ def present(src, block, marker, ins_aft, ins_bef, encoding): - BOF - '*regex*' encoding: {str} -- Encoding of the src. + force: {str} -- If not empty passes the -f option to dmod cmd. Returns: str -- Information in JSON format. keys: @@ -344,16 +357,17 @@ def present(src, block, marker, ins_aft, ins_bef, encoding): found: {int} -- Number of matching regex pattern changed: {bool} -- Indicates if the destination was modified. """ - return datasets.blockinfile(src, block=block, marker=marker, ins_aft=ins_aft, ins_bef=ins_bef, encoding=encoding, state=True, debug=True) + return datasets.blockinfile(src, block=block, marker=marker, ins_aft=ins_aft, ins_bef=ins_bef, encoding=encoding, state=True, debug=True, options=force) -def absent(src, marker, encoding): +def absent(src, marker, encoding, force): """Delete blocks with matching regex pattern Arguments: src: {str} -- The z/OS USS file or data set to modify. marker: {str} -- Identifies the block to be removed. encoding: {str} -- Encoding of the src. + force: {str} -- If not empty passes the -f option to dmod cmd. Returns: str -- Information in JSON format. keys: @@ -361,7 +375,7 @@ def absent(src, marker, encoding): found: {int} -- Number of matching regex pattern changed: {bool} -- Indicates if the destination was modified. """ - return datasets.blockinfile(src, marker=marker, encoding=encoding, state=False, debug=True) + return datasets.blockinfile(src, marker=marker, encoding=encoding, state=False, debug=True, options=force) def quotedString(string): @@ -420,6 +434,10 @@ def main(): type='str', default='IBM-1047' ), + force=dict( + type='bool', + default=False + ), indentation=dict( type='int', required=False, @@ -439,6 +457,7 @@ def main(): marker_begin=dict(arg_type='str', default='BEGIN', required=False), marker_end=dict(arg_type='str', default='END', required=False), encoding=dict(arg_type='str', default='IBM-1047', required=False), + force=dict(arg_type='bool', default=False, required=False), backup=dict(arg_type='bool', default=False, required=False), backup_name=dict(arg_type='data_set_or_pat', required=False, default=None), mutually_exclusive=[['insertbefore', 'insertafter']], @@ -462,6 +481,7 @@ def main(): marker = parsed_args.get('marker') marker_begin = parsed_args.get('marker_begin') marker_end = parsed_args.get('marker_end') + force = parsed_args.get('force') state = parsed_args.get('state') indentation = parsed_args.get('indentation') @@ -480,6 +500,7 @@ def main(): marker_begin = 'BEGIN' if not marker_end: marker_end = 'END' + force = '-f' if force else '' marker = "{0}\\n{1}\\n{2}".format(marker_begin, marker_end, marker) block = transformBlock(block, ' ', indentation) @@ -512,10 +533,10 @@ def main(): module.fail_json(msg="creating backup has failed") # state=present, insert/replace a block with matching regex pattern # state=absent, delete blocks with matching regex pattern - if state == 'present': - return_content = present(src, quotedString(block), quotedString(marker), quotedString(ins_aft), quotedString(ins_bef), encoding) + if parsed_args.get('state') == 'present': + return_content = present(src, quotedString(block), quotedString(marker), quotedString(ins_aft), quotedString(ins_bef), encoding, force) else: - return_content = absent(src, quotedString(marker), encoding) + return_content = absent(src, quotedString(marker), encoding, force) stdout = return_content.stdout_response stderr = return_content.stderr_response rc = return_content.rc diff --git a/tests/functional/modules/test_zos_blockinfile_func.py b/tests/functional/modules/test_zos_blockinfile_func.py index e9b9d5676..97a84bc7a 100644 --- a/tests/functional/modules/test_zos_blockinfile_func.py +++ b/tests/functional/modules/test_zos_blockinfile_func.py @@ -161,10 +161,16 @@ insertbefore="ZOAU_ROOT=", block="unset ZOAU_ROOT\nunset ZOAU_HOME\nunset ZOAU_DIR", state="present"), test_uss_block_insertafter_eof=dict( insertafter="EOF", block="export ZOAU_ROOT\nexport ZOAU_HOME\nexport ZOAU_DIR", state="present"), + test_uss_block_insert_with_force_option_as_true=dict( + insertafter="EOF", block="export ZOAU_ROOT\nexport ZOAU_HOME\nexport ZOAU_DIR", state="present", force=True), + test_uss_block_insert_with_force_option_as_false=dict( + insertafter="EOF", block="export ZOAU_ROOT\nexport ZOAU_HOME\nexport ZOAU_DIR", state="present", force=False), test_uss_block_insertbefore_bof=dict( insertbefore="BOF", block="# this is file is for setting env vars", state="present"), test_uss_block_absent=dict(block="", state="absent"), + test_uss_block_absent_with_force_option_as_true=dict(block="", state="absent", force=True), + test_uss_block_absent_with_force_option_as_false=dict(block="", state="absent", force=True), test_uss_block_replace_insertafter_regex=dict( insertafter="PYTHON_HOME=", block="ZOAU_ROOT=/mvsutil-develop_dsed\nZOAU_HOME=\\$ZOAU_ROOT\nZOAU_DIR=\\$ZOAU_ROOT", state="present"), @@ -182,6 +188,10 @@ test_ds_block_insertafter_eof=dict(test_name="T3"), test_ds_block_insertbefore_bof=dict(test_name="T4"), test_ds_block_absent=dict(test_name="T5"), + test_ds_block_insert_with_force_option_as_true=dict(block="export ZOAU_ROOT\nexport ZOAU_HOME\nexport ZOAU_DIR", state="present", force=True), + test_ds_block_absent_with_force_option_as_true=dict(block="", state="absent", force=True), + test_ds_block_insert_with_force_option_as_false=dict(block="export ZOAU_ROOT\nexport ZOAU_HOME\nexport ZOAU_DIR", state="present", force=False), + test_ds_block_absent_with_force_option_as_false=dict(block="", state="absent", force=False), test_ds_block_insert_with_indentation_level_specified=dict(test_name="T7"), expected=dict(test_uss_block_insertafter_regex_defaultmarker="""if [ -z STEPLIB ] && tty -s; then @@ -1088,6 +1098,42 @@ def test_uss_block_replace_insertbefore_bof_custommarker(ansible_zos_module): TEST_ENV["TEST_CONT"] = TEST_CONTENT +@pytest.mark.uss +def test_uss_block_insert_with_force_option_as_true(ansible_zos_module): + UssGeneral( + "test_uss_block_insertafter_eof_defaultmarker", ansible_zos_module, + TEST_ENV, TEST_INFO["test_uss_block_insert_with_force_option_as_true"], + TEST_INFO["expected"]["test_uss_block_insertafter_eof_defaultmarker"]) + + +@pytest.mark.uss +def test_uss_block_insert_with_force_option_as_false(ansible_zos_module): + UssGeneral( + "test_uss_block_insertafter_eof_defaultmarker", ansible_zos_module, + TEST_ENV, TEST_INFO["test_uss_block_insert_with_force_option_as_false"], + TEST_INFO["expected"]["test_uss_block_insertafter_eof_defaultmarker"]) + + +@pytest.mark.uss +def test_uss_block_absent_with_force_option_as_true(ansible_zos_module): + TEST_ENV["TEST_CONT"] = TEST_CONTENT_DEFAULTMARKER + UssGeneral( + "test_uss_block_absent_defaultmarker", ansible_zos_module, TEST_ENV, + TEST_INFO["test_uss_block_absent_with_force_option_as_true"], + TEST_INFO["expected"]["test_uss_block_absent"]) + TEST_ENV["TEST_CONT"] = TEST_CONTENT + + +@pytest.mark.uss +def test_uss_block_absent_with_force_option_as_false(ansible_zos_module): + TEST_ENV["TEST_CONT"] = TEST_CONTENT_DEFAULTMARKER + UssGeneral( + "test_uss_block_absent_defaultmarker", ansible_zos_module, TEST_ENV, + TEST_INFO["test_uss_block_absent_with_force_option_as_false"], + TEST_INFO["expected"]["test_uss_block_absent"]) + TEST_ENV["TEST_CONT"] = TEST_CONTENT + + @pytest.mark.uss def test_uss_block_insert_with_indentation_level_specified(ansible_zos_module): UssGeneral( @@ -1236,6 +1282,63 @@ def test_ds_block_absent(ansible_zos_module, dstype, encoding): TEST_ENV["TEST_CONT"] = TEST_CONTENT +@pytest.mark.ds +@pytest.mark.parametrize("dstype", DS_TYPE) +@pytest.mark.parametrize("encoding", ENCODING) +def test_ds_block_insert_with_force_option_as_true(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneral( + "T6", + ansible_zos_module, TEST_ENV, + TEST_INFO["test_ds_block_insert_with_force_option_as_true"], + TEST_INFO["expected"]["test_uss_block_insertafter_eof_defaultmarker"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("dstype", DS_TYPE) +@pytest.mark.parametrize("encoding", ENCODING) +def test_ds_block_absent_with_force_option_as_true(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + TEST_ENV["TEST_CONT"] = TEST_CONTENT_DEFAULTMARKER + DsGeneral( + "T7", ansible_zos_module, + TEST_ENV, TEST_INFO["test_ds_block_absent_with_force_option_as_true"], + TEST_INFO["expected"]["test_uss_block_absent"] + ) + TEST_ENV["TEST_CONT"] = TEST_CONTENT + + +@pytest.mark.ds +@pytest.mark.parametrize("dstype", DS_TYPE) +@pytest.mark.parametrize("encoding", ENCODING) +def test_ds_block_insert_with_force_option_as_false(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneral( + "T8", ansible_zos_module, + TEST_ENV, TEST_INFO["test_ds_block_insert_with_force_option_as_false"], + TEST_INFO["expected"]["test_uss_block_insertafter_eof_defaultmarker"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("dstype", DS_TYPE) +@pytest.mark.parametrize("encoding", ENCODING) +def test_ds_block_absent_with_force_option_as_false(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + TEST_ENV["TEST_CONT"] = TEST_CONTENT_DEFAULTMARKER + DsGeneral( + "T9", ansible_zos_module, + TEST_ENV, TEST_INFO["test_ds_block_absent_with_force_option_as_false"], + TEST_INFO["expected"]["test_uss_block_absent"] + ) + TEST_ENV["TEST_CONT"] = TEST_CONTENT + + @pytest.mark.ds @pytest.mark.parametrize("dstype", DS_TYPE) @pytest.mark.parametrize("encoding", ENCODING)