2929
3030notes:
3131 - Supports C(check_mode).
32+ - The user executing the module must have execute permissions for C(/usr/sap/hostctrl/exe/sapcontrol).
33+ - Only directories matching SAP SID and Instance naming conventions are scanned.
3234'''
3335
3436EXAMPLES = r'''
8486
8587def get_all_hana_sid ():
8688 hana_sid = list ()
87- if os .path .isdir ("/hana/shared" ):
88- # /hana/shared directory exists
89- for sid in os .listdir ('/hana/shared' ):
90- if os .path .isdir ("/usr/sap/" + sid ):
91- hana_sid = hana_sid + [sid ]
92- if hana_sid :
93- return hana_sid
89+
90+ # Expected SID pattern: 3 character in specific pattern (e.g., ABC, D01, etc.)
91+ sid_pattern = re .compile (r'^[A-Z][A-Z0-9][A-Z0-9]$' )
92+
93+ shared_path = "/hana/shared"
94+
95+ if is_accessible_dir (shared_path ):
96+ try :
97+ for sid in os .listdir (shared_path ):
98+ if not sid_pattern .match (sid ):
99+ continue
100+
101+ target_path = os .path .join ("/usr/sap" , sid )
102+
103+ try :
104+ if is_accessible_dir (target_path ):
105+ hana_sid .append (sid )
106+ except OSError :
107+ # Individual SID folder is problematic, move to next
108+ continue
109+
110+ except OSError :
111+ # Entire shared_path is inaccessible
112+ pass
113+
114+ return hana_sid
94115
95116
96117def get_all_nw_sid ():
97118 nw_sid = list ()
98- if os .path .isdir ("/sapmnt" ):
99- # /sapmnt directory exists
100- for sid in os .listdir ('/sapmnt' ):
101- if os .path .isdir ("/usr/sap/" + sid ):
102- nw_sid = nw_sid + [sid ]
103- else :
104- # Check to see if /sapmnt/SID/sap_bobj exists
105- if os .path .isdir ("/sapmnt/" + sid + "/sap_bobj" ):
106- # is a bobj system
107- nw_sid = nw_sid + [sid ]
108- if nw_sid :
109- return nw_sid
119+
120+ # Expected SID pattern with only 3 character in specific pattern (e.g., ABC, D01, etc.)
121+ sid_pattern = re .compile (r'^[A-Z][A-Z0-9][A-Z0-9]$' )
122+
123+ sapmnt_path = "/sapmnt"
124+
125+ if is_accessible_dir (sapmnt_path ):
126+ try :
127+ for sid in os .listdir (sapmnt_path ):
128+ if not sid_pattern .match (sid ):
129+ continue
130+
131+ target_path = os .path .join ("/usr/sap" , sid )
132+
133+ try :
134+ if is_accessible_dir (target_path ):
135+ nw_sid .append (sid )
136+ else :
137+ # Check to see if /sapmnt/SID/sap_bobj exists and is accessible
138+ bobj_path = os .path .join (sapmnt_path , sid , "sap_bobj" )
139+ if is_accessible_dir (bobj_path ):
140+ nw_sid .append (sid )
141+ except OSError :
142+ # Individual SID folder is problematic, move to next
143+ continue
144+
145+ except OSError :
146+ # Entire shared_path is inaccessible
147+ pass
148+
149+ return nw_sid
110150
111151
112152def get_hana_nr (sids , module ):
113153 hana_list = list ()
154+
155+ # Expected Instance pattern: HDB followed by exactly 2 digits (e.g., HDB00, HDB01, etc.)
156+ instance_pattern = re .compile (r'^HDB(\d{2})$' )
157+
158+ sapcontrol_path = module .get_bin_path ('/usr/sap/hostctrl/exe/sapcontrol' , required = True )
159+
114160 for sid in sids :
115- for instance in os .listdir ('/usr/sap/' + sid ):
116- if 'HDB' in instance :
117- instance_nr = instance [- 2 :]
118- # check if instance number exists
119- command = [module .get_bin_path ('/usr/sap/hostctrl/exe/sapcontrol' , required = True )]
161+ path = os .path .join ('/usr/sap' , sid )
162+
163+ if not is_accessible_dir (path ):
164+ continue
165+
166+ for instance in os .listdir (path ):
167+ match = instance_pattern .match (instance )
168+ if match :
169+ # 'match.group(1)' is the 2 digits captured by (\d{2})
170+ instance_nr = match .group (1 )
171+
172+ command = [sapcontrol_path ]
120173 command .extend (['-nr' , instance_nr , '-function' , 'GetProcessList' ])
174+
121175 check_instance = module .run_command (command , check_rc = False )
122- # sapcontrol returns c(0 - 5) exit codes only c(1) is unavailable
176+
177+ # sapcontrol returns (0-5) exit codes; (1) usually means unavailable
123178 if check_instance [0 ] != 1 :
124- hana_list .append ({'NR' : instance_nr , 'SID' : sid , 'TYPE' : 'HDB' , 'InstanceType' : 'HANA' })
179+ hana_list .append ({
180+ 'NR' : instance_nr ,
181+ 'SID' : sid ,
182+ 'TYPE' : 'HDB' ,
183+ 'InstanceType' : 'HANA'
184+ })
185+
125186 return hana_list
126187
127188
128189def get_nw_nr (sids , module ):
129190 nw_list = list ()
191+
192+ # Expected Instance pattern: letters followed by exactly 2 digits (e.g., ASCS00, D01)
193+ # Excludes 'SYS', 'exe', 'hdbclient', etc.
194+ instance_pattern = re .compile (r'^[a-zA-Z]+(\d{2})$' )
195+
196+ sapcontrol_path = module .get_bin_path ('/usr/sap/hostctrl/exe/sapcontrol' , required = True )
130197 type = ""
198+
131199 for sid in sids :
132- for instance in os .listdir ('/usr/sap/' + sid ):
133- instance_nr = instance [- 2 :]
134- command = [module .get_bin_path ('/usr/sap/hostctrl/exe/sapcontrol' , required = True )]
135- # check if returned instance_nr is a number because sapcontrol returns all if a random string is provided
136- if instance_nr .isdigit ():
200+ path = os .path .join ('/usr/sap' , sid )
201+
202+ if not is_accessible_dir (path ):
203+ continue
204+
205+ for instance in os .listdir (path ):
206+ match = instance_pattern .match (instance )
207+ if match :
208+ # 'match.group(1)' is the 2 digits captured by (\d{2})
209+ instance_nr = match .group (1 )
210+
211+ command = [sapcontrol_path ]
137212 command .extend (['-nr' , instance_nr , '-function' , 'GetInstanceProperties' ])
213+
138214 check_instance = module .run_command (command , check_rc = False )
139215 if check_instance [0 ] != 1 :
140216 for line in check_instance [1 ].splitlines ():
@@ -144,6 +220,7 @@ def get_nw_nr(sids, module):
144220 # split instance number
145221 type = type_raw [:- 2 ]
146222 nw_list .append ({'NR' : instance_nr , 'SID' : sid , 'TYPE' : get_instance_type (type ), 'InstanceType' : 'NW' })
223+
147224 return nw_list
148225
149226
@@ -172,6 +249,10 @@ def get_instance_type(raw_type):
172249 return type
173250
174251
252+ def is_accessible_dir (path ):
253+ return os .path .isdir (path ) and os .access (path , os .R_OK )
254+
255+
175256def run_module ():
176257 module_args = dict ()
177258 system_result = list ()
@@ -186,6 +267,11 @@ def run_module():
186267 supports_check_mode = True ,
187268 )
188269
270+ # Fail if execution user does not have permission for sapcontrol
271+ sapcontrol_path = module .get_bin_path ('/usr/sap/hostctrl/exe/sapcontrol' , required = True )
272+ if not os .access (sapcontrol_path , os .X_OK ):
273+ module .fail_json (msg = "Permission denied: Ansible user cannot execute {0}" .format (sapcontrol_path ))
274+
189275 hana_sid = get_all_hana_sid ()
190276 if hana_sid :
191277 system_result = system_result + get_hana_nr (hana_sid , module )
@@ -196,8 +282,10 @@ def run_module():
196282
197283 if system_result :
198284 result ['ansible_facts' ] = {'sap' : system_result }
285+ result ['msg' ] = "SAP System facts were collected."
199286 else :
200287 result ['ansible_facts' ]
288+ result ['msg' ] = "No running SAP instances found or Ansible user cannot access them."
201289
202290 if module .check_mode :
203291 module .exit_json (** result )
0 commit comments