diff --git a/roles/sap_swpm/README.md b/roles/sap_swpm/README.md
index a1140316..b14492d7 100644
--- a/roles/sap_swpm/README.md
+++ b/roles/sap_swpm/README.md
@@ -685,6 +685,13 @@ Define UID of Linux user SIDADM.
### Miscellaneous Variables
+#### sap_swpm_force
+- _Type:_ `bool`
+- _Default:_ `false`
+
+Optional override that forces execution of installation tasks if existing SAP instance is detected.
+This variable should be used with caution as running SWPM installation with active SAP instances can lead to corruption of existing SAP installation.
+
#### sap_swpm_ascs_install_gateway
- _Type:_ `string`
- _Default:_ `true`
diff --git a/roles/sap_swpm/defaults/main.yml b/roles/sap_swpm/defaults/main.yml
index 3e0903bd..b9ded361 100644
--- a/roles/sap_swpm/defaults/main.yml
+++ b/roles/sap_swpm/defaults/main.yml
@@ -508,3 +508,13 @@ sap_swpm_display_unattended_output: false
# Set which Ansible Collection to use when calling sap_install roles.
sap_swpm_sap_install_collection: 'community.sap_install'
+
+# Optional override that forces execution of installation tasks if existing SAP instance is detected.
+# WARNING: This variable should be used with caution
+# as running SWPM installation with active SAP instances can lead to corruption of existing SAP installation.
+sap_swpm_force: false
+
+# Skip all detection checks. Use ONLY for testing/development on clean systems.
+# WARNING: Skipping detection can lead to data loss if installation already exists!
+# This will bypass all safety checks including saphostctrl, sapcontrol, and file existence checks.
+# sap_swpm_skip_detection: false
diff --git a/roles/sap_swpm/tasks/main.yml b/roles/sap_swpm/tasks/main.yml
index 0b179883..4e5d0585 100644
--- a/roles/sap_swpm/tasks/main.yml
+++ b/roles/sap_swpm/tasks/main.yml
@@ -40,17 +40,22 @@
tags: always
- name: SAP SWPM - Run pre_install tasks
- ansible.builtin.import_tasks: pre_install.yml
+ ansible.builtin.import_tasks:
+ file: pre_install.yml
tags:
- sap_swpm_pre_install
- sap_swpm_install
- name: SAP SWPM - Run swpm
- ansible.builtin.import_tasks: swpm.yml
- when: sap_swpm_run_sapinst
+ ansible.builtin.import_tasks:
+ file: swpm.yml
+ when:
+ - sap_swpm_run_sapinst
+ - not __sap_swpm_sap_detected | d(false) or sap_swpm_force | d(false)
tags:
- sap_swpm_install
-- name: SAP SWPM - Run postinstall task
- ansible.builtin.import_tasks: post_install.yml
+- name: SAP SWPM - Run post_install task
+ ansible.builtin.import_tasks:
+ file: post_install.yml
when: sap_swpm_run_sapinst
diff --git a/roles/sap_swpm/tasks/post_install.yml b/roles/sap_swpm/tasks/post_install.yml
index c3cdc900..b4a5ce86 100644
--- a/roles/sap_swpm/tasks/post_install.yml
+++ b/roles/sap_swpm/tasks/post_install.yml
@@ -1,35 +1,25 @@
# SPDX-License-Identifier: Apache-2.0
---
-# Reason for noqa: The command might change things but we do not yet attempt to find out
-- name: SAP SWPM Post Install - ensure password expiry disabled {{ sap_swpm_sid | lower + 'adm' }}
- ansible.builtin.shell: |
- chage -m 0 -M 99999 -I -1 -E -1 {{ sap_swpm_sid | lower }}adm
- chage -m 0 -M 99999 -I -1 -E -1 sapadm
- args:
- executable: /bin/bash
- become: true
- register: __sap_swpm_post_install_register_sidadm_noexpire
- changed_when: __sap_swpm_post_install_register_sidadm_noexpire is succeeded
- when: sap_swpm_set_sidadm_noexpire | default(true)
-
-# Firewall steps are part of pre_tasks to ensure that ports are open before installation.
+# Set user expiration update
+- name: SAP SWPM Post Install - Get list of available OS users
+ ansible.builtin.getent:
+ database: passwd
-########################################################################################################################
+# New task file includes detailed steps to set each parameter, instead of all at same time.
+- name: SAP SWPM Post Install - Execute user expiration task file
+ ansible.builtin.include_tasks:
+ file: post_install/set_password_expiration.yml
+ loop:
+ - "{{ sap_swpm_sid | lower }}adm"
+ - "sapadm"
+ loop_control:
+ loop_var: user_item
+ when:
+ - user_item in ansible_facts['getent_passwd']
+ - sap_swpm_set_sidadm_noexpire | d(true)
-- name: SAP SWPM Deployment - Finished
- ansible.builtin.debug:
- msg: |
- " SAP SWPM deployment successfully completed "
- " "
- " SAP Product - {{ sap_swpm_product_catalog_id }} "
- " SID - {{ sap_swpm_sid | d('') }} "
- " Primary Instance - {{ sap_swpm_pas_instance_nr | d('') }} "
- " Host - {{ ansible_facts['hostname'] }} "
- " FQDN - {{ ansible_facts['fqdn'] }} "
- " IP - {{ ansible_facts['default_ipv4'].address | d(ansible_facts['all_ipv4_addresses'][0]) }} "
-# " Master Password - {{ sap_swpm_master_password }} "
-# " DDIC 000 Password - {{ sap_swpm_ddic_000_password }} "
+# Firewall steps are part of pre_tasks to ensure that ports are open before installation.
# SAP HANA Client will not be installed for any installation with SAP AnyDB
# and will only be installed alongside SAP NWAS PAS or AAS (not NWAS ASCS)
@@ -55,6 +45,36 @@
register: __sap_swpm_post_install_register_hdbuserstore_connection
changed_when: __sap_swpm_post_install_register_hdbuserstore_connection is succeeded
+
+########################################################################################################################
+
+- name: SAP SWPM Deployment - Display SAP System details after completion
+ ansible.builtin.debug:
+ msg: |
+ SAP System or Instance is deployed.
+ {% if __sap_swpm_sap_detected | d(false) and not sap_swpm_force | d(false) %}
+
+ NOTE: SAP Instance(s) were detected before installation.
+ SWPM installation steps were skipped to avoid potential issues with existing installation.
+
+ If you intend to proceed with the installation despite this warning,
+ set 'sap_swpm_force' variable to 'true' and execute this Ansible Role again.
+ {% endif %}
+ {% if __sap_swpm_sap_detected | d(false) and sap_swpm_force | d(false) %}
+
+ NOTE: SAP Instance(s) were detected before installation.
+ SWPM installation steps were executed regardless,
+ because 'sap_swpm_force' variable is set to 'true' (default is 'false').
+ {% endif %}
+
+ SAP Product - {{ sap_swpm_product_catalog_id }}
+ SID - {{ sap_swpm_sid | d('') }}
+ Primary Instance - {{ sap_swpm_pas_instance_nr | d('') }}
+ Host - {{ ansible_facts['hostname'] }}
+ FQDN - {{ ansible_facts['fqdn'] }}
+ IP - {{ ansible_facts['default_ipv4'].address | d(ansible_facts['all_ipv4_addresses'][0]) }}
+
+
# Now that SWPM finished we may need to deal with SUM before continuing when sap_swpm_sum_start: 'true'
# and if we are doing OneHost or CI/PAS installation
# If observer mode is enabled, SWPM will wait for SUM to finish before continuing so we can't do anything here
@@ -65,3 +85,4 @@
- not __sap_swpm_fact_observer_mode
- "'NW_ABAP_CI:' in sap_swpm_product_catalog_id | string or
'NW_ABAP_OneHost:' in sap_swpm_product_catalog_id | string"
+ - not __sap_swpm_sap_detected | d(false) or sap_swpm_force | d(false)
diff --git a/roles/sap_swpm/tasks/post_install/set_password_expiration.yml b/roles/sap_swpm/tasks/post_install/set_password_expiration.yml
new file mode 100644
index 00000000..f1adbbd5
--- /dev/null
+++ b/roles/sap_swpm/tasks/post_install/set_password_expiration.yml
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# NOTE: This can be replaced completely by 'ansible.builtin.user' module when we increase minimum ansible version to 2.18!
+# Argument '-I' is set by module parameter 'password_expire_account_disable' added in 2.18.
+# ansible.builtin.user:
+# name: "{{ user_item }}"
+# password_expire_min: 0 # chage -m 0
+# password_expire_max: 99999 # chage -M 99999
+# password_expire_account_disable: -1 # chage -I -1
+# expires: -1 # chage -E -1
+
+
+- name: SAP SWPM Post Install - Get current chage status for user {{ user_item }}
+ ansible.builtin.command:
+ cmd: chage -l {{ user_item }}
+ register: __sap_swpm_register_post_install_change
+ changed_when: false
+ become: true
+
+# Reason for noqa: Command 'chage' does not have stdout to check against so we let module decide what is considered change.
+- name: SAP SWPM Post Install - Ensure min days is 0 for {{ user_item }} # noqa no-changed-when
+ ansible.builtin.command:
+ cmd: chage -m 0 {{ user_item }}
+ become: true
+ when: not __sap_swpm_register_post_install_change.stdout
+ | regex_search('Minimum number of days between password change\\s+:\\s+0')
+
+# Reason for noqa: Command 'chage' does not have stdout to check against so we let module decide what is considered change.
+- name: SAP SWPM Post Install - Ensure max days is 99999 for {{ user_item }} # noqa no-changed-when
+ ansible.builtin.command:
+ cmd: chage -M 99999 {{ user_item }}
+ become: true
+ when: not __sap_swpm_register_post_install_change.stdout
+ | regex_search('Maximum number of days between password change\\s+:\\s+99999')
+
+# Reason for noqa: Command 'chage' does not have stdout to check against so we let module decide what is considered change.
+- name: SAP SWPM Post Install - Ensure password inactive is disabled (-1) for {{ user_item }} # noqa no-changed-when
+ ansible.builtin.command:
+ cmd: chage -I -1 {{ user_item }}
+ become: true
+ when: not __sap_swpm_register_post_install_change.stdout
+ | regex_search('Password inactive\\s+:\\s+never')
+
+# Reason for noqa: Command 'chage' does not have stdout to check against so we let module decide what is considered change.
+- name: SAP SWPM Post Install - Ensure account expiration is disabled (-1) for {{ user_item }} # noqa no-changed-when
+ ansible.builtin.command:
+ cmd: chage -E -1 {{ user_item }}
+ become: true
+ when: not __sap_swpm_register_post_install_change.stdout
+ | regex_search('Account expires\\s+:\\s+never')
diff --git a/roles/sap_swpm/tasks/pre_install.yml b/roles/sap_swpm/tasks/pre_install.yml
index 52a53829..1f79df28 100644
--- a/roles/sap_swpm/tasks/pre_install.yml
+++ b/roles/sap_swpm/tasks/pre_install.yml
@@ -88,7 +88,7 @@
- sap_swpm_templates_install_dictionary[sap_swpm_templates_product_input]['sap_swpm_inifile_parameters_dict'] is defined
- sap_swpm_templates_install_dictionary[sap_swpm_templates_product_input]['sap_swpm_inifile_custom_values_dictionary'] is defined
-# Unconditionally define sap_swpm_product_catalog_id from low level member.
+ # Unconditionally define sap_swpm_product_catalog_id from low level member.
- name: SAP SWPM Pre Install - Define 'sap_swpm_product_catalog_id' from low level member with the same name
ansible.builtin.set_fact:
sap_swpm_product_catalog_id: "{{ sap_swpm_templates_install_dictionary[sap_swpm_templates_product_input]['sap_swpm_product_catalog_id'] }}"
@@ -98,7 +98,7 @@
ansible.builtin.debug:
msg: "sap_swpm_product_catalog_id: '{{ sap_swpm_product_catalog_id }}'"
-# Unconditionally define sap_swpm_inifile_sections_list from low level member.
+ # Unconditionally define sap_swpm_inifile_sections_list from low level member.
- name: SAP SWPM Pre Install - Define 'sap_swpm_inifile_sections_list' from low level member
ansible.builtin.set_fact:
sap_swpm_inifile_sections_list: "{{ sap_swpm_templates_install_dictionary[sap_swpm_templates_product_input]['sap_swpm_inifile_list'] |
@@ -107,7 +107,7 @@
sap_swpm_templates_install_dictionary[sap_swpm_templates_product_input]['sap_swpm_inifile_sections_list'] is defined
no_log: true
-# Define sap_swpm_role_parameters_dict from low level member.
+ # Define sap_swpm_role_parameters_dict from low level member.
- name: SAP SWPM Pre Install - Define 'sap_swpm_role_parameters_dict' from low level member
ansible.builtin.set_fact:
sap_swpm_role_parameters_dict: "{{ sap_swpm_templates_install_dictionary[sap_swpm_templates_product_input]['sap_swpm_inifile_dictionary'] |
@@ -124,8 +124,8 @@
- sap_swpm_role_parameters_dict | length > 0
ignore_errors: true
-# Define role variables from low level member, looping over 'sap_swpm_role_parameters_dict'.
-# Reason for noqa: We are setting variables from the content of a dict, and the variable names are in the dict keys.
+ # Define role variables from low level member, looping over 'sap_swpm_role_parameters_dict'.
+ # Reason for noqa: We are setting variables from the content of a dict, and the variable names are in the dict keys.
- name: SAP SWPM Pre Install - Define role variables from low level member, looping over 'sap_swpm_role_parameters_dict' # noqa var-naming[no-jinja]
ansible.builtin.set_fact:
"{{ __sap_swpm_role_parameters_dict_line_item.key }}": "{{ __sap_swpm_role_parameters_dict_line_item.value }}"
@@ -137,7 +137,7 @@
- sap_swpm_role_parameters_dict | length > 0
no_log: true
-# Define sap_swpm_inifile_parameters_dict from low level member, or use top level variable if defined. Low level definition has precedence.
+ # Define sap_swpm_inifile_parameters_dict from low level member, or use top level variable if defined. Low level definition has precedence.
- name: SAP SWPM Pre Install - Define 'sap_swpm_inifile_parameters_dict' from low level member
ansible.builtin.set_fact:
sap_swpm_inifile_parameters_dict: "{{ sap_swpm_templates_install_dictionary[sap_swpm_templates_product_input]['sap_swpm_inifile_custom_values_dictionary'] |
@@ -271,11 +271,11 @@
ansible.builtin.fail:
msg: "Validation failed - Please configure the failed role variables correctly and restart!"
+
# Assert correct hostname and IP resolution of virtual hostname and IP if:
# - sapinst is requested to be run, and
# - /etc/hosts file is requested to be updated, and
# - sap_swpm_virtual_hostname has been defined
-
- name: SAP SWPM Pre Install - Assert hostname resolution for HA
ansible.builtin.include_tasks:
file: pre_install/assert_hostname_resolution_for_ha.yml
@@ -288,107 +288,107 @@
- sap_swpm_virtual_hostname | length > 0
tags: sap_swpm_update_etchosts
-################
-# Run Preinstallation Steps Based on Run Mode
-################
-- name: SAP SWPM Pre Install - Run Preinstallation Steps
+# Run Pre-installation Steps Based on Run Mode
+# NOTE: This task file includes SAP detection logic, which will be skipped for these tags.
+- name: SAP SWPM Pre Install - Run Pre-installation Steps
ansible.builtin.include_tasks: pre_install/swpm_prepare.yml
tags:
- sap_swpm_generate_inifile
- sap_swpm_sapinst_commandline
-################
-# Pre Install Optional Tasks
-################
+- name: Block to execute on empty host or in force mode
+ when: not __sap_swpm_sap_detected | d(false) or sap_swpm_force | d(false)
+ block:
-# Firewall
-- name: SAP SWPM Pre Install - Configure Firewall
- ansible.builtin.include_tasks:
- file: pre_install/firewall.yml
- apply:
+ ## Pre Install Optional Tasks
+ # Firewall
+ - name: SAP SWPM Pre Install - Configure Firewall
+ ansible.builtin.include_tasks:
+ file: pre_install/firewall.yml
+ apply:
+ tags: sap_swpm_configure_firewall
+ when:
+ - sap_swpm_configure_firewall
tags: sap_swpm_configure_firewall
- when:
- - sap_swpm_configure_firewall
- tags: sap_swpm_configure_firewall
-# /etc/hosts
-
-- name: SAP SWPM Pre Install - Update /etc/hosts
- ansible.builtin.include_tasks:
- file: pre_install/update_etchosts.yml
- apply:
+ # /etc/hosts
+ - name: SAP SWPM Pre Install - Update /etc/hosts
+ ansible.builtin.include_tasks:
+ file: pre_install/update_etchosts.yml
+ apply:
+ tags: sap_swpm_update_etchosts
+ when:
+ - sap_swpm_run_sapinst
+ - sap_swpm_update_etchosts
tags: sap_swpm_update_etchosts
- when:
- - sap_swpm_run_sapinst
- - sap_swpm_update_etchosts
- tags: sap_swpm_update_etchosts
-################
-# Display Parameters
-################
-- name: SAP SWPM - Display Software
- ansible.builtin.debug:
- msg: |
- SAPCAR: {{ __sap_swpm_fact_sapcar_path }}: {{ __sap_swpm_fact_sapcar_file_name }}
- SWPM: {{ __sap_swpm_fact_swpm_path }}: {{ __sap_swpm_fact_swpm_file_name }}
- {% if not sap_swpm_generic %}
- SAPEXEDB: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_sapexedb_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
- SAPEXE: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_sapexe_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
- IGS: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_igs_exe_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
- IGS Helper: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_igs_helper_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
- {% if 'Webdispatcher' in sap_swpm_product_catalog_id | string %}
- WEBDISP: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_sapwebdisp_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
- {% endif %}
- {% endif %}
- {% if __sap_swpm_fact_installation_type in ['maint_plan_stack', 'ha_maint_plan_stack'] %}
- MP Stack: {{ (sap_swpm_mp_stack_path | d('', true) + '/' + sap_swpm_mp_stack_file_name | d('', true)) | regex_replace('//*', '/') }}"
- {% endif %}
- {% if sap_swpm_backup_location is defined and sap_swpm_backup_location | string | trim | length > 0 %}
- Backup: {{ sap_swpm_backup_location }}
- {% endif %}
+ # Display Parameters
+ - name: SAP SWPM - Display Software
+ ansible.builtin.debug:
+ msg: |
+ SAPCAR: {{ __sap_swpm_fact_sapcar_path }}: {{ __sap_swpm_fact_sapcar_file_name }}
+ SWPM: {{ __sap_swpm_fact_swpm_path }}: {{ __sap_swpm_fact_swpm_file_name }}
+ {% if not sap_swpm_generic %}
+ SAPEXEDB: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_sapexedb_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
+ SAPEXE: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_sapexe_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
+ IGS: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_igs_exe_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
+ IGS Helper: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_igs_helper_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
+ {% if 'Webdispatcher' in sap_swpm_product_catalog_id | string %}
+ WEBDISP: {{ __sap_swpm_fact_software_path }}: {{ __sap_swpm_register_sapwebdisp_files.files | map(attribute='path') | map('basename') | join(', ') | d('') }}
+ {% endif %}
+ {% endif %}
+ {% if __sap_swpm_fact_installation_type in ['maint_plan_stack', 'ha_maint_plan_stack'] %}
+ MP Stack: {{ (sap_swpm_mp_stack_path | d('', true) + '/' + sap_swpm_mp_stack_file_name | d('', true)) | regex_replace('//*', '/') }}"
+ {% endif %}
+ {% if sap_swpm_backup_location is defined and sap_swpm_backup_location | string | trim | length > 0 %}
+ Backup: {{ sap_swpm_backup_location }}
+ {% endif %}
+
+
+ - name: SAP SWPM - Installation Process
+ ansible.builtin.debug:
+ msg: |
+ Ready to run SWPM for SAP Software Installation
+ - SAP Product - {{ sap_swpm_product_catalog_id }}
+ - SID - {{ sap_swpm_sid }}
+ - Host - {{ ansible_facts['hostname'] }}
-- name: SAP SWPM - Installation Process
- ansible.builtin.debug:
- msg:
- - " Ready to run SWPM for SAP Software Installation "
- - " "
- - " SAP Product - {{ sap_swpm_product_catalog_id }} "
- - " SID - {{ sap_swpm_sid }} "
- - " Host - {{ ansible_facts['hostname'] }} "
- - " "
- - " The installation can take up to 3 hours. Run the following command as root"
- - " on {{ ansible_facts['hostname'] }} to display the installation logs:"
- - " # tail -f $(cat {{ __sap_swpm_fact_tempdir_from_env }}/sapinst_instdir/.lastInstallationLocation)/sapinst.log"
-
-################
-# Set sapinst command
-################
-
-- name: SAP SWPM Pre Install - Set the sapinst command parameters
- vars:
- sap_swpm_command_guiserver: "{{ 'SAPINST_START_GUISERVER=false' if not __sap_swpm_fact_observer_mode else '' }}"
- sap_swpm_command_observer: "{{ 'SAPINST_REMOTE_ACCESS_USER=' + __sap_swpm_fact_remote_access_user + ' SAPINST_REMOTE_ACCESS_USER_IS_TRUSTED=true' if __sap_swpm_fact_observer_mode and __sap_swpm_fact_remote_access_user | d('', true) | length > 0 else '' }}"
- ansible.builtin.set_fact:
- __sap_swpm_fact_command_inifile: "SAPINST_INPUT_PARAMETERS_URL={{ __sap_swpm_register_tmpdir.path }}/inifile.params"
- __sap_swpm_fact_command_product_id: "SAPINST_EXECUTE_PRODUCT_ID={{ sap_swpm_product_catalog_id }}"
- # If SWPM is running a normal install Ansible Variable __sap_swpm_fact_command_virtual_hostname is blank
- # IF SWPM is running a HA installation, Ansible Variable __sap_swpm_fact_command_virtual_hostname is set and contains "SAPINST_USE_HOSTNAME={{ sap_swpm_virtual_hostname }} IS_HOST_LOCAL_USING_STRING_COMPARE=true"
- # If SWPM is running a MP Stack XML installation, Ansible Variable __sap_swpm_fact_command_mp_stack is set and contains "SAPINST_STACK_XML={{ sap_swpm_mp_stack_path }} + '/' (if needed) + {{ sap_swpm_mp_stack_file_name }}"
- __sap_swpm_fact_command_extra_args: "SAPINST_SKIP_DIALOGS=true {{ sap_swpm_command_guiserver }} {{ sap_swpm_command_observer }} {{ __sap_swpm_fact_command_virtual_hostname | d('') }} {{ __sap_swpm_fact_command_mp_stack | d('') }} {{ sap_swpm_command_extra_args | d('') }}"
- tags: sap_swpm_sapinst_commandline
-
-- name: Set fact for the sapinst command line
- ansible.builtin.set_fact:
- __sap_swpm_sapinst_command: >-
- umask {{ sap_swpm_umask | d('022') }} ; ./sapinst {{ __sap_swpm_fact_command_inifile }}
- {{ __sap_swpm_fact_command_product_id }}
- {{ __sap_swpm_fact_command_extra_args }}
- tags: sap_swpm_sapinst_commandline
+ - The installation can take up to 3 hours. Run the following command as root
+ - on {{ ansible_facts['hostname'] }} to display the installation logs:
+ - tail -f $(cat {{ __sap_swpm_fact_tempdir_from_env }}/sapinst_instdir/.lastInstallationLocation)/sapinst.log
-- name: Display the sapinst command line
- ansible.builtin.debug:
- msg: "SAP SWPM install command: '{{ __sap_swpm_sapinst_command }}'"
- tags: sap_swpm_sapinst_commandline
+
+ # Set sapinst command
+ - name: SAP SWPM Pre Install - Set the sapinst command parameters
+ vars:
+ sap_swpm_command_guiserver: "{{ 'SAPINST_START_GUISERVER=false' if not __sap_swpm_fact_observer_mode else '' }}"
+ sap_swpm_command_observer:
+ "{{ 'SAPINST_REMOTE_ACCESS_USER=' + __sap_swpm_fact_remote_access_user + ' SAPINST_REMOTE_ACCESS_USER_IS_TRUSTED=true'
+ if __sap_swpm_fact_observer_mode and __sap_swpm_fact_remote_access_user | d('', true) | length > 0 else '' }}"
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_command_inifile: "SAPINST_INPUT_PARAMETERS_URL={{ __sap_swpm_register_tmpdir.path }}/inifile.params"
+ __sap_swpm_fact_command_product_id: "SAPINST_EXECUTE_PRODUCT_ID={{ sap_swpm_product_catalog_id }}"
+ # If SWPM is running a normal install Ansible Variable __sap_swpm_fact_command_virtual_hostname is blank
+ # IF SWPM is running a HA installation, Ansible Variable __sap_swpm_fact_command_virtual_hostname is set
+ # and contains "SAPINST_USE_HOSTNAME={{ sap_swpm_virtual_hostname }} IS_HOST_LOCAL_USING_STRING_COMPARE=true"
+ # If SWPM is running a MP Stack XML installation, Ansible Variable __sap_swpm_fact_command_mp_stack is set
+ # and contains "SAPINST_STACK_XML={{ sap_swpm_mp_stack_path }} + '/' (if needed) + {{ sap_swpm_mp_stack_file_name }}"
+ __sap_swpm_fact_command_extra_args: "SAPINST_SKIP_DIALOGS=true {{ sap_swpm_command_guiserver }} {{ sap_swpm_command_observer }} {{
+ __sap_swpm_fact_command_virtual_hostname | d('') }} {{ __sap_swpm_fact_command_mp_stack | d('') }} {{ sap_swpm_command_extra_args | d('') }}"
+ tags: sap_swpm_sapinst_commandline
+
+ - name: Set fact for the sapinst command line
+ ansible.builtin.set_fact:
+ __sap_swpm_sapinst_command: >-
+ umask {{ sap_swpm_umask | d('022') }} ; ./sapinst {{ __sap_swpm_fact_command_inifile }}
+ {{ __sap_swpm_fact_command_product_id }}
+ {{ __sap_swpm_fact_command_extra_args }}
+ tags: sap_swpm_sapinst_commandline
+
+ - name: Display the sapinst command line
+ ansible.builtin.debug:
+ msg: "SAP SWPM install command: '{{ __sap_swpm_sapinst_command }}'"
+ tags: sap_swpm_sapinst_commandline
diff --git a/roles/sap_swpm/tasks/pre_install/detect_sap.yml b/roles/sap_swpm/tasks/pre_install/detect_sap.yml
new file mode 100644
index 00000000..446a3c46
--- /dev/null
+++ b/roles/sap_swpm/tasks/pre_install/detect_sap.yml
@@ -0,0 +1,373 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+### List of scenarios for determining next steps.
+# This ensures that installation can be skipped when needed to prevent any potential issues affecting existing installations.
+# Optional override is available in defaults/main.yml to execute regardless of warning.
+# NOTE: SAP Host Agent can be skipped during installation using 'sap_swpm_install_saphostagent: false'.
+
+## 'saphostctrl' is present:
+# 1. SKIP - 'root' saphostctrl check finds the desired SID and instance number.
+# 2. INSTALL - 'root' saphostctrl check does not find the desired SID and instance number, and no other indicators are found.
+# 3. FAIL - 'root' saphostctrl check does not find the desired SID and instance number, and any other indicators are found.
+
+# 'saphostctrl' is not present:
+# 4. SKIP - 'sidadm' sapcontrol check finds the desired SID and instance number.
+# 5. INSTALL - 'sidadm' sapcontrol check does not find the desired SID and instance number, and no other indicators are found.
+# 6. FAIL - 'sidadm' sapcontrol check does not find the desired SID and instance number, and any other indicators are found.
+
+# Breakdown of potential scenarios and actions:
+# | saphostctrl | sapcontrol | Instance Found | Files Found | Action
+# |-------------|------------|----------------|-------------|--------
+# | Yes | N/A | Yes | N/A | SKIP
+# | Yes | N/A | No | No | INSTALL
+# | Yes | N/A | No | Any | FAIL
+# | No | Yes | Yes | N/A | SKIP
+# | No | Yes | No | Any | FAIL
+# | No | No | No | No | INSTALL
+
+# List of non-saphostctrl indicators for potential existing SAP installation:
+# A. Files found in /usr/sap/ directory.
+# B. Profile files found in /sapmnt//profile/ directory.
+# C. Services found in /usr/sap/sapservices file.
+
+
+# Check 1: Check if the group 'sapsys' exists with correct ID.
+# The role supports specifying the 'sapsys' group id in variable `sap_swpm_sapsys_gid`.
+# The installation will fail if there is already a group named sapsys but with a different ID.
+# NOTE: This is executed even if the above checks detect an existing installation, because we want to identify conflicts.
+
+# This is also used for validation before using 'sidadm' sapcontrol.
+- name: SAP SWPM Pre Install - Detect - Get details of the 'sapsys' group
+ ansible.builtin.getent:
+ database: group
+ key: sapsys
+ # Hide output if group does not exist.
+ failed_when: false
+
+# getent_group will be populated, with fields:
+# [0] - 'X' if the password is set.
+# [1] - Group ID.
+# [2] - Group members.
+- name: SAP SWPM Pre Install - Detect - Fail if conflict exists for group ID 'sapsys'
+ ansible.builtin.fail:
+ msg: |
+ FAIL: Group 'sapsys' exists but with a different group ID.
+ Expected: '{{ sap_swpm_sapsys_gid }}'
+ Found: '{{ ansible_facts["getent_group"]['sapsys'][1] }}'
+ when:
+ - sap_swpm_sapsys_gid is defined
+ - sap_swpm_sapsys_gid is string
+ - sap_swpm_sapsys_gid | trim | length > 0
+ # Actual comparison of Group ID
+ - ansible_facts["getent_group"]['sapsys'][1] is defined
+ - ansible_facts["getent_group"]['sapsys'][1] | int != sap_swpm_sapsys_gid | int
+
+
+# Check 2: Check if the user 'sidadm' exists with correct ID.
+# The role supports specifying the 'sidadm' user id in variable `sap_swpm_sidadm_uid`.
+# The installation will fail if there is already a user named sidadm but with a different ID.
+# NOTE: This is executed even if above check detected existing installation, because we want to know of conflict.
+
+# This is also used for validation before using 'sidadm' sapcontrol.
+- name: SAP SWPM Pre Install - Detect - Get details about available users
+ ansible.builtin.getent:
+ database: passwd
+ # Hide output if user does not exist.
+ failed_when: false
+
+# getent_passwd will be populated, with fields:
+# [0] - 'X' if the password is set.
+# [1] - User ID.
+# [2] - Group ID.
+- name: SAP SWPM Pre Install - Detect - Fail if conflict exists for user ID '{{ __sap_swpm_sidadm_user }}'
+ ansible.builtin.fail:
+ msg: |
+ FAIL: User '{{ __sap_swpm_sidadm_user }}' exists but with a different user ID.
+ Expected: '{{ sap_swpm_sidadm_uid }}'
+ Found: '{{ ansible_facts["getent_passwd"][__sap_swpm_sidadm_user][1] }}'
+ when:
+ - sap_swpm_sidadm_uid is defined
+ - sap_swpm_sidadm_uid is string
+ - sap_swpm_sidadm_uid | trim | length > 0
+ # Actual comparison of User ID
+ - ansible_facts["getent_passwd"][__sap_swpm_sidadm_user][1] is defined
+ - ansible_facts["getent_passwd"][__sap_swpm_sidadm_user][1] | int != sap_swpm_sidadm_uid | int
+
+
+# Check 3: Check if the user 'sapadm' exists with correct ID.
+# The role supports specifying the 'sapadm' user id in variable `sap_swpm_sapadm_uid`.
+# The installation will fail if there is already a user named sapadm but with a different ID.
+# NOTE: This is executed even if above check detected existing installation, because we want to know of conflict.
+
+# The getent data for 'sapadm' is collected in the previous check.
+# Running getent twice with different key filters would prevent checking both users in subsequent tasks.
+
+# getent_passwd will be populated, with fields:
+# [0] - 'X' if the password is set.
+# [1] - User ID.
+# [2] - Group ID.
+- name: SAP SWPM Pre Install - Detect - Fail if conflict exists for user ID 'sapadm'
+ ansible.builtin.fail:
+ msg: |
+ FAIL: User 'sapadm' exists but with a different user ID.
+ Expected: '{{ sap_swpm_sapadm_uid }}'
+ Found: '{{ ansible_facts["getent_passwd"]['sapadm'][1] }}'
+ when:
+ - sap_swpm_sapadm_uid is defined
+ - sap_swpm_sapadm_uid is string
+ - sap_swpm_sapadm_uid | trim | length > 0
+ # Actual comparison of User ID
+ - ansible_facts["getent_passwd"]['sapadm'][1] is defined
+ - ansible_facts["getent_passwd"]['sapadm'][1] | int != sap_swpm_sapadm_uid | int
+
+
+### Execute global data gathering before per-instance detection.
+
+# Check 4: Detect 'saphostctrl' and search for SID and instance number in 'ListInstances' output.
+- name: SAP SWPM Pre Install - Detect - Stat /usr/sap/hostctrl/exe/saphostctrl file
+ ansible.builtin.stat:
+ path: /usr/sap/hostctrl/exe/saphostctrl
+ register: __sap_swpm_register_saphostctrl_stat
+
+# Output example:
+# Inst Info : AE1 - 00 - ae1ascs-ha - 793, patch 51, commit 375ae433dffb023985041c1d7b4687b1bde8d211
+# Inst Info : AE1 - 10 - ae1ers-ha - 793, patch 51, commit 375ae433dffb023985041c1d7b4687b1bde8d211
+- name: SAP SWPM Pre Install - Detect - Execute saphostctrl function ListInstances
+ ansible.builtin.command:
+ cmd: /usr/sap/hostctrl/exe/saphostctrl -function ListInstances
+ register: __sap_swpm_register_saphostctrl_list_instances
+ changed_when: false
+ # Hides failed output, but 'rc' can be still used for verification.
+ failed_when: false
+ when:
+ - __sap_swpm_register_saphostctrl_stat.stat.exists
+ - __sap_swpm_register_saphostctrl_stat.stat.executable
+
+- name: SAP SWPM Pre Install - Detect - Show reason why saphostctrl check was skipped
+ ansible.builtin.debug:
+ msg: |
+ INFO: Detection of existing SAP instance installation with 'saphostctrl' command was skipped.
+ {% if not __sap_swpm_register_saphostctrl_stat.stat.exists %}
+ The executable '/usr/sap/hostctrl/exe/saphostctrl' is not available.
+ {% elif not __sap_swpm_register_saphostctrl_stat.stat.executable %}
+ The executable '/usr/sap/hostctrl/exe/saphostctrl' is not executable.
+ {% endif %}
+ {% if __sap_swpm_register_saphostctrl_list_instances.rc is defined
+ and __sap_swpm_register_saphostctrl_list_instances.rc != 0 %}
+ Return code: {{ __sap_swpm_register_saphostctrl_list_instances.rc | d('1') }}
+ Output: {{ __sap_swpm_register_saphostctrl_list_instances.stdout | d('') }}
+ Error: {{ __sap_swpm_register_saphostctrl_list_instances.stderr | d('') }}
+ {% endif %}
+ when:
+ - not __sap_swpm_register_saphostctrl_stat.stat.exists
+ or not __sap_swpm_register_saphostctrl_stat.stat.executable
+ or (__sap_swpm_register_saphostctrl_list_instances.rc is defined
+ and __sap_swpm_register_saphostctrl_list_instances.rc != 0)
+
+- name: SAP SWPM Pre Install - Detect - Show saphostctrl command output in verbose mode
+ ansible.builtin.debug:
+ var: __sap_swpm_register_saphostctrl_list_instances
+ when: ansible_verbosity >= 2
+
+
+# Check 5: Use 'sapcontrol' and search for SID and instance number in 'GetInstanceProperties' output.
+# The `sapcontrol` command from SAP kernel files can be used to obtain instance information.
+- name: Block for sapcontrol checks
+ when:
+ - ansible_facts["getent_group"]['sapsys'][1] is defined
+ - ansible_facts["getent_passwd"][__sap_swpm_sidadm_user][1] is defined
+ block:
+
+ # Reason for noqa: Switch to user requires shell module to load environment variables.
+ - name: SAP SWPM Pre Install - Detect - Check if 'sapcontrol' command is available # noqa: command-instead-of-shell
+ ansible.builtin.shell:
+ cmd: "bash -lc 'which sapcontrol'"
+ become: true
+ become_user: "{{ __sap_swpm_sidadm_user }}"
+ register: __sap_swpm_register_which_sapcontrol
+ changed_when: false
+ ignore_errors: true
+
+ - name: SAP SWPM Pre Install - Detect - Show reason why sapcontrol check was skipped - unavailable
+ ansible.builtin.debug:
+ msg: |
+ INFO: Detection of existing SAP instance installation with 'sapcontrol' command was skipped.
+ Command 'sapcontrol' is not available for user '{{ __sap_swpm_sidadm_user }}'.
+ when: __sap_swpm_register_which_sapcontrol.rc != 0
+
+
+# Check 6: Check presence of files in directories.
+- name: SAP SWPM Pre Install - Detect - Stat directory {{ '/usr/sap/' ~ __sap_swpm_sid ~ '/' }}
+ ansible.builtin.stat:
+ path: "{{ '/usr/sap/' ~ __sap_swpm_sid ~ '' }}"
+ register: __sap_swpm_register_sapmnt_stat
+
+- name: SAP SWPM Pre Install - Detect - Find instance directories in {{ '/usr/sap/' ~ __sap_swpm_sid ~ '/' }}
+ ansible.builtin.find:
+ paths: "/usr/sap/{{ __sap_swpm_sid }}/"
+ file_type: directory
+ recurse: false
+ register: __sap_swpm_register_instance_dirs_find
+ when: __sap_swpm_register_sapmnt_stat.stat.exists
+
+
+# Check 7: Check contents of /usr/sap/sapservices
+# This file can be managed by cluster software like Veritas,
+# therefore instances may not appear if cluster is stopped.
+- name: SAP SWPM Pre Install - Detect - Stat /usr/sap/sapservices file
+ ansible.builtin.stat:
+ path: /usr/sap/sapservices
+ register: __sap_swpm_register_sapservices_stat
+
+- name: SAP SWPM Pre Install - Detect - Read /usr/sap/sapservices file
+ ansible.builtin.slurp:
+ path: /usr/sap/sapservices
+ register: __sap_swpm_register_sapservices_slurp
+ when: __sap_swpm_register_sapservices_stat.stat.exists
+
+
+# Check 8: Check profile files in /sapmnt//profile/
+- name: SAP SWPM Pre Install - Detect - Stat directory {{ '/sapmnt/' ~ __sap_swpm_sid ~ '/profile/' }}
+ ansible.builtin.stat:
+ path: "{{ '/sapmnt/' ~ __sap_swpm_sid ~ '/profile/' }}"
+ register: __sap_swpm_register_profile_dir_stat
+
+- name: SAP SWPM Pre Install - Detect - Find profile files in {{ '/sapmnt/' ~ __sap_swpm_sid ~ '/profile/' }}
+ ansible.builtin.find:
+ paths: "/sapmnt/{{ __sap_swpm_sid }}/profile/"
+ file_type: file
+ recurse: false
+ register: __sap_swpm_register_profiles_find
+ when: __sap_swpm_register_profile_dir_stat.stat.exists
+
+
+# Include task file for each instance.
+- name: SAP SWPM Pre Install - Detect - Include tasks for each instance
+ ansible.builtin.include_tasks:
+ file: detect_sap_instance.yml
+ loop: "{{ __sap_swpm_detect_instances }}"
+ loop_control:
+ loop_var: instance_item
+ label: "{{ instance_item.number }}"
+ when: __sap_swpm_detect_instances is defined
+
+
+### Decision section based on collected facts.
+- name: SAP SWPM Pre Install - Detect - Show detection results in verbose mode
+ ansible.builtin.debug:
+ var: __sap_swpm_instance_results
+ when: ansible_verbosity >= 2
+
+# Set boolean facts for easier decision making in the next steps, based on collected results for all instances.
+- name: SAP SWPM Pre Install - Detect - Set fact with results for all instances
+ ansible.builtin.set_fact:
+ # SKIP should run only if all expected instances are detected.
+ __sap_swpm_fact_detect_saphostctrl_all_found: >-
+ {{ __sap_swpm_instance_results | selectattr('saphostctrl', 'equalto', true) | list | length == __sap_swpm_detect_instances | length }}
+ __sap_swpm_fact_detect_sapcontrol_all_found: >-
+ {{ __sap_swpm_instance_results | selectattr('sapcontrol', 'equalto', true) | list | length == __sap_swpm_detect_instances | length }}
+ # FAIL should run if any of the indicators is found, but SKIP is not triggered.
+ __sap_swpm_fact_detect_files_any_found: >-
+ {{ __sap_swpm_instance_results | selectattr('files', 'equalto', true) | list | length > 0 }}
+ __sap_swpm_fact_detect_sapservices_any_found: >-
+ {{ __sap_swpm_instance_results | selectattr('sapservices', 'equalto', true) | list | length > 0 }}
+ __sap_swpm_fact_detect_profiles_any_found: >-
+ {{ __sap_swpm_instance_results | selectattr('profiles', 'equalto', true) | list | length > 0 }}
+
+
+# 3. FAIL - 'root' saphostctrl check does not find the desired SID and instance number, and any other indicators are found
+# 6. FAIL - 'sidadm' sapcontrol check does not find the desired SID and instance number, and any other indicators are found
+- name: SAP SWPM Pre Install - Detect - Fail if potential installation was detected
+ ansible.builtin.fail:
+ msg: |
+ FAIL: Potential existing SAP System installation detected based on the presence of identifiable indicators.
+ {% if __sap_swpm_fact_detect_files_any_found | d(false) %}
+ - Existence of files in '/usr/sap/{{ __sap_swpm_sid }}/' directory.
+ {% endif %}
+ {% if __sap_swpm_fact_detect_sapservices_any_found | d(false) %}
+ - Existence of instance entries in /usr/sap/sapservices file.
+ {% endif %}
+ {% if __sap_swpm_fact_detect_profiles_any_found | d(false) %}
+ - Existence of profile files in '/sapmnt/{{ __sap_swpm_sid }}/profile/' directory.
+ {% endif %}
+ These checks trigger failure only when 'saphostctrl' and 'sapcontrol' fail to detect all instances.
+
+ Please verify the details above and ensure that you are not trying to install over an existing installation.
+ If you want to proceed with the installation regardless, please set 'sap_swpm_force' variable to true.
+ when:
+ - not sap_swpm_force | d(false)
+ - not __sap_swpm_fact_detect_saphostctrl_all_found | d(false)
+ - not __sap_swpm_fact_detect_sapcontrol_all_found | d(false)
+ - __sap_swpm_fact_detect_files_any_found | d(false)
+ or __sap_swpm_fact_detect_sapservices_any_found | d(false)
+ or __sap_swpm_fact_detect_profiles_any_found | d(false)
+
+
+# 1. SKIP - 'root' saphostctrl check finds the desired SID and instance number.
+# 4. SKIP - 'sidadm' sapcontrol check finds the desired SID and instance number.
+- name: Block for SKIP
+ when:
+ # If saphostctrl is available, all instances must be detected from it,
+ # otherwise sapcontrol must have detected all instances.
+ - __sap_swpm_fact_detect_saphostctrl_all_found
+ or __sap_swpm_fact_detect_sapcontrol_all_found
+ block:
+ - name: SAP SWPM Pre Install - Detect - Set fact if installation was detected
+ ansible.builtin.set_fact:
+ __sap_swpm_sap_detected: true
+
+ - name: SAP SWPM Pre Install - Detect - Inform that installation was detected
+ ansible.builtin.debug:
+ msg: |
+ {% if not sap_swpm_force %}
+ WARN: Existing SAP System installation detected using 'saphostctrl' or 'sapcontrol' command checks.
+ Installation steps will be skipped!
+
+ If you intend to proceed with the installation despite this warning,
+ set 'sap_swpm_force' variable to 'true' and execute this Ansible Role again.
+ {% else %}
+ INFO: Existing SAP System installation detected using 'saphostctrl' or 'sapcontrol' command checks.
+ Installation steps will be executed regardless,
+ because 'sap_swpm_force' variable is set to 'true' (default is 'false').
+ {% endif %}
+
+
+# 2. INSTALL - 'root' saphostctrl check does not find the desired SID and instance number, and no other indicators are found.
+# 5. INSTALL - 'sidadm' sapcontrol check does not find the desired SID and instance number, and no other indicators are found.
+- name: SAP SWPM Pre Install - Detect - Inform that no existing installation was detected
+ ansible.builtin.debug:
+ msg: |
+ INFO: No existing SAP System installation detected for SID {{ __sap_swpm_sid }}.
+ Installation will proceed.
+
+ Detection methods used:
+ - saphostctrl command (ListInstances)
+ - sapcontrol command (GetInstanceProperties)
+ - Instance directories (/usr/sap/{{ __sap_swpm_sid }}/)
+ - SAP services file (/usr/sap/sapservices)
+ - Profile files (/sapmnt/{{ __sap_swpm_sid }}/profile/)
+ when:
+ - not __sap_swpm_fact_detect_saphostctrl_all_found | d(false)
+ - not __sap_swpm_fact_detect_sapcontrol_all_found | d(false)
+ - not __sap_swpm_fact_detect_files_any_found | d(false)
+ - not __sap_swpm_fact_detect_sapservices_any_found | d(false)
+ - not __sap_swpm_fact_detect_profiles_any_found | d(false)
+
+
+# We cannot reliably detect some products,
+# but we want to inform the user about that fact and potential risks of proceeding without detection.
+- name: SAP SWPM Pre Install - Detect - Inform about products with limited detection capabilities
+ ansible.builtin.pause:
+ seconds: "{{ 30 if not sap_swpm_force | d(false) else 5 }}"
+ prompt: |
+ WARN: SAP Product {{ sap_swpm_product_catalog_id }} cannot be reliably detected by this role due to its nature.
+
+ {% if __sap_swpm_fact_product_prefix in __sap_swpm_product_detection_skipped['database_load'] %}
+ - Database Load does not store any local files that can be detected.
+ SWPM handles repeated execution by itself, but you need to proceed with caution when targeting live SAP System.
+ {% endif %}
+ when:
+ - __sap_swpm_fact_product_prefix | d('') | trim | length > 0
+ - __sap_swpm_fact_product_prefix
+ in __sap_swpm_product_detection_skipped | dict2items | map(attribute='value') | flatten | unique
diff --git a/roles/sap_swpm/tasks/pre_install/detect_sap_instance.yml b/roles/sap_swpm/tasks/pre_install/detect_sap_instance.yml
new file mode 100644
index 00000000..6430c33c
--- /dev/null
+++ b/roles/sap_swpm/tasks/pre_install/detect_sap_instance.yml
@@ -0,0 +1,232 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+### This is a dedicated file to perform per-instance detection checks.
+- name: SAP SWPM Pre Install - Detect - Reset detection facts before checks
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_saphostctrl_instance_found: false
+ __sap_swpm_fact_detect_sapcontrol_instance_found: false
+ __sap_swpm_fact_detect_instance_files: false
+ __sap_swpm_fact_detect_instance_sapservices_found: false
+ __sap_swpm_fact_detect_instance_profiles_found: false
+
+
+# Check 4: Detect 'saphostctrl' and search for SID and instance number in 'ListInstances' output.
+# saphostctrl can return non-zero exit code when sapstartsrv process is not running.
+- name: Block if saphostctrl ListInstances command executed successfully
+ when:
+ - __sap_swpm_register_saphostctrl_list_instances is defined
+ - __sap_swpm_register_saphostctrl_list_instances.rc is defined
+ - __sap_swpm_register_saphostctrl_list_instances.rc == 0
+ block:
+
+ # Match pattern: " - ".
+ # Example: Inst Info : AE1 - 10 - ae1ers-ha - 793, patch 51, commit 375ae433dffb023985041c1d7b4687b1bde8d211
+ - name: SAP SWPM Pre Install - Detect - Set fact if instance is detected in saphostctrl output - {{ instance_item.number | d('') }}
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_saphostctrl_instance_found: >-
+ {{ __sap_swpm_register_saphostctrl_list_instances.stdout_lines | d([])
+ | select('search', ' ' ~ __sap_swpm_sid ~ ' - ' ~ instance_item.number ~ ' ')
+ | list | length > 0 }}
+
+ # Check for conflict: instance number exists but with a different SID.
+ - name: SAP SWPM Pre Install - Detect - Fail if instance number conflict was found (Same NR, Different SID) - {{ instance_item.number | d('') }}
+ ansible.builtin.fail:
+ msg: |
+ FAIL: Conflict detected for instance number {{ instance_item.number }}!
+ This instance number is already used by a different SID.
+ Expected SID {{ __sap_swpm_sid }}, but found following lines:
+ {% for line in __conflict_lines %}
+ - {{ line }}
+ {% endfor %}
+ when: __conflict_lines | length > 0
+ vars:
+ __conflict_lines: >-
+ {{ __sap_swpm_register_saphostctrl_list_instances.stdout_lines | d([])
+ | select('search', ' - ' ~ instance_item.number ~ ' ')
+ | reject('search', ' ' ~ __sap_swpm_sid ~ ' - ')
+ | list }}
+
+
+# Check 5: Use 'sapcontrol' and search for SID and instance number in 'GetInstanceProperties' output.
+- name: Block if sapcontrol is available
+ when:
+ - __sap_swpm_register_which_sapcontrol is defined
+ - __sap_swpm_register_which_sapcontrol.rc is defined
+ - __sap_swpm_register_which_sapcontrol.rc == 0
+ block:
+
+ # Reason for noqa: Switch to user requires shell module to load environment variables.
+ - name: SAP SWPM Pre Install - Detect - Execute sapcontrol - {{ instance_item.number | d('') }} # noqa: command-instead-of-shell
+ ansible.builtin.shell:
+ cmd: "bash -lc 'sapcontrol -nr {{ instance_item.number }} -function GetInstanceProperties'"
+ become: true
+ become_user: "{{ __sap_swpm_sidadm_user }}"
+ register: __sap_swpm_register_sapcontrol_instance_properties
+ changed_when: false
+ # Hides failed output, but 'rc' can be still used for verification.
+ failed_when: false
+
+ - name: SAP SWPM Pre Install - Detect - Show reason why sapcontrol check was skipped - command failed - {{ instance_item.number | d('') }}
+ ansible.builtin.debug:
+ msg: |
+ INFO: Detection of existing SAP instance installation with 'sapcontrol' command was skipped.
+ Return code: {{ __sap_swpm_register_sapcontrol_instance_properties.rc | d('1') }}
+ Output: {{ __sap_swpm_register_sapcontrol_instance_properties.stdout | d('') }}
+ Error: {{ __sap_swpm_register_sapcontrol_instance_properties.stderr | d('') }}
+ when:
+ - __sap_swpm_register_sapcontrol_instance_properties.rc is defined
+ - __sap_swpm_register_sapcontrol_instance_properties.rc != 0
+
+ # Check if output contains correct SAPSYSTEMNAME.
+ - name: SAP SWPM Pre Install - Detect - Set fact if matching SID is found in sapcontrol output - {{ instance_item.number | d('') }}
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_sapcontrol_sid_match: >-
+ {{ __sap_swpm_register_sapcontrol_instance_properties.stdout_lines | d([])
+ | select('search', 'SAPSYSTEMNAME.*,.*' ~ __sap_swpm_sid)
+ | list | length > 0 }}
+
+ # Extract INSTANCE_NAME value from output.
+ - name: SAP SWPM Pre Install - Detect - Set fact with discovered instance name from sapcontrol output - {{ instance_item.number | d('') }}
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_sapcontrol_instance_name: "{{ __parsed_instance_name }}"
+ when: __sap_swpm_fact_detect_sapcontrol_sid_match
+ vars:
+ # Extract the line containing INSTANCE_NAME.
+ __instance_name_line: >-
+ {{ __sap_swpm_register_sapcontrol_instance_properties.stdout_lines | d([])
+ | select('search', 'INSTANCE_NAME')
+ | first | d('') }}
+ # Extract the value after the last comma, trimming whitespaces/quotes.
+ __parsed_instance_name: "{{ __instance_name_line.split(',')[-1] | trim }}"
+
+ # Check if discovered name is in the list of expected names (ANY match = detected).
+ - name: SAP SWPM Pre Install - Detect - Set fact instance is detected in sapcontrol output - {{ instance_item.number | d('') }}
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_sapcontrol_instance_found: >-
+ {{ __sap_swpm_fact_detect_sapcontrol_sid_match
+ and __sap_swpm_fact_detect_sapcontrol_instance_name | d('') in instance_item.names }}
+
+ - name: SAP SWPM Pre Install - Detect - Show sapcontrol command output in verbose mode
+ ansible.builtin.debug:
+ var: __sap_swpm_register_sapcontrol_instance_properties
+ when: ansible_verbosity >= 2
+
+
+# Check 6: Check presence of files in directories.
+# Filter directories that match this instance's names.
+- name: SAP SWPM Pre Install - Detect - Set fact with filtered directories matching instance {{ instance_item.number }}
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_instance_dirs_matching: >-
+ {{ __sap_swpm_register_instance_dirs_find.files | d([])
+ | selectattr('path', 'defined')
+ | list
+ | selectattr('path', 'match', '^.*/(' ~ instance_item.names | join('|') ~ ')$')
+ | list }}
+
+# Check for files in matching directories (can take ~1 minute for installed instances).
+# It took around 1 minute to get 14000 files in S4HANA 2025 PAS instance.
+- name: SAP SWPM Pre Install - Detect - Find files in directories - {{ instance_item.number }}
+ ansible.builtin.find:
+ paths: "{{ dir_item.path }}"
+ file_type: file
+ recurse: true
+ loop: "{{ __sap_swpm_fact_detect_instance_dirs_matching }}"
+ loop_control:
+ label: "{{ dir_item.path }}"
+ loop_var: dir_item
+ register: __sap_swpm_register_detect_instance_files_found
+ when: __sap_swpm_fact_detect_instance_dirs_matching | length > 0
+
+- name: SAP SWPM Pre Install - Detect - Set fact if instance directories contain existing files - {{ instance_item.number | d('') }}
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_instance_files: >-
+ {{ __sap_swpm_register_detect_instance_files_found.results | d([])
+ | selectattr('matched', 'defined')
+ | selectattr('matched', 'gt', 0)
+ | list | length > 0 }}
+ when: __sap_swpm_register_detect_instance_files_found is defined
+
+
+# Check 7: Check contents of /usr/sap/sapservices
+- name: Block for sapservices checks
+ when:
+ - __sap_swpm_register_sapservices_slurp is defined
+ - __sap_swpm_register_sapservices_slurp.content is defined
+ block:
+
+ # Match pattern: SAP_ for this instance
+ - name: SAP SWPM Pre Install - Detect - Set fact if instance is detected in sapservices - {{ instance_item.number | d('') }}
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_instance_sapservices_found: >-
+ {{ (__sap_swpm_register_sapservices_slurp.content | b64decode)
+ | regex_search('SAP' ~ __sap_swpm_sid ~ '_' ~ instance_item.number) is not none }}
+
+
+# Check 8: Check profile files in /sapmnt//profile/
+- name: Block for profile files checks
+ when:
+ - __sap_swpm_register_profiles_find is defined
+ - __sap_swpm_register_profiles_find.files is defined
+ block:
+
+ # Match pattern: __ (e.g., AE1_ASCS00_)
+ # Check if any profile file matches any of this instance's expected names.
+ - name: SAP SWPM Pre Install - Detect - Set fact if instance is detected in profile files - {{ instance_item.number | d('') }}
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_detect_instance_profiles_found: >-
+ {{ __sap_swpm_register_profiles_find.files | d([])
+ | map(attribute='path')
+ | map('basename')
+ | select('match', '^' ~ __sap_swpm_sid ~ '_(' ~ instance_item.names | join('|') ~ ')_')
+ | list | length > 0 }}
+
+
+# Display detection results for this instance
+- name: SAP SWPM Pre Install - Detect - Show detection results - {{ instance_item.number | d('') }}
+ ansible.builtin.debug:
+ msg: |
+ INFO: Detection results for instance {{ instance_item.number }} ({{ instance_item.names | join(', ') }}):
+ {% if __sap_swpm_fact_detect_saphostctrl_instance_found | d(false) %}
+ - Found in saphostctrl output (ListInstances).
+ {% endif %}
+ {% if __sap_swpm_fact_detect_sapcontrol_instance_found | d(false) %}
+ - Found in sapcontrol output (GetInstanceProperties).
+ {% endif %}
+ {% if __sap_swpm_fact_detect_instance_files | d(false) %}
+ - Found files in instance directories (/usr/sap/{{ __sap_swpm_sid }}/):
+ {% for dir in __sap_swpm_register_detect_instance_files_found.results if dir.matched is defined and dir.matched > 0 %}
+ '{{ dir.dir_item.path }}': {{ dir.matched }} files.
+ {% endfor %}
+ {% endif %}
+ {% if __sap_swpm_fact_detect_instance_sapservices_found | d(false) %}
+ - Found in SAP services file (/usr/sap/sapservices).
+ {% endif %}
+ {% if __sap_swpm_fact_detect_instance_profiles_found | d(false) %}
+ - Found in profile files (/sapmnt/{{ __sap_swpm_sid }}/profile/):
+ {% endif %}
+ {% if not __any_checks_true %}
+ - No existing installation detected for this instance
+ {% endif %}
+ vars:
+ __any_checks_true: >-
+ {{ __sap_swpm_fact_detect_saphostctrl_instance_found | d(false)
+ or __sap_swpm_fact_detect_sapcontrol_instance_found | d(false)
+ or __sap_swpm_fact_detect_instance_files | d(false)
+ or __sap_swpm_fact_detect_instance_sapservices_found | d(false)
+ or __sap_swpm_fact_detect_instance_profiles_found | d(false) }}
+ when: __any_checks_true
+
+
+- name: SAP SWPM Pre Install - Detect - Set fact with detection results - {{ instance_item.number | d('') }}
+ ansible.builtin.set_fact:
+ __sap_swpm_instance_results: "{{ __sap_swpm_instance_results | d([]) + [__current_result] }}"
+ vars:
+ __current_result:
+ number: "{{ instance_item.number }}"
+ names: "{{ instance_item.names }}"
+ saphostctrl: "{{ __sap_swpm_fact_detect_saphostctrl_instance_found | d(false) }}"
+ sapcontrol: "{{ __sap_swpm_fact_detect_sapcontrol_instance_found | d(false) }}"
+ files: "{{ __sap_swpm_fact_detect_instance_files | d(false) }}"
+ sapservices: "{{ __sap_swpm_fact_detect_instance_sapservices_found | d(false) }}"
+ profiles: "{{ __sap_swpm_fact_detect_instance_profiles_found | d(false) }}"
diff --git a/roles/sap_swpm/tasks/pre_install/detect_sap_prepare.yml b/roles/sap_swpm/tasks/pre_install/detect_sap_prepare.yml
new file mode 100644
index 00000000..685ed9e9
--- /dev/null
+++ b/roles/sap_swpm/tasks/pre_install/detect_sap_prepare.yml
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# Prepare lists of instance names and numbers for detection based on provided variables and product catalog ID.
+
+# Extract prefix from Product ID, or inform that prefix is not recognized.
+- name: SAP SWPM Pre Install - Set fact with SAP Product ID prefix
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_product_prefix: >-
+ {{ sap_swpm_product_catalog_id.split(':') | first
+ if sap_swpm_product_catalog_id | d('') | trim | length > 0 and ':' in sap_swpm_product_catalog_id
+ else '' }}
+
+- name: SAP SWPM Pre Install - Detect - Inform if SAP Product ID prefix is unavailable
+ ansible.builtin.debug:
+ msg: |
+ WARN: Unable to extract SAP Product ID prefix for product catalog ID '{{ sap_swpm_product_catalog_id }}'.
+ Expected format of product catalog ID is 'PREFIX:REST_OF_ID' (e.g., 'NW_ABAP_ASCS:S4HANA2025.CORE.HDB.ABAP').
+ Detection of existing SAP instances will proceed based on contents of 'sap_swpm_inifile_sections_list' variable.
+
+ NOTE: Providing invalid SAP Product ID will most likely lead into failure during SWPM execution.
+ when: __sap_swpm_fact_product_prefix | length == 0
+
+
+# Prepare lists of instance names and numbers for detection.
+# Lists based on Product ID.
+# Regex is used to remove items with empty values.
+- name: SAP SWPM Pre Install - Detect - Set fact with lists for detection using product ID
+ ansible.builtin.set_fact:
+ __sap_swpm_detect_instances:
+ "{{ __sap_swpm_product_sections_dict[__sap_swpm_fact_product_prefix]['instances'] }}"
+ when:
+ - __sap_swpm_fact_product_prefix | trim | length > 0
+ - __sap_swpm_product_sections_dict[__sap_swpm_fact_product_prefix] is defined
+ - __sap_swpm_product_sections_dict[__sap_swpm_fact_product_prefix]['instances'] is defined
+
+
+# Lists based on sections list if Product ID cannot be used.
+# NOTE: Defaults before 'unique' do not serve any purpose, because when already validates it,
+# but it is required for linting to pass:
+# Falling back to Ansible unique filter as Jinja2 one failed: 'sap_swpm_ascs_instance_nr' is undefined
+- name: Block to fall back on sections if product ID is not in dictionary
+ when:
+ - __sap_swpm_fact_product_prefix | trim | length == 0
+ or __sap_swpm_fact_product_prefix not in (
+ __sap_swpm_product_sections_dict.keys() | list
+ + __sap_swpm_product_detection_skipped | dict2items | map(attribute='value') | flatten)
+ block:
+
+ - name: SAP SWPM Pre Install - Detect - Set fact with lists for ASCS detection
+ ansible.builtin.set_fact:
+ __sap_swpm_detect_instances:
+ "{{ __sap_swpm_detect_instances | d([]) + __ascs_instances }}"
+ vars:
+ __ascs_instances:
+ - number: "{{ sap_swpm_ascs_instance_nr }}"
+ names:
+ - "{{ 'ASCS' ~ sap_swpm_ascs_instance_nr }}"
+ when:
+ - sap_swpm_ascs_instance_nr | d('') | length > 0
+ - "'nw_config_central_services_abap' in sap_swpm_inifile_sections_list"
+
+ - name: SAP SWPM Pre Install - Detect - Set fact with lists for SCS detection
+ ansible.builtin.set_fact:
+ __sap_swpm_detect_instances:
+ "{{ __sap_swpm_detect_instances | d([]) + __scs_instances }}"
+ vars:
+ __scs_instances:
+ - number: "{{ sap_swpm_java_scs_instance_nr }}"
+ names:
+ - "{{ 'SCS' ~ sap_swpm_java_scs_instance_nr }}"
+ when:
+ - sap_swpm_java_scs_instance_nr | d('') | length > 0
+ - "'nw_config_central_services_java' in sap_swpm_inifile_sections_list"
+
+ - name: SAP SWPM Pre Install - Detect - Set fact with lists for ERS detection
+ ansible.builtin.set_fact:
+ __sap_swpm_detect_instances:
+ "{{ __sap_swpm_detect_instances | d([]) + __ers_instances }}"
+ vars:
+ __ers_instances:
+ - number: "{{ sap_swpm_ers_instance_nr }}"
+ names:
+ - "{{ 'ERS' ~ sap_swpm_ers_instance_nr }}"
+ when:
+ - sap_swpm_ers_instance_nr | d('') | length > 0
+ - "'nw_config_ers' in sap_swpm_inifile_sections_list"
+
+ # Since 'nw_config_primary_application_server_instance' is not ABAP specific,
+ # we need to check for both ABAP and JAVA.
+ - name: SAP SWPM Pre Install - Detect - Set fact with lists for PAS detection
+ ansible.builtin.set_fact:
+ __sap_swpm_detect_instances:
+ "{{ __sap_swpm_detect_instances | d([]) + __pas_instances }}"
+ vars:
+ __pas_instances:
+ - number: "{{ sap_swpm_pas_instance_nr }}"
+ names:
+ - "{{ 'D' ~ sap_swpm_pas_instance_nr }}"
+ # DVEBMGS is used as fallback to legacy instance name.
+ - "{{ 'DVEBMGS' ~ sap_swpm_pas_instance_nr }}"
+ - "{{ 'J' ~ sap_swpm_pas_instance_nr }}"
+ when:
+ - sap_swpm_pas_instance_nr | d('') | length > 0
+ - "'nw_config_primary_application_server_instance' in sap_swpm_inifile_sections_list"
+
+ - name: SAP SWPM Pre Install - Detect - Set fact with lists for AAS detection
+ ansible.builtin.set_fact:
+ __sap_swpm_detect_instances:
+ "{{ __sap_swpm_detect_instances | d([]) + __aas_instances }}"
+ vars:
+ __aas_instances:
+ - number: "{{ sap_swpm_aas_instance_nr }}"
+ names:
+ - "{{ 'D' ~ sap_swpm_aas_instance_nr }}"
+ - "{{ 'J' ~ sap_swpm_aas_instance_nr }}"
+ when:
+ - sap_swpm_aas_instance_nr | d('') | length > 0
+ - "'nw_config_additional_application_server_instance' in sap_swpm_inifile_sections_list"
diff --git a/roles/sap_swpm/tasks/pre_install/generate_inifile.yml b/roles/sap_swpm/tasks/pre_install/generate_inifile.yml
index 5bbc20c9..54965e94 100644
--- a/roles/sap_swpm/tasks/pre_install/generate_inifile.yml
+++ b/roles/sap_swpm/tasks/pre_install/generate_inifile.yml
@@ -38,7 +38,7 @@
# At this point, the following variables need to be set:
# sap_swpm_product_catalog_id, __sap_swpm_fact_software_path, sap_swpm_sid, sap_swpm_fqdn
-- name: SAP SWPM Pre Install - Assert that certain variables are set
+- name: SAP SWPM Pre Install - Assert that required variables are set correctly
ansible.builtin.include_tasks:
file: assert_vars_length_loop_block.yml
apply:
@@ -52,14 +52,40 @@
loop_var: __sap_swpm_vars_to_be_asserted_for_length_line_item
tags: sap_swpm_generate_inifile
-- name: SAP SWPM Pre Install - Display these variables
+- name: SAP SWPM Pre Install - Display contents of required variables
ansible.builtin.debug:
msg: |
- "sap_swpm_product_catalog_id: '{{ sap_swpm_product_catalog_id }}'"
- "sap_swpm_sid: '{{ sap_swpm_sid }}'"
- "sap_swpm_fqdn: '{{ sap_swpm_fqdn }}'"
- " "
- "The SAP software is taken from directory: '{{ __sap_swpm_fact_software_path }}'"
+ sap_swpm_product_catalog_id: '{{ sap_swpm_product_catalog_id }}'
+ sap_swpm_sid: '{{ sap_swpm_sid }}'
+ sap_swpm_fqdn: '{{ sap_swpm_fqdn }}'
+
+ The SAP software is taken from directory: '{{ __sap_swpm_fact_software_path }}'
+
+
+# While SWPM allows providing extra inputs and ignores them,
+# we want to ensure that some sections are provided before failure occurs inside of SWPM execution.
+- name: SAP SWPM Pre Install - Assert that inifile sections are provided for product
+ ansible.builtin.assert:
+ that: >-
+ (__sap_swpm_product_sections_dict[__product_prefix]['sections']
+ | intersect(sap_swpm_inifile_sections_list) | length)
+ == __sap_swpm_product_sections_dict[__product_prefix]['sections'] | length
+ fail_msg: |
+ FAIL: The SAP Product '{{ sap_swpm_product_catalog_id }}'
+ requires all of the following sections to be defined
+ in the variable 'sap_swpm_inifile_sections_list':
+ {% for section in __sap_swpm_product_sections_dict[__product_prefix]['sections'] | d([]) %}
+ - {{ section }}
+ {% endfor %}
+ vars:
+ __product_prefix: "{{ sap_swpm_product_catalog_id.split(':') | first }}"
+ when:
+ - sap_swpm_inifile_sections_list | d([]) | length > 0
+ - sap_swpm_product_catalog_id | d('') | trim | length > 0
+ - "':' in sap_swpm_product_catalog_id"
+ - __sap_swpm_product_sections_dict[__product_prefix] is defined
+ tags: sap_swpm_generate_inifile
+
# We are creating the inifile dynamically in one of the two cases:
# 1 - The tag sap_swpm_generate_inifile is specified
diff --git a/roles/sap_swpm/tasks/pre_install/install_type.yml b/roles/sap_swpm/tasks/pre_install/install_type.yml
index 284ecbfa..c3789d63 100644
--- a/roles/sap_swpm/tasks/pre_install/install_type.yml
+++ b/roles/sap_swpm/tasks/pre_install/install_type.yml
@@ -54,33 +54,19 @@
# Run Installation Type Steps
################
-- name: SAP SWPM Pre Install - Assert installation type is defined (including empty)
- ansible.builtin.assert:
- that:
- - __sap_swpm_fact_installation_type is defined
- fail_msg:
- - "The installation type (variable '__sap_swpm_fact_installation_type') is not defined!"
- success_msg:
- - "The installation type (variable '__sap_swpm_fact_installation_type') is defined (including an empty string)."
-
- name: SAP SWPM Pre Install - Assert supported installation types
ansible.builtin.assert:
that:
- - __sap_swpm_fact_installation_type == '' or
- __sap_swpm_fact_installation_type == 'restore' or
- __sap_swpm_fact_installation_type == 'ha' or
- __sap_swpm_fact_installation_type == 'maint_plan_stack' or
- __sap_swpm_fact_installation_type == 'ha_maint_plan_stack'
- fail_msg:
- - "The specified installation type, '{{ __sap_swpm_fact_installation_type }}', is not supported!"
- - "The following installation types are supported:"
- - "- '' (empty string, the default)"
- - "- 'restore'"
- - "- 'ha'"
- - "- 'maint_plan_stack'"
- - "- 'ha_maint_plan_stack'"
- success_msg:
- - "The specified installation type, '{{ __sap_swpm_fact_installation_type }}', is supported."
+ - __sap_swpm_fact_installation_type is string
+ - __sap_swpm_fact_installation_type in ['', 'restore', 'ha', 'maint_plan_stack', 'ha_maint_plan_stack']
+ fail_msg: |
+ FAIL: Manual change of internal variable was detected!
+ The variable '__sap_swpm_fact_installation_type' allows only following options:
+ - '' (empty string, the default)
+ - 'restore'
+ - 'ha'
+ - 'maint_plan_stack'
+ - 'ha_maint_plan_stack'
- name: SAP SWPM Pre Install - Assert that necessary variable for HA is defined
ansible.builtin.assert:
diff --git a/roles/sap_swpm/tasks/pre_install/swpm_prepare.yml b/roles/sap_swpm/tasks/pre_install/swpm_prepare.yml
index 018b4b0e..ee627b8d 100644
--- a/roles/sap_swpm/tasks/pre_install/swpm_prepare.yml
+++ b/roles/sap_swpm/tasks/pre_install/swpm_prepare.yml
@@ -24,52 +24,90 @@
ansible.builtin.include_tasks: "generate_inifile.yml"
tags: sap_swpm_generate_inifile
-# Determine Installation Type, e.g. System Copy, HA, Maintenance Planner, ...
-- name: SAP SWPM default mode - Determine Installation Type
- ansible.builtin.include_tasks:
- file: install_type.yml
-# apply:
-# tags: sap_swpm_generate_inifile
-# tags: sap_swpm_generate_inifile
-
-# Requires variables - sap_swpm_software_path (e.g. /software/download_basket), __sap_swpm_fact_sapcar_path (e.g. /software/sapcar), __sap_swpm_fact_swpm_path (e.g. /software/swpm)
-# Prepare Software
-- name: SAP SWPM Pre Install - Prepare Software
- ansible.builtin.include_tasks: prepare_software.yml
- when: sap_swpm_run_sapinst
-
-# Set fact for SWPM path
-- name: SAP SWPM Pre Install - Set fact for SWPM path when extract directory defined
- ansible.builtin.set_fact:
- __sap_swpm_fact_sapinst_path: "{{ sap_swpm_software_extract_directory }}"
- when:
- - sap_swpm_run_sapinst
- - sap_swpm_software_extract_directory is defined
- - not (sap_swpm_software_extract_directory is none or (sap_swpm_software_extract_directory | length == 0))
-
-# Set fact for SWPM path
-- name: SAP SWPM Pre Install - Set fact for SWPM path when undefined extract directory
- ansible.builtin.set_fact:
- __sap_swpm_fact_sapinst_path: "{{ (__sap_swpm_fact_swpm_path | regex_replace('\\/$', '')) + '/extracted' }}"
- when:
- - sap_swpm_run_sapinst
- - sap_swpm_software_extract_directory is undefined or (sap_swpm_software_extract_directory is none or (sap_swpm_software_extract_directory | length) == 0)
-
-- name: SAP SWPM Pre Install - Ensure directory '{{ __sap_swpm_fact_sapinst_path }}' exists
- ansible.builtin.file:
- path: "{{ __sap_swpm_fact_sapinst_path }}"
- state: directory
- mode: '0755'
- when: sap_swpm_run_sapinst
-
-# Extract SWPM
-- name: SAP SWPM Pre Install - Extract SWPM
- ansible.builtin.command: >-
- {{ __sap_swpm_fact_sapcar_path }}/{{ __sap_swpm_fact_sapcar_file_name }} \
- -xvf {{ __sap_swpm_fact_swpm_path }}/{{ __sap_swpm_fact_swpm_file_name }} \
- -manifest SIGNATURE.SMF
- register: sap_swpm_extractswpm
- args:
- chdir: "{{ __sap_swpm_fact_sapinst_path }}"
- changed_when: "'SAPCAR: processing archive' in sap_swpm_extractswpm.stdout"
- when: sap_swpm_run_sapinst
+
+# Detect running SAP instances using variables validated in tasks above.
+# NOTE: This will result in skipping of remaining tasks, unless force is used.
+- name: Block for SAP Detection
+ # Inherited tags are explicitly declared here to ensure this inclusion
+ # is skipped (and logs are hidden) when only sub-tags like 'sap_swpm_generate_inifile' are requested.
+ tags:
+ - sap_swpm_pre_install
+ - sap_swpm_install
+ block:
+ - name: SAP SWPM Pre Install - Skip all detection checks
+ ansible.builtin.debug:
+ msg: |
+ WARN: Detection checks are SKIPPED because 'sap_swpm_skip_detection' is set to 'true'.
+
+ This bypasses all safety mechanisms that prevent installation over existing SAP systems.
+ Use this ONLY for testing/development on clean systems.
+
+ Proceeding with installation without any detection checks.
+ when: sap_swpm_skip_detection | d(false)
+
+ - name: SAP SWPM Pre Install - Prepare variables for SAP detection
+ ansible.builtin.include_tasks:
+ file: detect_sap_prepare.yml
+ when: not sap_swpm_skip_detection | d(false)
+
+ - name: SAP SWPM Pre Install - Detect running SAP instances
+ ansible.builtin.include_tasks:
+ file: pre_install/detect_sap.yml
+ when:
+ - not sap_swpm_skip_detection | d(false)
+ - __sap_swpm_detect_instances | d([]) | length > 0
+
+
+- name: Block to execute only if SAP instance is not detected or in force mode.
+ when: not __sap_swpm_sap_detected | d(false) or sap_swpm_force | d(false)
+ block:
+
+ # Determine Installation Type, e.g. System Copy, HA, Maintenance Planner, ...
+ - name: SAP SWPM default mode - Determine Installation Type
+ ansible.builtin.include_tasks:
+ file: install_type.yml
+ # apply:
+ # tags: sap_swpm_generate_inifile
+ # tags: sap_swpm_generate_inifile
+
+ # Requires variables - sap_swpm_software_path (e.g. /software/download_basket), __sap_swpm_fact_sapcar_path (e.g. /software/sapcar), __sap_swpm_fact_swpm_path (e.g. /software/swpm)
+ # Prepare Software
+ - name: SAP SWPM Pre Install - Prepare Software
+ ansible.builtin.include_tasks: prepare_software.yml
+ when: sap_swpm_run_sapinst
+
+ # Set fact for SWPM path
+ - name: SAP SWPM Pre Install - Set fact for SWPM path when extract directory defined
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_sapinst_path: "{{ sap_swpm_software_extract_directory }}"
+ when:
+ - sap_swpm_run_sapinst
+ - sap_swpm_software_extract_directory is defined
+ - not (sap_swpm_software_extract_directory is none or (sap_swpm_software_extract_directory | length == 0))
+
+ # Set fact for SWPM path
+ - name: SAP SWPM Pre Install - Set fact for SWPM path when undefined extract directory
+ ansible.builtin.set_fact:
+ __sap_swpm_fact_sapinst_path: "{{ (__sap_swpm_fact_swpm_path | regex_replace('\\/$', '')) + '/extracted' }}"
+ when:
+ - sap_swpm_run_sapinst
+ - sap_swpm_software_extract_directory is undefined or (sap_swpm_software_extract_directory is none or (sap_swpm_software_extract_directory | length) == 0)
+
+ - name: SAP SWPM Pre Install - Ensure directory '{{ __sap_swpm_fact_sapinst_path }}' exists
+ ansible.builtin.file:
+ path: "{{ __sap_swpm_fact_sapinst_path }}"
+ state: directory
+ mode: '0755'
+ when: sap_swpm_run_sapinst
+
+ # Extract SWPM
+ - name: SAP SWPM Pre Install - Extract SWPM
+ ansible.builtin.command: >-
+ {{ __sap_swpm_fact_sapcar_path }}/{{ __sap_swpm_fact_sapcar_file_name }} \
+ -xvf {{ __sap_swpm_fact_swpm_path }}/{{ __sap_swpm_fact_swpm_file_name }} \
+ -manifest SIGNATURE.SMF
+ register: sap_swpm_extractswpm
+ args:
+ chdir: "{{ __sap_swpm_fact_sapinst_path }}"
+ changed_when: "'SAPCAR: processing archive' in sap_swpm_extractswpm.stdout"
+ when: sap_swpm_run_sapinst
diff --git a/roles/sap_swpm/vars/main.yml b/roles/sap_swpm/vars/main.yml
index b55cd647..5385c8ea 100644
--- a/roles/sap_swpm/vars/main.yml
+++ b/roles/sap_swpm/vars/main.yml
@@ -42,3 +42,114 @@ __sap_swpm_firewall_extra_packages: []
# Predefined firewall service names.
__sap_swpm_firewall_service_name_cs: "sap-nw-central"
__sap_swpm_firewall_service_name_app: "sap-nw-app"
+
+
+### Internal variables for SAP detection.
+# Boolean state of detection of SAP Instances.
+__sap_swpm_sap_detected: false
+
+# List of combinations of SAP instance numbers and names generated in pre_install/detect_sap_prepare.yml.
+__sap_swpm_detect_instances: []
+
+# Dictionary Matrix of product prefixes and sections combinations that must be enforced.
+# This will ensure that required sections are present for specific products,
+# while allowing other products to pass through and potentially fail during SWPM.
+# NOTE: Dictionary contains variables that will be expanded only when accessed, which is after validation of inifile!
+# - sections - list of sections that must be present in inifile for this product.
+# - instances - list of dictionaries with 'number' and 'names' keys to define expected instance number and name format for the instance.
+# - number - unique instance number for the instance.
+# - names - list of possible instance names for the instance. Any of them means that the instance is detected.
+__sap_swpm_product_sections_dict:
+ # Product Catalog ID prefix.
+ # Example: 'NW_ABAP_ASCS' for product catalog ID 'NW_ABAP_ASCS:S4HANA2025.CORE.HDB.ABAP'.
+ # Contains: ASCS
+ NW_ABAP_ASCS:
+ # Name of section in 'sap_swpm_inifile_sections_list' that must be present for this product.
+ sections:
+ - nw_config_central_services_abap
+ instances:
+ - number: "{{ sap_swpm_ascs_instance_nr }}"
+ names:
+ - "{{ 'ASCS' ~ sap_swpm_ascs_instance_nr }}"
+
+ # Contains: SCS
+ NW_Java_SCS:
+ sections:
+ - nw_config_central_services_java
+ instances:
+ - number: "{{ sap_swpm_java_scs_instance_nr }}"
+ names:
+ - "{{ 'SCS' ~ sap_swpm_java_scs_instance_nr }}"
+
+ # Contains: ERS
+ NW_ERS:
+ sections:
+ - nw_config_ers
+ instances:
+ - number: "{{ sap_swpm_ers_instance_nr }}"
+ names:
+ - "{{ 'ERS' ~ sap_swpm_ers_instance_nr }}"
+
+ # Contains: ASCS, PAS (ABAP)
+ # DVEBMGS is used as fallback to legacy instance name.
+ NW_ABAP_OneHost:
+ sections:
+ - nw_config_central_services_abap
+ - nw_config_primary_application_server_instance
+ instances:
+ - number: "{{ sap_swpm_ascs_instance_nr }}"
+ names:
+ - "{{ 'ASCS' ~ sap_swpm_ascs_instance_nr }}"
+ - number: "{{ sap_swpm_pas_instance_nr }}"
+ names:
+ - "{{ 'D' ~ sap_swpm_pas_instance_nr }}"
+ - "{{ 'DVEBMGS' ~ sap_swpm_pas_instance_nr }}"
+
+ # Contains: PAS
+ NW_ABAP_CI:
+ sections:
+ - nw_config_primary_application_server_instance
+ instances:
+ - number: "{{ sap_swpm_pas_instance_nr }}"
+ names:
+ - "{{ 'DVEBMGS' ~ sap_swpm_pas_instance_nr }}"
+ - "{{ 'D' ~ sap_swpm_pas_instance_nr }}"
+
+ # Contains: ASCS, PAS (JAVA)
+ NW_Java_CI:
+ sections:
+ - nw_config_primary_application_server_instance
+ instances:
+ - number: "{{ sap_swpm_pas_instance_nr }}"
+ names:
+ - "{{ 'J' ~ sap_swpm_pas_instance_nr }}"
+
+ # Contains: AAS (ABAP or JAVA)
+ NW_DI:
+ sections:
+ - nw_config_additional_application_server_instance
+ instances:
+ - number: "{{ sap_swpm_aas_instance_nr }}"
+ names:
+ - "{{ 'D' ~ sap_swpm_aas_instance_nr }}"
+ - "{{ 'J' ~ sap_swpm_aas_instance_nr }}"
+
+
+# This is special list of product prefixes, where we cannot detect SAP,
+# because it does not create any recognizable files.
+# NOTE: Do not add product prefixes already in '__sap_swpm_product_sections_dict'!
+__sap_swpm_product_detection_skipped:
+ # Product prefixes that are known for Database Load.
+ # Installs only 'saphostctrl' and rest is done in database.
+ # This is not conclusive enough for us to decide and SWPM handles this itself.
+ database_load:
+ - NW_ABAP_DB
+ - NW_Java_DB
+ # Space for more categories, if more products are found. Candidates:
+ # NW_ABAP_DB_DBRefresh, NW_Java_DB_DBRefresh, NW_Webdispatcher.
+
+# System ID (SID) in upper case.
+__sap_swpm_sid: "{{ sap_swpm_sid | upper }}"
+
+# Name of 'adm' user.
+__sap_swpm_sidadm_user: "{{ sap_swpm_sid | lower }}adm"