Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, Sean Freeman ,
# Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.basic import missing_required_lib
import traceback
PYRFC_LIBRARY_IMPORT_ERROR = None
try:
import pyrfc
except ImportError:
PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
HAS_PYRFC_LIBRARY = False
else:
HAS_PYRFC_LIBRARY = True
def get_connection(module, conn_params):
if not HAS_PYRFC_LIBRARY:
module.fail_json(msg=missing_required_lib(
"pyrfc"), exception=PYRFC_LIBRARY_IMPORT_ERROR)
module.warn('Connecting ... %s' % conn_params['ashost'])
if "saprouter" in conn_params:
module.warn("...via SAPRouter to SAP System")
elif "gwhost" in conn_params:
module.warn("...via Gateway to SAP System")
else:
module.warn("...direct to SAP System")
conn = pyrfc.Connection(**conn_params)
module.warn("Verifying connection is open/alive: %s" % conn.alive)
return conn

View File

@@ -0,0 +1,271 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, Sean Freeman ,
# Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.basic import missing_required_lib
import traceback
import sys
import os
BS4_LIBRARY_IMPORT_ERROR = None
try:
from bs4 import BeautifulSoup
except ImportError:
BS4_LIBRARY_IMPORT_ERROR = traceback.format_exc()
HAS_BS4_LIBRARY = False
else:
HAS_BS4_LIBRARY = True
LXML_LIBRARY_IMPORT_ERROR = None
try:
from lxml import etree
except ImportError:
LXML_LIBRARY_IMPORT_ERROR = traceback.format_exc()
HAS_LXML_LIBRARY = False
else:
HAS_LXML_LIBRARY = True
def debug_bs4(module):
# Diagnose XML file parsing errors in Beautiful Soup
# https://stackoverflow.com/questions/56942892/cannot-parse-iso-8859-15-encoded-xml-with-bs4/56947172#56947172
if not HAS_BS4_LIBRARY:
module.fail_json(msg=missing_required_lib(
"bs4"), exception=BS4_LIBRARY_IMPORT_ERROR)
from bs4.diagnose import diagnose
with open('control.xml', 'rb') as f:
diagnose(f)
# SWPM2 control.xml conversion to utf8
def control_xml_utf8(filepath, module):
if not HAS_LXML_LIBRARY:
module.fail_json(msg=missing_required_lib(
"lxml"), exception=LXML_LIBRARY_IMPORT_ERROR)
source = filepath + "/control.xml"
# Convert control.xml from iso-8859-1 to UTF-8, so it can be used with Beautiful Soup lxml-xml parser
# https://stackoverflow.com/questions/64629600/how-can-you-convert-a-xml-iso-8859-1-to-utf-8-using-python-3-7-7/64634454#64634454
with open(source, 'rb') as source:
parser = etree.XMLParser(encoding="iso-8859-1", strip_cdata=False)
root = etree.parse(source, parser)
string = etree.tostring(root, xml_declaration=True, encoding="UTF-8",
pretty_print=True).decode('utf8').encode('iso-8859-1')
# string1 = etree.tostring(root, xml_declaration=True, encoding="UTF-8",
# pretty_print=True).decode('utf8').encode('utf-8').strip()
with open('control_utf8.xml', 'wb') as target:
target.write(string)
# SWPM2 Component and Parameters extract all as CSV
def control_xml_to_csv(filepath, module):
if not HAS_BS4_LIBRARY:
module.fail_json(msg=missing_required_lib(
"bs4"), exception=BS4_LIBRARY_IMPORT_ERROR)
infile = open(filepath + "/control_utf8.xml", "r")
contents = infile.read()
soup = BeautifulSoup(markup=contents, features='lxml-xml')
space = soup.find('components')
component_list = space.findChildren("component", recursive=False)
csv_output = open('control_output.csv', 'w')
csv_header = '"' + 'Component Name' + '","' + 'Component Display Name' + '","' + 'Parameter Name' + '","' + 'Parameter Inifile Key' + \
'","' + 'Parameter Access' + '","' + 'Parameter Encode' + '","' + \
'Parameter Default Value' + '","' + 'Parameter Inifile description' + '"'
csv_output.write("%s\n" % csv_header)
for component in component_list:
for parameter in component.findChildren("parameter"):
component_key = parameter.findParent("component")
component_key_name_text = component_key["name"]
for child in component_key.findChildren("display-name"):
component_key_display_name_text = child.get_text().replace('\n', '')
component_parameter_key_name = parameter["name"]
component_parameter_key_inifile_name = parameter.get(
"defval-for-inifile-generation", "")
component_parameter_key_access = parameter.get("access", "")
component_parameter_key_encode = parameter.get("encode", "")
component_parameter_key_defval = parameter.get("defval", "")
component_parameter_contents_doclong_text = parameter.get_text().replace('\n', '')
component_parameter_contents_doclong_text_quote_replacement = component_parameter_contents_doclong_text.replace(
'"', '\'')
csv_string = '"' + component_key_name_text + '","' + component_key_display_name_text + '","' + \
component_parameter_key_name + '","' + component_parameter_key_inifile_name + '","' + \
component_parameter_key_access + '","' + component_parameter_key_encode + '","' + \
component_parameter_key_defval + '","' + \
component_parameter_contents_doclong_text_quote_replacement + '"'
csv_output.write("%s\n" % csv_string)
csv_output.close()
# SWPM2 Component and Parameters extract all and generate template inifile.params
def control_xml_to_inifile_params(filepath, module):
if not HAS_BS4_LIBRARY:
module.fail_json(msg=missing_required_lib(
"bs4"), exception=BS4_LIBRARY_IMPORT_ERROR)
infile = open(filepath + "/control_utf8.xml", "r")
contents = infile.read()
soup = BeautifulSoup(markup=contents, features='lxml-xml')
space = soup.find('components')
component_list = space.findChildren("component", recursive=False)
inifile_output = open('generated_inifile_params', 'w')
inifile_params_header = """############
# SWPM Unattended Parameters inifile.params generated export
#
#
# Export of all SWPM Component and the SWPM Unattended Parameters. Not all components have SWPM Unattended Parameters.
#
# All parameters are commented-out, each hash # before the parameter is removed to activate the parameter.
# When running SWPM in Unattended Mode, the activated parameters will create a new SWPM file in the sapinst directory.
# If any parameter is marked as 'encode', the plaintext value will be coverted to DES hash
# for this parameter in the new SWPM file (in the sapinst directory).
#
# An inifile.params is otherwise obtained after running SWPM as GUI or Unattended install,
# and will be generated for a specific Product ID (such as 'NW_ABAP_OneHost:S4HANA1809.CORE.HDB.CP').
############
############
# MANUAL
############
# The folder containing all archives that have been downloaded from http://support.sap.com/swdc and are supposed to be used in this procedure
# archives.downloadBasket =
"""
inifile_output.write(inifile_params_header)
for component in component_list:
component_key_name_text = component["name"]
component_key_display_name = component.find("display-name")
if component_key_display_name is not None:
component_key_display_name_text = component_key_display_name.get_text()
inifile_output.write("\n\n\n\n############\n# Component: %s\n# Component Display Name: %s\n############\n" % (
component_key_name_text, component_key_display_name_text))
for parameter in component.findChildren("parameter"):
# component_key=parameter.findParent("component")
component_parameter_key_encode = parameter.get("encode", None)
component_parameter_key_inifile_name = parameter.get(
"defval-for-inifile-generation", None)
component_parameter_key_defval = parameter.get("defval", "")
component_parameter_contents_doclong_text = parameter.get_text().replace('\n', '')
# component_parameter_contents_doclong_text_quote_replacement=component_parameter_contents_doclong_text.replace('"','\'')
if component_parameter_key_inifile_name is not None:
inifile_output.write("\n# %s" % (
component_parameter_contents_doclong_text))
if component_parameter_key_encode == "true":
inifile_output.write(
"\n# Encoded parameter. Plaintext values will be coverted to DES hash")
inifile_output.write("\n# %s = %s\n" % (
component_parameter_key_inifile_name, component_parameter_key_defval))
inifile_output.close()
# SWPM2 product.catalog conversion to utf8
def product_catalog_xml_utf8(filepath, module):
if not HAS_LXML_LIBRARY:
module.fail_json(msg=missing_required_lib(
"lxml"), exception=LXML_LIBRARY_IMPORT_ERROR)
source = filepath + "/product.catalog"
# Convert control.xml from iso-8859-1 to UTF-8, so it can be used with Beautiful Soup lxml-xml parser
# https://stackoverflow.com/questions/64629600/how-can-you-convert-a-xml-iso-8859-1-to-utf-8-using-python-3-7-7/64634454#64634454
with open(source, 'rb') as source:
parser = etree.XMLParser(encoding="iso-8859-1", strip_cdata=False)
root = etree.parse(source, parser)
string = etree.tostring(root, xml_declaration=True, encoding="UTF-8",
pretty_print=True).decode('utf8').encode('iso-8859-1')
with open('product_catalog_utf8.xml', 'wb') as target:
target.write(string)
# SWPM2 Product Catalog entries to CSV
# Each Product Catalog entry is part of a components group, which may have attributes:
# output-dir, control-file, product-dir (link to SWPM directory of param file etc)
# Attributes possible for each entry = control-file, db, id, name, os, os-type, output-dir,
# ppms-component, ppms-component-release, product, product-dir, release, table
def product_catalog_xml_to_csv(filepath, module):
if not HAS_BS4_LIBRARY:
module.fail_json(msg=missing_required_lib(
"bs4"), exception=BS4_LIBRARY_IMPORT_ERROR)
infile = open(filepath + "/product_catalog_utf8.xml", "r")
contents = infile.read()
soup = BeautifulSoup(markup=contents, features='lxml-xml')
space = soup.find_all('component')
csv_output = open('product_catalog_output.csv', 'w')
csv_header = '"' + 'Product Catalog Component Name' + '","' + 'Product Catalog Component ID' + '","' + 'Product Catalog Component Table' + '","' + \
'Product Catalog Component Output Dir' + '","' + 'Product Catalog Component Display Name' + \
'","' + 'Product Catalog Component UserInfo' + '"'
csv_output.write("%s\n" % csv_header)
for component in space:
component_name = component.get("name", "")
component_id = component.get("id", "")
component_table = component.get("table", "")
component_output_dir = component.get("output-dir", "")
for displayname in component.findChildren("display-name"):
component_displayname = displayname.get_text().strip()
for userinfo in component.findChildren("user-info"):
html_raw = userinfo.get_text().strip()
html_parsed = BeautifulSoup(html_raw, 'html.parser')
component_userinfo = html_parsed.get_text().replace('"', '\'')
csv_string = '"' + component_name + '","' + component_id + '","' + component_table + '","' + \
component_output_dir + '","' + component_displayname + \
'","' + component_userinfo + '"'
csv_output.write("%s\n" % csv_string)
csv_output.close()
# Get arguments passed to Python script session
# Define path to control.xml, else assume in /tmp directory
if len(sys.argv) > 1:
control_xml_path = sys.argv[1]
else:
control_xml_path = "/tmp"
if control_xml_path == "":
control_xml_path = os.getcwd()
if os.path.exists(control_xml_path + '/control.xml'):
control_xml_utf8(control_xml_path, '')
control_xml_to_csv(control_xml_path, '')
control_xml_to_inifile_params(control_xml_path, '')

