summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/sap/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/sap/plugins
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/sap/plugins')
-rw-r--r--ansible_collections/community/sap/plugins/doc_fragments/__init__.py0
-rw-r--r--ansible_collections/community/sap/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/community/sap/plugins/modules/database/saphana/hana_query.py238
-rw-r--r--ansible_collections/community/sap/plugins/modules/files/sapcar_extract.py220
-rw-r--r--ansible_collections/community/sap/plugins/modules/hana_query.py238
-rw-r--r--ansible_collections/community/sap/plugins/modules/identity/sap_company.py326
-rw-r--r--ansible_collections/community/sap/plugins/modules/identity/sap_user.py499
-rw-r--r--ansible_collections/community/sap/plugins/modules/sap_company.py326
-rw-r--r--ansible_collections/community/sap/plugins/modules/sap_snote.py258
-rw-r--r--ansible_collections/community/sap/plugins/modules/sap_system_facts.py206
-rw-r--r--ansible_collections/community/sap/plugins/modules/sap_task_list_execute.py340
-rw-r--r--ansible_collections/community/sap/plugins/modules/sap_user.py499
-rw-r--r--ansible_collections/community/sap/plugins/modules/sapcar_extract.py220
-rw-r--r--ansible_collections/community/sap/plugins/modules/system/sap_snote.py258
-rw-r--r--ansible_collections/community/sap/plugins/modules/system/sap_system_facts.py206
-rw-r--r--ansible_collections/community/sap/plugins/modules/system/sap_task_list_execute.py340
16 files changed, 4174 insertions, 0 deletions
diff --git a/ansible_collections/community/sap/plugins/doc_fragments/__init__.py b/ansible_collections/community/sap/plugins/doc_fragments/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/doc_fragments/__init__.py
diff --git a/ansible_collections/community/sap/plugins/module_utils/__init__.py b/ansible_collections/community/sap/plugins/module_utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/module_utils/__init__.py
diff --git a/ansible_collections/community/sap/plugins/modules/database/saphana/hana_query.py b/ansible_collections/community/sap/plugins/modules/database/saphana/hana_query.py
new file mode 100644
index 000000000..9eb43db09
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/database/saphana/hana_query.py
@@ -0,0 +1,238 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: hana_query
+short_description: Execute SQL on HANA
+version_added: "0.1.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.hana_query:
+ sid: "hdb"
+ instance: "01"
+ password: "Test123"
+ query: select user_name from users
+
+- name: RUN select query with host port
+ community.sap.hana_query:
+ sid: "hdb"
+ instance: "01"
+ password: "Test123"
+ host: "10.10.2.4:30001"
+ query: select user_name from users
+
+- name: Run several queries
+ community.sap.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 with path
+ community.sap.hana_query:
+ 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.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.sap.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=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()
diff --git a/ansible_collections/community/sap/plugins/modules/files/sapcar_extract.py b/ansible_collections/community/sap/plugins/modules/files/sapcar_extract.py
new file mode 100644
index 000000000..d586dd330
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/files/sapcar_extract.py
@@ -0,0 +1,220 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sapcar_extract
+short_description: Manages SAP SAPCAR archives
+version_added: "0.1.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.sapcar_extract:
+ path: "~/source/hana.sar"
+
+- name: Extract SAR file with destination
+ community.sap.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.sapcar_extract:
+ path: "~/source/hana.sar"
+ dest: "~/dest/"
+ binary_path: "https://myserver/SAPCAR"
+
+- name: Extract SAR file and delete SAR after extract
+ community.sap.sapcar_extract:
+ path: "~/source/hana.sar"
+ remove: true
+
+- name: Extract SAR file with manifest
+ community.sap.sapcar_extract:
+ path: "~/source/hana.sar"
+ signature: true
+
+- name: Extract SAR file with manifest and rename it
+ community.sap.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()
diff --git a/ansible_collections/community/sap/plugins/modules/hana_query.py b/ansible_collections/community/sap/plugins/modules/hana_query.py
new file mode 100644
index 000000000..9eb43db09
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/hana_query.py
@@ -0,0 +1,238 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: hana_query
+short_description: Execute SQL on HANA
+version_added: "0.1.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.hana_query:
+ sid: "hdb"
+ instance: "01"
+ password: "Test123"
+ query: select user_name from users
+
+- name: RUN select query with host port
+ community.sap.hana_query:
+ sid: "hdb"
+ instance: "01"
+ password: "Test123"
+ host: "10.10.2.4:30001"
+ query: select user_name from users
+
+- name: Run several queries
+ community.sap.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 with path
+ community.sap.hana_query:
+ 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.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.sap.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=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()
diff --git a/ansible_collections/community/sap/plugins/modules/identity/sap_company.py b/ansible_collections/community/sap/plugins/modules/identity/sap_company.py
new file mode 100644
index 000000000..8d3838e5f
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/identity/sap_company.py
@@ -0,0 +1,326 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_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.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.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.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:
+ 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()
diff --git a/ansible_collections/community/sap/plugins/modules/identity/sap_user.py b/ansible_collections/community/sap/plugins/modules/identity/sap_user.py
new file mode 100644
index 000000000..f83472657
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/identity/sap_user.py
@@ -0,0 +1,499 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_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.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.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.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.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.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:
+ 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()
diff --git a/ansible_collections/community/sap/plugins/modules/sap_company.py b/ansible_collections/community/sap/plugins/modules/sap_company.py
new file mode 100644
index 000000000..8d3838e5f
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/sap_company.py
@@ -0,0 +1,326 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_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.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.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.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:
+ 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()
diff --git a/ansible_collections/community/sap/plugins/modules/sap_snote.py b/ansible_collections/community/sap/plugins/modules/sap_snote.py
new file mode 100644
index 000000000..24f393927
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/sap_snote.py
@@ -0,0 +1,258 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_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.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.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:
+ 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()
diff --git a/ansible_collections/community/sap/plugins/modules/sap_system_facts.py b/ansible_collections/community/sap/plugins/modules/sap_system_facts.py
new file mode 100644
index 000000000..b5f4eb9b6
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/sap_system_facts.py
@@ -0,0 +1,206 @@
+#!/usr/bin/python
+
+# Copyright: (c) 2022, Rainer Leber rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_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.sap_system_fact:
+'''
+
+RETURN = r'''
+# These are examples of possible return values,
+# and in general should use other names for return values.
+ansible_facts:
+ description: Facts to add to ansible_facts.
+ returned: always
+ type: list
+ elements: dict
+ contains:
+ sap:
+ description: Facts about the running SAP system.
+ 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()
diff --git a/ansible_collections/community/sap/plugins/modules/sap_task_list_execute.py b/ansible_collections/community/sap/plugins/modules/sap_task_list_execute.py
new file mode 100644
index 000000000..0ae25903f
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/sap_task_list_execute.py
@@ -0,0 +1,340 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_task_list_execute
+short_description: Perform SAP Task list execution
+version_added: "0.1.0"
+description:
+ - The M(community.sap.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.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.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.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
+try:
+ import xmltodict
+except ImportError:
+ HAS_XMLTODICT_LIBRARY = False
+ XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ HAS_XMLTODICT_LIBRARY = True
+
+
+def call_rfc_method(connection, method_name, kwargs):
+ # PyRFC call function
+ return connection.call(method_name, **kwargs)
+
+
+def process_exec_settings(task_settings):
+ # processes task settings to objects
+ exec_settings = {}
+ for settings in task_settings:
+ temp_dict = {settings.upper(): 'X'}
+ for key, value in temp_dict.items():
+ exec_settings[key] = value
+ return exec_settings
+
+
+def xml_to_dict(xml_raw):
+ try:
+ xml_parsed = xmltodict.parse(xml_raw, dict_constructor=dict)
+ xml_dict = xml_parsed['asx:abap']['asx:values']['SESSION']['TASKLIST']
+ except KeyError:
+ xml_dict = "No logs available."
+ return xml_dict
+
+
+def run_module():
+
+ params_spec = dict(
+ TASKNAME=dict(type='str', required=True),
+ FIELDNAME=dict(type='str'),
+ VALUE=dict(type='raw'),
+ )
+
+ # define available arguments/parameters a user can pass to the module
+ module = AnsibleModule(
+ argument_spec=dict(
+ # values for connection
+ conn_username=dict(type='str', required=True),
+ conn_password=dict(type='str', required=True, no_log=True),
+ host=dict(type='str', required=True),
+ sysnr=dict(type='str', default="00"),
+ client=dict(type='str', default="000"),
+ # values for execution tasks
+ task_to_execute=dict(type='str', required=True),
+ task_parameters=dict(type='list', elements='dict', options=params_spec),
+ task_settings=dict(type='list', elements='str', default=['BATCH']),
+ task_skip=dict(type='bool', default=False),
+ ),
+ supports_check_mode=False,
+ )
+ result = dict(changed=False, msg='', out={})
+
+ params = module.params
+
+ username = params['conn_username'].upper()
+ password = params['conn_password']
+ host = params['host']
+ sysnr = params['sysnr']
+ client = params['client']
+
+ task_parameters = params['task_parameters']
+ task_to_execute = params['task_to_execute']
+ task_settings = params['task_settings']
+ task_skip = params['task_skip']
+
+ if not HAS_PYRFC_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('pyrfc'),
+ exception=PYRFC_LIBRARY_IMPORT_ERROR)
+
+ if not HAS_XMLTODICT_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('xmltodict'),
+ exception=XMLTODICT_LIBRARY_IMPORT_ERROR)
+
+ # basic RFC connection with pyrfc
+ try:
+ conn = Connection(user=username, passwd=password, ashost=host, sysnr=sysnr, client=client)
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'Something went wrong connecting to the SAP system.'
+ module.fail_json(**result)
+
+ try:
+ raw_params = call_rfc_method(conn, 'STC_TM_SCENARIO_GET_PARAMETERS',
+ {'I_SCENARIO_ID': task_to_execute})
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'The task list does not 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()
diff --git a/ansible_collections/community/sap/plugins/modules/sap_user.py b/ansible_collections/community/sap/plugins/modules/sap_user.py
new file mode 100644
index 000000000..f83472657
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/sap_user.py
@@ -0,0 +1,499 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_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.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.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.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.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.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:
+ 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()
diff --git a/ansible_collections/community/sap/plugins/modules/sapcar_extract.py b/ansible_collections/community/sap/plugins/modules/sapcar_extract.py
new file mode 100644
index 000000000..d586dd330
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/sapcar_extract.py
@@ -0,0 +1,220 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sapcar_extract
+short_description: Manages SAP SAPCAR archives
+version_added: "0.1.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.sapcar_extract:
+ path: "~/source/hana.sar"
+
+- name: Extract SAR file with destination
+ community.sap.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.sapcar_extract:
+ path: "~/source/hana.sar"
+ dest: "~/dest/"
+ binary_path: "https://myserver/SAPCAR"
+
+- name: Extract SAR file and delete SAR after extract
+ community.sap.sapcar_extract:
+ path: "~/source/hana.sar"
+ remove: true
+
+- name: Extract SAR file with manifest
+ community.sap.sapcar_extract:
+ path: "~/source/hana.sar"
+ signature: true
+
+- name: Extract SAR file with manifest and rename it
+ community.sap.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()
diff --git a/ansible_collections/community/sap/plugins/modules/system/sap_snote.py b/ansible_collections/community/sap/plugins/modules/system/sap_snote.py
new file mode 100644
index 000000000..24f393927
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/system/sap_snote.py
@@ -0,0 +1,258 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_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.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.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:
+ 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()
diff --git a/ansible_collections/community/sap/plugins/modules/system/sap_system_facts.py b/ansible_collections/community/sap/plugins/modules/system/sap_system_facts.py
new file mode 100644
index 000000000..b5f4eb9b6
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/system/sap_system_facts.py
@@ -0,0 +1,206 @@
+#!/usr/bin/python
+
+# Copyright: (c) 2022, Rainer Leber rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_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.sap_system_fact:
+'''
+
+RETURN = r'''
+# These are examples of possible return values,
+# and in general should use other names for return values.
+ansible_facts:
+ description: Facts to add to ansible_facts.
+ returned: always
+ type: list
+ elements: dict
+ contains:
+ sap:
+ description: Facts about the running SAP system.
+ 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()
diff --git a/ansible_collections/community/sap/plugins/modules/system/sap_task_list_execute.py b/ansible_collections/community/sap/plugins/modules/system/sap_task_list_execute.py
new file mode 100644
index 000000000..0ae25903f
--- /dev/null
+++ b/ansible_collections/community/sap/plugins/modules/system/sap_task_list_execute.py
@@ -0,0 +1,340 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_task_list_execute
+short_description: Perform SAP Task list execution
+version_added: "0.1.0"
+description:
+ - The M(community.sap.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.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.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.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
+try:
+ import xmltodict
+except ImportError:
+ HAS_XMLTODICT_LIBRARY = False
+ XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ HAS_XMLTODICT_LIBRARY = True
+
+
+def call_rfc_method(connection, method_name, kwargs):
+ # PyRFC call function
+ return connection.call(method_name, **kwargs)
+
+
+def process_exec_settings(task_settings):
+ # processes task settings to objects
+ exec_settings = {}
+ for settings in task_settings:
+ temp_dict = {settings.upper(): 'X'}
+ for key, value in temp_dict.items():
+ exec_settings[key] = value
+ return exec_settings
+
+
+def xml_to_dict(xml_raw):
+ try:
+ xml_parsed = xmltodict.parse(xml_raw, dict_constructor=dict)
+ xml_dict = xml_parsed['asx:abap']['asx:values']['SESSION']['TASKLIST']
+ except KeyError:
+ xml_dict = "No logs available."
+ return xml_dict
+
+
+def run_module():
+
+ params_spec = dict(
+ TASKNAME=dict(type='str', required=True),
+ FIELDNAME=dict(type='str'),
+ VALUE=dict(type='raw'),
+ )
+
+ # define available arguments/parameters a user can pass to the module
+ module = AnsibleModule(
+ argument_spec=dict(
+ # values for connection
+ conn_username=dict(type='str', required=True),
+ conn_password=dict(type='str', required=True, no_log=True),
+ host=dict(type='str', required=True),
+ sysnr=dict(type='str', default="00"),
+ client=dict(type='str', default="000"),
+ # values for execution tasks
+ task_to_execute=dict(type='str', required=True),
+ task_parameters=dict(type='list', elements='dict', options=params_spec),
+ task_settings=dict(type='list', elements='str', default=['BATCH']),
+ task_skip=dict(type='bool', default=False),
+ ),
+ supports_check_mode=False,
+ )
+ result = dict(changed=False, msg='', out={})
+
+ params = module.params
+
+ username = params['conn_username'].upper()
+ password = params['conn_password']
+ host = params['host']
+ sysnr = params['sysnr']
+ client = params['client']
+
+ task_parameters = params['task_parameters']
+ task_to_execute = params['task_to_execute']
+ task_settings = params['task_settings']
+ task_skip = params['task_skip']
+
+ if not HAS_PYRFC_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('pyrfc'),
+ exception=PYRFC_LIBRARY_IMPORT_ERROR)
+
+ if not HAS_XMLTODICT_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('xmltodict'),
+ exception=XMLTODICT_LIBRARY_IMPORT_ERROR)
+
+ # basic RFC connection with pyrfc
+ try:
+ conn = Connection(user=username, passwd=password, ashost=host, sysnr=sysnr, client=client)
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'Something went wrong connecting to the SAP system.'
+ module.fail_json(**result)
+
+ try:
+ raw_params = call_rfc_method(conn, 'STC_TM_SCENARIO_GET_PARAMETERS',
+ {'I_SCENARIO_ID': task_to_execute})
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'The task list does not 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()