Skip to content

Commit c0baef8

Browse files
committed
Merge branch 'master' into dev
2 parents b23b000 + 8b7c28d commit c0baef8

File tree

8 files changed

+295
-7
lines changed

8 files changed

+295
-7
lines changed

docs/source/playbooks.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ details, see the sample `ansible.cfg`_ notes.
7575
For more information about available configurations for ``ansible.cfg``, read
7676
the Ansible documentation on `Ansible configuration settings`_.
7777

78-
7978
.. _ansible.cfg:
8079
https://github.com/ansible-collections/ibm_zos_core/blob/master/playbooks/ansible.cfg
8180
.. _Ansible configuration settings:

playbooks/group_vars/all.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,4 @@ environment_vars:
6363
_TAG_REDIR_ERR: "txt"
6464
_TAG_REDIR_IN: "txt"
6565
_TAG_REDIR_OUT: "txt"
66-
LANG: "C"
66+
LANG: "C"

playbooks/inventory

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@ zsystem:
44
ansible_host: zos_target_address
55
ansible_user: zos_target_username
66
ansible_python_interpreter: path_to_python_interpreter_binary_on_zos_target
7-
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# Copyright (c) IBM Corporation 2020
2+
# Apache License, Version 2.0 (see https://opensource.org/licenses/Apache-2.0)
3+
4+
from __future__ import absolute_import, division, print_function
5+
6+
import re
7+
from os import path
8+
from random import choice
9+
from string import ascii_uppercase
10+
from ansible.module_utils._text import to_bytes
11+
12+
try:
13+
from zoautil_py import Datasets, MVSCmd, types
14+
except Exception:
15+
Datasets = ""
16+
MVSCmd = ""
17+
types = ""
18+
19+
__metaclass__ = type
20+
21+
LISTDS_COMMAND = " LISTDS '{0}'"
22+
LISTCAT_COMMAND = " LISTCAT ENT({0}) ALL"
23+
24+
25+
class DataSetUtils(object):
26+
def __init__(self, module, data_set):
27+
"""A standard utility to gather information about
28+
a particular data set. Note that the input data set is assumed
29+
to be cataloged.
30+
31+
Arguments:
32+
module {AnsibleModule} -- The AnsibleModule object from currently
33+
running module.
34+
data_set {str} -- Name of the input data set
35+
"""
36+
self.module = module
37+
self.data_set = data_set
38+
self.uss_path = '/' in data_set
39+
self.ds_info = dict()
40+
if not self.uss_path:
41+
self.ds_info = self._gather_data_set_info()
42+
43+
def data_set_exists(self):
44+
"""Determines whether the input data set exists. The input data
45+
set can be VSAM or non-VSAM.
46+
47+
Returns:
48+
bool -- If the data set exists
49+
"""
50+
if self.uss_path:
51+
return path.exists(to_bytes(self.data_set))
52+
return self.ds_info.get('exists')
53+
54+
def data_set_member_exists(self, member):
55+
"""Determines whether the input data set contains the given member.
56+
57+
Arguments:
58+
member {str} -- The name of the data set member
59+
60+
Returns:
61+
bool -- If the member exists
62+
"""
63+
if self.get_data_set_type() == "PO":
64+
rc, out, err = self.module.run_command(
65+
"head \"//'{0}({1})'\"".format(self.data_set, member)
66+
)
67+
if rc == 0 and not re.findall(r"EDC5067I", err):
68+
return True
69+
return False
70+
71+
def get_data_set_type(self):
72+
"""Retrieves the data set type of the input data set.
73+
74+
Returns:
75+
str -- Type of the input data set.
76+
None -- If the data set does not exist or a non-existent USS file
77+
78+
Possible return values:
79+
'PS' -- Physical Sequential
80+
'PO' -- Partitioned (PDS or PDS(E))
81+
'VSAM' -- Virtual Storage Access Method
82+
'DA' -- Direct Access
83+
'IS' -- Indexed Sequential
84+
'USS' -- USS file or directory
85+
"""
86+
if self.uss_path and self.data_set_exists():
87+
return 'USS'
88+
return self.ds_info.get('dsorg')
89+
90+
@staticmethod
91+
def create_temp_data_set(
92+
LLQ,
93+
ds_type="SEQ",
94+
size="5M",
95+
ds_format="FB",
96+
lrecl=80
97+
):
98+
"""Creates a temporary data set with the given low level qualifier.
99+
100+
Arguments:
101+
LLQ {str} -- Low Level Qualifier to be used for temporary data set
102+
ds_type {str} -- The data set type, default: Sequential
103+
size {str} -- The size of the data set, default: 5M
104+
format {str} -- The record format of the data set, default: FB
105+
lrecl {int} -- The record length of the data set, default: 80
106+
107+
Returns:
108+
str -- Name of the created data set
109+
110+
Raises:
111+
OSError: When non-zero return code is received
112+
from Datasets.create()
113+
"""
114+
chars = ascii_uppercase
115+
HLQ2 = ''.join(choice(chars) for i in range(5))
116+
HLQ3 = ''.join(choice(chars) for i in range(6))
117+
temp_ds_name = "{0}.{1}.{2}.{3}".format(Datasets.hlq(), HLQ2, HLQ3, LLQ)
118+
119+
rc = Datasets.create(temp_ds_name, ds_type, size, ds_format, "", lrecl)
120+
if rc != 0:
121+
raise OSError("Unable to create temporary data set")
122+
123+
return temp_ds_name
124+
125+
def get_data_set_volume(self):
126+
"""Retrieves the volume name where the input data set is stored.
127+
128+
Returns:
129+
str -- Volume where the data set is stored
130+
None -- If the data set does not exist
131+
132+
Raises:
133+
AttributeError -- When input data set is a USS file or directory
134+
"""
135+
if self.uss_path:
136+
raise AttributeError(
137+
"USS file or directory has no attribute 'Volume'"
138+
)
139+
return self.ds_info.get('volser')
140+
141+
def get_data_set_lrecl(self):
142+
"""Retrieves the record length of the input data set. Record length
143+
specifies the length, in bytes, of each record in the data set.
144+
145+
Returns:
146+
int -- The record length, in bytes, of each record
147+
None -- If the data set does not exist or the data set is VSAM
148+
149+
Raises:
150+
AttributeError -- When input data set is a USS file or directory
151+
"""
152+
if self.uss_path:
153+
raise AttributeError(
154+
"USS file or directory has no attribute 'lrecl'"
155+
)
156+
return self.ds_info.get('lrecl')
157+
158+
def get_data_set_recfm(self):
159+
"""Retrieves the record format of the input data set.
160+
161+
Returns:
162+
str -- Record format
163+
None -- If the data set does not exist or the data set is VSAM
164+
165+
Raises:
166+
AttributeError -- When input data set is a USS file or directory
167+
168+
Possible return values:
169+
'F' -- Fixed
170+
'FB' -- Fixed Blocked
171+
'V' -- Variable
172+
'VB' -- Variable Blocked
173+
'U' -- Undefined
174+
'VBS' -- Variable Blocked Spanned
175+
'VS' -- Variable Spanned
176+
"""
177+
if self.uss_path:
178+
raise AttributeError(
179+
"USS file or directory has no attribute 'recfm'"
180+
)
181+
return self.ds_info.get('recfm')
182+
183+
def _gather_data_set_info(self):
184+
"""Retrieves information about the input data set using LISTDS and
185+
LISTCAT commands.
186+
187+
Returns:
188+
dict -- Dictionary containing data set attributes
189+
"""
190+
result = dict()
191+
192+
try:
193+
listds_out = self._run_mvs_cmd('IKJEFT01', LISTDS_COMMAND.format(self.data_set))
194+
listcat_out = self._run_mvs_cmd('IDCAMS', LISTCAT_COMMAND.format(self.data_set))
195+
except Exception:
196+
raise
197+
result.update(self._process_listds_output(listds_out))
198+
result.update(self._process_listcat_output(listcat_out))
199+
return result
200+
201+
def _run_mvs_cmd(self, pgm, input_cmd):
202+
"""Executes mvscmd with the given program and input command.
203+
204+
Arguments:
205+
pgm {str} -- The name of the MVS program to execute
206+
input_cmd {str} -- The command to execute
207+
208+
Returns:
209+
str -- The generated 'sysprint' of the executed command
210+
211+
Raises:
212+
MVSCmdExecError: When non-zero return code is received while
213+
executing MVSCmd
214+
215+
DatasetBusyError: When the data set is being edited by another user
216+
"""
217+
sysprint = "sysprint"
218+
sysin = "sysin"
219+
if pgm == "IKJEFT01":
220+
sysprint = "systsprt"
221+
sysin = "systsin"
222+
223+
rc, out, err = self.module.run_command(
224+
"mvscmdauth --pgm={0} --{1}=* --{2}=stdin".format(pgm, sysprint, sysin),
225+
data=input_cmd
226+
)
227+
if rc != 0:
228+
if (re.findall(r"ALREADY IN USE", out)):
229+
raise DatasetBusyError(self.data_set)
230+
if (re.findall(r"NOT IN CATALOG|NOT FOUND|NOT LISTED", out)):
231+
self.ds_info['exists'] = False
232+
else:
233+
raise MVSCmdExecError(rc, out, err)
234+
return out
235+
236+
def _process_listds_output(self, output):
237+
"""Parses the output generated by LISTDS command.
238+
239+
Arguments:
240+
output {str} -- The output of LISTDS command
241+
242+
Returns:
243+
dict -- Dictionary containing the output parameters of LISTDS
244+
"""
245+
result = dict()
246+
if "NOT IN CATALOG" in output:
247+
result['exists'] = False
248+
else:
249+
result['exists'] = True
250+
ds_search = re.search(r"(-|--)DSORG(-\s*|\s*)\n(.*)", output, re.MULTILINE)
251+
if ds_search:
252+
ds_params = ds_search.group(3).split()
253+
result['dsorg'] = ds_params[-1]
254+
if result.get('dsorg') != "VSAM":
255+
result['recfm'] = ds_params[0]
256+
result['lrecl'] = ds_params[1]
257+
return result
258+
259+
def _process_listcat_output(self, output):
260+
"""Parses the output generated by LISTCAT command.
261+
262+
Arguments:
263+
output {str} -- The output of LISTCAT command
264+
265+
Returns:
266+
dict -- Dictionary containing the output parameters of LISTCAT
267+
"""
268+
result = dict()
269+
if "NOT FOUND" not in output:
270+
volser_output = re.findall(r"VOLSER-*[A-Z|0-9]*", output)
271+
result['volser'] = ''.join(
272+
re.findall(r"-[A-Z|0-9]*", volser_output[0])
273+
).replace('-', '')
274+
return result
275+
276+
277+
class MVSCmdExecError(Exception):
278+
def __init__(self, rc, out, err):
279+
self.msg = (
280+
"Failure during execution of mvscmd; Return code: {0}; "
281+
"stdout: {1}; stderr: {2}".format(rc, out, err)
282+
)
283+
super().__init__(self.msg)
284+
285+
286+
class DatasetBusyError(Exception):
287+
def __init__(self, data_set):
288+
self.msg = (
289+
"Dataset {0} may already be open by another user. "
290+
"Close the dataset and try again".format(data_set)
291+
)
292+
super().__init__(self.msg)

