mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
* add sap task list execute
* Apply suggestions from code review
Co-authored-by: Felix Fontein <felix@fontein.de>
* remove json out
* Apply suggestions from code review
Co-authored-by: Felix Fontein <felix@fontein.de>
* change logic
Co-authored-by: Rainer Leber <rainer.leber@sva.de>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 1705335ba7
)
Co-authored-by: rainerleber <39616583+rainerleber@users.noreply.github.com>
This commit is contained in:
parent
e6323433ff
commit
0feb38f2b1
4 changed files with 433 additions and 0 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
@ -1058,6 +1058,8 @@ files:
|
||||||
ignore: ryansb
|
ignore: ryansb
|
||||||
$modules/system/runit.py:
|
$modules/system/runit.py:
|
||||||
maintainers: jsumners
|
maintainers: jsumners
|
||||||
|
$modules/system/sap_task_list_execute:
|
||||||
|
maintainers: rainerleber
|
||||||
$modules/system/sefcontext.py:
|
$modules/system/sefcontext.py:
|
||||||
maintainers: dagwieers
|
maintainers: dagwieers
|
||||||
$modules/system/selinux_permissive.py:
|
$modules/system/selinux_permissive.py:
|
||||||
|
|
1
plugins/modules/sap_task_list_execute.py
Symbolic link
1
plugins/modules/sap_task_list_execute.py
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
system/sap_task_list_execute.py
|
341
plugins/modules/system/sap_task_list_execute.py
Normal file
341
plugins/modules/system/sap_task_list_execute.py
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: sap_task_list_execute
|
||||||
|
short_description: Perform SAP Task list execution
|
||||||
|
version_added: "3.5.0"
|
||||||
|
description:
|
||||||
|
- The C(sap_task_list_execute) module depends on C(pyrfc) Python library (version 2.4.0 and upwards).
|
||||||
|
Depending on distribution you are using, you may need to install additional packages to
|
||||||
|
have these available.
|
||||||
|
- Tasks in the task list which requires manual activities will be confirmed automatically.
|
||||||
|
- This module will use the RFC package C(STC_TM_API).
|
||||||
|
|
||||||
|
requirements:
|
||||||
|
- pyrfc >= 2.4.0
|
||||||
|
- xmltodict
|
||||||
|
|
||||||
|
options:
|
||||||
|
conn_username:
|
||||||
|
description: The required username for the SAP system.
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
conn_password:
|
||||||
|
description: The required password for the SAP system.
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
host:
|
||||||
|
description: The required host for the SAP system. Can be either an FQDN or IP Address.
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
sysnr:
|
||||||
|
description:
|
||||||
|
- The system number of the SAP system.
|
||||||
|
- You must quote the value to ensure retaining the leading zeros.
|
||||||
|
default: '00'
|
||||||
|
type: str
|
||||||
|
client:
|
||||||
|
description:
|
||||||
|
- The client number to connect to.
|
||||||
|
- You must quote the value to ensure retaining the leading zeros.
|
||||||
|
default: '000'
|
||||||
|
type: str
|
||||||
|
task_to_execute:
|
||||||
|
description: The task list which will be executed.
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
task_parameters:
|
||||||
|
description:
|
||||||
|
- The tasks and the parameters for execution.
|
||||||
|
- If the task list do not need any parameters. This could be empty.
|
||||||
|
- If only specific tasks from the task list should be executed.
|
||||||
|
The tasks even when no parameter is needed must be provided.
|
||||||
|
Alongside with the module parameter I(task_skip=true).
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
suboptions:
|
||||||
|
TASKNAME:
|
||||||
|
description: The name of the task in the task list.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
FIELDNAME:
|
||||||
|
description: The name of the field of the task.
|
||||||
|
type: str
|
||||||
|
VALUE:
|
||||||
|
description: The value which have to be set.
|
||||||
|
type: raw
|
||||||
|
task_settings:
|
||||||
|
description:
|
||||||
|
- Setting for the execution of the task list. This can be the following as in TCODE SE80 described.
|
||||||
|
Check Mode C(CHECKRUN), Background Processing Active C(BATCH) (this is the default value),
|
||||||
|
Asynchronous Execution C(ASYNC), Trace Mode C(TRACE), Server Name C(BATCH_TARGET).
|
||||||
|
default: ['BATCH']
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
task_skip:
|
||||||
|
description:
|
||||||
|
- If this parameter is C(true) not defined tasks in I(task_parameters) are skipped.
|
||||||
|
- This could be the case when only certain tasks should run from the task list.
|
||||||
|
default: false
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
notes:
|
||||||
|
- Does not support C(check_mode).
|
||||||
|
author:
|
||||||
|
- Rainer Leber (@rainerleber)
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
# Pass in a message
|
||||||
|
- name: Test task execution
|
||||||
|
community.general.sap_task_list_execute:
|
||||||
|
conn_username: DDIC
|
||||||
|
conn_password: Passwd1234
|
||||||
|
host: 10.1.8.10
|
||||||
|
sysnr: '01'
|
||||||
|
client: '000'
|
||||||
|
task_to_execute: SAP_BASIS_SSL_CHECK
|
||||||
|
task_settings: batch
|
||||||
|
|
||||||
|
- name: Pass in input parameters
|
||||||
|
community.general.sap_task_list_execute:
|
||||||
|
conn_username: DDIC
|
||||||
|
conn_password: Passwd1234
|
||||||
|
host: 10.1.8.10
|
||||||
|
sysnr: '00'
|
||||||
|
client: '000'
|
||||||
|
task_to_execute: SAP_BASIS_SSL_CHECK
|
||||||
|
task_parameters :
|
||||||
|
- { 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO', 'FIELDNAME': 'P_OPT2', 'VALUE': 'X' }
|
||||||
|
- TASKNAME: CL_STCT_CHECK_SEC_CRYPTO
|
||||||
|
FIELDNAME: P_OPT3
|
||||||
|
VALUE: X
|
||||||
|
task_settings: batch
|
||||||
|
|
||||||
|
# Exported environement variables.
|
||||||
|
- name: Hint if module will fail with error message like ImportError libsapnwrfc.so...
|
||||||
|
community.general.sap_task_list_execute:
|
||||||
|
conn_username: DDIC
|
||||||
|
conn_password: Passwd1234
|
||||||
|
host: 10.1.8.10
|
||||||
|
sysnr: '00'
|
||||||
|
client: '000'
|
||||||
|
task_to_execute: SAP_BASIS_SSL_CHECK
|
||||||
|
task_settings: batch
|
||||||
|
environment:
|
||||||
|
SAPNWRFC_HOME: /usr/local/sap/nwrfcsdk
|
||||||
|
LD_LIBRARY_PATH: /usr/local/sap/nwrfcsdk/lib
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
msg:
|
||||||
|
description: A small execution description.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: 'Successful'
|
||||||
|
out:
|
||||||
|
description: A complete description of the executed tasks. If this is available.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
returned: on success
|
||||||
|
sample: [...,{
|
||||||
|
"LOG": {
|
||||||
|
"STCTM_S_LOG": [
|
||||||
|
{
|
||||||
|
"ACTIVITY": "U_CONFIG",
|
||||||
|
"ACTIVITY_DESCR": "Configuration changed",
|
||||||
|
"DETAILS": null,
|
||||||
|
"EXEC_ID": "20210728184903.815739",
|
||||||
|
"FIELD": null,
|
||||||
|
"ID": "STC_TASK",
|
||||||
|
"LOG_MSG_NO": "000000",
|
||||||
|
"LOG_NO": null,
|
||||||
|
"MESSAGE": "For radiobutton group ICM too many options are set; choose only one option",
|
||||||
|
"MESSAGE_V1": "ICM",
|
||||||
|
"MESSAGE_V2": null,
|
||||||
|
"MESSAGE_V3": null,
|
||||||
|
"MESSAGE_V4": null,
|
||||||
|
"NUMBER": "048",
|
||||||
|
"PARAMETER": null,
|
||||||
|
"PERIOD": "M",
|
||||||
|
"PERIOD_DESCR": "Maintenance",
|
||||||
|
"ROW": "0",
|
||||||
|
"SRC_LINE": "170",
|
||||||
|
"SRC_OBJECT": "CL_STCTM_REPORT_UI IF_STCTM_UI_TASK~SET_PARAMETERS",
|
||||||
|
"SYSTEM": null,
|
||||||
|
"TIMESTMP": "20210728184903",
|
||||||
|
"TSTPNM": "DDIC",
|
||||||
|
"TYPE": "E"
|
||||||
|
},...
|
||||||
|
]}}]
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
|
from ansible.module_utils.json_utils import json
|
||||||
|
import traceback
|
||||||
|
try:
|
||||||
|
from pyrfc import Connection
|
||||||
|
except ImportError:
|
||||||
|
HAS_PYRFC_LIBRARY = False
|
||||||
|
PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
|
||||||
|
else:
|
||||||
|
HAS_PYRFC_LIBRARY = True
|
||||||
|
try:
|
||||||
|
import xmltodict
|
||||||
|
except ImportError:
|
||||||
|
HAS_XMLTODICT_LIBRARY = False
|
||||||
|
XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc()
|
||||||
|
else:
|
||||||
|
HAS_XMLTODICT_LIBRARY = True
|
||||||
|
|
||||||
|
|
||||||
|
def call_rfc_method(connection, method_name, kwargs):
|
||||||
|
# PyRFC call function
|
||||||
|
return connection.call(method_name, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def process_exec_settings(task_settings):
|
||||||
|
# processes task settings to objects
|
||||||
|
exec_settings = {}
|
||||||
|
for settings in task_settings:
|
||||||
|
temp_dict = {settings.upper(): 'X'}
|
||||||
|
for key, value in temp_dict.items():
|
||||||
|
exec_settings[key] = value
|
||||||
|
return exec_settings
|
||||||
|
|
||||||
|
|
||||||
|
def xml_to_dict(xml_raw):
|
||||||
|
try:
|
||||||
|
xml_parsed = xmltodict.parse(xml_raw, dict_constructor=dict)
|
||||||
|
xml_dict = xml_parsed['asx:abap']['asx:values']['SESSION']['TASKLIST']
|
||||||
|
except KeyError:
|
||||||
|
xml_dict = "No logs available."
|
||||||
|
return xml_dict
|
||||||
|
|
||||||
|
|
||||||
|
def run_module():
|
||||||
|
|
||||||
|
params_spec = dict(
|
||||||
|
TASKNAME=dict(type='str', required=True),
|
||||||
|
FIELDNAME=dict(type='str'),
|
||||||
|
VALUE=dict(type='raw'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# define available arguments/parameters a user can pass to the module
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
# values for connection
|
||||||
|
conn_username=dict(type='str', required=True),
|
||||||
|
conn_password=dict(type='str', required=True, no_log=True),
|
||||||
|
host=dict(type='str', required=True),
|
||||||
|
sysnr=dict(type='str', default="00"),
|
||||||
|
client=dict(type='str', default="000"),
|
||||||
|
# values for execution tasks
|
||||||
|
task_to_execute=dict(type='str', required=True),
|
||||||
|
task_parameters=dict(type='list', elements='dict', options=params_spec),
|
||||||
|
task_settings=dict(type='list', elements='str', default=['BATCH']),
|
||||||
|
task_skip=dict(type='bool', default=False),
|
||||||
|
),
|
||||||
|
supports_check_mode=False,
|
||||||
|
)
|
||||||
|
result = dict(changed=False, msg='', out={})
|
||||||
|
|
||||||
|
params = module.params
|
||||||
|
|
||||||
|
username = params['conn_username'].upper()
|
||||||
|
password = params['conn_password']
|
||||||
|
host = params['host']
|
||||||
|
sysnr = params['sysnr']
|
||||||
|
client = params['client']
|
||||||
|
|
||||||
|
task_parameters = params['task_parameters']
|
||||||
|
task_to_execute = params['task_to_execute']
|
||||||
|
task_settings = params['task_settings']
|
||||||
|
task_skip = params['task_skip']
|
||||||
|
|
||||||
|
if not HAS_PYRFC_LIBRARY:
|
||||||
|
module.fail_json(
|
||||||
|
msg=missing_required_lib('pyrfc'),
|
||||||
|
exception=PYRFC_LIBRARY_IMPORT_ERROR)
|
||||||
|
|
||||||
|
if not HAS_XMLTODICT_LIBRARY:
|
||||||
|
module.fail_json(
|
||||||
|
msg=missing_required_lib('xmltodict'),
|
||||||
|
exception=XMLTODICT_LIBRARY_IMPORT_ERROR)
|
||||||
|
|
||||||
|
# basic RFC connection with pyrfc
|
||||||
|
try:
|
||||||
|
conn = Connection(user=username, passwd=password, ashost=host, sysnr=sysnr, client=client)
|
||||||
|
except Exception as err:
|
||||||
|
result['error'] = str(err)
|
||||||
|
result['msg'] = 'Something went wrong connecting to the SAP system.'
|
||||||
|
module.fail_json(**result)
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_params = call_rfc_method(conn, 'STC_TM_SCENARIO_GET_PARAMETERS',
|
||||||
|
{'I_SCENARIO_ID': task_to_execute})
|
||||||
|
except Exception as err:
|
||||||
|
result['error'] = str(err)
|
||||||
|
result['msg'] = 'The task list does not exsist.'
|
||||||
|
module.fail_json(**result)
|
||||||
|
exec_settings = process_exec_settings(task_settings)
|
||||||
|
# initialize session task
|
||||||
|
session_init = call_rfc_method(conn, 'STC_TM_SESSION_BEGIN',
|
||||||
|
{'I_SCENARIO_ID': task_to_execute,
|
||||||
|
'I_INIT_ONLY': 'X'})
|
||||||
|
# Confirm Tasks which requires manual activities from Task List Run
|
||||||
|
for task in raw_params['ET_PARAMETER']:
|
||||||
|
call_rfc_method(conn, 'STC_TM_TASK_CONFIRM',
|
||||||
|
{'I_SESSION_ID': session_init['E_SESSION_ID'],
|
||||||
|
'I_TASKNAME': task['TASKNAME']})
|
||||||
|
if task_skip:
|
||||||
|
for task in raw_params['ET_PARAMETER']:
|
||||||
|
call_rfc_method(conn, 'STC_TM_TASK_SKIP',
|
||||||
|
{'I_SESSION_ID': session_init['E_SESSION_ID'],
|
||||||
|
'I_TASKNAME': task['TASKNAME'], 'I_SKIP_DEP_TASKS': 'X'})
|
||||||
|
# unskip defined tasks and set parameters
|
||||||
|
if task_parameters is not None:
|
||||||
|
for task in task_parameters:
|
||||||
|
call_rfc_method(conn, 'STC_TM_TASK_UNSKIP',
|
||||||
|
{'I_SESSION_ID': session_init['E_SESSION_ID'],
|
||||||
|
'I_TASKNAME': task['TASKNAME'], 'I_UNSKIP_DEP_TASKS': 'X'})
|
||||||
|
|
||||||
|
call_rfc_method(conn, 'STC_TM_SESSION_SET_PARAMETERS',
|
||||||
|
{'I_SESSION_ID': session_init['E_SESSION_ID'],
|
||||||
|
'IT_PARAMETER': task_parameters})
|
||||||
|
# start the task
|
||||||
|
try:
|
||||||
|
session_start = call_rfc_method(conn, 'STC_TM_SESSION_RESUME',
|
||||||
|
{'I_SESSION_ID': session_init['E_SESSION_ID'],
|
||||||
|
'IS_EXEC_SETTINGS': exec_settings})
|
||||||
|
except Exception as err:
|
||||||
|
result['error'] = str(err)
|
||||||
|
result['msg'] = 'Something went wrong. See error.'
|
||||||
|
module.fail_json(**result)
|
||||||
|
# get task logs because the execution may successfully but the tasks shows errors or warnings
|
||||||
|
# returned value is ABAPXML https://help.sap.com/doc/abapdocu_755_index_htm/7.55/en-US/abenabap_xslt_asxml_general.htm
|
||||||
|
session_log = call_rfc_method(conn, 'STC_TM_SESSION_GET_LOG',
|
||||||
|
{'I_SESSION_ID': session_init['E_SESSION_ID']})
|
||||||
|
|
||||||
|
task_list = xml_to_dict(session_log['E_LOG'])
|
||||||
|
|
||||||
|
result['changed'] = True
|
||||||
|
result['msg'] = session_start['E_STATUS_DESCR']
|
||||||
|
result['out'] = task_list
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
run_module()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,89 @@
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from ansible_collections.community.general.tests.unit.compat.mock import patch, MagicMock
|
||||||
|
from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
|
||||||
|
|
||||||
|
sys.modules['pyrfc'] = MagicMock()
|
||||||
|
sys.modules['pyrfc.Connection'] = MagicMock()
|
||||||
|
sys.modules['xmltodict'] = MagicMock()
|
||||||
|
sys.modules['xmltodict.parse'] = MagicMock()
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.modules.system import sap_task_list_execute
|
||||||
|
|
||||||
|
|
||||||
|
class TestSAPRfcModule(ModuleTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestSAPRfcModule, self).setUp()
|
||||||
|
self.module = sap_task_list_execute
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestSAPRfcModule, self).tearDown()
|
||||||
|
|
||||||
|
def define_rfc_connect(self, mocker):
|
||||||
|
return mocker.patch(self.module.call_rfc_method)
|
||||||
|
|
||||||
|
def test_without_required_parameters(self):
|
||||||
|
"""Failure must occurs when all parameters are missing"""
|
||||||
|
with self.assertRaises(AnsibleFailJson):
|
||||||
|
set_module_args({})
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
def test_error_no_task_list(self):
|
||||||
|
"""tests fail to exec task list"""
|
||||||
|
|
||||||
|
set_module_args({
|
||||||
|
"conn_username": "DDIC",
|
||||||
|
"conn_password": "Test1234",
|
||||||
|
"host": "10.1.8.9",
|
||||||
|
"task_to_execute": "SAP_BASIS_SSL_CHECK"
|
||||||
|
})
|
||||||
|
|
||||||
|
with patch.object(self.module, 'Connection') as conn:
|
||||||
|
conn.return_value = ''
|
||||||
|
with self.assertRaises(AnsibleFailJson) as result:
|
||||||
|
self.module.main()
|
||||||
|
self.assertEqual(result.exception.args[0]['msg'], 'The task list does not exsist.')
|
||||||
|
|
||||||
|
def test_success(self):
|
||||||
|
"""test execute task list success"""
|
||||||
|
|
||||||
|
set_module_args({
|
||||||
|
"conn_username": "DDIC",
|
||||||
|
"conn_password": "Test1234",
|
||||||
|
"host": "10.1.8.9",
|
||||||
|
"task_to_execute": "SAP_BASIS_SSL_CHECK"
|
||||||
|
})
|
||||||
|
with patch.object(self.module, 'xml_to_dict') as XML:
|
||||||
|
XML.return_value = {'item': [{'TASK': {'CHECK_STATUS_DESCR': 'Check successfully',
|
||||||
|
'STATUS_DESCR': 'Executed successfully', 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO',
|
||||||
|
'LNR': '1', 'DESCRIPTION': 'Check SAP Cryptographic Library', 'DOCU_EXIST': 'X',
|
||||||
|
'LOG_EXIST': 'X', 'ACTION_SKIP': None, 'ACTION_UNSKIP': None, 'ACTION_CONFIRM': None,
|
||||||
|
'ACTION_MAINTAIN': None}}]}
|
||||||
|
|
||||||
|
with self.assertRaises(AnsibleExitJson) as result:
|
||||||
|
sap_task_list_execute.main()
|
||||||
|
self.assertEqual(result.exception.args[0]['out'], {'item': [{'TASK': {'CHECK_STATUS_DESCR': 'Check successfully',
|
||||||
|
'STATUS_DESCR': 'Executed successfully', 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO',
|
||||||
|
'LNR': '1', 'DESCRIPTION': 'Check SAP Cryptographic Library', 'DOCU_EXIST': 'X',
|
||||||
|
'LOG_EXIST': 'X', 'ACTION_SKIP': None, 'ACTION_UNSKIP': None,
|
||||||
|
'ACTION_CONFIRM': None, 'ACTION_MAINTAIN': None}}]})
|
||||||
|
|
||||||
|
def test_success_no_log(self):
|
||||||
|
"""test execute task list success without logs"""
|
||||||
|
|
||||||
|
set_module_args({
|
||||||
|
"conn_username": "DDIC",
|
||||||
|
"conn_password": "Test1234",
|
||||||
|
"host": "10.1.8.9",
|
||||||
|
"task_to_execute": "SAP_BASIS_SSL_CHECK"
|
||||||
|
})
|
||||||
|
with patch.object(self.module, 'xml_to_dict') as XML:
|
||||||
|
XML.return_value = "No logs available."
|
||||||
|
with self.assertRaises(AnsibleExitJson) as result:
|
||||||
|
sap_task_list_execute.main()
|
||||||
|
self.assertEqual(result.exception.args[0]['out'], 'No logs available.')
|
Loading…
Reference in a new issue