Skip to content

Commit 3af291a

Browse files
authored
[Enhancement] [zos_archive] Add optional encoding of files before archiving (#2081)
* Adding encoding support for zos_archive * test cases to check encoding support * adding changelog * review comments changes * pipeline failure resolve * updating exception and review comments * modifying the error handling
1 parent 8c56429 commit 3af291a

File tree

4 files changed

+333
-5
lines changed

4 files changed

+333
-5
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
minor_changes:
2+
- zos_archive - Adds support for encoding before archiving files.
3+
(https://github.com/ansible-collections/ibm_zos_core/pull/2081)

plugins/module_utils/encode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) IBM Corporation 2020, 2024
1+
# Copyright (c) IBM Corporation 2020, 2025
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at

plugins/modules/zos_archive.py

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,31 @@
306306
type: bool
307307
default: false
308308
required: false
309-
309+
encoding:
310+
description:
311+
- Specifies the character encoding conversion to be applied to the
312+
source files before archiving.
313+
- Supported character sets rely on the charset conversion utility
314+
C(iconv) version the most common character sets are supported.
315+
- After conversion the files are stored in same location and name
316+
as src and the same src is taken in consideration for archive.
317+
- Source files will be converted to the new encoding and will not
318+
be restored to their original encoding.
319+
- If encoding fails for any file in a set of multiple files, an
320+
exception will be raised and archiving will be skipped.
321+
type: dict
322+
required: false
323+
suboptions:
324+
from:
325+
description:
326+
- The character set of the source I(src).
327+
required: false
328+
type: str
329+
to:
330+
description:
331+
- The destination I(dest) character set for the files to be written as.
332+
required: false
333+
type: str
310334
attributes:
311335
action:
312336
support: none
@@ -404,6 +428,16 @@
404428
name: terse
405429
format_options:
406430
use_adrdssu: true
431+
432+
- name: Encode the source data set into Latin-1 before archiving into a terse data set
433+
zos_archive:
434+
src: "USER.ARCHIVE.TEST"
435+
dest: "USER.ARCHIVE.RESULT.TRS"
436+
format:
437+
name: terse
438+
encoding:
439+
from: IBM-1047
440+
to: ISO8859-1
407441
'''
408442

409443
RETURN = r'''
@@ -465,7 +499,7 @@
465499
from ansible.module_utils._text import to_bytes
466500
from ansible.module_utils.basic import AnsibleModule
467501
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import (
468-
better_arg_parser, data_set, mvs_cmd, validation)
502+
better_arg_parser, data_set, mvs_cmd, validation, encode)
469503
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.import_handler import \
470504
ZOAUImportError
471505

@@ -618,6 +652,7 @@ def __init__(self, module):
618652
The state of the input C(src).
619653
xmit_log_data_set : str
620654
The name of the data set to store xmit log output.
655+
621656
"""
622657
self.module = module
623658
self.dest = module.params['dest']
@@ -637,6 +672,9 @@ def __init__(self, module):
637672
self.dest_state = STATE_ABSENT
638673
self.state = STATE_PRESENT
639674
self.xmit_log_data_set = ""
675+
encoding_param = module.params.get("encoding") or {}
676+
self.from_encoding = encoding_param.get("from")
677+
self.to_encoding = encoding_param.get("to")
640678

641679
def targets_exist(self):
642680
"""Returns if there are targets or not.
@@ -684,6 +722,10 @@ def remove_targets(self):
684722
def compute_dest_size(self):
685723
pass
686724

