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"