2626 - Provides support for sapstartsrv formaly known as sapcontrol
2727 - A complete information of all functions and the parameters can be found here
2828 U(https://www.sap.com/documents/2016/09/0a40e60d-8b7c-0010-82c7-eda71af511fa.html)
29+ - When hostname is 'localhost', sysnr is set and no username/password are provided, the module will attempt
30+ to use local Unix socket authentication (which works with 'become' privilege escalation).
2931
3032options:
3133 sysnr:
3638 port:
3739 description:
3840 - The port number of the sapstartsrv.
41+ - If provided, the module will use always use http connection instead of local socket.
3942 required: false
4043 type: int
4144 username:
157160 function: GetProcessList
158161 port: 50113
159162
160- - name: ParameterValue
163+ - name: ParameterValue with authentication
161164 community.sap_libs.sap_control_exec:
162165 hostname: 192.168.8.15
163166 sysnr: "01"
164167 username: hdbadm
165- password: test1234#
168+ password: test1234
166169 function: ParameterValue
167170 parameter: ztta
171+
172+ - name: GetVersionInfo using local Unix socket (requires become)
173+ community.sap_libs.sap_control_exec:
174+ sysnr: "00"
175+ function: GetVersionInfo
176+ become: true
177+
178+ - name: GetProcessList using local Unix socket as SAP admin user
179+ community.sap_libs.sap_control_exec:
180+ sysnr: "00"
181+ function: GetProcessList
182+ become: true
183+ become_user: "{{ sap_sid | lower }}adm"
168184"""
169185
170186RETURN = r'''
213229
214230from ansible .module_utils .basic import AnsibleModule , missing_required_lib
215231import traceback
232+ import socket
233+ import os
234+
235+ try :
236+ from urllib .request import HTTPHandler
237+ except ImportError :
238+ from ansible .module_utils .urls import (
239+ UnixHTTPHandler as HTTPHandler ,
240+ )
241+
242+ try :
243+ from http .client import HTTPConnection
244+ except ImportError :
245+ from httplib import HTTPConnection
246+
216247try :
217248 from suds .client import Client
218249 from suds .sudsobject import asdict
250+ from suds .transport .http import HttpAuthenticated , HttpTransport
251+ HAS_SUDS_LIBRARY = True
252+ SUDS_LIBRARY_IMPORT_ERROR = None
253+
254+ class LocalSocketHttpAuthenticated (HttpAuthenticated ):
255+ """Authenticated HTTP transport using Unix domain sockets."""
256+ def __init__ (self , socketpath , ** kwargs ):
257+ HttpAuthenticated .__init__ (self , ** kwargs )
258+ self ._socketpath = socketpath
259+
260+ def u2handlers (self ):
261+ handlers = HttpTransport .u2handlers (self )
262+ handlers .append (LocalSocketHandler (socketpath = self ._socketpath ))
263+ return handlers
264+
219265except ImportError :
220266 HAS_SUDS_LIBRARY = False
221267 SUDS_LIBRARY_IMPORT_ERROR = traceback .format_exc ()
222- else :
223- SUDS_LIBRARY_IMPORT_ERROR = None
224- HAS_SUDS_LIBRARY = True
268+
269+ # Define dummy class when suds is not available
270+ class LocalSocketHttpAuthenticated (object ):
271+ def __init__ (self , socketpath , ** kwargs ):
272+ pass
273+
274+ def u2handlers (self ):
275+ return []
276+
277+
278+ class LocalSocketHttpConnection (HTTPConnection ):
279+ """HTTP connection class that uses Unix domain sockets."""
280+ def __init__ (self , host , port = None , timeout = socket ._GLOBAL_DEFAULT_TIMEOUT ,
281+ source_address = None , socketpath = None ):
282+ super (LocalSocketHttpConnection , self ).__init__ (host , port , timeout , source_address )
283+ self .socketpath = socketpath
284+
285+ def connect (self ):
286+ """Connect to Unix domain socket."""
287+ self .sock = socket .socket (socket .AF_UNIX , socket .SOCK_STREAM )
288+ self .sock .connect (self .socketpath )
289+
290+
291+ class LocalSocketHandler (HTTPHandler ):
292+ """HTTP handler for Unix domain sockets."""
293+ def __init__ (self , debuglevel = 0 , socketpath = None ):
294+ self ._debuglevel = debuglevel
295+ self ._socketpath = socketpath
296+
297+ def http_open (self , req ):
298+ return self .do_open (LocalSocketHttpConnection , req , socketpath = self ._socketpath )
225299
226300
227301def choices ():
@@ -260,9 +334,27 @@ def recursive_dict(suds_object):
260334 return out
261335
262336
263- def connection (hostname , port , username , password , function , parameter ):
264- url = 'http://{0}:{1}/sapcontrol?wsdl' .format (hostname , port )
265- client = Client (url , username = username , password = password )
337+ def connection (hostname , port , username , password , function , parameter , sysnr = None , use_local = False ):
338+ if use_local and sysnr is not None :
339+ # Use Unix domain socket for local connection
340+ unix_socket = "/tmp/.sapstream5{0}13" .format (str (sysnr ).zfill (2 ))
341+
342+ # Check if socket exists
343+ if not os .path .exists (unix_socket ):
344+ raise Exception ("SAP control Unix socket not found: {0}" .format (unix_socket ))
345+
346+ url = "http://localhost/sapcontrol?wsdl"
347+
348+ try :
349+ localsocket = LocalSocketHttpAuthenticated (unix_socket )
350+ client = Client (url , transport = localsocket )
351+ except Exception as e :
352+ raise Exception ("Failed to connect via Unix socket: {0}" .format (str (e )))
353+ else :
354+ # Use HTTP connection (original behavior)
355+ url = 'http://{0}:{1}/sapcontrol?wsdl' .format (hostname , port )
356+ client = Client (url , username = username , password = password )
357+
266358 _function = getattr (client .service , function )
267359 if parameter is not None :
268360 result = _function (parameter )
@@ -288,6 +380,7 @@ def main():
288380 parameter = dict (type = 'str' , required = False ),
289381 force = dict (type = 'bool' , default = False ),
290382 ),
383+ # Remove strict requirements to allow local mode
291384 required_one_of = [('sysnr' , 'port' )],
292385 mutually_exclusive = [('sysnr' , 'port' )],
293386 supports_check_mode = False ,
@@ -309,26 +402,46 @@ def main():
309402 msg = missing_required_lib ('suds' ),
310403 exception = SUDS_LIBRARY_IMPORT_ERROR )
311404
405+ # Validate arguments
406+ if sysnr is None and port is None :
407+ module .fail_json (msg = "Either 'sysnr' or 'port' must be provided" )
408+
409+ if sysnr is not None and port is not None :
410+ module .fail_json (msg = "'sysnr' and 'port' are mutually exclusive" )
411+
312412 if function == "Stop" :
313413 if force is False :
314414 module .fail_json (msg = "Stop function requires force: True" )
315415
416+ # Determine if we should use local Unix socket connection
417+ # Use local if hostname is localhost and no username/password provided
418+ use_local = (hostname == "localhost" and
419+ username is None and
420+ password is None and
421+ sysnr is not None )
422+
316423 if port is None :
317424 try :
318- try :
319- conn = connection (hostname , "5{0}14" .format ((sysnr ).zfill (2 )), username , password , function , parameter )
320- except Exception :
321- conn = connection (hostname , "5{0}13" .format ((sysnr ).zfill (2 )), username , password , function , parameter )
425+ if use_local :
426+ # Try local connection first
427+ conn = connection (hostname , None , username , password , function , parameter , sysnr , use_local = True )
428+ else :
429+ # Try HTTP ports
430+ try :
431+ conn = connection (hostname , "5{0}14" .format ((sysnr ).zfill (2 )), username , password , function , parameter , sysnr )
432+ except Exception :
433+ conn = connection (hostname , "5{0}13" .format ((sysnr ).zfill (2 )), username , password , function , parameter , sysnr )
322434 except Exception as err :
323435 result ['error' ] = str (err )
324436 else :
325437 try :
326- conn = connection (hostname , port , username , password , function , parameter )
438+ conn = connection (hostname , port , username , password , function , parameter , sysnr , use_local = False )
327439 except Exception as err :
328440 result ['error' ] = str (err )
329441
330442 if result ['error' ] != '' :
331- result ['msg' ] = 'Something went wrong connecting to the SAPCONTROL SOAP API.'
443+ connection_type = "Unix socket" if use_local else "SOAP API"
444+ result ['msg' ] = 'Something went wrong connecting to the {0}.' .format (connection_type )
332445 module .fail_json (** result )
333446
334447 if conn is not None :
0 commit comments