Redirect and Remove sap modules (#5592)

* redirect and remove sap modules

* remove botmeta redirect

* add changelog fragment

* revert runtime.yml changes and add new entries

* Update meta/runtime.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update changelogs/fragments/5592-redirect-remove-sap-modules.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update changelogs/fragments/5592-redirect-remove-sap-modules.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update 5592-redirect-remove-sap-modules.yml

Fix indentation

* Fix RST syntax.

* Update meta/runtime.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update meta/runtime.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Rainer Leber 2022-11-27 13:59:29 +01:00 committed by GitHub
commit b1094d840f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 19 additions and 1032 deletions

View file

@ -1,214 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Rainer Leber <rainerleber@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: hana_query
short_description: Execute SQL on HANA
version_added: 3.2.0
description: This module executes SQL statements on HANA with hdbsql.
options:
sid:
description: The system ID.
type: str
required: true
instance:
description: The instance number.
type: str
required: true
user:
description: A dedicated username. The user could be also in hdbuserstore. Defaults to C(SYSTEM).
type: str
default: SYSTEM
userstore:
description: If C(true) the user must be in hdbuserstore.
type: bool
default: false
version_added: 3.5.0
password:
description:
- The password to connect to the database.
- "B(Note:) Since the passwords have to be passed as command line arguments, I(userstore=true) should
be used whenever possible, as command line arguments can be seen by other users
on the same machine."
type: str
autocommit:
description: Autocommit the statement.
type: bool
default: true
host:
description: The Host IP address. The port can be defined as well.
type: str
database:
description: Define the database on which to connect.
type: str
encrypted:
description: Use encrypted connection. Defaults to C(false).
type: bool
default: false
filepath:
description:
- One or more files each containing one SQL query to run.
- Must be a string or list containing strings.
type: list
elements: path
query:
description:
- SQL query to run.
- Must be a string or list containing strings. Please note that if you supply a string, it will be split by commas (C(,)) to a list.
It is better to supply a one-element list instead to avoid mangled input.
type: list
elements: str
notes:
- Does not support C(check_mode).
author:
- Rainer Leber (@rainerleber)
'''
EXAMPLES = r'''
- name: Simple select query
community.general.hana_query:
sid: "hdb"
instance: "01"
password: "Test123"
query: "select user_name from users"
- name: Run several queries
community.general.hana_query:
sid: "hdb"
instance: "01"
password: "Test123"
query:
- "select user_name from users;"
- select * from SYSTEM;
host: "localhost"
autocommit: false
- name: Run several queries from file
community.general.hana_query:
sid: "hdb"
instance: "01"
password: "Test123"
filepath:
- /tmp/HANA_CPU_UtilizationPerCore_2.00.020+.txt
- /tmp/HANA.txt
host: "localhost"
- name: Run several queries from user store
community.general.hana_query:
sid: "hdb"
instance: "01"
user: hdbstoreuser
userstore: true
query:
- "select user_name from users;"
- select * from users;
autocommit: false
'''
RETURN = r'''
query_result:
description: List containing results of all queries executed (one sublist for every query).
returned: on success
type: list
elements: list
sample: [[{"Column": "Value1"}, {"Column": "Value2"}], [{"Column": "Value1"}, {"Column": "Value2"}]]
'''
import csv
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import StringIO
from ansible.module_utils.common.text.converters import to_native
def csv_to_list(rawcsv):
reader_raw = csv.DictReader(StringIO(rawcsv))
reader = [dict((k, v.strip()) for k, v in row.items()) for row in reader_raw]
return list(reader)
def main():
module = AnsibleModule(
argument_spec=dict(
sid=dict(type='str', required=True),
instance=dict(type='str', required=True),
encrypted=dict(type='bool', default=False),
host=dict(type='str', required=False),
user=dict(type='str', default="SYSTEM"),
userstore=dict(type='bool', default=False),
password=dict(type='str', no_log=True),
database=dict(type='str', required=False),
query=dict(type='list', elements='str', required=False),
filepath=dict(type='list', elements='path', required=False),
autocommit=dict(type='bool', default=True),
),
required_one_of=[('query', 'filepath')],
required_if=[('userstore', False, ['password'])],
supports_check_mode=False,
)
rc, out, err, out_raw = [0, [], "", ""]
params = module.params
sid = (params['sid']).upper()
instance = params['instance']
user = params['user']
userstore = params['userstore']
password = params['password']
autocommit = params['autocommit']
host = params['host']
database = params['database']
encrypted = params['encrypted']
filepath = params['filepath']
query = params['query']
bin_path = "/usr/sap/{sid}/HDB{instance}/exe/hdbsql".format(sid=sid, instance=instance)
try:
command = [module.get_bin_path(bin_path, required=True)]
except Exception as e:
module.fail_json(msg='Failed to find hdbsql at the expected path "{0}". Please check SID and instance number: "{1}"'.format(bin_path, to_native(e)))
if encrypted is True:
command.extend(['-attemptencrypt'])
if autocommit is False:
command.extend(['-z'])
if host is not None:
command.extend(['-n', host])
if database is not None:
command.extend(['-d', database])
# -x Suppresses additional output, such as the number of selected rows in a result set.
if userstore:
command.extend(['-x', '-U', user])
else:
command.extend(['-x', '-i', instance, '-u', user, '-p', password])
if filepath is not None:
command.extend(['-I'])
for p in filepath:
# makes a command like hdbsql -i 01 -u SYSTEM -p secret123# -I /tmp/HANA_CPU_UtilizationPerCore_2.00.020+.txt,
# iterates through files and append the output to var out.
query_command = command + [p]
(rc, out_raw, err) = module.run_command(query_command)
out.append(csv_to_list(out_raw))
if query is not None:
for q in query:
# makes a command like hdbsql -i 01 -u SYSTEM -p secret123# "select user_name from users",
# iterates through multiple commands and append the output to var out.
query_command = command + [q]
(rc, out_raw, err) = module.run_command(query_command)
out.append(csv_to_list(out_raw))
changed = True
module.exit_json(changed=changed, rc=rc, query_result=out, stderr=err)
if __name__ == '__main__':
main()

View file

@ -1,343 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Rainer Leber <rainerleber@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
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
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
PYRFC_LIBRARY_IMPORT_ERROR = None
try:
import xmltodict
except ImportError:
HAS_XMLTODICT_LIBRARY = False
XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
HAS_XMLTODICT_LIBRARY = True
XMLTODICT_LIBRARY_IMPORT_ERROR = None
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()

View file

@ -1,221 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Rainer Leber <rainerleber@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
module: sapcar_extract
short_description: Manages SAP SAPCAR archives
version_added: "3.2.0"
description:
- Provides support for unpacking C(sar)/C(car) files with the SAPCAR binary from SAP and pulling
information back into Ansible.
options:
path:
description: The path to the SAR/CAR file.
type: path
required: true
dest:
description:
- The destination where SAPCAR extracts the SAR file. Missing folders will be created.
If this parameter is not provided it will unpack in the same folder as the SAR file.
type: path
binary_path:
description:
- The path to the SAPCAR binary, for example, C(/home/dummy/sapcar) or C(https://myserver/SAPCAR).
If this parameter is not provided the module will look in C(PATH).
type: path
signature:
description:
- If C(true) the signature will be extracted.
default: false
type: bool
security_library:
description:
- The path to the security library, for example, C(/usr/sap/hostctrl/exe/libsapcrytp.so), for signature operations.
type: path
manifest:
description:
- The name of the manifest.
default: "SIGNATURE.SMF"
type: str
remove:
description:
- If C(true) the SAR/CAR file will be removed. B(This should be used with caution!)
default: false
type: bool
author:
- Rainer Leber (@RainerLeber)
notes:
- Always returns C(changed=true) in C(check_mode).
'''
EXAMPLES = """
- name: Extract SAR file
community.general.sapcar_extract:
path: "~/source/hana.sar"
- name: Extract SAR file with destination
community.general.sapcar_extract:
path: "~/source/hana.sar"
dest: "~/test/"
- name: Extract SAR file with destination and download from webserver can be a fileshare as well
community.general.sapcar_extract:
path: "~/source/hana.sar"
dest: "~/dest/"
binary_path: "https://myserver/SAPCAR"
- name: Extract SAR file and delete SAR after extract
community.general.sapcar_extract:
path: "~/source/hana.sar"
remove: true
- name: Extract SAR file with manifest
community.general.sapcar_extract:
path: "~/source/hana.sar"
signature: true
- name: Extract SAR file with manifest and rename it
community.general.sapcar_extract:
path: "~/source/hana.sar"
manifest: "MyNewSignature.SMF"
signature: true
"""
import os
from tempfile import NamedTemporaryFile
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import open_url
from ansible.module_utils.common.text.converters import to_native
def get_list_of_files(dir_name):
# create a list of file and directories
# names in the given directory
list_of_file = os.listdir(dir_name)
allFiles = list()
# Iterate over all the entries
for entry in list_of_file:
# Create full path
fullPath = os.path.join(dir_name, entry)
# If entry is a directory then get the list of files in this directory
if os.path.isdir(fullPath):
allFiles = allFiles + [fullPath]
allFiles = allFiles + get_list_of_files(fullPath)
else:
allFiles.append(fullPath)
return allFiles
def download_SAPCAR(binary_path, module):
bin_path = None
# download sapcar binary if url is provided otherwise path is returned
if binary_path is not None:
if binary_path.startswith('https://') or binary_path.startswith('http://'):
random_file = NamedTemporaryFile(delete=False)
with open_url(binary_path) as response:
with random_file as out_file:
data = response.read()
out_file.write(data)
os.chmod(out_file.name, 0o700)
bin_path = out_file.name
module.add_cleanup_file(bin_path)
else:
bin_path = binary_path
return bin_path
def check_if_present(command, path, dest, signature, manifest, module):
# manipuliating output from SAR file for compare with already extracted files
iter_command = [command, '-tvf', path]
sar_out = module.run_command(iter_command)[1]
sar_raw = sar_out.split("\n")[1:]
if dest[-1] != "/":
dest = dest + "/"
sar_files = [dest + x.split(" ")[-1] for x in sar_raw if x]
# remove any SIGNATURE.SMF from list because it will not unpacked if signature is false
if not signature:
sar_files = [item for item in sar_files if '.SMF' not in item]
# if signature is renamed manipulate files in list of sar file for compare.
if manifest != "SIGNATURE.SMF":
sar_files = [item for item in sar_files if '.SMF' not in item]
sar_files = sar_files + [manifest]
# get extracted files if present
files_extracted = get_list_of_files(dest)
# compare extracted files with files in sar file
present = all(elem in files_extracted for elem in sar_files)
return present
def main():
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', required=True),
dest=dict(type='path'),
binary_path=dict(type='path'),
signature=dict(type='bool', default=False),
security_library=dict(type='path'),
manifest=dict(type='str', default="SIGNATURE.SMF"),
remove=dict(type='bool', default=False),
),
supports_check_mode=True,
)
rc, out, err = [0, "", ""]
params = module.params
check_mode = module.check_mode
path = params['path']
dest = params['dest']
signature = params['signature']
security_library = params['security_library']
manifest = params['manifest']
remove = params['remove']
bin_path = download_SAPCAR(params['binary_path'], module)
if dest is None:
dest_head_tail = os.path.split(path)
dest = dest_head_tail[0] + '/'
else:
if not os.path.exists(dest):
os.makedirs(dest, 0o755)
if bin_path is not None:
command = [module.get_bin_path(bin_path, required=True)]
else:
try:
command = [module.get_bin_path('sapcar', required=True)]
except Exception as e:
module.fail_json(msg='Failed to find SAPCAR at the expected path or URL "{0}". Please check whether it is available: {1}'
.format(bin_path, to_native(e)))
present = check_if_present(command[0], path, dest, signature, manifest, module)
if not present:
command.extend(['-xvf', path, '-R', dest])
if security_library:
command.extend(['-L', security_library])
if signature:
command.extend(['-manifest', manifest])
if not check_mode:
(rc, out, err) = module.run_command(command, check_rc=True)
changed = True
else:
changed = False
out = "already unpacked"
if remove:
os.remove(path)
module.exit_json(changed=changed, message=rc, stdout=out,
stderr=err, command=' '.join(command))
if __name__ == '__main__':
main()