Skip to content

Commit f9658fd

Browse files
Fix CMCI context and scope when using allowed special chars
1 parent a3a2e15 commit f9658fd

File tree

4 files changed

+120
-31
lines changed

4 files changed

+120
-31
lines changed

plugins/module_utils/cmci.py

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -232,29 +232,7 @@ def __init__(self, method):
232232
request_params = self.init_request_params()
233233

234234
if request_params:
235-
if version_info.major <= 2:
236-
# This is a workaround for python 2, where we can't specify the
237-
# encoding as a parameter in urlencode. Store the quote_plus
238-
# setting, then override it with quote, so that spaces will be
239-
# encoded as %20 instead of +. Then set the quote_plus value
240-
# back so we haven't changed the behaviour long term
241-
default_quote_plus = urllib.quote_plus
242-
urllib.quote_plus = urllib.quote
243-
self._url = self._url + \
244-
"?" + \
245-
urllib.urlencode(
246-
requests.utils.to_key_val_list(request_params)
247-
)
248-
urllib.quote_plus = default_quote_plus
249-
else:
250-
# If running at python 3 and above
251-
self._url = self._url + \
252-
"?" + \
253-
urllib.parse.urlencode(
254-
requests.utils.to_key_val_list(request_params),
255-
quote_via=urllib.parse.quote
256-
)
257-
235+
self._url = _url_encode_params(self._url, requests.utils.to_key_val_list(request_params))
258236
result_request = {
259237
'url': self._url,
260238
'method': self._method,
@@ -345,16 +323,16 @@ def init_p(self):
345323

346324
self.validate(
347325
CONTEXT,
348-
'^([A-Za-z0-9]{1,8})$',
326+
'^([A-Za-z0-9$@#]{1,8})$',
349327
'a CPSM context name. CPSM context names are max 8 characters. '
350-
'Valid characters are A-Z a-z 0-9.'
328+
'Valid characters are A-Z a-z 0-9 $ @ #.'
351329
)
352330

353331
self.validate(
354332
SCOPE,
355-
'^([A-Za-z0-9]{1,8})$',
333+
'^([A-Za-z0-9$@#]{1,8})$',
356334
'a CPSM scope name. CPSM scope names are max 8 characters. '
357-
'Valid characters are A-Z a-z 0-9.'
335+
'Valid characters are A-Z a-z 0-9 $ @ #.'
358336
)
359337

360338
self.validate(
@@ -453,9 +431,9 @@ def init_url(self): # type: () -> str
453431
'/CICSSystemManagement/' + \
454432
t + \
455433
'/' + \
456-
self._p.get(CONTEXT) + '/'
434+
_url_encode_string(self._p.get(CONTEXT)) + '/'
457435
if self._p.get(SCOPE):
458-
url = url + self._p.get(SCOPE)
436+
url = url + _url_encode_string(self._p.get(SCOPE))
459437

460438
return url
461439

@@ -777,3 +755,36 @@ def _append_filter_string(existing, to_append, joiner=' AND '):
777755
return existing + '(' + to_append + ')'
778756
else:
779757
return existing + joiner + '(' + to_append + ')'
758+
759+
760+
def _url_encode_params(url, params):
761+
if version_info.major <= 2:
762+
# This is a workaround for python 2, where we can't specify the
763+
# encoding as a parameter in urlencode. Store the quote_plus
764+
# setting, then override it with quote, so that spaces will be
765+
# encoded as %20 instead of +. Then set the quote_plus value
766+
# back so we haven't changed the behaviour long term
767+
default_quote_plus = urllib.quote_plus
768+
urllib.quote_plus = urllib.quote
769+
url = url + \
770+
"?" + \
771+
urllib.urlencode(
772+
params
773+
)
774+
urllib.quote_plus = default_quote_plus
775+
else:
776+
# If running at python 3 and above
777+
url = url + \
778+
"?" + \
779+
urllib.parse.urlencode(
780+
params,
781+
quote_via=urllib.parse.quote
782+
)
783+
return url
784+
785+
786+
def _url_encode_string(url):
787+
if version_info.major <= 2:
788+
return urllib.quote_plus(url)
789+
else:
790+
return urllib.parse.quote_plus(url)

tests/integration/targets/cics_cmci/playbooks/cmci_bas_install.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
region_one: "{{ cmci_scope_region_1 }}"
1717
region_two: "{{ cmci_scope_region_2 }}"
1818
target_scope: "{{ cmci_program_name_1 }}"
19+
target_scope_special_char: "{{ cmci_system_group_special_chars }}"
1920
def_ver: "1"
2021

2122
module_defaults:
@@ -163,6 +164,17 @@
163164
failed_when: >
164165
'cpsm_response' not in result or result.cpsm_response not in ['OK', 'NODATA']
165166
167+
- name: Delete system group with special chars
168+
delegate_to: localhost
169+
ibm.ibm_zos_cics.cmci_delete:
170+
type: CICSRegionGroup
171+
resources:
172+
filter:
173+
group: "{{ target_scope_special_char }}"
174+
register: result
175+
failed_when: >
176+
'cpsm_response' not in result or result.cpsm_response not in ['OK', 'NODATA']
177+
166178
##################################################################################
167179
# Main Test
168180
##################################################################################
@@ -451,6 +463,50 @@
451463
- result.record_count == 1
452464
- result.records[0].program == program
453465

466+
- name: Create System Group with special chars
467+
delegate_to: localhost
468+
ibm.ibm_zos_cics.cmci_create:
469+
type: CICSRegionGroup
470+
attributes:
471+
group: "{{ target_scope_special_char }}"
472+
register: result
473+
474+
- name: Assert create System group with special chars
475+
ansible.builtin.assert:
476+
that:
477+
- result is changed
478+
- result.cpsm_response == 'OK'
479+
- result.record_count == 1
480+
- result.records[0].group == target_scope_special_char
481+
482+
- name: Query System group with special chars in scope
483+
delegate_to: localhost
484+
ibm.ibm_zos_cics.cmci_get:
485+
scope: "{{ target_scope_special_char }}"
486+
type: CICSRegionGroup
487+
resources:
488+
filter:
489+
changeusrid: "{{ cmci_user }}"
490+
group: "{{ target_scope_special_char }}"
491+
register: result
492+
493+
- name: Assert query System group with special chars
494+
ansible.builtin.assert:
495+
that:
496+
- result.cpsm_response == 'OK'
497+
- result.record_count == 1
498+
- result.records[0].group == target_scope_special_char
499+
- result.records[0].changeusrid == cmci_user
500+
501+
- name: Delete System group with special chars
502+
delegate_to: localhost
503+
ibm.ibm_zos_cics.cmci_delete:
504+
type: CICSRegionGroup
505+
resources:
506+
filter:
507+
group: "{{ target_scope_special_char }}"
508+
register: result
509+
454510
##################################################################################
455511
# Tear down
456512
##################################################################################

tests/integration/variables/template.cmci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ cmci_scope_region_2:
1010
cmci_program_name_1:
1111
cmci_program_name_2:
1212
cmci_program_filter:
13+
cmci_system_group_special_chars:

tests/unit/modules/test_cmci_get.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def test_invalid_port_high(cmci_module): # type: (CMCITestHelper) -> None
172172
def test_invalid_context(cmci_module): # type: (CMCITestHelper) -> None
173173
cmci_module.expect({
174174
'msg': 'Parameter "context" with value "^&iyk3z0r9" was not valid. Expected a CPSM context name. CPSM '
175-
'context names are max 8 characters. Valid characters are A-Z a-z 0-9.',
175+
'context names are max 8 characters. Valid characters are A-Z a-z 0-9 $ @ #.',
176176
'changed': False,
177177
'failed': True
178178
})
@@ -189,7 +189,7 @@ def test_invalid_context(cmci_module): # type: (CMCITestHelper) -> None
189189
def test_invalid_scope(cmci_module): # type: (CMCITestHelper) -> None
190190
cmci_module.expect({
191191
'msg': 'Parameter "scope" with value "&^iyk3z0r8" was not valid. Expected a CPSM scope name. CPSM scope '
192-
'names are max 8 characters. Valid characters are A-Z a-z 0-9.',
192+
'names are max 8 characters. Valid characters are A-Z a-z 0-9 $ @ #.',
193193
'changed': False,
194194
'failed': True
195195
})
@@ -203,6 +203,27 @@ def test_invalid_scope(cmci_module): # type: (CMCITestHelper) -> None
203203
})
204204

205205

206+
def test_valid_context_with_special_chars(cmci_module): # type: (CMCITestHelper) -> None
207+
records = [
208+
{'name': 'bat', 'dsname': 'STEWF.BLOP.BLIP'},
209+
{'name': 'bing', 'dsname': 'STEWF.BAT.BAZ'}
210+
]
211+
cmci_module.stub_records('GET', 'cicslocalfile', records, scope=SCOPE, context='C%40%23%24EX61')
212+
213+
cmci_module.expect(result(
214+
'https://winmvs2c.hursley.ibm.com:26040/CICSSystemManagement/cicslocalfile/C%40%23%24EX61/IYCWEMW2',
215+
records=records
216+
))
217+
218+
cmci_module.run(cmci_get, {
219+
'cmci_host': HOST,
220+
'cmci_port': '26040',
221+
'context': 'C@#$EX61',
222+
'scope': SCOPE,
223+
'type': 'cicslocalfile'
224+
})
225+
226+
206227
def test_auth(cmci_module): # type: (CMCITestHelper) -> None
207228
records = [
208229
{'name': 'bat', 'dsname': 'STEWF.BLOP.BLIP'},

0 commit comments

Comments
 (0)