plugins/modules/zos_data_set.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,6 @@
480480
default: false
481481
version_added: "2.9"
482482
483-
484483
"""
485484
EXAMPLES = r"""
486485
- name: Create a sequential data set if it does not exist

plugins/modules/zos_job_query.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ def run_module():
161161
validate_arguments(module.params)
162162
jobs_raw = query_jobs(module.params)
163163
jobs = parsing_jobs(jobs_raw)
164-
165164
except Exception as e:
166165
module.fail_json(msg=e, **result)
167166
result["jobs"] = jobs

tests/sanity/ignore-2.10.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ plugins/modules/zos_lineinfile.py validate-modules:missing-gplv3-license # Licen
1818
plugins/modules/zos_copy.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
1919
plugins/modules/zos_copy.py validate-modules:parameter-type-not-in-doc # Passing args from action plugin
2020
plugins/modules/zos_copy.py validate-modules:undocumented-parameter # Passing args from action plugin
21-
plugins/modules/zos_mvs_raw.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
21+
plugins/modules/zos_mvs_raw.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0

tests/sanity/ignore-2.9.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ plugins/modules/zos_lineinfile.py validate-modules:missing-gplv3-license # Licen
1818
plugins/modules/zos_copy.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
1919
plugins/modules/zos_copy.py validate-modules:parameter-type-not-in-doc # Passing args from action plugin
2020
plugins/modules/zos_copy.py validate-modules:undocumented-parameter # Passing args from action plugin
21-
plugins/modules/zos_mvs_raw.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
21+
plugins/modules/zos_mvs_raw.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0

0 commit comments

Comments
 (0)