View File

@@ -0,0 +1,335 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sap_company
short_description: This module will manage a company entities in a SAP S4HANA environment
version_added: "1.0.0"
description:
- The M(community.sap_libs.sap_user) 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.
- This module will use the company BAPIs C(BAPI_COMPANY_CLONE) and C(BAPI_COMPANY_DELETE) to manage company entities.
options:
state:
description:
- The decision what to do with the company.
default: 'present'
choices:
- 'present'
- 'absent'
required: false
type: str
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.
required: false
default: '01'
type: str
client:
description:
- The client number to connect to.
- You must quote the value to ensure retaining the leading zeros.
required: false
default: '000'
type: str
company_id:
description: The company id.
required: true
type: str
name:
description: The company name.
required: false
type: str
name_2:
description: Additional company name.
required: false
type: str
country:
description: The country code for the company. For example, C('DE').
required: false
type: str
time_zone:
description: The timezone.
required: false
type: str
city:
description: The city where the company is located.
required: false
type: str
post_code:
description: The post code from the city.
required: false
type: str
street:
description: Street where the company is located.
required: false
type: str
street_no:
description: Street number.
required: false
type: str
e_mail:
description: General E-Mail address.
required: false
type: str
requirements:
- pyrfc >= 2.4.0
author:
- Rainer Leber (@rainerleber)
notes:
- Does not support C(check_mode).
'''
EXAMPLES = r'''
- name: Create SAP Company
community.sap_libs.sap_company:
conn_username: 'DDIC'
conn_password: 'HECtna2021#'
host: 100.0.201.20
sysnr: '01'
client: '000'
state: present
company_id: "Comp_ID"
name: "Test_comp"
name_2: "LTD"
country: "DE"
time_zone: "UTC"
city: "City"
post_code: "12345"
street: "test_street"
street_no: "1"
e_mail: "test@test.de"
# pass in a message and have changed true
- name: Delete SAP Company
community.sap_libs.sap_company:
conn_username: 'DDIC'
conn_password: 'HECtna2021#'
host: 100.0.201.20
sysnr: '01'
client: '000'
state: absent
company_id: "Comp_ID"
name: "Test_comp"
name_2: "LTD"
country: "DE"
time_zone: "UTC"
city: "City"
post_code: "12345"
street: "test_street"
street_no: "1"
e_mail: "test@test.de"
'''
RETURN = r'''
# These are examples of possible return values, and in general should use other names for return values.
msg:
description: A small execution description.
type: str
returned: always
sample: 'Company address COMP_ID created'
out:
description: A complete description of the executed tasks. If this is available.
type: list
elements: dict
returned: always
sample: '{
"RETURN": [
{
"FIELD": "",
"ID": "01",
"LOG_MSG_NO": "000000",
"LOG_NO": "",
"MESSAGE": "Company address COMP_ID created",
"MESSAGE_V1": "COMP_ID",
"MESSAGE_V2": "",
"MESSAGE_V3": "",
"MESSAGE_V4": "",
"NUMBER": "078",
"PARAMETER": "",
"ROW": 0,
"SYSTEM": "",
"TYPE": "S"
}
]
}
}'
'''
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
import traceback
try:
from pyrfc import Connection
except ImportError:
HAS_PYRFC_LIBRARY = False
ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
ANOTHER_LIBRARY_IMPORT_ERROR = None
HAS_PYRFC_LIBRARY = True
def call_rfc_method(connection, method_name, kwargs):
# PyRFC call function
return connection.call(method_name, **kwargs)
def build_company_params(name, name_2, country, time_zone, city, post_code, street, street_no, e_mail):
# Creates RFC parameters for creating organizations
# define dicts in batch
params = dict()
# define company name
params['NAME'] = name
params['NAME_2'] = name_2
# define location
params['COUNTRY'] = country
params['TIME_ZONE'] = time_zone
params['CITY'] = city
params['POSTL_COD1'] = post_code
params['STREET'] = street
params['STREET_NO'] = street_no
# define communication
params['E_MAIL'] = e_mail
# return dict
return params
def return_analysis(raw):
change = False
failed = False
msg = raw['RETURN'][0]['MESSAGE']
for state in raw['RETURN']:
if state['TYPE'] == "E":
if state['NUMBER'] == '081':
change = False
else:
failed = True
if state['TYPE'] == "S":
if state['NUMBER'] != '079':
change = True
else:
msg = "No changes where made."
return [{"change": change}, {"failed": failed}, {"msg": msg}]
def run_module():
module = AnsibleModule(
argument_spec=dict(
state=dict(default='present', choices=['absent', 'present']),
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="01"),
client=dict(type='str', default="000"),
company_id=dict(type='str', required=True),
name=dict(type='str', required=False),
name_2=dict(type='str', required=False),
country=dict(type='str', required=False),
time_zone=dict(type='str', required=False),
city=dict(type='str', required=False),
post_code=dict(type='str', required=False),
street=dict(type='str', required=False),
street_no=dict(type='str', required=False),
e_mail=dict(type='str', required=False),
),
supports_check_mode=False,
)
result = dict(changed=False, msg='', out={})
raw = ""
params = module.params
state = params['state']
conn_username = (params['conn_username']).upper()
conn_password = params['conn_password']
host = params['host']
sysnr = params['sysnr']
client = params['client']
company_id = (params['company_id']).upper()
name = params['name']
name_2 = params['name_2']
country = params['country']
time_zone = params['time_zone']
city = params['city']
post_code = params['post_code']
street = params['street']
street_no = params['street_no']
e_mail = params['e_mail']
if not HAS_PYRFC_LIBRARY:
module.fail_json(
msg=missing_required_lib('pyrfc'),
exception=ANOTHER_LIBRARY_IMPORT_ERROR)
# basic RFC connection with pyrfc
try:
conn = Connection(user=conn_username, passwd=conn_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)
# build parameter dict of dict
company_params = build_company_params(name, name_2, country, time_zone, city, post_code, street, street_no, e_mail)
if state == "absent":
raw = call_rfc_method(conn, 'BAPI_COMPANY_DELETE', {'COMPANY': company_id})
if state == "present":
raw = call_rfc_method(conn, 'BAPI_COMPANY_CLONE',
{'METHOD': {'USMETHOD': 'COMPANY_CLONE'}, 'COMPANY': company_id, 'COMP_DATA': company_params})
analysed = return_analysis(raw)
result['out'] = raw
result['changed'] = analysed[0]['change']
result['msg'] = analysed[2]['msg']
if analysed[1]['failed']:
module.fail_json(**result)
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,401 @@
#!/usr/bin/python
# Copyright: (c) 2022, Rainer Leber rainerleber@gmail.com, rainer.leber@sva.de,
# Robert Kraemer @rkpobe, robert.kraemer@sva.de
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sap_control_exec
short_description: Ansible Module to execute SAPCONTROL
version_added: "1.1.0"
description:
- Provides support for sapstartsrv formaly known as sapcontrol
- A complete information of all functions and the parameters can be found here
U(https://www.sap.com/documents/2016/09/0a40e60d-8b7c-0010-82c7-eda71af511fa.html)
options:
sysnr:
description:
- The system number of the instance.
required: false
type: str
port:
description:
- The port number of the sapstartsrv.
required: false
type: int
username:
description:
- The username to connect to the sapstartsrv.
required: false
type: str
password:
description:
- The password to connect to the sapstartsrv.
required: false
type: str
hostname:
description:
- The hostname to connect to the sapstartsrv.
- Could be an IP address, FQDN or hostname.
required: false
default: localhost
type: str
function:
description:
- The function to execute.
required: true
choices:
- Start
- Stop
- RestartInstance
- Shutdown
- InstanceStart
- GetProcessList
- Bootstrap
- InstanceStop
- StopService
- StartService
- RestartService
- ParameterValue
- GetStartProfile
- GetTraceFile
- GetAlertTree
- GetAlerts
- GetEnvironment
- GetVersionInfo
- GetQueueStatistic
- GetInstanceProperties
- ListDeveloperTraces
- ReadDeveloperTrace
- ListLogFiles
- ReadLogFile
- AnalyseLogFiles
- ConfigureLogFileList
- GetLogFileList
- CreateSnapshot
- ReadSnapshot
- ListSnapshots
- DeleteSnapshots
- GetAccessPointList
- GetProcessParameter
- SetProcessParameter
- SetProcessParameter2
- CheckParameter
- OSExecute
- SendSignal
- GetCallstack
- GetSystemInstanceList
- StartSystem
- StopSystem
- RestartSystem
- GetSystemUpdateList
- UpdateSystem
- UpdateSCSInstance
- CheckUpdateSystem
- AccessCheck
- GetSecNetworkId
- GetNetworkId
- RequestLogonFile
- UpdateSystemPKI
- UpdateInstancePSE
- StorePSE
- DeletePSE
- CheckPSE
- CreatePSECredential
- HACheckConfig
- HACheckFailoverConfig
- HAGetFailoverConfig
- HAFailoverToNode
- HASetMaintenanceMode
- HACheckMaintenanceMode
- ABAPReadSyslog
- ABAPReadRawSyslog
- ABAPGetWPTable
- ABAPGetComponentList
- ABAPCheckRFCDestinations
- ABAPGetSystemWPTable
- J2EEControlProcess
- J2EEControlCluster
- J2EEEnableDbgSession
- J2EEDisableDbgSession
- J2EEGetProcessList
- J2EEGetProcessList2
- J2EEGetThreadList
- J2EEGetThreadList2
- J2EEGetThreadCallStack
- J2EEGetThreadTaskStack
- J2EEGetSessionList
- J2EEGetCacheStatistic
- J2EEGetCacheStatistic2
- J2EEGetApplicationAliasList
- J2EEGetComponentList
- J2EEControlComponents
- J2EEGetWebSessionList
- J2EEGetWebSessionList2
- J2EEGetEJBSessionList
- J2EEGetRemoteObjectList
- J2EEGetVMGCHistory
- J2EEGetVMGCHistory2
- J2EEGetVMHeapInfo
- J2EEGetClusterMsgList
- J2EEGetSharedTableInfo
- ICMGetThreadList
- ICMGetConnectionList
- ICMGetProxyConnectionList
- ICMGetCacheEntries
- WebDispGetServerList
- WebDispGetGroupList
- WebDispGetVirtHostList
- WebDispGetUrlPrefixList
- EnqGetStatistic
- EnqGetLockTable
- EnqRemoveUserLocks
- StartWait
- StopWait
- WaitforStarted
- WaitforStopped
- RestartServiceWait
- WaitforServiceStarted
- CheckHostAgent
type: str
parameter:
description:
- The parameter to pass to the function.
required: false
type: str
force:
description:
- Forces the execution of the function C(Stop).
required: false
default: false
type: bool
author:
- Rainer Leber (@RainerLeber)
- Robert Kraemer (@rkpobe)
notes:
- Does not support C(check_mode).
'''
EXAMPLES = r"""
- name: GetProcessList with sysnr
community.sap_libs.sap_control_exec:
hostname: 192.168.8.15
sysnr: "01"
function: GetProcessList
- name: GetProcessList with custom port
community.sap_libs.sap_control_exec:
hostname: 192.168.8.15
function: GetProcessList
port: 50113
- name: ParameterValue
community.sap_libs.sap_control_exec:
hostname: 192.168.8.15
sysnr: "01"
username: hdbadm
password: test1234#
function: ParameterValue
parameter: ztta
"""
RETURN = r'''
msg:
description: Success-message with functionname.
type: str
returned: always
sample: 'Succesful execution of: GetProcessList'
out:
description: The full output of the required function.
type: list
elements: dict
returned: always
sample: [{
"item": [
{
"description": "MessageServer",
"dispstatus": "SAPControl-GREEN",
"elapsedtime": "412:30:50",
"name": "msg_server",
"pid": 70643,
"starttime": "2022 03 13 15:22:42",
"textstatus": "Running"
},
{
"description": "EnqueueServer",
"dispstatus": "SAPControl-GREEN",
"elapsedtime": "412:30:50",
"name": "enserver",
"pid": 70644,
"starttime": "2022 03 13 15:22:42",
"textstatus": "Running"
},
{
"description": "Gateway",
"dispstatus": "SAPControl-GREEN",
"elapsedtime": "412:30:50",
"name": "gwrd",
"pid": 70645,
"starttime": "2022 03 13 15:22:42",
"textstatus": "Running"
}
]
}]
'''
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
import traceback
try:
from suds.client import Client
from suds.sudsobject import asdict
except ImportError:
HAS_SUDS_LIBRARY = False
SUDS_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
SUDS_LIBRARY_IMPORT_ERROR = None
HAS_SUDS_LIBRARY = True
def choices():
retlist = ["Start", "Stop", "RestartInstance", "Shutdown", "InstanceStart", 'GetProcessList',
'Bootstrap', 'InstanceStop', 'StopService', 'StartService', 'RestartService', 'ParameterValue',
'GetStartProfile', 'GetTraceFile', 'GetAlertTree', 'GetAlerts', 'GetEnvironment', 'GetVersionInfo',
'GetQueueStatistic', 'GetInstanceProperties', 'ListDeveloperTraces', 'ReadDeveloperTrace',
'ListLogFiles', 'ReadLogFile', 'AnalyseLogFiles', 'ConfigureLogFileList', 'GetLogFileList', 'CreateSnapshot', 'ReadSnapshot',
'ListSnapshots', 'DeleteSnapshots', 'GetAccessPointList', 'GetProcessParameter', 'SetProcessParameter',
'SetProcessParameter2', 'CheckParameter', 'OSExecute', 'SendSignal', 'GetCallstack', 'GetSystemInstanceList',
'StartSystem', 'StopSystem', 'RestartSystem', 'GetSystemUpdateList', 'UpdateSystem', 'UpdateSCSInstance',
'CheckUpdateSystem', 'AccessCheck', 'GetSecNetworkId', 'GetNetworkId', 'RequestLogonFile',
'UpdateSystemPKI', 'UpdateInstancePSE', 'StorePSE', 'DeletePSE', 'CheckPSE', 'CreatePSECredential',
'HACheckConfig', 'HACheckFailoverConfig', 'HAGetFailoverConfig', 'HAFailoverToNode',
'HASetMaintenanceMode', 'HACheckMaintenanceMode', 'ABAPReadSyslog', 'ABAPReadRawSyslog',
'ABAPGetWPTable', 'ABAPGetComponentList', 'ABAPCheckRFCDestinations',
'ABAPGetSystemWPTable', 'J2EEControlProcess', 'J2EEControlCluster', 'J2EEEnableDbgSession',
'J2EEDisableDbgSession', 'J2EEGetProcessList', 'J2EEGetProcessList2', 'J2EEGetThreadList', 'J2EEGetThreadList2',
'J2EEGetThreadCallStack', 'J2EEGetThreadTaskStack', 'J2EEGetSessionList', 'J2EEGetCacheStatistic',
'J2EEGetCacheStatistic2', 'J2EEGetApplicationAliasList', 'J2EEGetComponentList',
'J2EEControlComponents', 'J2EEGetWebSessionList', 'J2EEGetWebSessionList2', 'J2EEGetEJBSessionList', 'J2EEGetRemoteObjectList',
'J2EEGetVMGCHistory', 'J2EEGetVMGCHistory2', 'J2EEGetVMHeapInfo', 'J2EEGetClusterMsgList', 'J2EEGetSharedTableInfo',
'ICMGetThreadList', 'ICMGetConnectionList', 'ICMGetProxyConnectionList', 'ICMGetCacheEntries', 'WebDispGetServerList',
'WebDispGetGroupList', 'WebDispGetVirtHostList', 'WebDispGetUrlPrefixList', 'EnqGetStatistic', 'EnqGetLockTable',
'EnqRemoveUserLocks', 'StartWait', 'StopWait', 'WaitforStarted', 'WaitforStopped', 'RestartServiceWait',
'WaitforServiceStarted', 'CheckHostAgent']
return retlist
# converts recursively the suds object to a dictionary e.g. {'item': [{'name': hdbdaemon, 'value': '1'}]}
def recursive_dict(suds_object):
out = {}
if isinstance(suds_object, str):
return suds_object
for k, v in asdict(suds_object).items():
if hasattr(v, '__keylist__'):
out[k] = recursive_dict(v)
elif isinstance(v, list):
out[k] = []
for item in v:
if hasattr(item, '__keylist__'):
out[k].append(recursive_dict(item))
else:
out[k].append(item)
else:
out[k] = v
return out
def connection(hostname, port, username, password, function, parameter):
url = 'http://{0}:{1}/sapcontrol?wsdl'.format(hostname, port)
client = Client(url, username=username, password=password)
_function = getattr(client.service, function)
if parameter is not None:
result = _function(parameter)
else:
result = _function()
return result
def main():
module = AnsibleModule(
argument_spec=dict(
sysnr=dict(type='str', required=False),
port=dict(type='int', required=False),
username=dict(type='str', required=False),
password=dict(type='str', no_log=True, required=False),
hostname=dict(type='str', default="localhost"),
function=dict(type='str', required=True, choices=choices()),
parameter=dict(type='str', required=False),
force=dict(type='bool', default=False),
),
required_one_of=[('sysnr', 'port')],
mutually_exclusive=[('sysnr', 'port')],
supports_check_mode=False,
)
result = dict(changed=False, msg='', out={}, error='')
params = module.params
sysnr = params['sysnr']
port = params['port']
username = params['username']
password = params['password']
hostname = params['hostname']
function = params['function']
parameter = params['parameter']
force = params['force']
if not HAS_SUDS_LIBRARY:
module.fail_json(
msg=missing_required_lib('suds'),
exception=SUDS_LIBRARY_IMPORT_ERROR)
if function == "Stop":
if force is False:
module.fail_json(msg="Stop function requires force: True")
if port is None:
try:
try:
conn = connection(hostname, "5{0}14".format((sysnr).zfill(2)), username, password, function, parameter)
except Exception:
conn = connection(hostname, "5{0}13".format((sysnr).zfill(2)), username, password, function, parameter)
except Exception as err:
result['error'] = str(err)
else:
try:
conn = connection(hostname, port, username, password, function, parameter)
except Exception as err:
result['error'] = str(err)
if result['error'] != '':
result['msg'] = 'Something went wrong connecting to the SAPCONTROL SOAP API.'
module.fail_json(**result)
if conn is not None:
returned_data = recursive_dict(conn)
else:
returned_data = conn
result['changed'] = True
result['msg'] = "Succesful execution of: " + function
result['out'] = [returned_data]
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,246 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sap_hdbsql
short_description: Ansible Module to execute SQL on SAP HANA
version_added: "1.0.0"
description: This module executes SQL statements on HANA with hdbsql.
options:
sid:
description: The system ID.
type: str
required: false
bin_path:
description: The path to the hdbsql binary.
type: str
required: false
instance:
description: The instance number.
type: str
required: true
user:
description: A dedicated username. The user could be also in hdbuserstore.
type: str
default: SYSTEM
userstore:
description: If C(true), the user must be in hdbuserstore.
type: bool
default: false
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.
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). Always reports that the state has changed even if no changes have been made.
author:
- Rainer Leber (@rainerleber)
'''
EXAMPLES = r'''
- name: Simple select query
community.sap_libs.sap_hdbsql:
sid: "hdb"
instance: "01"
password: "Test123"
query: select user_name from users
- name: RUN select query with host port
community.sap_libs.sap_hdbsql:
sid: "hdb"
instance: "01"
password: "Test123"
host: "10.10.2.4:30001"
query: select user_name from users
- name: Run several queries
community.sap_libs.sap_hdbsql:
sid: "hdb"
instance: "01"
password: "Test123"
query:
- select user_name from users
- select * from SYSTEM
host: "localhost"
autocommit: False
- name: Run several queries with path
community.sap_libs.sap_hdbsql:
bin_path: "/usr/sap/HDB/HDB01/exe/hdbsql"
instance: "01"
password: "Test123"
query:
- select user_name from users
- select * from users
host: "localhost"
autocommit: False
- name: Run several queries from file
community.sap_libs.sap_hdbsql:
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.sap_libs.sap_hdbsql:
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=False),
bin_path=dict(type='str', required=False),
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'), ('sid', 'instance')],
required_if=[('userstore', False, ['password'])],
supports_check_mode=False,
)
rc, out, err, out_raw = [0, [], "", ""]
params = module.params
sid = params['sid']
bin_path = params['bin_path']
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']
if bin_path is None:
bin_path = "/usr/sap/{sid}/HDB{instance}/exe/hdbsql".format(sid=sid.upper(), 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

@@ -0,0 +1,187 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, Sean Freeman ,
# Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sap_pyrfc
short_description: Ansible Module for use of SAP PyRFC to execute SAP RFCs (Remote Function Calls) to SAP remote-enabled function modules
version_added: "1.2.0"
description:
- This module will executes rfc calls on a sap system.
- It is a generic approach to call rfc functions on a SAP System.
- This module should be used where no module or role is provided.
options:
function:
description: The SAP RFC function to call.
required: true
type: str
parameters:
description: The parameters which are needed by the function.
required: true
type: dict
connection:
description: The required connection details.
required: true
type: dict
suboptions:
ashost:
description: The required host for the SAP system. Can be either an FQDN or IP Address.
type: str
required: true
sysid:
description: The systemid of the SAP system.
type: str
required: false
sysnr:
description:
- The system number of the SAP system.
- You must quote the value to ensure retaining the leading zeros.
type: str
required: true
client:
description:
- The client number to connect to.
- You must quote the value to ensure retaining the leading zeros.
type: str
required: true
user:
description: The required username for the SAP system.
type: str
required: true
passwd:
description: The required password for the SAP system.
type: str
required: true
lang:
description: The used language to execute.
type: str
required: false
requirements:
- pyrfc >= 2.4.0
author:
- Sean Freeman (@seanfreeman)
- Rainer Leber (@rainerleber)
'''
EXAMPLES = '''
- name: test the pyrfc module
community.sap_libs.sap_pyrfc:
function: STFC_CONNECTION
parameters:
REQUTEXT: "Hello SAP!"
connection:
ashost: s4hana.poc.cloud
sysid: TDT
sysnr: "01"
client: "400"
user: DDIC
passwd: Password1
lang: EN
'''
RETURN = r'''
result:
description: The execution description.
type: dict
returned: always
sample: {"ECHOTEXT": "Hello SAP!",
"RESPTEXT": "SAP R/3 Rel. 756 Sysid: TST Date: 20220710 Time: 140717 Logon_Data: 000/DDIC/E"}
'''
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ..module_utils.pyrfc_handler import get_connection
try:
from pyrfc import ABAPApplicationError, ABAPRuntimeError, CommunicationError, Connection, LogonError
except ImportError:
HAS_PYRFC_LIBRARY = False
PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
PYRFC_LIBRARY_IMPORT_ERROR = None
HAS_PYRFC_LIBRARY = True
def main():
msg = None
params_spec = dict(
ashost=dict(type='str', required=True),
sysid=dict(type='str', required=False),
sysnr=dict(type='str', required=True),
client=dict(type='str', required=True),
user=dict(type='str', required=True),
passwd=dict(type='str', required=True, no_log=True),
lang=dict(type='str', required=False),
)
argument_spec = dict(function=dict(required=True, type='str'),
parameters=dict(required=True, type='dict'),
connection=dict(
required=True, type='dict', options=params_spec),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
function = module.params.get('function')
func_params = module.params.get('parameters')
conn_params = module.params.get('connection')
if not HAS_PYRFC_LIBRARY:
module.fail_json(
msg=missing_required_lib('pyrfc'),
exception=PYRFC_LIBRARY_IMPORT_ERROR)
# Check mode
if module.check_mode:
msg = "function: %s; params: %s; login: %s" % (
function, func_params, conn_params)
module.exit_json(msg=msg, changed=True)
try:
conn = get_connection(module, conn_params)
result = conn.call(function, **func_params)
error_msg = None
except CommunicationError as err:
msg = "Could not connect to server"
error_msg = err.message
except LogonError as err:
msg = "Could not log in"
error_msg = err.message
except (ABAPApplicationError, ABAPRuntimeError) as err:
msg = "ABAP error occurred"
error_msg = err.message
except Exception as err:
msg = "Something went wrong."
error_msg = err
else:
module.exit_json(changed=True, result=result)
if msg:
module.fail_json(msg=msg, exception=error_msg)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,267 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sap_snote
short_description: This module will upload and (de)implements C(SNOTES) in a SAP S4HANA environment.
version_added: "1.0.0"
description:
- The C(sap_snote) 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.
- This module will use the Function Group C(SCWB_API).
- The C(TMS) must be configured at first.
- Integrating SNOTES cannot be done via C(DDIC)- or C(SAP*)-User.
options:
state:
description:
- The decision what to do with the SNOTE.
- Could be C('present'), C('absent')
default: 'present'
choices:
- 'present'
- 'absent'
required: false
type: str
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.
required: false
default: '01'
type: str
client:
description:
- The client number to connect to.
- You must quote the value to ensure retaining the leading zeros.
required: false
default: '000'
type: str
snote_path:
description:
- The path to the extracted SNOTE txt file.
- The File could be extracted from SAR package.
- If C(snote_path) is not provided, the C(snote) parameter must be defined.
- The SNOTE txt file must be at a place where the SAP System is authorized for. For example C(/usr/sap/trans/files).
required: false
type: str
snote:
description:
- With the C(snote) paramter only implementation and deimplementation will work.
- Upload SNOTES to the System is only available if C(snote_path) is provided.
required: false
type: str
requirements:
- pyrfc >= 2.4.0
author:
- Rainer Leber (@rainerleber)
'''
EXAMPLES = r'''
- name: test snote module
hosts: localhost
tasks:
- name: implement SNOTE
community.sap_libs.sap_snote:
conn_username: 'DDIC'
conn_password: 'Passwd1234'
host: 192.168.1.100
sysnr: '01'
client: '000'
state: present
snote_path: /usr/sap/trans/tmp/0002949148.txt
- name: test snote module without path
hosts: localhost
tasks:
- name: deimplement SNOTE
community.sap_libs.sap_snote:
conn_username: 'DDIC'
conn_password: 'Passwd1234'
host: 192.168.1.100
sysnr: '01'
client: '000'
state: absent
snote: 0002949148
'''
RETURN = r'''
msg:
description: A small execution description.
type: str
returned: always
sample: 'SNOTE 000298026 implemented.'
out:
description: A complete description of the SNOTE implementation. If this is available.
type: list
elements: dict
returned: always
sample: '{
"RETURN": [{"ES_MSG": { "MSGNO": "000", "MSGTY": "", "MSGTXT": "", "MSGV1": "" },
"ET_MSG": [],
"EV_RC": 0,
"ET_MISSING_NOTES": [],
"IT_FILENAME": [{"FILENAME": "/usr/sap/trans/tmp/0002980265.txt"}],
"IT_NOTES": [{"NUMM": "0002980265", "VERSNO": "0000"}]
}]}'
'''
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from os import path as os_path
import traceback
try:
from pyrfc import Connection
except ImportError:
HAS_PYRFC_LIBRARY = False
ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
ANOTHER_LIBRARY_IMPORT_ERROR = None
HAS_PYRFC_LIBRARY = True
def call_rfc_method(connection, method_name, kwargs):
# PyRFC call function
return connection.call(method_name, **kwargs)
def check_implementation(conn, snote):
check_implemented = call_rfc_method(conn, 'SCWB_API_GET_NOTES_IMPLEMENTED', {})
for snote_list in check_implemented['ET_NOTES_IMPL']:
if snote in snote_list['NUMM']:
return True
return False
def run_module():
module = AnsibleModule(
argument_spec=dict(
state=dict(default='present', choices=['absent', 'present']),
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="01"),
client=dict(type='str', default="000"),
snote_path=dict(type='str', required=False),
snote=dict(type='str', required=False),
),
required_one_of=[('snote_path', 'snote')],
supports_check_mode=False,
)
result = dict(changed=False, msg='', out={}, error='')
raw = ""
post_check = False
params = module.params
state = params['state']
conn_username = (params['conn_username']).upper()
conn_password = params['conn_password']
host = params['host']
sysnr = (params['sysnr']).zfill(2)
client = params['client']
path = params['snote_path']
snote = params['snote']
if not HAS_PYRFC_LIBRARY:
module.fail_json(
msg=missing_required_lib('pyrfc'),
exception=ANOTHER_LIBRARY_IMPORT_ERROR)
if conn_username == "DDIC" or conn_username == "SAP*":
result['msg'] = 'User C(DDIC) or C(SAP*) not allowed for this operation.'
module.fail_json(**result)
# basic RFC connection with pyrfc
try:
conn = Connection(user=conn_username, passwd=conn_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)
# pre evaluation of parameters
if path is not None:
if path.endswith('.txt'):
# splits snote number from path and txt extension
snote = os_path.basename(os_path.normpath(path)).split('.')[0]
else:
result['msg'] = 'The path must include the extracted snote file and ends with txt.'
module.fail_json(**result)
pre_check = check_implementation(conn, snote)
if state == "absent" and pre_check:
raw = call_rfc_method(conn, 'SCWB_API_NOTES_DEIMPLEMENT', {'IT_NOTES': [snote]})
if state == "present" and not pre_check:
if path:
raw_upload = call_rfc_method(conn, 'SCWB_API_UPLOAD_NOTES', {'IT_FILENAME': [path], 'IT_NOTES': [snote]})
if raw_upload['EV_RC'] != 0:
result['out'] = raw_upload
result['msg'] = raw_upload['ES_MSG']['MSGTXT']
module.fail_json(**result)
raw = call_rfc_method(conn, 'SCWB_API_NOTES_IMPLEMENT', {'IT_NOTES': [snote]})
queued = call_rfc_method(conn, 'SCWB_API_CINST_QUEUE_GET', {})
if queued['ET_MANUAL_ACTIVITIES']:
raw = call_rfc_method(conn, 'SCWB_API_CONFIRM_MAN_ACTIVITY', {})
if raw:
if raw['EV_RC'] == 0:
post_check = check_implementation(conn, snote)
if post_check and state == "present":
result['changed'] = True
result['msg'] = 'SNOTE "{0}" implemented.'.format(snote)
if not post_check and state == "absent":
result['changed'] = True
result['msg'] = 'SNOTE "{0}" deimplemented.'.format(snote)
else:
result['msg'] = "Something went wrong."
module.fail_json(**result)
result['out'] = raw
else:
result['msg'] = "Nothing to do."
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,213 @@
#!/usr/bin/python
# Copyright: (c) 2022, Rainer Leber rainerleber@gmail.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sap_system_facts
short_description: Gathers SAP facts in a host
version_added: "1.0.0"
description:
- This facts module gathers SAP system facts about the running instance.
author:
- Rainer Leber (@rainerleber)
notes:
- Supports C(check_mode).
'''
EXAMPLES = r'''
- name: Return SAP system ansible_facts
community.sap_libs.sap_system_facts:
'''
RETURN = r'''
# These are examples of possible return values,
# and in general should use other names for return values.
ansible_facts:
description: Facts about the running SAP systems.
returned: always
type: dict
contains:
sap:
description: Facts about the running SAP systems.
type: list
elements: dict
returned: When SAP system fact is present
sample: [
{
"InstanceType": "NW",
"NR": "00",
"SID": "ABC",
"TYPE": "ASCS"
},
{
"InstanceType": "NW",
"NR": "01",
"SID": "ABC",
"TYPE": "PAS"
},
{
"InstanceType": "HANA",
"NR": "02",
"SID": "HDB",
"TYPE": "HDB"
},
{
"InstanceType": "NW",
"NR": "80",
"SID": "WEB",
"TYPE": "WebDisp"
}
]
'''
from ansible.module_utils.basic import AnsibleModule
import os
import re
def get_all_hana_sid():
hana_sid = list()
if os.path.isdir("/hana/shared"):
# /hana/shared directory exists
for sid in os.listdir('/hana/shared'):
if os.path.isdir("/usr/sap/" + sid):
hana_sid = hana_sid + [sid]
if hana_sid:
return hana_sid
def get_all_nw_sid():
nw_sid = list()
if os.path.isdir("/sapmnt"):
# /sapmnt directory exists
for sid in os.listdir('/sapmnt'):
if os.path.isdir("/usr/sap/" + sid):
nw_sid = nw_sid + [sid]
else:
# Check to see if /sapmnt/SID/sap_bobj exists
if os.path.isdir("/sapmnt/" + sid + "/sap_bobj"):
# is a bobj system
nw_sid = nw_sid + [sid]
if nw_sid:
return nw_sid
def get_hana_nr(sids, module):
hana_list = list()
for sid in sids:
for instance in os.listdir('/usr/sap/' + sid):
if 'HDB' in instance:
instance_nr = instance[-2:]
# check if instance number exists
command = [module.get_bin_path('/usr/sap/hostctrl/exe/sapcontrol', required=True)]
command.extend(['-nr', instance_nr, '-function', 'GetProcessList'])
check_instance = module.run_command(command, check_rc=False)
# sapcontrol returns c(0 - 5) exit codes only c(1) is unavailable
if check_instance[0] != 1:
hana_list.append({'NR': instance_nr, 'SID': sid, 'TYPE': 'HDB', 'InstanceType': 'HANA'})
return hana_list
def get_nw_nr(sids, module):
nw_list = list()
type = ""
for sid in sids:
for instance in os.listdir('/usr/sap/' + sid):
instance_nr = instance[-2:]
command = [module.get_bin_path('/usr/sap/hostctrl/exe/sapcontrol', required=True)]
# check if returned instance_nr is a number because sapcontrol returns all if a random string is provided
if instance_nr.isdigit():
command.extend(['-nr', instance_nr, '-function', 'GetInstanceProperties'])
check_instance = module.run_command(command, check_rc=False)
if check_instance[0] != 1:
for line in check_instance[1].splitlines():
if re.search('INSTANCE_NAME', line):
# convert to list and extract last
type_raw = (line.strip('][').split(', '))[-1]
# split instance number
type = type_raw[:-2]
nw_list.append({'NR': instance_nr, 'SID': sid, 'TYPE': get_instance_type(type), 'InstanceType': 'NW'})
return nw_list
def get_instance_type(raw_type):
if raw_type[0] == "D":
# It's a PAS
type = "PAS"
elif raw_type[0] == "A":
# It's an ASCS
type = "ASCS"
elif raw_type[0] == "W":
# It's a Webdisp
type = "WebDisp"
elif raw_type[0] == "J":
# It's a Java
type = "Java"
elif raw_type[0] == "S":
# It's an SCS
type = "SCS"
elif raw_type[0] == "E":
# It's an ERS
type = "ERS"
else:
# Unknown instance type
type = "XXX"
return type
def run_module():
module_args = dict()
system_result = list()
result = dict(
changed=False,
ansible_facts=dict(),
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
)
hana_sid = get_all_hana_sid()
if hana_sid:
system_result = system_result + get_hana_nr(hana_sid, module)
nw_sid = get_all_nw_sid()
if nw_sid:
system_result = system_result + get_nw_nr(nw_sid, module)
if system_result:
result['ansible_facts'] = {'sap': system_result}
else:
result['ansible_facts']
if module.check_mode:
module.exit_json(**result)
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,350 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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: "0.1.0"
description:
- The M(community.sap_libs.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 does 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). Always returns that the state has changed.
author:
- Rainer Leber (@rainerleber)
'''
EXAMPLES = r'''
# Pass in a message
- name: Test task execution
community.sap_libs.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.sap_libs.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 environment variables
- name: Hint if module will fail with error message like ImportError libsapnwrfc.so...
community.sap_libs.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:
PYRFC_LIBRARY_IMPORT_ERROR = None
HAS_PYRFC_LIBRARY = True
try:
import xmltodict
except ImportError:
HAS_XMLTODICT_LIBRARY = False
XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
XMLTODICT_LIBRARY_IMPORT_ERROR = None
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 exist.'
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

@@ -0,0 +1,508 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sap_user
short_description: This module will manage a user entities in a SAP S4/HANA environment
version_added: "1.0.0"
description:
- The M(community.sap_libs.sap_user) 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.
- This module will use the following user BAPIs to manage user entities.
- C(BAPI_USER_GET_DETAIL)
- C(BAPI_USER_DELETE)
- C(BAPI_USER_CREATE1)
- C(BAPI_USER_CHANGE)
- C(BAPI_USER_ACTGROUPS_ASSIGN)
- C(BAPI_USER_PROFILES_ASSIGN)
- C(BAPI_USER_UNLOCK)
- C(BAPI_USER_LOCK)
options:
state:
description:
- The decision what to do with the user.
default: 'present'
choices:
- 'present'
- 'absent'
- 'lock'
- 'unlock'
required: false
type: str
force:
description:
- Must be C('True') if the password or type should be overwritten.
default: False
required: false
type: bool
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
username:
description:
- The username.
type: str
required: true
firstname:
description:
- The Firstname of the user in the SAP system.
type: str
required: false
lastname:
description:
- The lastname of the user in the SAP system.
type: str
required: false
email:
description:
- The email address of the user in the SAP system.
type: str
required: false
password:
description:
- The password for the user in the SAP system.
type: str
required: false
useralias:
description:
- The alias for the user in the SAP system.
type: str
required: false
user_type:
description:
- The type for the user in the SAP system.
- C('A') Dialog user, C('B') System User, C('C') Communication User,
C('S') Service User, C('L') Reference User.
- Must be in uppercase.
type: str
required: false
default: 'A'
choices: ['A', 'B', 'C', 'S', 'L']
company:
description:
- The specific company the user belongs to.
- The company name must be available in the SAP system.
type: str
required: false
profiles:
description:
- Assign profiles to the user.
- Should be in uppercase, for example C('SAP_NEW') or C('SAP_ALL').
type: list
elements: str
default: ['']
required: false
roles:
description:
- Assign roles to the user.
type: list
elements: str
default: ['']
required: false
requirements:
- pyrfc >= 2.4.0
author:
- Rainer Leber (@rainerleber)
notes:
- Does not support C(check_mode).
'''
EXAMPLES = r'''
- name: Create SAP User
community.sap_libs.sap_user:
conn_username: 'DDIC'
conn_password: 'Test123'
host: 192.168.1.150
sysnr: '01'
client: '000'
state: present
username: ADMIN
firstname: first_admin
lastname: last_admin
email: admin@test.de
password: Test123456
useralias: ADMIN
company: DEFAULT_COMPANY
roles:
- "SAP_ALL"
- name: Force change SAP User
community.sap_libs.sap_user:
conn_username: 'DDIC'
conn_password: 'Test123'
host: 192.168.1.150
sysnr: '01'
client: '000'
state: present
force: true
username: ADMIN
firstname: first_admin
lastname: last_admin
email: admin@test.de
password: Test123456
useralias: ADMIN
company: DEFAULT_COMPANY
roles:
- "SAP_ALL"
- name: Delete SAP User
community.sap_libs.sap_user:
conn_username: 'DDIC'
conn_password: 'Test123'
host: 192.168.1.150
sysnr: '01'
client: '000'
state: absent
force: true
username: ADMIN
- name: Unlock SAP User
community.sap_libs.sap_user:
conn_username: 'DDIC'
conn_password: 'Test123'
host: 192.168.1.150
sysnr: '01'
client: '000'
state: unlock
force: true
username: ADMIN
'''
RETURN = r'''
msg:
description: A small execution description about the user action.
type: str
returned: always
sample: 'User ADMIN created'
out:
description: A detailed description about the user action.
type: list
elements: dict
returned: on success
sample: [...,{
"RETURN": [
{
"FIELD": "BNAME",
"ID": "01",
"LOG_MSG_NO": "000000",
"LOG_NO": "",
"MESSAGE": "User ADMIN created",
"MESSAGE_V1": "ADMIN",
"MESSAGE_V2": "",
"MESSAGE_V3": "",
"MESSAGE_V4": "",
"NUMBER": "102",
"PARAMETER": "",
"ROW": 0,
"SYSTEM": "",
"TYPE": "S"
}
],
"SAPUSER_UUID_HIST": []}]
'''
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
import traceback
import datetime
try:
from pyrfc import Connection
except ImportError:
HAS_PYRFC_LIBRARY = False
PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
PYRFC_LIBRARY_IMPORT_ERROR = None
HAS_PYRFC_LIBRARY = True
def add_to_dict(target_dict, target_key, value):
# Adds the given value to a dict as the key
# check if the given key is in the given dict yet
if target_key in target_dict:
return False
target_dict[target_key] = value
return True
def call_rfc_method(connection, method_name, kwargs):
# PyRFC call function
return connection.call(method_name, **kwargs)
def build_rfc_user_params(username, firstname, lastname, email, raw_password,
useralias, user_type, raw_company, user_change, force):
"""Creates RFC parameters for Creating users"""
# define dicts in batch
params = dict()
address = dict()
password = dict()
alias = dict()
logondata = dict()
company = dict()
# for change parameters
addressx = dict()
passwordx = dict()
logondatax = dict()
companyx = dict()
# define username
add_to_dict(params, 'USERNAME', username)
# define Address
add_to_dict(address, 'FIRSTNAME', firstname)
add_to_dict(address, 'LASTNAME', lastname)
add_to_dict(address, 'E_MAIL', email)
# define Password
add_to_dict(password, 'BAPIPWD', raw_password)
# define Alias
add_to_dict(alias, 'USERALIAS', useralias)
# define LogonData
add_to_dict(logondata, 'GLTGV', datetime.date.today())
add_to_dict(logondata, 'GLTGB', '20991231')
add_to_dict(logondata, 'USTYP', user_type)
# define company
add_to_dict(company, 'COMPANY', raw_company)
params['LOGONDATA'] = logondata
params['ADDRESS'] = address
params['COMPANY'] = company
params['ALIAS'] = alias
params['PASSWORD'] = password
# add change if user exists
if user_change and force:
add_to_dict(addressx, 'FIRSTNAME', 'X')
add_to_dict(addressx, 'LASTNAME', 'X')
add_to_dict(addressx, 'E_MAIL', 'X')
# define Password
add_to_dict(passwordx, 'BAPIPWD', 'X')
# define LogonData
add_to_dict(logondatax, 'USTYP', 'X')
# define company
add_to_dict(companyx, 'COMPANY', 'X')
params['LOGONDATAX'] = logondatax
params['ADDRESSX'] = addressx
params['COMPANYX'] = companyx
params['PASSWORDX'] = passwordx
return params
def user_role_assignment_build_rfc_params(roles, username):
rfc_table = []
for role_name in roles:
table_row = {'AGR_NAME': role_name}
add_to_dict(table_row, 'FROM_DAT', datetime.date.today())
add_to_dict(table_row, 'TO_DAT', '20991231')
rfc_table.append(table_row)
return {
'USERNAME': username,
'ACTIVITYGROUPS': rfc_table
}
def user_profile_assignment_build_rfc_params(profiles, username):
rfc_table = []
for profile_name in profiles:
table_row = {'BAPIPROF': profile_name}
rfc_table.append(table_row)
return {
'USERNAME': username,
'PROFILES': rfc_table
}
def check_user(user_detail):
if len(user_detail['RETURN']) > 0:
for sub in user_detail['RETURN']:
if sub['NUMBER'] == '124':
return False
return True
def return_analysis(raw):
change = False
failed = False
for state in raw['RETURN']:
if state['TYPE'] == "E":
if state['NUMBER'] == '224' or state['NUMBER'] == '124':
change = False
else:
failed = True
if state['TYPE'] == "S":
if state['NUMBER'] != '029':
change = True
if state['TYPE'] == "W":
if state['NUMBER'] == '049' or state['NUMBER'] == '047':
change = True
if state['NUMBER'] == '255':
change = True
return [{"change": change}, {"failed": failed}]
def run_module():
module = AnsibleModule(
argument_spec=dict(
# logical values
state=dict(default='present', choices=[
'absent', 'present', 'lock', 'unlock']),
force=dict(type='bool', default=False),
# 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 the new or existing user
username=dict(type='str', required=True),
firstname=dict(type='str', required=False),
lastname=dict(type='str', required=False),
email=dict(type='str', required=False),
password=dict(type='str', required=False, no_log=True),
useralias=dict(type='str', required=False),
user_type=dict(default="A",
choices=['A', 'B', 'C', 'S', 'L']),
company=dict(type='str', required=False),
# values for profile must a list
# Example ["SAP_NEW", "SAP_ALL"]
profiles=dict(type='list', elements='str', default=[""]),
# values for roles must a list
roles=dict(type='list', elements='str', default=[""]),
),
supports_check_mode=False,
required_if=[('state', 'present', ['useralias', 'company'])]
)
result = dict(changed=False, msg='', out='')
count = 0
raw = ""
params = module.params
state = params['state']
conn_username = (params['conn_username']).upper()
conn_password = params['conn_password']
host = params['host']
sysnr = params['sysnr']
client = params['client']
username = (params['username']).upper()
firstname = params['firstname']
lastname = params['lastname']
email = params['email']
password = params['password']
force = params['force']
if not params['useralias'] is None:
useralias = (params['useralias']).upper()
user_type = (params['user_type']).upper()
company = params['company']
profiles = params['profiles']
roles = params['roles']
if not HAS_PYRFC_LIBRARY:
module.fail_json(
msg=missing_required_lib('pyrfc'),
exception=PYRFC_LIBRARY_IMPORT_ERROR)
# basic RFC connection with pyrfc
try:
conn = Connection(user=conn_username, passwd=conn_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)
# user details
user_detail = call_rfc_method(conn, 'BAPI_USER_GET_DETAIL', {'USERNAME': username})
user_exists = check_user(user_detail)
if state == "absent":
if user_exists:
raw = call_rfc_method(conn, 'BAPI_USER_DELETE', {'USERNAME': username})
if state == "present":
user_params = build_rfc_user_params(username, firstname, lastname, email, password, useralias, user_type, company, user_exists, force)
if not user_exists:
raw = call_rfc_method(conn, 'BAPI_USER_CREATE1', user_params)
if user_exists:
# check for address changes when user exists
user_no_changes = all((user_detail.get('ADDRESS')).get(k) == v for k, v in (user_params.get('ADDRESS')).items())
if not user_no_changes or force:
raw = call_rfc_method(conn, 'BAPI_USER_CHANGE', user_params)
call_rfc_method(conn, 'BAPI_USER_ACTGROUPS_ASSIGN', user_role_assignment_build_rfc_params(roles, username))
call_rfc_method(conn, 'BAPI_USER_PROFILES_ASSIGN', user_profile_assignment_build_rfc_params(profiles, username))
if state == "unlock":
if user_exists:
raw = call_rfc_method(conn, 'BAPI_USER_UNLOCK', {'USERNAME': username})
if state == "lock":
if user_exists:
raw = call_rfc_method(conn, 'BAPI_USER_LOCK', {'USERNAME': username})
# analyse return value
if raw != '':
analysed = return_analysis(raw)
result['out'] = raw
result['changed'] = analysed[0]['change']
for msgs in raw['RETURN']:
if count > 0:
result['msg'] = result['msg'] + '\n'
result['msg'] = result['msg'] + msgs['MESSAGE']
count = count + 1
if analysed[1]['failed']:
module.fail_json(**result)
else:
result['msg'] = "No changes where made."
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,228 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sapcar_extract
short_description: Manages SAP SAPCAR archives
version_added: "1.0.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 = r"""
- name: Extract SAR file
community.sap_libs.sapcar_extract:
path: "~/source/hana.sar"
- name: Extract SAR file with destination
community.sap_libs.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.sap_libs.sapcar_extract:
path: "~/source/hana.sar"
dest: "~/dest/"
binary_path: "https://myserver/SAPCAR"
- name: Extract SAR file and delete SAR after extract
community.sap_libs.sapcar_extract:
path: "~/source/hana.sar"
remove: true
- name: Extract SAR file with manifest
community.sap_libs.sapcar_extract:
path: "~/source/hana.sar"
signature: true
- name: Extract SAR file with manifest and rename it
community.sap_libs.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):
# manipulating 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 not item.endswith('.SMF')]
# 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 not item.endswith('.SMF')]
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()