725+
@abc.abstractmethod
726+
def encode_source(self):
727+
pass
728+
687729
@property
688730
def result(self):
689731
"""Returns a dict with the results.
@@ -906,6 +948,21 @@ def get_state(self):
906948
if bool(self.not_found):
907949
self.dest_state = STATE_INCOMPLETE
908950

951+
def encode_source(self):
952+
"""Convert encoding for given src
953+
"""
954+
enc_utils = encode.EncodeUtils()
955+
try:
956+
for target in self.targets:
957+
convert_rc = enc_utils.uss_convert_encoding_prev(
958+
target, target, self.from_encoding, self.to_encoding
959+
)
960+
if convert_rc:
961+
enc_utils.uss_tag_encoding(target, self.to_encoding)
962+
963+
except Exception as e:
964+
raise EncodeError("Failed to encode in the required codeset: {e}") from e
965+
909966

910967
class TarArchive(USSArchive):
911968
def __init__(self, module):
@@ -1372,6 +1429,28 @@ def compute_dest_size(self):
13721429
dest_space = math.ceil(dest_space / 1024)
13731430
self.dest_data_set.update(space_primary=dest_space, space_type="k")
13741431

1432+
def encode_source(self):
1433+
"""Convert encoding for given src
1434+
"""
1435+
enc_utils = encode.EncodeUtils()
1436+
1437+
try:
1438+
for target in self.targets:
1439+
ds_type = data_set.DataSetUtils(target, tmphlq=self.tmphlq).ds_type()
1440+
if not ds_type:
1441+
raise EncodeError("Unable to determine data set type of {0}".format(target))
1442+
enc_utils.mvs_convert_encoding(
1443+
target,
1444+
target,
1445+
self.from_encoding,
1446+
self.to_encoding,
1447+
src_type=ds_type,
1448+
dest_type=ds_type,
1449+
tmphlq=self.tmphlq
1450+
)
1451+
except Exception as e:
1452+
raise EncodeError(f"Failed to encode in the required codeset: {e}") from e
1453+
13751454

13761455
class AMATerseArchive(MVSArchive):
13771456
def __init__(self, module):
@@ -1604,6 +1683,24 @@ def get_error_hint(self, output):
16041683
return msg.format(sys_abend, reason_code, error_hint)
16051684

16061685

1686+
class EncodeError(Exception):
1687+
def __init__(self, message):
1688+
"""Error during encoding.
1689+
1690+
Parameters
1691+
----------
1692+
message : str
1693+
Human readable string describing the exception.
1694+
1695+
Attributes
1696+
----------
1697+
msg : str
1698+
Human readable string describing the exception.
1699+
"""
1700+
self.msg = 'An error occurred during encoding: "{0}"'.format(message)
1701+
super(EncodeError, self).__init__(self.msg)
1702+
1703+
16071704
def run_module():
16081705
"""Initialize module.
16091706
@@ -1686,7 +1783,21 @@ def run_module():
16861783
)
16871784
),
16881785
tmp_hlq=dict(type='str'),
1689-
force=dict(type='bool', default=False)
1786+
force=dict(type='bool', default=False),
1787+
encoding=dict(
1788+
type='dict',
1789+
required=False,
1790+
options={
1791+
'from': dict(
1792+
type='str',
1793+
required=False,
1794+
),
1795+
"to": dict(
1796+
type='str',
1797+
required=False,
1798+
)
1799+
}
1800+
)
16901801
),
16911802
supports_check_mode=True,
16921803
)
@@ -1761,7 +1872,14 @@ def run_module():
17611872
)
17621873
),
17631874
tmp_hlq=dict(type='qualifier_or_empty', default=''),
1764-
force=dict(type='bool', default=False)
1875+
force=dict(type='bool', default=False),
1876+
encoding=dict(
1877+
type='dict',
1878+
options={
1879+
'from' : dict(type='str'),
1880+
"to" : dict(type='str')
1881+
}
1882+
)
17651883
)
17661884

17671885
result = dict(
@@ -1779,13 +1897,16 @@ def run_module():
17791897
except ValueError as err:
17801898
module.fail_json(msg="Parameter verification failed", stderr=str(err))
17811899

1900+
encoding = parsed_args.get("encoding")
17821901
archive = get_archive_handler(module)
17831902

17841903
if archive.dest_exists() and not archive.force:
17851904
module.fail_json(msg="%s file exists. Use force flag to replace dest" % archive.dest)
17861905

17871906
archive.find_targets()
17881907
if archive.targets_exist():
1908+
if encoding:
1909+
archive.encode_source()
17891910
archive.compute_dest_size()
17901911
archive.archive_targets()
17911912
if archive.remove:

0 commit comments

Comments
 (0)