Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ansible-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ jobs:
- stable-2.11
- stable-2.12
- stable-2.13
- devel
runs-on: ubuntu-latest
steps:

Expand Down
48 changes: 48 additions & 0 deletions plugins/module_utils/pyrfc_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-

# Copyright: (c) 2022, Sean Freeman ,
# Rainer Leber <[email protected]> <[email protected]>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible.module_utils.basic import missing_required_lib

import traceback

PYRFC_LIBRARY_IMPORT_ERROR = None
try:
import pyrfc
except ImportError:
PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
HAS_PYRFC_LIBRARY = False
else:
HAS_PYRFC_LIBRARY = True


def get_connection(module, conn_params):
if not HAS_PYRFC_LIBRARY:
module.fail_json(msg=missing_required_lib(
"python-gitlab"), exception=PYRFC_LIBRARY_IMPORT_ERROR)

module.warn('Connecting ... %s' % conn_params['ashost'])
if "saprouter" in conn_params:
module.warn("...via SAPRouter to SAP System")
elif "gwhost" in conn_params:
module.warn("...via Gateway to SAP System")
else:
module.warn("...direct to SAP System")

conn = pyrfc.Connection(**conn_params)

module.warn("Verifying connection is open/alive: %s" % conn.alive)
return conn
185 changes: 185 additions & 0 deletions plugins/modules/sap_pyrfc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2022, Sean Freeman ,
# Rainer Leber <[email protected]> <[email protected]>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = r'''
---
module: sap_pyrfc

short_description: This module executes rfc functions.

version_added: "1.2.0"

description:
- This module will executes rfc calls on a sap system.
- It is a generic approach to call rfc functions on a SAP System.
- This module should be used where no module or role is provided.

options:
function:
description: The SAP RFC function to call.
required: true
type: str
parameters:
description: The parameters which are needed by the function.
required: true
type: dict
connection:
description: The required connection details.
required: true
type: dict
suboptions:
ashost:
description: The required host for the SAP system. Can be either an FQDN or IP Address.
type: str
required: true
sysid:
description: The systemid of the SAP system.
type: str
required: false
sysnr:
description:
- The system number of the SAP system.
- You must quote the value to ensure retaining the leading zeros.
type: str
required: true
client:
description:
- The client number to connect to.
- You must quote the value to ensure retaining the leading zeros.
type: str
required: true
user:
description: The required username for the SAP system.
type: str
required: true
passwd:
description: The required password for the SAP system.
type: str
required: true
lang:
description: The used language to execute.
type: str
required: false

requirements:
- pyrfc >= 2.4.0

author:
- Sean Freeman (@seanfreeman)
- Rainer Leber (@rainerleber)
'''

EXAMPLES = '''
- name: test the pyrfc module
community.sap_libs.sap_pyrfc:
function: STFC_CONNECTION
parameters:
REQUTEXT: "Hello SAP!"
connection:
ashost: s4hana.poc.cloud
sysid: TDT
sysnr: "01"
client: "400"
user: DDIC
passwd: Password1
lang: EN
'''

RETURN = r'''
result:
description: The execution description.
type: dict
returned: always
sample: {"ECHOTEXT": "Hello SAP!",
"RESPTEXT": "SAP R/3 Rel. 756 Sysid: TST Date: 20220710 Time: 140717 Logon_Data: 000/DDIC/E"}
'''

import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ..module_utils.pyrfc_handler import get_connection

try:
from pyrfc import ABAPApplicationError, ABAPRuntimeError, CommunicationError, Connection, LogonError
except ImportError:
HAS_PYRFC_LIBRARY = False
PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
HAS_PYRFC_LIBRARY = True


def main():
msg = None
params_spec = dict(
ashost=dict(type='str', required=True),
sysid=dict(type='str', required=False),
sysnr=dict(type='str', required=True),
client=dict(type='str', required=True),
user=dict(type='str', required=True),
passwd=dict(type='str', required=True, no_log=True),
lang=dict(type='str', required=False),
)

argument_spec = dict(function=dict(required=True, type='str'),
parameters=dict(required=True, type='dict'),
connection=dict(
required=True, type='dict', options=params_spec),
)

module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)

function = module.params.get('function')
func_params = module.params.get('parameters')
conn_params = module.params.get('connection')

if not HAS_PYRFC_LIBRARY:
module.fail_json(
msg=missing_required_lib('pyrfc'),
exception=PYRFC_LIBRARY_IMPORT_ERROR)

# Check mode
if module.check_mode:
msg = "function: %s; params: %s; login: %s" % (
function, func_params, conn_params)
module.exit_json(msg=msg, changed=True)

try:
conn = get_connection(module, conn_params)
result = conn.call(function, **func_params)
except CommunicationError as err:
msg = "Could not connect to server"
error_msg = err.message
except LogonError as err:
msg = "Could not log in"
error_msg = err.message
except (ABAPApplicationError, ABAPRuntimeError) as err:
msg = "ABAP error occurred"
error_msg = err.message
except Exception as err:
msg = "Something went wrong."
error_msg = err
else:
module.exit_json(changed=True, result=result)

if msg:
module.fail_json(msg=msg, exception=error_msg)


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions tests/sanity/ignore-2.10.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
1 change: 1 addition & 0 deletions tests/sanity/ignore-2.11.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
1 change: 1 addition & 0 deletions tests/sanity/ignore-2.12.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
1 change: 1 addition & 0 deletions tests/sanity/ignore-2.13.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
1 change: 1 addition & 0 deletions tests/sanity/ignore-2.9.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
76 changes: 76 additions & 0 deletions tests/unit/plugins/modules/test_sap_pyrfc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import (absolute_import, division, print_function)

__metaclass__ = type

import sys
from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock
from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args

sys.modules['pyrfc'] = MagicMock()
sys.modules['pyrfc.Connection'] = MagicMock()
from ansible_collections.community.sap_libs.plugins.modules import sap_pyrfc


class TestSAPRfcModule(ModuleTestCase):

def setUp(self):
super(TestSAPRfcModule, self).setUp()
self.module = sap_pyrfc

def tearDown(self):
super(TestSAPRfcModule, self).tearDown()

def test_without_required_parameters(self):
"""Failure must occurs when all parameters are missing"""
with self.assertRaises(AnsibleFailJson):
set_module_args({})
self.module.main()

def test_error_module_not_found(self):
"""tests fail module error"""

set_module_args({
"function": "STFC_CONNECTION",
"parameters": {"REQUTEXT": "Hello SAP!"},
"connection": {"ashost": "s4hana.poc.cloud",
"sysnr": "01",
"client": "400",
"user": "DDIC",
"passwd": "Password1",
"lang": "EN"}
})

with self.assertRaises(AnsibleFailJson) as result:
self.module.HAS_PYRFC_LIBRARY = False
self.module.PYRFC_LIBRARY_IMPORT_ERROR = 'Module not found'
self.module.main()
self.assertEqual(
result.exception.args[0]['exception'], 'Module not found')

def test_success_communication(self):
"""tests success"""
set_module_args({
"function": "STFC_CONNECTION",
"parameters": {"REQUTEXT": "Hello SAP!"},
"connection": {"ashost": "s4hana.poc.cloud",
"sysnr": "01",
"client": "400",
"user": "DDIC",
"passwd": "Password1",
"lang": "EN"}
})
with patch.object(self.module, 'get_connection') as patch_call:
patch_call.call.return_value = 'Patched'
with self.assertRaises(AnsibleExitJson) as result:
self.module.main()
self.assertEqual(result.exception.args[0]['changed'], True)