diff --git a/.ansible-lint b/.ansible-lint
new file mode 100644
index 0000000..ff93a8f
--- /dev/null
+++ b/.ansible-lint
@@ -0,0 +1,24 @@
+---
+# Collection wide lint-file
+# DO NOT CHANGE
+exclude_paths:
+ - .ansible/
+ - .cache/
+ - .github/
+ # - docs/
+ - changelogs/
+ - playbooks/
+ - tests/
+enable_list:
+ - yaml
+skip_list:
+ # We don't want to enforce new Ansible versions for Galaxy:
+ - meta-runtime[unsupported-version]
+ # We do not want to use checks which are marked as experimental:
+ - experimental
+ # We use ignore_errors for all the assert tasks, which should be acceptable:
+ - ignore-errors
+ # We want to allow single digit version numbers in a role's meta/main.yml file:
+ - schema
+ # Allow templating inside name because it creates more detailed output:
+ - name[template]
diff --git a/.gitignore b/.gitignore
index 4938417..9ff6291 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,3 +52,4 @@ __pycache__/
# VSCode
.vscode
+.ansible
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ea6fdcd..137fc0d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,36 +1,77 @@
===================================
-community.sap_launchpad Release Notes
+community.sap\_launchpad Release Notes
===================================
.. contents:: Topics
+v1.2.0
+======
+
+Release Summary
+---------------
+
+Enhancements to Modules and introduction of new Ansible Role.
+
+Minor Changes
+-------------
+- sap_software_download: New Ansible Role with enhanced logic for downloading software using Ansible Module software_center_download (https://github.com/sap-linuxlab/community.sap_launchpad/pull/32)
+- sap_software_download: Download stack XML option (https://github.com/sap-linuxlab/community.sap_launchpad/pull/35)
+- software_center_download: Add option to search for latest packages (https://github.com/sap-linuxlab/community.sap_launchpad/pull/28)
+- maintenance_planner modules: Add option to use Display ID instead of name (https://github.com/sap-linuxlab/community.sap_launchpad/pull/31)
+- Collection Readme update and preparation for 1.2.0 release (https://github.com/sap-linuxlab/community.sap_launchpad/pull/34)
-v1.1.0
+Bugfixes
+--------
+
+- fix: cache gigya sdk build number (https://github.com/sap-linuxlab/community.sap_launchpad/pull/33)
+
+
+v1.1.1
======
Release Summary
---------------
-| Release Date: 2023-11-28
-| Community contribution Ansible Module systems_info to query registered systems in me.sap.com
-| Community contribution Ansible Module license_keys to create systems and license keys on me.sap.com/licensekey
+Various bug fixes
+Bugfixes
+--------
+- Append logic for Account Temporarily Locked Out
+- Fix errors in the example file
-v1.0.1
+
+v1.1.0 (2023-11-28)
======
Release Summary
---------------
-| Release Date: 2023-09-14
-| Fix for handling old password prompt
+Community contribution with new Ansible Modules `systems_info` and `license_keys``
+
+Minor Changes
+-------------
+
+- Create/update systems and license keys (https://github.com/sap-linuxlab/community.sap_launchpad/pull/16)
+
+
+v1.0.1 (2023-09-14)
+======
+
+Release Summary
+---------------
+
+Various bug fixes
+
+Bugfixes
+--------
+
+- Fix for handling old password prompt
-v1.0.0
+v1.0.0 (2023-08-22)
======
Release Summary
---------------
-| Release Date: 2023-08-22
-| Initial Release on Galaxy
+Initial Release on Galaxy
diff --git a/README.md b/README.md
index 3217a6a..11d1a40 100644
--- a/README.md
+++ b/README.md
@@ -1,108 +1,131 @@
-# community.sap_launchpad Ansible Collection 
+# community.sap_launchpad Ansible Collection
-This Ansible Collection executes basic SAP.com Support operations tasks.
+
-## Functionality
+## Description
-This Ansible Collection executes basic SAP.com Support operations tasks, including:
+This Ansible Collection provides roles and modules to automate interaction with SAP Launchpad API, primarily focusing on downloading software and files from the SAP Software Download Center and Maintenance Planner.
-- **Software Center Catalog**
- - Search and Download of SAP software center catalog files
- - Search and Extraction of SAP software center catalog information
-- **Maintenance Planner**
- - Lookup and download files from an existing 'New Implementation' MP Transaction and Stack, using SAP software center's download basket
+Included role and modules cover range of options:
+- Preparation of environment before download.
+- Download of specific SAP Software files.
+- Download of alternative SAP Software files if specific was not available.
+- Download of SAP Software files from existing Maintenance Plan transaction.
+- Download of Stack file from existing Maintenance Plan transaction.
+- Register Systems and License Keys
+- Query registered Systems
-## Contents
-An Ansible Playbook can call either an Ansible Role, or the individual Ansible Modules for handling the API calls to various SAP Support Portal API capabilities:
-- **Ansible Roles** (runs multiple Ansible Modules)
-- **Ansible Modules** (and adjoining Python Functions)
+## Requirements
-For further information regarding the development, code structure and execution workflow please read the [Development documentation](./docs/DEVELOPMENT.md).
+### Control Nodes
+| Type | Version |
+| :--- | :--- |
+| Operating system | Any operating system with required Python and Ansible versions |
+| Python | 3.11 or higher |
+| Ansible | 9.9 or higher |
+| Ansible-core | 2.16 or higher |
-Within this Ansible Collection, there are various Ansible Modules.
-#### Ansible Modules
+### Managed Nodes
+| Type | Version |
+| :--- | :--- |
+| Operating system | SUSE Linux Enterprise Server 15 SP5+, 16
Red Hat Enterprise Linux 8.x, 9.x, 10.x |
+| Python | 3.11 or higher (SUSE)
3.9 or higher (Red Hat) |
-| Name | Summary |
-| :-- | :-- |
-| [sap_launchpad.software_center_download](./docs/module_software_center_download.md) | search for files and download |
-| [sap_launchpad.maintenance_planner_files](./docs/module_maintenance_planner_files.md) | maintenance planner files retrieval |
-| [sap_launchpad.maintenance_planner_stack_xml_download](./docs/module_maintenance_planner_stack_xml_download.md) | maintenance planner stack xml download |
+**NOTE: Operating system needs to have access to required package repositories either directly or via subscription registration.**
-## Execution
-### Credentials - SAP User ID
+## Installation Instructions
-SAP software installation media must be obtained from SAP directly, and requires valid license agreements with SAP in order to access these files.
+### Installation
+Install this collection with Ansible Galaxy command:
+```console
+ansible-galaxy collection install community.sap_launchpad
+```
-An SAP Company Number (SCN) contains one or more Installation Number/s, providing licences for specified SAP Software. When an SAP User ID is created within the SAP Customer Number (SCN), the administrator must provide SAP Download authorizations for the SAP User ID.
+Optionally you can include collection in requirements.yml file and include it together with other collections using: `ansible-galaxy collection install -r requirements.yml`
+Requirements file need to be maintained in following format:
+```yaml
+collections:
+ - name: community.sap_launchpad
+```
-When an SAP User ID (e.g. S-User) is enabled with and part of an SAP Universal ID, then the `sap_launchpad` Ansible Collection **must** use:
-- the SAP User ID
-- the password for login with the SAP Universal ID
+### Upgrade
+Installed Ansible Collection will not be upgraded automatically when Ansible package is upgraded.
-In addition, if a SAP Universal ID is used then the recommendation is to check and reset the SAP User ID ‘Account Password’ in the [SAP Universal ID Account Manager](https://account.sap.com/manage/accounts), which will help to avoid any potential conflicts.
+To upgrade the collection to the latest available version, run the following command:
+```console
+ansible-galaxy collection install community.sap_launchpad --upgrade
+```
-For further information regarding connection errors, please see the FAQ section [Errors with prefix 'SAP SSO authentication failed - '](./docs/FAQ.md#errors-with-prefix-sap-sso-authentication-failed---).
+You can also install a specific version of the collection, when you encounter issues with latest version. Please report these issues in affected Role repository if that happens.
+Example of downgrading collection to version 1.0.0:
+```
+ansible-galaxy collection install community.sap_launchpad:==1.0.0
+```
-### Execution examples
+See [Installing collections](https://docs.ansible.com/ansible/latest/collections_guide/collections_installing.html) for more details on installation methods.
-There are various methods to execute the Ansible Collection, dependant on the use case. For more information, see [Execution examples with code samples](./docs/EXEC_EXAMPLES.md) and the summary below:
-| Execution Scenario | Use Case | Target |
-| --- | --- | --- |
-| Ansible Playbook
-> source Ansible Collection
-> execute Ansible Task
--> run Ansible Module
---> run Python/Bash Functions | Simple executions with a few activities | Localhost or Remote |
-| Ansible Playbook
-> source Ansible Collection
-> execute Ansible Task
--> run Ansible Role
---> run Ansible Module
----> run Python/Bash Functions
--> run Ansible Role
---> ... | Complex executions with various interlinked activities;
run in parallel or sequentially | Localhost or Remote |
-| Python/Bash Functions | Simple testing or non-Ansible use cases | Localhost |
+## Contents
----
+### Ansible Modules
+| Name | Summary |
+| :-- | :-- |
+| [sap_launchpad.software_center_download](./docs/module_software_center_download.md) | Search and download SAP Software file |
+| [sap_launchpad.maintenance_planner_files](./docs/module_maintenance_planner_files.md) | Get list of files from Maintenance Planner |
+| [sap_launchpad.maintenance_planner_stack_xml_download](./docs/module_maintenance_planner_stack_xml_download.md) | Get stack file from Maintenance Planner |
-## Requirements, Dependencies and Testing
+### Ansible Roles
+| Name | Summary |
+| :-- | :-- |
+| [sap_software_download](./roles/sap_software_download/README.md) | Prepare environment and download SAP Software files or Maintenance Plan transaction files |
-### Operating System requirements
-Designed for Linux operating systems, e.g. RHEL.
+## Testing
+This Ansible Collection was tested across different Operating Systems and SAP products.
-This role has not been tested and amended for SAP NetWeaver Application Server instantiations on IBM AIX or Windows Server.
+| Type | Version |
+| :--- | :--- |
+| Operating system | SUSE Linux Enterprise Server 15 SP5+, 16
Red Hat Enterprise Linux 8.x, 9.x, 10.x |
+| Python | 3.11, 3.12 |
+| Ansible | 9, 10, 11 |
+| Ansible-core | 2.16, 2.17, 2.18 |
-Assumptions for executing this role include:
-- Registered OS License and OS Package repositories are available (from the relevant content delivery network of the OS vendor)
-- Simultaneous Ansible Playbook executions will require amendment of Ansible Variable name `softwarecenter_search_list` shown in execution samples (containing the list of installation media to download). This avoids accidental global variable clashes from occuring in Ansible Playbooks executed from the same controller host with an inline Ansible Inventory against 'all' target hosts.
-### Python requirements
+## Contributing
+You can find more information about ways you can contribute at [sap-linuxlab website](https://sap-linuxlab.github.io/initiative_contributions/).
-Execution/Controller/Management host:
-- Python 3
-Target host:
-- Python 3, with Python Modules `beautifulsoup4 lxml requests` (see [Execution examples with code samples](./docs/EXEC_EXAMPLES.md))
+## Support
+You can report any issues using [Issues](https://github.com/sap-linuxlab/community.sap_launchpad/issues) section.
-### Testing on execution/controller host
-**Tests with Ansible Core release versions:**
-- Ansible Core 2.11.5 community edition
+## Release Notes and Roadmap
+You can find the release notes of this collection in [Changelog file](./CHANGELOG.rst)
-**Tests with Python release versions:**
-- Python 3.9.7 (i.e. CPython distribution)
-**Tests with Operating System release versions:**
-- RHEL 8.4
-- macOS 11.6 (Big Sur), with Homebrew used for Python 3.x via PyEnv
+## Further Information
-### Testing on target/remote host
+### Credentials - SAP S-User
-**Tests with Operating System release versions:**
-- RHEL 8.2 for SAP
+SAP software files must be obtained from SAP directly, and requires valid license agreements with SAP in order to access these files.
-**Tests with Python release versions:**
-- Python 3.6.x (i.e. CPython distribution), default for RHEL 8.x and SLES 15.x
-- Python 3.8.x (i.e. CPython distribution)
+An SAP Company Number (SCN) contains one or more Installation Number/s, providing licenses for specified SAP Software. When an SAP User ID is created within the SAP Customer Number (SCN), the administrator must provide SAP Download authorizations for the SAP User ID.
-## License
+When an SAP User ID (e.g. S-User) is enabled with and part of an SAP Universal ID, then the `sap_launchpad` Ansible Collection **must** use:
+- the SAP User ID
+- the password for login with the SAP Universal ID
-- [Apache 2.0](./LICENSE)
+In addition, if a SAP Universal ID is used then the recommendation is to check and reset the SAP User ID ‘Account Password’ in the [SAP Universal ID Account Manager](https://account.sap.com/manage/accounts), which will help to avoid any potential conflicts.
-## Contributors
+For further information regarding connection errors, please see the FAQ section [Errors with prefix 'SAP SSO authentication failed - '](./docs/FAQ.md#errors-with-prefix-sap-sso-authentication-failed---).
+
+**Multi Factor Authentication is not supported.**
-Contributors to the Ansible Roles within this Ansible Collection, are shown within [/docs/contributors](./docs/CONTRIBUTORS.md).
+### Variable Precedence Rules
+Please follow [Ansible Precedence guidelines](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable) on how to pass variables when using this collection.
+
+## License
+[Apache 2.0](./LICENSE)
diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml
index af771c1..d0abede 100644
--- a/changelogs/changelog.yaml
+++ b/changelogs/changelog.yaml
@@ -1,33 +1,43 @@
ancestor: null
releases:
-
- 1.1.0:
+ 1.2.0:
+ release_date: '2025-04-04'
changes:
- release_summary: '| Release Date: 2023-11-28
-
- | Community contribution Ansible Module systems_info to query registered systems in me.sap.com
-
- | Community contribution Ansible Module license_keys to create systems and license keys on me.sap.com/licensekey
+ release_summary: Enhancements to Modules and introduction of new Ansible Role
+ minor_changes:
+ - sap_software_download - New Ansible Role with enhanced logic for downloading software using Ansible Module software_center_download (https://github.com/sap-linuxlab/community.sap_launchpad/pull/32)
+ - sap_software_download - Download stack XML option (https://github.com/sap-linuxlab/community.sap_launchpad/pull/35)
+ - software_center_download - Add option to search for latest packages (https://github.com/sap-linuxlab/community.sap_launchpad/pull/28)
+ - maintenance_planner modules - Add option to use Display ID instead of name (https://github.com/sap-linuxlab/community.sap_launchpad/pull/31)
+ - Collection Readme update and preparation for 1.2.0 release (https://github.com/sap-linuxlab/community.sap_launchpad/pull/34)
+ bugfixes:
+ - fix - cache gigya sdk build number (https://github.com/sap-linuxlab/community.sap_launchpad/pull/33)
+
+ 1.1.1:
+ release_date: '2024-03-18'
+ changes:
+ release_summary: Various bug fixes
+ bugfixes:
+ - Append logic for Account Temporarily Locked Out
+ - Fix errors in the example file
- '
+ 1.1.0:
release_date: '2023-11-28'
-
- 1.0.1:
changes:
- release_summary: '| Release Date: 2023-09-14
+ release_summary: Community contribution with new Ansible Modules systems_info and license_keys
+ minor_changes:
+ - Create/update systems and license keys (https://github.com/sap-linuxlab/community.sap_launchpad/pull/16)
- | Fix for handling old password prompt
-
- '
+ 1.0.1:
release_date: '2023-09-14'
+ changes:
+ release_summary: Various bug fixes
+ bugfixes:
+ - Fix for handling old password prompt
1.0.0:
+ release_date: '2023-08-22'
changes:
- release_summary: '| Release Date: 2023-08-22
-
- | Initial Release on Galaxy
-
- '
+ release_summary: Initial Release on Galaxy
fragments:
- v1.0.0_summary.yaml
- release_date: '2023-08-22'
diff --git a/docs/CONTRIBUTORS.md b/docs/CONTRIBUTORS.md
index 3905cb4..833ab8c 100644
--- a/docs/CONTRIBUTORS.md
+++ b/docs/CONTRIBUTORS.md
@@ -8,3 +8,6 @@
- **Sean Freeman** - Origin developer of Python API constructs, and project owner
- **SVA GmbH - System Vertrieb Alexander GmbH**
- **Rainer Leber** - User acceptance testing
+- **SUSE**
+ - SUSE SAP Emerging Technology Solutions
+ - **Marcel Mamula** - Developer of Ansible Collection
diff --git a/docs/EXEC_EXAMPLES.md b/docs/DEVELOPER_NOTES.md
similarity index 52%
rename from docs/EXEC_EXAMPLES.md
rename to docs/DEVELOPER_NOTES.md
index 2288d4f..2949687 100644
--- a/docs/EXEC_EXAMPLES.md
+++ b/docs/DEVELOPER_NOTES.md
@@ -1,61 +1,79 @@
-# Execution examples
+# Developer notes for community.sap_launchpad Ansible Collection
-## Execution example with Ansible Playbook calling Ansible Module
+This document contains details for maintaining Ansible Collection.
-**Ansible Playbook YAML, execute Ansible Module**
+## Dependencies for all modules
+Modules require the following Python modules to be installed on the target node (the machine where SAP software will be downloaded):
+
+- wheel
+- urllib3
+- requests
+- beautifulsoup4
+- lxml
+
+### Installation of dependencies using role `sap_software_download`
+Ansible Role `sap_software_download` installs all required dependencies as part of `02_prepare_python_environment.yml` task file.
+
+### Installation of dependencies with Python Virtual Environment (venv)
+It is recommended to install dependencies in venv that can be removed after execution is completed.
```yaml
----
-- hosts: all
-
- collections:
- - community.sap_launchpad
-
- pre_tasks:
- - name: Install Python package manager pip3 to system Python
- yum:
- name: python3-pip
- state: present
- - name: Install Python dependencies for Ansible Modules to system Python
- pip:
+- name: Example play to install prerequisites with Python Virtual Environment
+ hosts: all
+ tasks:
+ - name: Create temporary directory for Python Virtual Environment
+ ansible.builtin.tempfile:
+ state: directory
+ suffix: __sap_software_download_venv
+ register: __sap_software_download_venv
+
+ - name: Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - python311
+ - python311-pip
+ state: present
+
+ - name: Install Python modules to Python Virtual Environment
+ ansible.builtin.pip:
name:
+ - wheel
- urllib3
- requests
- beautifulsoup4
- lxml
+ virtualenv: "{{ __sap_software_download_venv.path }}"
+ virtualenv_command: "python3.11 -m venv"
-# Prompt for Ansible Variables
- vars_prompt:
- - name: suser_id
- prompt: Please enter S-User
- private: no
- - name: suser_password
- prompt: Please enter Password
- private: yes
-
-# Define Ansible Variables
- vars:
- ansible_python_interpreter: python3
- softwarecenter_search_list:
- - 'SAPCAR_1324-80000936.EXE'
- - 'HCMT_057_0-80003261.SAR'
-
-# Use task block to call Ansible Module
- tasks:
- - name: Execute Ansible Module to download SAP software
- community.sap_launchpad.software_center_download:
- suser_id: "{{ suser_id }}"
- suser_password: "{{ suser_password }}"
- softwarecenter_search_query: "{{ item }}"
- dest: "/tmp/"
- loop: "{{ softwarecenter_search_list }}"
- loop_control:
- label: "{{ item }} : {{ download_task.msg }}"
- register: download_task
- retries: 1
- until: download_task is not failed
+ - name: Remove temporary Python Virtual Environment
+ ansible.builtin.file:
+ path: "{{ __sap_software_download_venv.path }}"
+ state: absent
```
-**Execution of Ansible Playbook, with in-line Ansible Inventory set as localhost**
+### Installation of dependencies with Python system default
+```yaml
+- name: Example play to install prerequisites with Python system default
+ hosts: all
+ tasks:
+ - name: Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - python31
+ - python311-pip
+ state: present
+
+ - name: Install Python modules to Python system default
+ ansible.builtin.pip:
+ name:
+ - wheel
+ - urllib3
+ - requests
+ - beautifulsoup4
+ - lxml
+```
+
+## Additional execution methods
+### Execution of Ansible Playbook, with in-line Ansible Inventory set as localhost
```shell
# Install from local source directory for Ansible 2.11+
@@ -68,7 +86,7 @@ ansible-galaxy collection install community.sap_launchpad
ansible-playbook --timeout 60 ./community.sap_launchpad/playbooks/sample-download-install-media.yml --inventory "localhost," --connection=local
```
-**Execution of Ansible Playbook, with in-line Ansible Inventory of target/remote hosts**
+### Execution of Ansible Playbook, with in-line Ansible Inventory of target/remote hosts
```shell
# Install from local source directory for Ansible 2.11+
@@ -93,9 +111,8 @@ ansible-playbook --timeout 60 ./sample-playbook.yml \
--ssh-extra-args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ProxyCommand='ssh -W %h:%p $bastion_user@$bastion_host -p $bastion_port -i $bastion_private_key_file -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'"
```
-## Execution example with Python environment
-
-**Setup local Python environment**
+## Execution of Python Modules directly
+### Setup local Python environment
```shell
# Change directory to Python scripts source
cd ./plugins
@@ -112,7 +129,7 @@ pip3 install beautifulsoup4 lxml requests
python3
```
-**Execute Python Functions**
+### Execute Python Functions
```python
>>> from module_utils.sap_id_sso import sap_sso_login
>>> from module_utils.sap_launchpad_software_center_download_runner import *
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
deleted file mode 100644
index 20caca7..0000000
--- a/docs/DEVELOPMENT.md
+++ /dev/null
@@ -1,78 +0,0 @@
-
-# Development of community.sap_launchpad Ansible Collection
-
-This Ansible Collection is developed with several design principles and code practices.
-
-## Code structure
-
-This Ansible Collection is heavily focused on Ansible Modules to perform required SAP Support API calls. The directory tree structure is shown below:
-```code
-collection/
-├── docs/
-├── meta/
-├── plugins/
-│ ├── modules/
-│ │ ├── users.py
-│ │ ├── software_center_download.py
-│ │ ├── software_center_catalog.py
-│ │ ├── maintenance_planner_files.py
-│ │ ├── maintenance_planner_stack_xml_download.py
-│ │ ├── licenses.py
-│ │ └── incidents.py
-│ └── module_utils/
-│ ├── sap_id_sso.py
-│ ├── sap_launchpad_software_center_download_runner.py
-│ ├── sap_launchpad_software_center_catalog_runner.py
-│ └── sap_launchpad_maintenance_planner_runner.py
-├── roles/
-├── playbooks/
-│ ├── sample-download-install-media.yml
-│ └── sample-maintenance-planner-download.yml
-├── tests/
-├── galaxy.yml
-└── README.md
-```
-
-## Execution logic
-
-This Ansible Collection is designed to be heavily re-usable for various SAP Support scenarios (both server-side and client-side), and avoid encapsulation of commands within Ansible's syntax; this ensures the scripts (and the sequence of commands) could be re-used manually or re-used by another automation framework.
-
-It is important to understand the execution flow by an Ansible Playbook to either an Ansible Role (with or without embedded Playbooks), an Ansible Task, or an Ansible Module (and contained Script files). Alternatively it is possible to call the script files manually.
-
-
-See examples below:
-
-### Ansible Playbook to call many Ansible Roles (and the contained interlinked Ansible Tasks)
-```code
-# Produce outcome scenario, using many interlinked tasks
-- Run: Ansible Playbook
- - Run: Ansible Role
- - Ansible Task
- - Ansible Playbook 1..n
- - Ansible Task
- - execute custom Ansible Module
- - execute specified Python Module Functions
- - call APIs or CLIs/binaries
- - Ansible Task
- - Ansible Playbook 1..n
- - Ansible Task
- - subsequent OS commands using output from APIs or CLIs/binaries
-```
-
-### Ansible Playbook to call single set of Ansible Tasks
-```code
-# Produce outcome scenario, with single set of tasks
-- Run: Ansible Playbook
- - Ansible Task
- - execute custom Ansible Module
- - execute specified Python Module Functions
- - call APIs or CLIs/binaries
-```
-
-### Python Shell to call single Python Function
-```code
-# Produce outcome scenario manually with singular code execution
-- Run: Python Shell
- - Import Python Module file for APIs or CLIs/binaries
- - Execute specificed Python Functions
-```
diff --git a/docs/module_maintenance_planner_files.md b/docs/module_maintenance_planner_files.md
index 285fdb5..b1340af 100644
--- a/docs/module_maintenance_planner_files.md
+++ b/docs/module_maintenance_planner_files.md
@@ -1,2 +1,140 @@
# maintenance_planner_files Ansible Module
+## Description
+The Ansible Module `maintenance_planner_files` is used to obtain list of SAP Software files belonging to Maintenance Plan transaction.
+
+## Dependencies
+This module requires the following Python modules to be installed on the target node (the machine where SAP software will be downloaded):
+
+- wheel
+- urllib3
+- requests
+- beautifulsoup4
+- lxml
+
+Installation instructions are available at [Installation of prerequisites](#installation-of-prerequisites)
+
+## Execution
+
+### Example
+Obtain list of SAP Software files
+```yaml
+- name: Obtain list of SAP Software files
+ community.sap_launchpad.maintenance_planner_files:
+ suser_id: "Enter SAP S-User ID"
+ suser_password: "Enter SAP S-User Password"
+ transaction_name: "Transaction Name or Display ID from Maintenance Planner"
+ register: __module_results
+```
+
+Obtain list of SAP Software files using Python Virtual Environment `/tmp/python_venv`
+```yaml
+- name: Obtain list of SAP Software files using Python Virtual Environment
+ community.sap_launchpad.maintenance_planner_files:
+ suser_id: "Enter SAP S-User ID"
+ suser_password: "Enter SAP S-User Password"
+ transaction_name: "Transaction Name or Display ID from Maintenance Planner"
+ register: __module_results
+ environment:
+ PATH: "/tmp/python_venv:{{ ansible_env.PATH }}"
+ PYTHONPATH: "/tmp/python_venv/lib/python3.11/site-packages"
+ VIRTUAL_ENV: "/tmp/python_venv"
+ vars:
+ ansible_python_interpreter: "/tmp/python_venv/bin/python3.11 }}"
+```
+
+### Output format
+#### msg
+- _Type:_ `string`
+
+The status of execution.
+
+#### download_basket
+- _Type:_ `list` with elements of type `dictionary`
+
+A Json list of software download links and filenames.
+```yml
+- DirectLink: https://softwaredownloads.sap.com/file/0020000001739942021
+ Filename: IMDB_SERVER20_060_0-80002031.SAR
+- DirectLink: https://softwaredownloads.sap.com/file/0010000001440232021
+ Filename: KD75379.SAR
+```
+
+## Further Information
+### Installation of prerequisites
+**All preparation steps are included in role `sap_launchpad.sap_software_download`.**
+
+Prerequisite preparation using Python 3.11 Virtual Environment `/tmp/python_venv` (Recommended)
+```yaml
+---
+- name: Example play to install prerequisites for sap_launchpad
+ hosts: all
+ tasks:
+ - name: Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - python311
+ - python311-pip
+ state: present
+
+ - name: Pre-Steps - Install Python modules to Python venv
+ ansible.builtin.pip:
+ name:
+ - wheel
+ - urllib3
+ - requests
+ - beautifulsoup4
+ - lxml
+ virtualenv: "/tmp/python_venv"
+ virtualenv_command: "python3.11 -m venv"
+```
+
+Prerequisite preparation using Python 3.11 system default
+```yaml
+---
+- name: Example play to install prerequisites for sap_launchpad
+ hosts: all
+ tasks:
+ - name: Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - python311
+ - python311-pip
+ state: present
+
+ - name: Install Python module packages
+ ansible.builtin.package:
+ name:
+ - python311-wheel
+ - python311-urllib3
+ - python311-requests
+ - python311-beautifulsoup4
+ - python311-lxml
+ state: present
+```
+**NOTE:** Python modules are installed as packages to avoid `externally-managed-environment` error.
+
+## License
+Apache 2.0
+
+## Maintainers
+Maintainers are shown within [/docs/contributors](./CONTRIBUTORS.md).
+
+## Module Variables
+### suser_id
+- _Required:_ `true`
+- _Type:_ `string`
+
+The SAP S-User ID with download authorization for SAP software.
+
+### suser_password
+- _Required:_ `true`
+- _Type:_ `string`
+
+The password for the SAP S-User specified in `suser_id`.
+
+### transaction_name
+- _Required:_ `true`
+- _Type:_ `string`
+
+The name or display ID of a transaction from the SAP Maintenance Planner.
diff --git a/docs/module_maintenance_planner_stack_xml_download.md b/docs/module_maintenance_planner_stack_xml_download.md
index 066a120..c920652 100644
--- a/docs/module_maintenance_planner_stack_xml_download.md
+++ b/docs/module_maintenance_planner_stack_xml_download.md
@@ -1,2 +1,137 @@
# maintenance_planner_stack_xml_download Ansible Module
+## Description
+The Ansible Module `maintenance_planner_stack_xml_download` is used to obtain Stack file belonging to Maintenance Plan transaction.
+
+## Dependencies
+This module requires the following Python modules to be installed on the target node (the machine where SAP software will be downloaded):
+
+- wheel
+- urllib3
+- requests
+- beautifulsoup4
+- lxml
+
+Installation instructions are available at [Installation of prerequisites](#installation-of-prerequisites)
+
+## Execution
+
+### Example
+Obtain Stack file
+```yaml
+- name: Obtain Stack file
+ community.sap_launchpad.maintenance_planner_stack_xml_download:
+ suser_id: "Enter SAP S-User ID"
+ suser_password: "Enter SAP S-User Password"
+ transaction_name: "Transaction Name or Display ID from Maintenance Planner"
+ dest: "/software"
+ register: __module_results
+```
+
+Obtain Stack file using Python Virtual Environment `/tmp/venv`
+```yaml
+- name: Obtain Stack file using Python Virtual Environment
+ community.sap_launchpad.maintenance_planner_stack_xml_download:
+ suser_id: "Enter SAP S-User ID"
+ suser_password: "Enter SAP S-User Password"
+ transaction_name: "Transaction Name or Display ID from Maintenance Planner"
+ dest: "/software"
+ register: __module_results
+ environment:
+ PATH: "/tmp/venv:{{ ansible_env.PATH }}"
+ PYTHONPATH: "/tmp/venv/lib/python3.11/site-packages"
+ VIRTUAL_ENV: "/tmp/venv"
+ vars:
+ ansible_python_interpreter: "/tmp/venv/bin/python3.11 }}"
+```
+
+### Output format
+#### msg
+- _Type:_ `string`
+
+The status of execution.
+
+## Further Information
+### Installation of prerequisites
+**All preparation steps are included in role `sap_launchpad.sap_software_download`.**
+
+Prerequisite preparation using Python 3.11 Virtual Environment `/tmp/python_venv` (Recommended)
+```yaml
+---
+- name: Example play to install prerequisites for sap_launchpad
+ hosts: all
+ tasks:
+ - name: Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - python311
+ - python311-pip
+ state: present
+
+ - name: Pre-Steps - Install Python modules to Python venv
+ ansible.builtin.pip:
+ name:
+ - wheel
+ - urllib3
+ - requests
+ - beautifulsoup4
+ - lxml
+ virtualenv: "/tmp/python_venv"
+ virtualenv_command: "python3.11 -m venv"
+```
+
+Prerequisite preparation using Python 3.11 system default
+```yaml
+---
+- name: Example play to install prerequisites for sap_launchpad
+ hosts: all
+ tasks:
+ - name: Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - python311
+ - python311-pip
+ state: present
+
+ - name: Install Python module packages
+ ansible.builtin.package:
+ name:
+ - python311-wheel
+ - python311-urllib3
+ - python311-requests
+ - python311-beautifulsoup4
+ - python311-lxml
+ state: present
+```
+**NOTE:** Python modules are installed as packages to avoid `externally-managed-environment` error.
+
+## License
+Apache 2.0
+
+## Maintainers
+Maintainers are shown within [/docs/contributors](./CONTRIBUTORS.md).
+
+## Module Variables
+### suser_id
+- _Required:_ `true`
+- _Type:_ `string`
+
+The SAP S-User ID with download authorization for SAP software.
+
+### suser_password
+- _Required:_ `true`
+- _Type:_ `string`
+
+The password for the SAP S-User specified in `suser_id`.
+
+### transaction_name
+- _Required:_ `true`
+- _Type:_ `string`
+
+The name or display ID of a transaction from the SAP Maintenance Planner.
+
+### dest
+- _Required:_ `true`
+- _Type:_ `string`
+
+The directory where downloaded SAP software files will be stored.
diff --git a/docs/module_software_center_download.md b/docs/module_software_center_download.md
index 8b7c939..84254a1 100644
--- a/docs/module_software_center_download.md
+++ b/docs/module_software_center_download.md
@@ -1,2 +1,204 @@
# software_center_download Ansible Module
+## Description
+The Ansible Module `software_center_download` is used to download SAP Software file from SAP.
+
+## Dependencies
+This module requires the following Python modules to be installed on the target node (the machine where SAP software will be downloaded):
+
+- wheel
+- urllib3
+- requests
+- beautifulsoup4
+- lxml
+
+Installation instructions are available at [Installation of prerequisites](#installation-of-prerequisites)
+
+## Execution
+
+### Example
+Download SAP Software file
+```yaml
+- name: Download SAP Software file
+ community.sap_launchpad.software_center_download:
+ suser_id: "Enter SAP S-User ID"
+ suser_password: "Enter SAP S-User Password"
+ search_query: "Enter SAP Software file name"
+ dest: "Enter download path (e.g. /software)"
+```
+
+Download SAP Software file, but search for alternatives if not found
+```yaml
+- name: Download SAP Software file with alternative
+ community.sap_launchpad.software_center_download:
+ suser_id: "Enter SAP S-User ID"
+ suser_password: "Enter SAP S-User Password"
+ search_query: "Enter SAP Software file name"
+ dest: "Enter download path (e.g. /software)"
+ search_alternatives: true
+ deduplicate: "last"
+```
+
+Download list of SAP Software files, but search for alternatives if not found
+```yaml
+- name: Download list of SAP Software files
+ community.sap_launchpad.software_center_download:
+ suser_id: "Enter SAP S-User ID"
+ suser_password: "Enter SAP S-User Password"
+ search_query: "{{ item }}"
+ dest: "Enter download path (e.g. /software)"
+ search_alternatives: true
+ deduplicate: "last"
+ loop:
+ - "Enter SAP Software file name 1"
+ - "Enter SAP Software file name 2"
+ loop_control:
+ label: "{{ item }} : {{ __module_results.msg | d('') }}"
+ register: __module_results
+ retries: 1
+ until: __module_results is not failed
+```
+
+Download SAP Software file using Python Virtual Environment `/tmp/venv`
+```yaml
+- name: Download list of SAP Software files
+ community.sap_launchpad.software_center_download:
+ suser_id: "Enter SAP S-User ID"
+ suser_password: "Enter SAP S-User Password"
+ search_query: "{{ item }}"
+ dest: "Enter download path (e.g. /software)"
+ loop:
+ - "Enter SAP Software file name 1"
+ - "Enter SAP Software file name 2"
+ loop_control:
+ label: "{{ item }} : {{ __module_results.msg | d('') }}"
+ register: __module_results
+ retries: 1
+ until: __module_results is not failed
+ environment:
+ PATH: "/tmp/venv:{{ ansible_env.PATH }}"
+ PYTHONPATH: "/tmp/venv/lib/python3.11/site-packages"
+ VIRTUAL_ENV: "/tmp/venv"
+ vars:
+ ansible_python_interpreter: "/tmp/venv/bin/python3.11 }}"
+```
+
+### Output format
+#### msg
+- _Type:_ `string`
+
+The status of execution.
+
+## Further Information
+### Installation of prerequisites
+**All preparation steps are included in role `sap_launchpad.sap_software_download`.**
+
+Prerequisite preparation using Python 3.11 Virtual Environment `/tmp/python_venv` (Recommended)
+```yaml
+---
+- name: Example play to install prerequisites for sap_launchpad
+ hosts: all
+ tasks:
+ - name: Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - python311
+ - python311-pip
+ state: present
+
+ - name: Pre-Steps - Install Python modules to Python venv
+ ansible.builtin.pip:
+ name:
+ - wheel
+ - urllib3
+ - requests
+ - beautifulsoup4
+ - lxml
+ virtualenv: "/tmp/python_venv"
+ virtualenv_command: "python3.11 -m venv"
+```
+
+Prerequisite preparation using Python 3.11 system default
+```yaml
+---
+- name: Example play to install prerequisites for sap_launchpad
+ hosts: all
+ tasks:
+ - name: Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - python311
+ - python311-pip
+ state: present
+
+ - name: Install Python module packages
+ ansible.builtin.package:
+ name:
+ - python311-wheel
+ - python311-urllib3
+ - python311-requests
+ - python311-beautifulsoup4
+ - python311-lxml
+ state: present
+```
+**NOTE:** Python modules are installed as packages to avoid `externally-managed-environment` error.
+
+## License
+Apache 2.0
+
+## Maintainers
+Maintainers are shown within [/docs/contributors](./CONTRIBUTORS.md).
+
+## Module Variables
+### suser_id
+- _Required:_ `true`
+- _Type:_ `string`
+
+The SAP S-User ID with download authorization for SAP software.
+
+### suser_password
+- _Required:_ `true`
+- _Type:_ `string`
+
+The password for the SAP S-User specified in `suser_id`.
+
+### search_query
+- _Type:_ `string`
+
+The SAP software file name to download.
+
+### download_link
+- _Type:_ `string`
+
+Direct download link to the SAP software.
+Download links can be obtained from SAP Software Center or using module `module_maintenance_planner_files`.
+
+### download_filename
+- _Type:_ `string`
+
+Download filename of the SAP software.
+Download names can be obtained from SAP Software Center or using module `module_maintenance_planner_files`.
+
+### dest
+- _Required:_ `true`
+- _Type:_ `string`
+
+The directory where downloaded SAP software files will be stored.
+
+### deduplicate
+- _Type:_ `string`
+
+Specifies how to handle duplicate file results.
+If multiple files with the same name are found, this setting determines which one to download.
+- `first`: Download the first file found
+- `last`: Download the last file found.
+
+### search_alternatives
+- _Type:_ `boolean`
+
+Enables searching for alternative files if the requested file is not found.
+
+### dry_run
+- _Type:_ `boolean
+
+Check availability of SAP Software without downloading.
diff --git a/galaxy.yml b/galaxy.yml
index 90db301..02fd643 100644
--- a/galaxy.yml
+++ b/galaxy.yml
@@ -8,26 +8,26 @@ namespace: community
name: sap_launchpad
# The version of the collection. Must be compatible with semantic versioning
-version: 1.1.0
+version: 1.2.0
# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
readme: README.md
# A list of the collection's content authors. Can be just the name or in the format 'Full Name (url)
authors:
- - IBM Lab for SAP Solutions
- - IBM Cloud for SAP
- - IBM Consulting for SAP
- - MatthiasWinzeler
+ - Sean Freeman
+ - Sheng Li Zhu
+ - Matthias Winzeler
+ - Marcel Mamula
### OPTIONAL but strongly recommended
# A short summary description of the collection
-description: Collection of Ansible Modules for SAP Launchpad APIs
+description: Collection for automation of SAP Launchpad API, such as SAP Software downloads
# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only
# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file'
license:
-- Apache-2.0
+ - Apache-2.0
# The path to the license file for the collection. This path is relative to the root of the collection. This key is
# mutually exclusive with 'license'
@@ -35,7 +35,11 @@ license_file: ''
# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character
# requirements as 'namespace' and 'name'
-tags: []
+tags:
+ - sap
+ - download
+ - application
+ - tools
# Collections that this collection requires to be installed for it to be usable. The key of the dict is the
# collection label 'namespace.name'. The value is a version range
@@ -59,4 +63,15 @@ issues: https://github.com/sap-linuxlab/community.sap_launchpad/issues
# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This
# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry',
# and '.git' are always filtered
-build_ignore: ['tests', 'internal-*']
+build_ignore:
+ - 'tests'
+ - 'roles/*/tests'
+ - '.git*'
+ - 'roles/.git*'
+ - 'roles/*/.git*'
+ - '.ansible-lint'
+ - 'roles/*/.ansible-lint'
+ - '.yamllint*'
+ - 'roles/*/.yamllint*'
+ - '.pylintrc*'
+ - 'bindep*'
diff --git a/plugins/module_utils/sap_api_common.py b/plugins/module_utils/sap_api_common.py
index 204b492..164b173 100644
--- a/plugins/module_utils/sap_api_common.py
+++ b/plugins/module_utils/sap_api_common.py
@@ -37,9 +37,23 @@ def _request(url, **kwargs):
method = 'POST' if kwargs.get('data') or kwargs.get('json') else 'GET'
res = https_session.request(method, url, **kwargs)
- if (res.status_code == 403
- and res.json()['errorMessage'].startswith('Account Temporarily Locked Out')):
- raise Exception('SAP ID Service has reported `Account Temporarily Locked Out`. Please reset password to regain access and try again.')
+ # Validating against `res.text` can cause long execution time, because fuzzy search result can contain large `res.text`.
+ # This can be prevented by validating `res.status_code` check before `res.text`.
+ # Example: 'Two-Factor Authentication' is only in `res.text`, which can lead to long execution.
+
+ if res.status_code == 403:
+ if 'You are not authorized to download this file' in res.text:
+ raise Exception(f'You are not authorized to download this file.')
+ elif 'Account Temporarily Locked Out' in res.text:
+ raise Exception(f'Account Temporarily Locked Out. Please reset password to regain access and try again.')
+ else:
+ res.raise_for_status()
+
+ if res.status_code == 404:
+ if 'The file you have requested cannot be found' in res.text:
+ raise Exception(f'The file you have requested cannot be found.')
+ else:
+ res.raise_for_status()
res.raise_for_status()
diff --git a/plugins/module_utils/sap_id_sso.py b/plugins/module_utils/sap_id_sso.py
index c0028d2..1078d56 100644
--- a/plugins/module_utils/sap_id_sso.py
+++ b/plugins/module_utils/sap_id_sso.py
@@ -215,6 +215,7 @@ def _get_sdk_build_number(api_key):
build_number = match.group(1)
logger.debug(f'gigya sdk build number: {build_number}')
+ GIGYA_SDK_BUILD_NUMBER = build_number
return build_number
diff --git a/plugins/module_utils/sap_launchpad_maintenance_planner_runner.py b/plugins/module_utils/sap_launchpad_maintenance_planner_runner.py
index bae7495..81a9679 100644
--- a/plugins/module_utils/sap_launchpad_maintenance_planner_runner.py
+++ b/plugins/module_utils/sap_launchpad_maintenance_planner_runner.py
@@ -281,6 +281,26 @@ def _get_transaction_name(trans_id):
return transaction['trans_name']
+def get_transaction_id(name):
+ """
+ Search transaction ID using transaction Name or Display ID.
+
+ Args:
+ name: transaction name or display id.
+ """
+ transactions = get_transactions()
+ transaction_name = [t for t in transactions if t['trans_name'] == name]
+ if not transaction_name:
+ # Repeat search using Display ID
+ transaction_display_id = [t for t in transactions if t['trans_display_id'] == name]
+ if not transaction_display_id:
+ raise KeyError(f'Name or Display ID {name} not found in transactionsX')
+ else:
+ return transaction_display_id[0]['trans_id']
+ else:
+ return transaction_name[0]['trans_id']
+
+
def _get_transaction(key, value):
transactions = get_transactions()
trans = [t for t in transactions if t[key] == value]
diff --git a/plugins/module_utils/sap_launchpad_software_center_download_runner.py b/plugins/module_utils/sap_launchpad_software_center_download_runner.py
index 86cc55b..d4914fd 100644
--- a/plugins/module_utils/sap_launchpad_software_center_download_runner.py
+++ b/plugins/module_utils/sap_launchpad_software_center_download_runner.py
@@ -10,6 +10,7 @@
from . import constants as C
from .sap_api_common import _request, https_session
from .sap_id_sso import _get_sso_endpoint_meta
+from .sap_launchpad_software_center_download_search_fuzzy import *
logger = logging.getLogger(__name__)
@@ -17,27 +18,77 @@
MAX_RETRY_TIMES = 3
-def search_software_filename(name, deduplicate):
- """Return a single software that matched the filename
+def search_software_filename(name, deduplicate, search_alternatives):
"""
- search_results = _search_software(name)
- softwares = [r for r in search_results if r['Title'] == name or r['Description'] == name]
- num_files=len(softwares)
- if num_files == 0:
- raise ValueError(f'no result found for {name}')
- elif num_files > 1 and deduplicate == '':
- names = [s['Title'] for s in softwares]
- raise ValueError('more than one results were found: %s. '
+ Execute search for SAP Software or its alternative when search_alternatives is true.
+
+ Args:
+ name: The filename name to check (e.g. 'SAPCAR_1115-70006178.EXE').
+ deduplicate: Select deduplication logic from 'first', 'last'
+ search_alternatives: Boolean for enabling fuzzy search.
+
+ Returns:
+ download_link: Download link of matched SAP Software.
+ filename: File name of matched SAP Software.
+ alternative_found: True if alternative search was successful.
+ """
+
+ alternative_found = False
+ software_search = _search_software(name)
+ software_filtered = [r for r in software_search if r['Title'] == name or r['Description'] == name]
+
+ files_count=len(software_filtered)
+ if files_count == 0:
+ # Run fuzzy search if search_alternatives was selected
+ if search_alternatives:
+ software_fuzzy_found = search_software_fuzzy(name)
+ software_fuzzy_filtered, suggested_filename = filter_fuzzy_search(software_fuzzy_found, name)
+ if len(software_fuzzy_filtered) == 0:
+ raise ValueError(f'File {name} is not available to download and has no alternatives')
+
+ software_fuzzy_alternatives = software_fuzzy_filtered[0].get('Title')
+
+ # Search has to be filtered again, because API call can get
+ # duplicates like 70SWPM10SP43_2-20009701.sar for SWPM10SP43_2-20009701.SAR
+ software_search_alternatives = _search_software(software_fuzzy_alternatives)
+ software_search_alternatives_filtered = [
+ file for file in software_search_alternatives
+ if file.get('Title', '').startswith(suggested_filename)
+ ]
+ alternatives_count=len(software_search_alternatives_filtered)
+ if alternatives_count == 0:
+ raise ValueError(f'File {name} is not available to download and has no alternatives')
+ elif alternatives_count > 1 and deduplicate == '':
+ names = [s['Title'] for s in software_search_alternatives_filtered]
+ raise ValueError('More than one results were found: %s. '
+ 'please use the correct full filename' % names)
+ elif alternatives_count > 1 and deduplicate == 'first':
+ software_found = software_search_alternatives_filtered[0]
+ alternative_found = True
+ elif alternatives_count > 1 and deduplicate == 'last':
+ software_found = software_search_alternatives_filtered[alternatives_count-1]
+ alternative_found = True
+ else:
+ software_found = software_search_alternatives_filtered[0]
+ alternative_found = True
+ else:
+ raise ValueError(f'File {name} is not available to download. Enable "search_alternatives" to search for alternatives.')
+
+ elif files_count > 1 and deduplicate == '':
+ names = [s['Title'] for s in software_filtered]
+ raise ValueError('More than one results were found: %s. '
'please use the correct full filename' % names)
- elif num_files > 1 and deduplicate == 'first':
- software = softwares[0]
- elif num_files > 1 and deduplicate == 'last':
- software = softwares[num_files-1]
+ elif files_count > 1 and deduplicate == 'first':
+ software_found = software_filtered[0]
+ elif files_count > 1 and deduplicate == 'last':
+ software_found = software_filtered[files_count-1]
else:
- software = softwares[0]
+ software_found = software_filtered[0]
- download_link, filename = software['DownloadDirectLink'], name
- return (download_link, filename)
+ download_link = software_found['DownloadDirectLink']
+ filename = _get_valid_filename(software_found)
+
+ return (download_link, filename, alternative_found)
def download_software(download_link, filename, output_dir, retry=0):
@@ -151,6 +202,7 @@ def download_software_via_legacy_api(username, password, download_link,
def _search_software(keyword):
+
url = C.URL_SOFTWARE_CENTER_SERVICE + '/SearchResultSet'
params = {
'SEARCH_MAX_RESULT': 500,
@@ -167,7 +219,7 @@ def _search_software(keyword):
j = json.loads(res.text)
results = j['d']['results']
except json.JSONDecodeError:
- # When use has no authority to search some specified softwares,
+ # When use has no authority to search some specified files,
# it will return non-json response, which is actually expected.
# So just return an empty list.
logger.warning('Non-JSON response returned for software searching')
@@ -246,3 +298,27 @@ def _is_checksum_matched(f, etag):
for chunk in iter(lambda: f.read(4096 * hash.block_size), b""):
hash.update(chunk)
return hash.hexdigest() == checksum
+
+
+def _get_valid_filename(software_found):
+ """
+ Ensure that CD Media have correct filenames from description.
+ Example: S4CORE105_INST_EXPORT_1.zip downloads as 19118000000000004323
+
+ Args:
+ software_found: List[0] with dictionary of file.
+
+ Returns:
+ Valid filename for CD Media files, where applicable.
+ """
+
+ # Check if Title contains filename and extension
+ if re.match(r'^[^/\\\0]+\.[^/\\\0]+$', software_found['Title']):
+ return software_found['Title']
+ else:
+ # Check if Description contains filename and extension
+ if re.match(r'^[^/\\\0]+\.[^/\\\0]+$', software_found['Description']):
+ return software_found['Description']
+ else:
+ # Default to Title if Description does not help
+ return software_found['Title']
diff --git a/plugins/module_utils/sap_launchpad_software_center_download_search_fuzzy.py b/plugins/module_utils/sap_launchpad_software_center_download_search_fuzzy.py
index 98be35f..9fba4cf 100644
--- a/plugins/module_utils/sap_launchpad_software_center_download_search_fuzzy.py
+++ b/plugins/module_utils/sap_launchpad_software_center_download_search_fuzzy.py
@@ -1,5 +1,7 @@
import csv
import logging
+import os
+import re
import requests
@@ -8,16 +10,38 @@
def search_software_fuzzy(query, max=None, csv_filename=None):
- """Returns a list of dict for the software results.
"""
- results = _search_software(query)
+ Execute fuzzy search using Unique Software ID instead of name.
+ ID is unique to Product and Platform combination.
+ Example of shared ID 80002616:
+ - SYBCTRL_1440-80002616.SAR
+ - SYBCTRL_1436-80002616.SAR
+
+ Args:
+ query: The filename name to check (e.g. 'SYBCTRL_1440-80002616.SAR').
+
+ Returns:
+ The list of dict for the software results.
+ Empty list is returned if query does not contain ID.
+ """
+ # Format query to split filename.
+ filename_base = os.path.splitext(query)[0] # Remove extension
+
+ # Ensure that fuzzy search is run only for valid IDs.
+ # This excludes unique files without ID like: S4CORE105_INST_EXPORT_1.zip
+ if '-' in filename_base:
+ filename_id = filename_base.split('-')[-1] # Split id from filename
+ else:
+ return []
+
+ results = _search_software(filename_id)
num = 0
- softwares = []
+ fuzzy_results = []
while True:
for r in results:
r = _remove_useless_keys(r)
- softwares.append(r)
+ fuzzy_results.append(r)
num += len(results)
# quit if no results or results number reach the max
if num == 0 or (max and num >= max):
@@ -35,9 +59,216 @@ def search_software_fuzzy(query, max=None, csv_filename=None):
break
if csv_filename:
- _write_software_results(softwares, csv_filename)
+ _write_software_results(fuzzy_results, csv_filename)
return
- return softwares
+ return fuzzy_results
+
+
+def filter_fuzzy_search(fuzzy_results, filename):
+ """
+ Filter fuzzy search output using filename.
+
+ Args:
+ fuzzy_results: Output of search_software_fuzzy.
+ filename: The filename name to check
+
+ Returns:
+ fuzzy_results_sorted: The list of files that match the filter criteria, sorted by 'Title' in descending order.
+ suggested_filename: Return generated keyword for further reuse after API call.
+ """
+
+ # Prepare filtered list for specific SPS
+ suggested_filename = _prepare_search_filename_specific(filename)
+
+ fuzzy_results_filtered = [
+ file for file in fuzzy_results
+ if file.get('Title', '').startswith(suggested_filename)
+ ]
+
+ # Repeat filtering without specific SPS
+ if len(fuzzy_results_filtered) == 0:
+ suggested_filename = _prepare_search_filename_nonspecific(filename)
+
+ fuzzy_results_filtered = [
+ file for file in fuzzy_results
+ if file.get('Title', '').startswith(suggested_filename)
+ ]
+
+ # fuzzy_results_sorted = sorted(fuzzy_results_filtered, key=lambda item: item.get('Title', ''), reverse=True)
+ fuzzy_results_sorted =_sort_fuzzy_results(fuzzy_results_filtered, filename)
+
+ return fuzzy_results_sorted, suggested_filename
+
+
+def _prepare_search_filename_specific(filename):
+ """
+ Prepare suggested search keyword for known products specific to SPS version.
+
+ Args:
+ filename: The filename name to check
+
+ Returns:
+ Suggested filename to filter fuzzy search.
+ """
+
+ # Format query to split filename.
+ filename_base = os.path.splitext(filename)[0] # Remove extension
+ filename_name = filename_base.rsplit('_', 1)[0] # Split software name from version
+ # Following filenames will be processed using default filename_name split.
+ # Return SYBCTRL for SYBCTRL_1436-80002616.SAR
+ # Return SMDA720 for SMDA720_SP11_22-80003641.SAR
+
+
+ for swpm_version in ("70SWPM1", "70SWPM2", "SWPM1", "SWPM2"):
+ if filename_base.startswith(swpm_version):
+ return swpm_version
+
+ # Return SUM11SP04 for SUM11SP04_2-80006858.SAR
+ if filename_base.startswith('SUM'):
+ return filename.split('-')[0].split('_')[0]
+
+ # Return DBATL740O11 for DBATL740O11_48-80002605.SAR
+ elif filename_base.startswith('DBATL'):
+ return filename.split('-')[0].split('_')[0]
+
+ # Return IMDB_AFL20_077 for IMDB_AFL20_077_0-80002045.SAR
+ # Return IMDB_AFL100_102P for IMDB_AFL100_102P_41-10012328.SAR
+ elif filename_base.startswith('IMDB_AFL'):
+ return "_".join(filename.split('-')[0].split('_')[:3])
+
+ # Return IMDB_CLIENT20_021 for IMDB_CLIENT20_021_31-80002082.SAR
+ elif filename_base.startswith('IMDB_CLIENT'):
+ return "_".join(filename.split('-')[0].split('_')[:3])
+
+ # IMDB_LCAPPS for SAP HANA 1.0
+ # Return IMDB_LCAPPS_122 for IMDB_LCAPPS_122P_3300-20010426.SAR
+ elif filename_base.startswith('IMDB_LCAPPS_1'):
+ filename_parts = filename.split('-')[0].rsplit('_', 2)
+ return f"{filename_parts[0]}_{filename_parts[1][:3]}"
+
+ # IMDB_LCAPPS for SAP HANA 2.0
+ # Return IMDB_LCAPPS_206 for IMDB_LCAPPS_2067P_400-80002183.SAR
+ elif filename_base.startswith('IMDB_LCAPPS_2'):
+ filename_parts = filename.split('-')[0].rsplit('_', 2)
+ return f"{filename_parts[0]}_{filename_parts[1][:3]}"
+
+ # Return IMDB_SERVER20_06 (SPS06) for IMDB_SERVER20_067_4-80002046.SAR
+ elif filename_base.startswith('IMDB_SERVER'):
+ filename_parts = filename.split('-')[0].rsplit('_', 2)
+ return f"{filename_parts[0]}_{filename_parts[1][:2]}"
+
+ # Return SAPEXE_100 for SAPEXE_100-80005374.SAR
+ elif filename_base.startswith('SAPEXE'):
+ return filename_base.split('-')[0]
+
+ # Return SAPHANACOCKPIT02 (SPS02) for SAPHANACOCKPIT02_0-70002300.SAR
+ elif filename_base.startswith('SAPHANACOCKPIT'):
+ return filename_base.split('-')[0].rsplit('_', 1)[0]
+
+ # Return unchanged filename_name
+ else:
+ return filename_name
+
+
+def _prepare_search_filename_nonspecific(filename):
+ """
+ Prepare suggested search keyword for known products nonspecific to SPS version.
+
+ Args:
+ filename: The filename name to check
+
+ Returns:
+ Suggested filename to filter fuzzy search.
+ """
+
+ # Format query to split filename.
+ filename_base = os.path.splitext(filename)[0] # Remove extension
+ filename_name = filename_base.rsplit('_', 1)[0] # Split software name from version
+
+ # Return SUM11 for SUM11SP04_2-80006858.SAR
+ if filename_base.startswith('SUM'):
+ if filename_base.startswith('SUMHANA'):
+ return 'SUMHANA'
+ elif filename_base[3:5].isdigit(): # Allow only SUM and 2 digits
+ return filename_base[:5]
+
+ # Return DBATL740O11 for DBATL740O11_48-80002605.SAR
+ elif filename_base.startswith('DBATL'):
+ return filename.split('-')[0].split('_')[0]
+
+ # Return IMDB_AFL20 for IMDB_AFL20_077_0-80002045.SAR
+ # Return IMDB_AFL100 for IMDB_AFL100_102P_41-10012328.SAR
+ elif filename_base.startswith('IMDB_AFL'):
+ return "_".join(filename.split('-')[0].split('_')[:2])
+
+ # Return IMDB_CLIENT for IMDB_CLIENT20_021_31-80002082.SAR
+ elif filename_base.startswith('IMDB_CLIENT'):
+ return 'IMDB_CLIENT'
+
+ # Return IMDB_LCAPPS for IMDB_LCAPPS_122P_3300-20010426.SAR
+ elif filename_base.startswith('IMDB_LCAPPS'):
+ return "_".join(filename.split('-')[0].split('_')[:2])
+
+ # Return IMDB_SERVER20 for IMDB_SERVER20_067_4-80002046.SAR
+ elif filename_base.startswith('IMDB_SERVER'):
+ return "_".join(filename.split('-')[0].split('_')[:2])
+
+ # Return SAPHANACOCKPIT for SAPHANACOCKPIT02_0-70002300.SAR
+ elif filename_base.startswith('SAPHANACOCKPIT'):
+ return 'SAPHANACOCKPIT'
+
+ # Return SAPHOSTAGENT for SAPHOSTAGENT61_61-80004831.SAR
+ elif filename_base.startswith('SAPHOSTAGENT'):
+ return 'SAPHOSTAGENT'
+
+ # Return unchanged filename_name
+ else:
+ return filename_name
+
+
+def _sort_fuzzy_results(fuzzy_results_filtered, filename):
+ """
+ Sort results of fuzzy search for known nonstandard versions.
+ Example:
+ IMDB_LCAPPS_122P_3500-20010426.SAR, IMDB_LCAPPS_122P_600-70001332.SAR
+
+ Args:
+ fuzzy_results_filtered: The list of filtered fuzzy results.
+ filename: The filename name to check.
+
+ Returns:
+ Ordered list of fuzzy results, based on known nonstandard versions.
+ """
+
+ if _get_numeric_search_keyword(filename):
+ software_fuzzy_sorted = sorted(
+ fuzzy_results_filtered,
+ key= lambda item: _get_numeric_search_keyword(item.get('Title', '')),
+ reverse=True,
+ )
+ else:
+ software_fuzzy_sorted = sorted(
+ fuzzy_results_filtered,
+ key=lambda item: item.get('Title', ''),
+ reverse=True,
+ )
+
+ return software_fuzzy_sorted
+
+
+def _get_numeric_search_keyword(filename):
+ """
+ Extract integer value of version from filename.
+
+ Args:
+ filename: The filename name to check.
+
+ """
+ match = re.search(r'_(\d+)-', filename)
+ if match:
+ return int(match.group(1))
+ else:
+ return None
def _search_software(keyword, remove_useless_keys=False):
diff --git a/plugins/modules/maintenance_planner_files.py b/plugins/modules/maintenance_planner_files.py
index 5735920..f15d477 100644
--- a/plugins/modules/maintenance_planner_files.py
+++ b/plugins/modules/maintenance_planner_files.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
# SAP Maintenance Planner files retrieval
@@ -27,11 +27,11 @@
type: str
transaction_name:
description:
- - Transaction name of your Maintenance Planner session.
+ - Transaction Name or Transaction Display ID from Maintenance Planner.
required: true
type: str
author:
- - Lab for SAP Solutions
+ - SAP LinuxLab
'''
@@ -115,7 +115,7 @@ def run_module():
auth_userapps()
# EXEC: Get MP stack transaction id from transaction name
- transaction_id = get_transaction_id_by_name(transaction_name)
+ transaction_id = get_transaction_id(transaction_name)
# EXEC: Get a json list of download_links and download_filenames
download_basket_details = get_transaction_filename_url(transaction_id)
diff --git a/plugins/modules/maintenance_planner_stack_xml_download.py b/plugins/modules/maintenance_planner_stack_xml_download.py
index e7a5182..6e958eb 100644
--- a/plugins/modules/maintenance_planner_stack_xml_download.py
+++ b/plugins/modules/maintenance_planner_stack_xml_download.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
# SAP Maintenance Planner Stack XML download
@@ -27,16 +27,16 @@
type: str
transaction_name:
description:
- - Transaction name of your Maintenance Planner session.
+ - Transaction Name or Transaction Display ID from Maintenance Planner.
required: true
type: str
dest:
description:
- - Destination folder.
+ - Destination folder path.
required: true
type: str
author:
- - Lab for SAP Solutions
+ - SAP LinuxLab
'''
@@ -115,7 +115,7 @@ def run_module():
auth_userapps()
# EXEC: Get MP stack transaction id from transaction name
- transaction_id = get_transaction_id_by_name(transaction_name)
+ transaction_id = get_transaction_id(transaction_name)
# EXEC: Download the MP Stack XML file
get_transaction_stack_xml(transaction_id, dest)
diff --git a/plugins/modules/software_center_download.py b/plugins/modules/software_center_download.py
index 9278572..5b909e8 100644
--- a/plugins/modules/software_center_download.py
+++ b/plugins/modules/software_center_download.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
# SAP software download module
@@ -26,6 +26,14 @@
required: true
type: str
softwarecenter_search_query:
+ description:
+ - "Deprecated. Use 'search_query' instead."
+ required: false
+ type: str
+ deprecated:
+ alternative: search_query
+ removed_in: "1.2.0"
+ search_query:
description:
- Filename of the SAP software to download.
required: false
@@ -42,7 +50,7 @@
type: str
dest:
description:
- - Destination folder.
+ - Destination folder path.
required: true
type: str
deduplicate:
@@ -50,8 +58,18 @@
- How to handle multiple search results.
required: false
type: str
+ search_alternatives:
+ description:
+ - Enable search for alternative packages, when filename is not available.
+ required: false
+ type: bool
+ dry_run:
+ description:
+ - Check availability of SAP Software without downloading.
+ required: false
+ type: bool
author:
- - Lab for SAP Solutions
+ - SAP LinuxLab
'''
@@ -60,7 +78,7 @@
community.sap_launchpad.sap_launchpad_software_center_download:
suser_id: 'SXXXXXXXX'
suser_password: 'password'
- softwarecenter_search_query:
+ search_query:
- 'SAPCAR_1324-80000936.EXE'
dest: "/tmp/"
- name: Download using direct link and filename
@@ -77,6 +95,14 @@
description: the status of the process
returned: always
type: str
+filename:
+ description: the name of the original or alternative file found to download.
+ returned: always
+ type: str
+alternative:
+ description: true if alternative file was found
+ returned: always
+ type: bool
'''
@@ -90,6 +116,29 @@
from ..module_utils.sap_launchpad_software_center_download_runner import *
from ..module_utils.sap_id_sso import sap_sso_login
+def _check_similar_files(dest, filename):
+ """
+ Checks for similar files in the download path based on the given filename.
+
+ Args:
+ dest (str): The path where files are downloaded.
+ filename (str): The filename to check for similar files.
+
+ Returns:
+ bool: True if similar files exist, False otherwise.
+ filename_similar_names: A list of similar filenames if they exist, empty list otherwise.
+ """
+ # pattern = dest + '/**/' + os.path.splitext(filename)[0]
+ filename_base = os.path.splitext(filename)[0]
+ filename_pattern = os.path.join(dest, "**", filename_base)
+ filename_similar = glob.glob(filename_pattern, recursive=True)
+
+ if filename_similar:
+ filename_similar_names = [os.path.basename(f) for f in filename_similar]
+ return True, filename_similar_names
+ else:
+ return False, []
+
def run_module():
@@ -98,17 +147,13 @@ def run_module():
suser_id=dict(type='str', required=True),
suser_password=dict(type='str', required=True, no_log=True),
softwarecenter_search_query=dict(type='str', required=False, default=''),
+ search_query=dict(type='str', required=False, default=''),
download_link=dict(type='str', required=False, default=''),
download_filename=dict(type='str', required=False, default=''),
dest=dict(type='str', required=True),
dry_run=dict(type='bool', required=False, default=False),
- deduplicate=dict(type='str', required=False, default='')
- )
-
- # Define result dictionary objects to be passed back to Ansible
- result = dict(
- changed=False,
- msg=''
+ deduplicate=dict(type='str', required=False, default=''),
+ search_alternatives=dict(type='bool', required=False, default=False)
)
# Instantiate module
@@ -117,52 +162,133 @@ def run_module():
supports_check_mode=True
)
- # Check mode
- if module.check_mode:
- module.exit_json(**result)
-
# Define variables based on module inputs
username = module.params.get('suser_id')
password = module.params.get('suser_password')
- query = module.params.get('softwarecenter_search_query')
+
+ if module.params['search_query'] != '':
+ query = module.params['search_query']
+ elif module.params['softwarecenter_search_query'] != '':
+ query = module.params['softwarecenter_search_query']
+ module.warn("The 'softwarecenter_search_query' is deprecated and will be removed in a future version. Use 'search_query' instead.")
+ else:
+ query = None
+
+ dest = module.params['dest']
download_link= module.params.get('download_link')
download_filename= module.params.get('download_filename')
- dest = module.params.get('dest')
dry_run = module.params.get('dry_run')
deduplicate = module.params.get('deduplicate')
+ search_alternatives = module.params.get('search_alternatives')
+
+ # Define result dictionary objects to be passed back to Ansible
+ result = dict(
+ changed=False,
+ msg='',
+ filename=download_filename,
+ alternative=False,
+ skipped=False,
+ failed=False
+ )
- # Main run
+ # Check mode
+ if module.check_mode:
+ module.exit_json(**result)
+
+ # Main
try:
- # Search directory and subdirectories for filename without file extension
- filename = query if query else download_filename
- pattern = dest + '/**/' + os.path.splitext(filename)[0] + '*'
- for file in glob.glob(pattern, recursive=True):
- if os.path.exists(file):
- module.exit_json(skipped=True, msg="file {} already exists".format(file))
+ # Ensure that required parameters are provided
+ if not (query or (download_link and download_filename)):
+ module.fail_json(
+ msg="Either 'search_query' or both 'download_link' and 'download_filename' must be provided."
+ )
+ # Search for existing files using exact filename
+ filename = query if query else download_filename
+ filename_exact = os.path.join(dest, filename)
+ result['filename'] = filename
+
+ if os.path.exists(filename_exact):
+ result['skipped'] = True
+ result['msg'] = f"File already exists: {filename}"
+ module.exit_json(**result)
+ else:
+ # Exact file not found, search for similar files with pattern
+ filename_similar_exists, filename_similar_names = _check_similar_files(dest, filename)
+ if filename_similar_exists and not (query and search_alternatives):
+ result['skipped'] = True
+ result['msg'] = f"Similar file(s) already exist: {', '.join(filename_similar_names)}"
+ module.exit_json(**result)
+
# Initiate login with given credentials
sap_sso_login(username, password)
- # EXEC: query
- # execute search_software_filename first to get download link and filename
+ # Execute search_software_filename first to get download link and filename
+ alternative_found = False # True if search_alternatives was successful
if query:
- download_link, download_filename = search_software_filename(query,deduplicate)
-
- # execute download_software
+ download_link, download_filename, alternative_found = search_software_filename(query,deduplicate,search_alternatives)
+
+ # Search for existing files again with alternative filename
+ if search_alternatives and alternative_found:
+ result['filename'] = download_filename
+ result['alternative'] = True
+
+ filename_alternative_exact = os.path.join(dest, download_filename)
+ if os.path.exists(filename_alternative_exact):
+ result['skipped'] = True
+ result['msg'] = f"Alternative file already exists: {download_filename} - original file {query} is not available to download"
+ module.exit_json(**result)
+ else:
+ # Exact file not found, search for similar files with pattern
+ filename_similar_exists, filename_similar_names = _check_similar_files(dest, download_filename)
+ if filename_similar_exists:
+ result['skipped'] = True
+ result['msg'] = f"Similar alternative file(s) already exist: {', '.join(filename_similar_names)}"
+ module.exit_json(**result)
+
+ # Triggers for CD Media, where number was changed to name using _get_valid_filename
+ elif filename != download_filename and not alternative_found:
+ result['filename'] = download_filename
+
+ if os.path.exists(os.path.join(dest, download_filename)):
+ result['skipped'] = True
+ result['msg'] = f"File already exists with correct name: {download_filename}"
+ module.exit_json(**result)
+ else:
+ # Exact file not found, search for similar files with pattern
+ filename_similar_exists, filename_similar_names = _check_similar_files(dest, download_filename)
+ if filename_similar_exists:
+ result['skipped'] = True
+ result['msg'] = f"Similar file(s) already exist for correct name {download_filename}: {', '.join(filename_similar_names)}"
+ module.exit_json(**result)
+
+
+ # Ensure that download_link is provided when query was not provided
+ if not download_link:
+ module.fail_json(msg="Missing parameters 'query' or 'download_link'.")
+
+ # Exit module before download when dry_run is true
if dry_run:
available = is_download_link_available(download_link)
- if available:
- module.exit_json(changed=False, msg="download link {} is available".format(download_link))
+ if available and query and not alternative_found:
+ result['msg'] = f"SAP Software is available to download: {download_filename}"
+ module.exit_json(**result)
+ elif available and query and alternative_found:
+ result['msg'] = f"Alternative SAP Software is available to download: {download_filename} - original file {query} is not available to download"
+ module.exit_json(**result)
else:
- module.fail_json(msg="download link {} is not available".format(download_link))
+ module.fail_json(msg="Download link {} is not available".format(download_link))
download_software(download_link, download_filename, dest)
- # Process return dictionary for Ansible
+ # Update final results json
result['changed'] = True
- result['msg'] = "SAP software download successful"
+ if query and alternative_found:
+ result['msg'] = f"Successfully downloaded alternative SAP software: {download_filename} - original file {query} is not available to download"
+ else:
+ result['msg'] = f"Successfully downloaded SAP software: {download_filename}"
except requests.exceptions.HTTPError as e:
# module.fail_json(msg='SAP SSO authentication failed' + str(e), **result)
diff --git a/requirements.yml b/requirements.yml
index ed97d53..1d00cb5 100644
--- a/requirements.yml
+++ b/requirements.yml
@@ -1 +1,4 @@
---
+# There are no requirements defined for collection.
+collections: []
+roles: []
diff --git a/roles/.gitkeep b/roles/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/roles/sap_software_download/.yamllint.yml b/roles/sap_software_download/.yamllint.yml
new file mode 100644
index 0000000..ee4457c
--- /dev/null
+++ b/roles/sap_software_download/.yamllint.yml
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+# Based on ansible-lint config
+extends: default
+
+rules:
+ braces: {max-spaces-inside: 1, level: error}
+ brackets: {max-spaces-inside: 1, level: error}
+# colons: {max-spaces-after: -1, level: error}
+# commas: {max-spaces-after: -1, level: error}
+ comments:
+ require-starting-space: false
+ min-spaces-from-content: 1
+ comments-indentation: disable
+# document-start: disable
+# empty-lines: {max: 3, level: error}
+# hyphens: {level: error}
+# indentation: disable
+# key-duplicates: enable
+ line-length: disable
+# new-line-at-end-of-file: disable
+# new-lines: {type: unix}
+# trailing-spaces: disable
+ truthy: disable
+ octal-values:
+ forbid-implicit-octal: true
+ forbid-explicit-octal: true
diff --git a/roles/sap_software_download/README.md b/roles/sap_software_download/README.md
new file mode 100644
index 0000000..e26dbe0
--- /dev/null
+++ b/roles/sap_software_download/README.md
@@ -0,0 +1,328 @@
+
+# sap_software_download Ansible Role
+
+
+## Description
+
+The Ansible Role `sap_software_download` is used to download SAP Software Media from SAP.
+
+
+
+## Dependencies
+This role requires the following Python modules to be installed on the target node (the machine where SAP software will be downloaded):
+
+- wheel
+- urllib3
+- requests
+- beautifulsoup4
+- lxml
+
+The role installs these modules if they are not already present. Installation can be done in one of the following ways:
+
+- **Virtual Environment (Default):**
+ - When `sap_software_download_use_venv` is `true` (default), a temporary virtual environment is created, and modules are installed via `pip`.
+ - The Python version is determined by `sap_software_download_python_interpreter`.
+- **System Environment:**
+ - When `sap_software_download_use_venv` is `false`, modules are installed directly into the system's Python environment using OS packages specified by `sap_software_download_python_module_packages`.
+
+
+
+## Prerequisites
+The target node must meet the following requirements:
+
+* **OS Package Repositories:** The operating system must be registered and have access to repositories to install the required Python packages.
+ * The actual package name is determined by the `sap_software_download_python_package` variable.
+ * For example, on some systems, these packages might be named `python3` and `python3-pip`.
+
+
+
+
+## Execution
+
+
+
+
+
+
+### Execution Flow
+
+1. **Input Validation:** The role first checks if all required input variables have been provided.
+2. **Python Environment Preparation:** The role prepares the Python environment:
+ * **Virtual Environment (Default):** A temporary Python virtual environment is created, and all necessary dependencies are installed within it.
+ * **System Environment:** Alternatively, if `sap_software_download_use_venv` is set to `false`, dependencies are installed directly into the system's default Python environment.
+3. **Validate provided S-User credentials** The role will search for `SAPCAR` file to validate credentials and download authorization.
+4. **Maintenance Plan File List:** If the `sap_software_download_mp_transaction` variable is provided, the role retrieves the list of files associated with the specified Maintenance Plan transaction.
+5. **File Relationship Validation:** If `sap_software_download_validate_relationships` is `true`, the role performs validation checks on the relationships between the files to be downloaded.
+ * **Alternative File Search:** If `sap_software_download_find_alternatives` is `true`, the role will search for alternative files if the requested files are not found.
+ * More information about validation logic is available at [Explanation of relationship validation logic](#explanation-of-relationship-validation-logic)
+6. **Maintenance Plan File Download:** If `sap_software_download_mp_transaction` is provided, the role downloads the files associated with the Maintenance Plan.
+7. **Direct File Download:** If `sap_software_download_files` is provided, the role downloads the specified files.
+8. **Virtual Environment Cleanup:** If a temporary Python virtual environment was used, it is removed.
+
+
+
+### Example
+
+Download of SAP Software files using input list
+```yaml
+---
+- name: Ansible Play for downloading SAP Software
+ hosts: localhost
+ become: true
+ tasks:
+ - name: Include role sap_software_download
+ ansible.builtin.include_role:
+ name: community.sap_launchpad.sap_software_download
+ vars:
+ sap_software_download_suser_id: "Enter SAP S-User ID"
+ sap_software_download_suser_password: "Enter SAP S-User Password"
+ sap_software_download_directory: "/software"
+ sap_software_download_files:
+ - 'SAPCAR_1115-70006178.EXE'
+ - 'SAPEXE_100-80005509.SAR'
+```
+
+Download of SAP Software files using Maintenance Plan
+```yaml
+---
+- name: Ansible Play for downloading SAP Software
+ hosts: localhost
+ become: true
+ tasks:
+ - name: Include role sap_software_download
+ ansible.builtin.include_role:
+ name: community.sap_launchpad.sap_software_download
+ vars:
+ sap_software_download_suser_id: "Enter SAP S-User ID"
+ sap_software_download_suser_password: "Enter SAP S-User Password"
+ sap_software_download_directory: "/software"
+ sap_software_download_mp_transaction: 'Transaction Name or Display ID from Maintenance Planner'
+```
+
+Combined download of SAP Software files and Maintenance Plan transaction together with settings:
+- Use default Python instead of Python virtual environment
+- No validation of S-User credentials
+- No validation of relationships
+- No warnings for unavailable files
+- No warnings for unavailable Maintenance Plan transaction
+```yaml
+- name: Ansible Play for downloading SAP Software
+ hosts: localhost
+ become: true
+ tasks:
+ - name: Include role sap_software_download
+ ansible.builtin.include_role:
+ name: community.sap_launchpad.sap_software_download
+ vars:
+ sap_software_download_suser_id: "Enter SAP S-User ID"
+ sap_software_download_suser_password: "Enter SAP S-User Password"
+ sap_software_download_directory: "/software"
+ sap_software_download_use_venv: false
+ sap_software_download_ignore_validate_credentials: true
+ sap_software_download_ignore_file_not_found: true
+ sap_software_download_ignore_plan_not_found: true
+ sap_software_download_validate_relationships: false
+ sap_software_download_deduplicate: first
+ sap_software_download_files:
+ - 'SAPCAR_1115-70006178.EXE'
+ - 'SAPEXE_100-80005509.SAR'
+ sap_software_download_mp_transaction: 'Transaction Name or Display ID from Maintenance Planner'
+```
+Download of SAP Software files using Python version `3.13`.
+```yaml
+---
+- name: Ansible Play for downloading SAP Software
+ hosts: localhost
+ become: true
+ tasks:
+ - name: Include role sap_software_download
+ ansible.builtin.include_role:
+ name: community.sap_launchpad.sap_software_download
+ vars:
+ sap_software_download_python_interpreter: python3.13
+ sap_software_download_python_package: python313
+ sap_software_download_python_module_packages:
+ - python313-wheel
+ - python313-urllib3
+ - python313-requests
+ - python313-beautifulsoup4
+ - python313-lxml
+ sap_software_download_suser_id: "Enter SAP S-User ID"
+ sap_software_download_suser_password: "Enter SAP S-User Password"
+ sap_software_download_directory: "/software"
+ sap_software_download_files:
+ - 'SAPCAR_1115-70006178.EXE'
+ - 'SAPEXE_100-80005509.SAR'
+```
+
+
+
+
+
+
+## Further Information
+### Explanation of relationship validation logic
+Validation is executed for known combinations of files, where we can validate their name and version.
+Example for SAP HANA Database Server 2.0 with LCAPPS and AFL.
+
+1. All files are examined, and a file starting with `IMDB_SERVER2` is found: `IMDB_SERVER20_084_0-80002031.SAR (Revision 2.00.084.0 (SPS08))`. This indicates a SAP HANA 2.0 database server file.
+2. The HANA version and revision are extracted from the file name: `HANA 2.0`, `Version 084`.
+3. Validation for HANA 1.0 is skipped because it expects files starting with `IMDB_SERVER1`. The following steps are only for `IMDB_SERVER2` (HANA 2.0).
+4. All files are examined for files starting with `IMDB_LCAPPS_2` (indicating LCAPPS for HANA 2.0). Then the list is filtered to only include files starting with `IMDB_LCAPPS_2084` (indicating LCAPPS for HANA 2.0 revision 084).
+5. Validation will have two outcomes:
+ - A file like `IMDB_LCAPPS_2084_0-20010426.SAR` is present. In this case, validation will pass because the LCAPPS version is compatible with the HANA revision.
+ - No file starting with `IMDB_LCAPPS_2084` is present, but there are files starting with `IMDB_LCAPPS_2`.
+ This indicates a mismatch because the LCAPPS version is not compatible with the specific HANA revision (084) found in step 2.
+ In this case, validation will fail. This can be ignored by setting `sap_software_download_ignore_relationship_warning` to `true`.
+6. All files are examined for files starting with `IMDB_AFL20` (indicating AFL for HANA 2.0). Then the list is filtered to only include files starting with `IMDB_AFL20_084` (indicating AFL for HANA 2.0 revision 084).
+7. Validation will have two outcomes:
+ - A file like `IMDB_AFL20_084_1-80001894.SAR` is present. In this case, validation will pass because the AFL version is compatible with the HANA revision.
+ - No file starting with `IMDB_AFL20_084` is present, but there are files starting with `IMDB_AFL20`.
+ This indicates a mismatch because the AFL version is not compatible with the specific HANA revision (084) found in step 2.
+ In this case, validation will fail. This can be ignored by setting `sap_software_download_ignore_relationship_warning` to `true`.
+
+This validation example checks major and minor release (SPS and Revision), but it does not validate patch version.
+
+
+## License
+
+Apache 2.0
+
+
+## Maintainers
+
+- [Marcel Mamula](https://github.com/marcelmamula)
+
+
+## Role Variables
+
+### sap_software_download_python_interpreter
+- _Type:_ `string`
+- _Default:_ **Determined by the operating system.**
+
+The Python interpreter executable to use when creating a Python virtual environment.
+**Mandatory** when `sap_software_download_use_venv` is `true`.
+This is the name of the Python executable (e.g., `python3.11`, `python3.9`), which may differ from the Python package name.
+The default value is determined by the operating system and is set in the corresponding OS-specific variables file.
+Examples: `python3.11` (SUSE), `python3.9` (Red Hat)
+
+### sap_software_download_python_package
+- _Type:_ `string`
+- _Default:_ **Determined by the operating system.**
+
+The name of the OS package that provides the desired Python version.
+The Python version provided by this package must match the version specified by `sap_software_download_python_interpreter`.
+The default value is determined by the operating system and is set in the corresponding OS-specific variables file.
+Examples: `python311` (SUSE), `python3.9` (Red Hat)
+
+### sap_software_download_python_module_packages
+- _Type:_ `list` with elements of type `string`
+- _Default:_ **Determined by the operating system.**
+
+The list of the OS packages that provide modules for the desired Python version.
+Required modules are wheel, urllib3, requests, beautifulsoup4, lxml
+The listed package versions must match the Python version specified by `sap_software_download_python_interpreter`.
+The default value is determined by the operating system and is set in the corresponding OS-specific variables file.
+Examples:
+- `['python311-wheel', 'python311-urllib3', 'python311-requests', 'python311-beautifulsoup4', 'python311-lxml']` (SUSE)
+- `['python3.9-wheel', 'python3.9-urllib3', 'python3.9-requests', 'python3.9-beautifulsoup4', 'python3.9-lxml']` (Red Hat)
+
+### sap_software_download_use_venv
+- _Type:_ `boolean`
+- _Default:_ `true`
+
+Determines whether to execute the role within a Python virtual environment.
+Using a virtual environment is strongly recommended to isolate dependencies.
+If set to `false`, the role will install Python dependencies directly into the system's Python environment.
+
+### sap_software_download_suser_id
+- _Type:_ `string`
+
+The SAP S-User ID with download authorization for SAP software.
+
+### sap_software_download_suser_password
+- _Type:_ `string`
+
+The password for the SAP S-User specified in `sap_software_download_suser_id`.
+
+### sap_software_download_files
+- _Type:_ `list` with elements of type `string`
+
+A list of SAP software file names to download.
+
+### sap_software_download_mp_transaction
+- _Type:_ `string`
+
+The name or display ID of a transaction from the SAP Maintenance Planner.
+If provided, the role will download all files associated with this Maintenance Plan transaction.
+
+### sap_software_download_mp_stack_xml
+- _Type:_ `boolean`
+- _Default:_ `true`
+
+Enables download of Maintenance Plan Stack XML file together with files.
+If set to `false`, Stack XML file will not be downloaded.
+
+### sap_software_download_find_alternatives
+- _Type:_ `boolean`
+- _Default:_ `true`
+
+Enables searching for alternative files if the requested file is not found.
+Only applies to files specified in `sap_software_download_files`.
+If set to `false`, the role will not search for alternatives.
+
+### sap_software_download_directory
+- _Type:_ `string`
+
+The directory where downloaded SAP software files will be stored.
+
+### sap_software_download_validate_relationships
+- _Type:_ `bool`
+- _Default:_ `true`
+
+Enables validation of relationships between SAP software files.
+Only applies to files specified in `sap_software_download_files`.
+If set to `false`, no relationship validation will be performed.
+Example: Verify version of IMDB_LCAPPS against IMDB_SERVER if IMDB_SERVER was found.
+
+### sap_software_download_ignore_file_not_found
+- _Type:_ `bool`
+- _Default:_ `false`
+
+Determines whether to ignore errors when a requested file is not found.
+If set to `true`, the role will continue execution and download other files, even if some files are not found.
+If set to `false`, the role will fail if any requested file is not found.
+
+### sap_software_download_ignore_plan_not_found
+- _Type:_ `bool`
+- _Default:_ `false`
+
+Determines whether to ignore errors when a specified Maintenance Plan transaction is not found.
+If set to `true` and a Maintenance Plan is not found, the role will continue execution, downloading any files specified in `sap_software_download_files`.
+If set to `false`, the role will fail if the specified Maintenance Plan is not found.
+
+### sap_software_download_ignore_relationship_warning
+- _Type:_ `bool`
+- _Default:_ `false`
+
+Determines whether to ignore warnings during file relationship validation.
+If set to `true`, the role will continue execution even if there are warnings during the validation of file relationships.
+If set to `false`, the role will fail if any warnings are encountered during file relationship validation.
+
+### sap_software_download_ignore_validate_credentials
+- _Type:_ `bool`
+- _Default:_ `false`
+
+Determines whether to ignore validate credentials task.
+Disabling this check can lead to locked account, if password is incorrect.
+If set to `true`, the role will continue execution without validating S-User credentials.
+If set to `false`, the role will execute dry run to validate S-User credentials.
+
+### sap_software_download_deduplicate
+- _Type:_ `string`
+
+Specifies how to handle duplicate file results when using `sap_software_download_files`.
+If multiple files with the same name are found, this setting determines which one to download.
+- `first`: Download the first file found
+- `last`: Download the last file found.
+
diff --git a/roles/sap_software_download/defaults/main.yml b/roles/sap_software_download/defaults/main.yml
new file mode 100644
index 0000000..d4a209f
--- /dev/null
+++ b/roles/sap_software_download/defaults/main.yml
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# The Python interpreter executable to use when creating a Python virtual environment.
+# Mandatory when `sap_software_download_use_venv` is `true`.
+# This is the name of the Python executable (e.g., `python3.11`, `python3.9`), which may differ from the Python package name.
+# The default value is determined by the operating system and is set in the corresponding OS-specific variables file.
+# Examples: `python3.11` (SUSE), `python3.9` (Red Hat)
+sap_software_download_python_interpreter:
+ "{{ __sap_software_download_python_interpreter }}"
+
+# The name of the OS package that provides the desired Python version.
+# The Python version provided by this package must match the version specified by `sap_software_download_python_interpreter`.
+# The default value is determined by the operating system and is set in the corresponding OS-specific variables file.
+# Examples: `python311` (SUSE), `python3.9` (Red Hat)
+sap_software_download_python_package:
+ "{{ __sap_software_download_python_package }}"
+
+# The list of the OS packages that provide modules for the desired Python version.
+# Required modules: wheel, urllib3, requests, beautifulsoup4, lxml
+# The listed package versions must match the Python version specified by `sap_software_download_python_interpreter`.
+# The default value is determined by the operating system and is set in the corresponding OS-specific variables file. Examples:
+# python311-wheel, python311-urllib3, python311-requests, python311-beautifulsoup4, python311-lxml (SUSE)
+# python3.9-wheel, python3.9-urllib3, python3.9-requests, python3.9-beautifulsoup4, python3.9-lxml (Red Hat)
+sap_software_download_python_module_packages:
+ "{{ __sap_software_download_python_module_packages }}"
+
+# Determines whether to execute the role within a Python virtual environment.
+# Using a virtual environment is strongly recommended to isolate dependencies.
+# If set to `false`, the role will install Python dependencies directly into the system's Python environment.
+sap_software_download_use_venv: true
+
+# The SAP S-User ID with download authorization for SAP software.
+sap_software_download_suser_id: ''
+
+# The password for the SAP S-User specified in `sap_software_download_suser_id`.
+sap_software_download_suser_password: ''
+
+# A list of SAP software file names to download.
+sap_software_download_files: []
+
+# The name or display ID of a transaction from the SAP Maintenance Planner.
+# If provided, the role will download all files associated with this Maintenance Plan transaction.
+sap_software_download_mp_transaction: ''
+
+# Enables download of Maintenance Plan Stack XML file together with files.
+# If set to `false`, Stack XML file will not be downloaded.
+sap_software_download_mp_stack_xml: true
+
+# Enables searching for alternative files if the requested file is not found.
+# Only applies to files specified in `sap_software_download_files`.
+# If set to `false`, the role will not search for alternatives.
+sap_software_download_find_alternatives: true
+
+# The directory where downloaded SAP software files will be stored.
+sap_software_download_directory: '/software'
+
+# Determines whether to ignore errors when a requested file is not found.
+# If set to `true`, the role will continue execution and download other files, even if some files are not found.
+# If set to `false`, the role will fail if any requested file is not found.
+sap_software_download_ignore_file_not_found: false
+
+# Determines whether to ignore errors when a specified Maintenance Plan transaction is not found.
+# If set to `true` and a Maintenance Plan is not found, the role will continue execution,
+# downloading any files specified in `sap_software_download_files`.
+# If set to `false`, the role will fail if the specified Maintenance Plan is not found.
+sap_software_download_ignore_plan_not_found: false
+
+# Enables validation of relationships between SAP software files.
+# Only applies to files specified in `sap_software_download_files`.
+# If set to `false`, no relationship validation will be performed.
+# Example: Verify version of IMDB_LCAPPS against IMDB_SERVER if IMDB_SERVER was found.
+sap_software_download_validate_relationships: true
+
+# Determines whether to ignore warnings during file relationship validation.
+# If set to `true`, the role will continue execution even if there are warnings during the validation of file relationships.
+# If set to `false`, the role will fail if any warnings are encountered during file relationship validation.
+sap_software_download_ignore_relationship_warning: false
+
+# Determines whether to ignore validate credentials task.
+# Disabling this check can lead to locked account, if password is incorrect.
+# If set to `true`, the role will continue execution without validating S-User credentials.
+# If set to `false`, the role will execute dry run to validate S-User credentials.
+sap_software_download_ignore_validate_credentials: false
+
+# Specifies how to handle duplicate file results when using `sap_software_download_files`.
+# If multiple files with the same name are found, this setting determines which one to download.
+# `first`: Download the first file found.
+# `last`: Download the last file found.
+# sap_software_download_deduplicate: first
diff --git a/roles/sap_software_download/meta/argument_spec.yml b/roles/sap_software_download/meta/argument_spec.yml
new file mode 100644
index 0000000..f62f9da
--- /dev/null
+++ b/roles/sap_software_download/meta/argument_spec.yml
@@ -0,0 +1,166 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+# Requires: ansible 2.16
+# Argument specifications in this separate file maintain backwards compatibility.
+argument_specs:
+
+ main:
+ short_description: Downloads SAP Software Media from SAP using an S-User ID and password.
+ description:
+ - This role downloads SAP Software Media from SAP using an S-User ID and password.
+ - It supports both direct file downloads and Maintenance Plan transactions.
+ options:
+
+ sap_software_download_python_interpreter:
+ description:
+ - If set to `false`, the role will only execute or verify the installation or configuration steps of SAP notes.
+ - Default is to perform installation and configuration steps.
+ required: false
+ type: str
+ - The Python interpreter executable to use when creating a Python virtual environment.
+ - Mandatory when `sap_software_download_use_venv` is `true`.
+ - This is the name of the Python executable (e.g., `python3.11`, `python3.9`), which may differ from the Python package name.
+ - The default value is determined by the operating system and is set in the corresponding OS-specific variables file.
+ - Examples are `python3.11` (SUSE), `python3.9` (Red Hat)
+
+ sap_software_download_python_package:
+ type: str
+ required: false
+ description:
+ - The name of the OS package that provides the desired Python version.
+ - The Python version provided by this package must match the version specified by `sap_software_download_python_interpreter`.
+ - The default value is determined by the operating system and is set in the corresponding OS-specific variables file.
+ - Examples are `python311` (SUSE), `python3.9` (Red Hat)
+
+ sap_software_download_python_module_packages:
+ type: list
+ elements: str
+ required: false
+ description:
+ - The list of the OS packages that provide modules for the desired Python version.
+ - Required modules are wheel, urllib3, requests, beautifulsoup4, lxml
+ - The listed package versions must match the Python version specified by `sap_software_download_python_interpreter`.
+ - The default value is determined by the operating system and is set in the corresponding OS-specific variables file. Examples are
+ - python311-wheel, python311-urllib3, python311-requests, python311-beautifulsoup4, python311-lxml (SUSE)
+ - python3.9-wheel, python3.9-urllib3, python3.9-requests, python3.9-beautifulsoup4, python3.9-lxml (Red Hat)
+
+ sap_software_download_use_venv:
+ type: bool
+ required: false
+ default: true
+ description:
+ - Determines whether to execute the role within a Python virtual environment.
+ - Using a virtual environment is strongly recommended to isolate dependencies.
+ - If set to `false`, the role will install Python dependencies directly into the system's Python environment.
+
+ sap_software_download_suser_id:
+ type: str
+ required: true
+ description:
+ - The SAP S-User ID with download authorization for SAP software.
+
+ sap_software_download_suser_password:
+ type: str
+ required: true
+ no_log: true
+ description:
+ - The password for the SAP S-User specified in `sap_software_download_suser_id`.
+
+ sap_software_download_files:
+ type: list
+ elements: str
+ required: false
+ default: []
+ description:
+ - A list of SAP software file names to download.
+
+ sap_software_download_mp_transaction:
+ type: str
+ required: false
+ default: ""
+ description:
+ - The name or display ID of a transaction from the SAP Maintenance Planner.
+ - If provided, the role will download all files associated with this Maintenance Plan transaction.
+
+ sap_software_download_mp_stack_xml:
+ type: bool
+ required: false
+ default: true
+ description:
+ - Enables download of Maintenance Plan Stack XML file together with files.
+ - If set to `false`, Stack XML file will not be downloaded.
+
+ sap_software_download_find_alternatives:
+ type: bool
+ required: true
+ default: true
+ description:
+ - Enables searching for alternative files if the requested file is not found.
+ - Only applies to files specified in `sap_software_download_files`.
+ - If set to `false`, the role will not search for alternatives.
+
+ sap_software_download_directory:
+ type: str
+ required: true
+ default: "/software"
+ description:
+ - The directory where downloaded SAP software files will be stored.
+
+ sap_software_download_validate_relationships:
+ type: bool
+ required: false
+ default: true
+ description:
+ - Enables validation of relationships between SAP software files.
+ - Only applies to files specified in `sap_software_download_files`.
+ - If set to `false`, no relationship validation will be performed.
+ - Example Verify version of IMDB_LCAPPS against IMDB_SERVER if IMDB_SERVER was found.
+
+ sap_software_download_ignore_file_not_found:
+ type: bool
+ required: false
+ default: false
+ description:
+ - Determines whether to ignore errors when a requested file is not found.
+ - If set to `true`, the role will continue execution and download other files, even if some files are not found.
+ - If set to `false`, the role will fail if any requested file is not found.
+
+ sap_software_download_ignore_plan_not_found:
+ type: bool
+ required: false
+ default: false
+ description:
+ - Determines whether to ignore errors when a specified Maintenance Plan transaction is not found.
+ - If set to `true` and a Maintenance Plan is not found, the role will continue execution,
+ - downloading any files specified in `sap_software_download_files`.
+ - If set to `false`, the role will fail if the specified Maintenance Plan is not found.
+
+ sap_software_download_ignore_relationship_warning:
+ type: bool
+ required: false
+ default: false
+ description:
+ - Determines whether to ignore warnings during file relationship validation.
+ - If set to `true`, the role will continue execution even if there are warnings during the validation of file relationships.
+ - If set to `false`, the role will fail if any warnings are encountered during file relationship validation.
+
+ sap_software_download_ignore_validate_credentials:
+ type: bool
+ required: false
+ default: false
+ description:
+ - Determines whether to ignore warnings during file relationship validation.
+ - Disabling this check can lead to locked account, if password is incorrect.
+ - If set to `true`, the role will continue execution even if there are warnings during the validation of file relationships.
+ - If set to `false`, the role will fail if any warnings are encountered during file relationship validation.
+
+ sap_software_download_deduplicate:
+ type: str
+ required: false
+ default: "first"
+ choices: ["first", "last"]
+ description:
+ - Specifies how to handle duplicate file results when using `sap_software_download_files`.
+ - If multiple files with the same name are found, this setting determines which one to download.
+ - Value `first` - Download the first file found.
+ - Value `last` - Download the last file found.
diff --git a/roles/sap_software_download/meta/main.yml b/roles/sap_software_download/meta/main.yml
new file mode 100644
index 0000000..cdd1066
--- /dev/null
+++ b/roles/sap_software_download/meta/main.yml
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+galaxy_info:
+ namespace: community
+ author: SUSE, Marcel Mamula
+ description: Downloads SAP Software Media from SAP using an S-User ID and password, supporting both direct file downloads and Maintenance Plan transactions.
+ company: SUSE
+ license: Apache-2.0
+ min_ansible_version: 2.16
+ platforms:
+ - name: EL
+ versions: ["7", "8", "9"]
+ - name: SLES
+ versions: ["15", "16"]
+ galaxy_tags:
+ - sap
+ - download
+ - suse
+ - redhat
+dependencies: []
diff --git a/roles/sap_software_download/tasks/download_files.yml b/roles/sap_software_download/tasks/download_files.yml
new file mode 100644
index 0000000..177e76f
--- /dev/null
+++ b/roles/sap_software_download/tasks/download_files.yml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Download - SAP Software Files - Get files with Python venv
+ community.sap_launchpad.software_center_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ search_query: "{{ item }}"
+ dest: "{{ sap_software_download_directory }}"
+ search_alternatives: "{{ sap_software_download_find_alternatives | d(true) }}"
+ deduplicate: "{{ sap_software_download_deduplicate | d('') }}"
+ # Loop condition acts as when conditional
+ loop: "{{ sap_software_download_files if sap_software_download_use_venv | d(true) else [] }}"
+ loop_control:
+ label: "{{ item }} : {{ __sap_software_download_files_results_venv.msg | d('') }}"
+ register: __sap_software_download_files_results_venv
+ retries: 1
+ until: __sap_software_download_files_results_venv is not failed
+ environment:
+ PATH: "{{ __sap_software_download_venv.path }}/bin:{{ ansible_env.PATH }}"
+ PYTHONPATH: "{{ __sap_software_download_venv.path }}/lib/{{ sap_software_download_python_interpreter }}/site-packages"
+ VIRTUAL_ENV: "{{ __sap_software_download_venv.path }}"
+ vars:
+ ansible_python_interpreter: "{{ __sap_software_download_venv.path ~ '/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+
+- name: Download - SAP Software Files - Get files with Python system default
+ community.sap_launchpad.software_center_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ search_query: "{{ item }}"
+ dest: "{{ sap_software_download_directory }}"
+ search_alternatives: "{{ sap_software_download_find_alternatives | d(true) }}"
+ deduplicate: "{{ sap_software_download_deduplicate | d('') }}"
+ # Loop condition acts as when conditional
+ loop: "{{ sap_software_download_files if not sap_software_download_use_venv | d(true) else [] }}"
+ loop_control:
+ label: "{{ item }} : {{ __sap_software_download_files_results_default.msg | d('') }}"
+ register: __sap_software_download_files_results_default
+ retries: 1
+ until: __sap_software_download_files_results_default is not failed
+ vars:
+ ansible_python_interpreter: "{{ '/usr/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+
+- name: Download - SAP Software Files - Set fact with software_center_download output
+ ansible.builtin.set_fact:
+ __sap_software_download_files_results: "{{ __sap_software_download_files_results_venv
+ if sap_software_download_use_venv | d(true) else __sap_software_download_files_results_default }}"
+
+- name: Download - SAP Software Files - Show failed results
+ ansible.builtin.fail:
+ msg: |
+ Download failed for following files: {{ __failed_items | map(attribute='item') | list | join(', ') }}
+ Either set `sap_software_download_find_alternatives` to `true` to search for alternative files
+ or ignore this error with `sap_software_download_ignore_file_not_found` set to `true`.
+ vars:
+ __failed_items: "{{ __sap_software_download_files_results.results
+ | selectattr('failed', 'defined') | selectattr('failed', 'true') }}"
+ when:
+ - not sap_software_download_ignore_file_not_found | d(false)
+ - __failed_items | length > 0
diff --git a/roles/sap_software_download/tasks/download_plan.yml b/roles/sap_software_download/tasks/download_plan.yml
new file mode 100644
index 0000000..b89af1f
--- /dev/null
+++ b/roles/sap_software_download/tasks/download_plan.yml
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Download - Maintenance Plan - Get files with Python venv
+ community.sap_launchpad.software_center_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ download_link: "{{ item.DirectLink }}"
+ download_filename: "{{ item.Filename }}"
+ dest: "{{ sap_software_download_directory }}"
+ # Loop condition acts as when conditional
+ loop: "{{ __sap_software_download_mp_transaction_results.download_basket if sap_software_download_use_venv | d(true) else [] }}"
+ loop_control:
+ label: "{{ item.Filename }} : {{ __sap_software_download_files_plan_results_venv.msg | d('') }}"
+ register: __sap_software_download_files_plan_results_venv
+ retries: 1
+ until: __sap_software_download_files_plan_results_venv is not failed
+ environment:
+ PATH: "{{ __sap_software_download_venv.path }}/bin:{{ ansible_env.PATH }}"
+ PYTHONPATH: "{{ __sap_software_download_venv.path }}/lib/{{ sap_software_download_python_interpreter }}/site-packages"
+ VIRTUAL_ENV: "{{ __sap_software_download_venv.path }}"
+ vars:
+ ansible_python_interpreter: "{{ __sap_software_download_venv.path ~ '/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+
+- name: Download - Maintenance Plan - Get files with Python system default
+ community.sap_launchpad.software_center_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ download_link: "{{ item.DirectLink }}"
+ download_filename: "{{ item.Filename }}"
+ dest: "{{ sap_software_download_directory }}"
+ # Loop condition acts as when conditional
+ loop: "{{ __sap_software_download_mp_transaction_results.download_basket if not sap_software_download_use_venv | d(true) else [] }}"
+ loop_control:
+ label: "{{ item.Filename }} : {{ __sap_software_download_files_plan_results_default.msg | d('') }}"
+ register: __sap_software_download_files_plan_results_default
+ retries: 1
+ until: __sap_software_download_files_plan_results_default is not failed
+ vars:
+ ansible_python_interpreter: "{{ '/usr/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+
+- name: Download - Maintenance Plan - Set fact with software_center_download output
+ ansible.builtin.set_fact:
+ __sap_software_download_files_plan_results: "{{ __sap_software_download_files_plan_results_venv
+ if sap_software_download_use_venv | d(true) else __sap_software_download_files_plan_results_default }}"
+
+- name: Download - Maintenance Plan - Show failed results
+ ansible.builtin.fail:
+ msg: |
+ Maintenance Plan file(s) not found: {{ __failed_items | map(attribute='item.Filename') | list | join(', ') }}
+ Verify your Maintenance Plan on SAP Launchpad before retrying.
+ vars:
+ __failed_items: "{{ __sap_software_download_files_plan_results.results
+ | selectattr('failed', 'defined') | selectattr('failed', 'true') }}"
+ when:
+ - not sap_software_download_ignore_file_not_found | d(false)
+ - __failed_items | length > 0
diff --git a/roles/sap_software_download/tasks/download_stack.yml b/roles/sap_software_download/tasks/download_stack.yml
new file mode 100644
index 0000000..c7d6ff2
--- /dev/null
+++ b/roles/sap_software_download/tasks/download_stack.yml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Download - Maintenance Plan Stack XML - Get file with Python venv
+ when: sap_software_download_use_venv | d(true)
+ community.sap_launchpad.maintenance_planner_stack_xml_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ transaction_name: "{{ sap_software_download_mp_transaction }}"
+ dest: "{{ sap_software_download_directory }}"
+ register: __sap_software_download_stack_results_venv
+ retries: 1
+ environment:
+ PATH: "{{ __sap_software_download_venv.path }}/bin:{{ ansible_env.PATH }}"
+ PYTHONPATH: "{{ __sap_software_download_venv.path }}/lib/{{ sap_software_download_python_interpreter }}/site-packages"
+ VIRTUAL_ENV: "{{ __sap_software_download_venv.path }}"
+ vars:
+ ansible_python_interpreter: "{{ __sap_software_download_venv.path ~ '/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+
+- name: Download - Maintenance Plan Stack XML - Get file with Python system default
+ when: not sap_software_download_use_venv | d(true)
+ community.sap_launchpad.maintenance_planner_stack_xml_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ transaction_name: "{{ sap_software_download_mp_transaction }}"
+ dest: "{{ sap_software_download_directory }}"
+ register: __sap_software_download_stack_results_default
+ retries: 1
+ vars:
+ ansible_python_interpreter: "{{ '/usr/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+
+- name: Download - Maintenance Plan Stack XML - Set fact with maintenance_planner_stack_xml_download output
+ ansible.builtin.set_fact:
+ __sap_software_download_stack_results: "{{ __sap_software_download_stack_results_venv
+ if sap_software_download_use_venv | d(true) else __sap_software_download_stack_results_default }}"
+
+- name: Download - Maintenance Plan Stack XML - Show failed results
+ ansible.builtin.fail:
+ msg: |
+ Download of Stack XML failed.
+ Either ensure correct value in `sap_software_download_mp_transaction`
+ or ignore this error with `sap_software_download_ignore_plan_not_found` set to `true`.
+ when:
+ - not sap_software_download_ignore_plan_not_found | d(false)
+ - __sap_software_download_stack_results.failed
diff --git a/roles/sap_software_download/tasks/main.yml b/roles/sap_software_download/tasks/main.yml
new file mode 100644
index 0000000..4763943
--- /dev/null
+++ b/roles/sap_software_download/tasks/main.yml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: SAP Software Download - Pre-Steps - Include role variables
+ ansible.builtin.include_tasks:
+ file: pre_steps/01_include_variables.yml
+
+- name: SAP Software Download - Pre-Steps - Prepare Python environment
+ ansible.builtin.include_tasks:
+ file: pre_steps/02_prepare_python_environment.yml
+
+- name: SAP Software Download - Pre-Steps - Validate user credentials
+ ansible.builtin.include_tasks:
+ file: pre_steps/03_validate_credentials.yml
+ when: not sap_software_download_ignore_validate_credentials | d(false)
+
+- name: SAP Software Download - Pre-Steps - Find files for Maintenance Plan
+ ansible.builtin.include_tasks:
+ file: pre_steps/04_get_plan_files.yml
+ when:
+ - sap_software_download_mp_transaction | length > 0
+
+# NOTE: We do not validate files generated by Maintenance Plan.
+- name: SAP Software Download - Pre-Steps - Check related SAP Software combinations
+ ansible.builtin.include_tasks:
+ file: pre_steps/05_validate_relations.yml
+ when:
+ - sap_software_download_files | length > 0
+ - sap_software_download_validate_relationships
+
+
+- name: SAP Software Download - Download - Stack XML {{ sap_software_download_mp_transaction | d('') }}
+ ansible.builtin.include_tasks:
+ file: download_stack.yml
+ when:
+ - sap_software_download_mp_transaction | length > 0
+ - sap_software_download_mp_stack_xml | d(true)
+
+- name: SAP Software Download - Download - Maintenance Plan {{ sap_software_download_mp_transaction | d('') }}
+ ansible.builtin.include_tasks:
+ file: download_plan.yml
+ when:
+ - sap_software_download_mp_transaction | length > 0
+ - __sap_software_download_mp_transaction_results.download_basket is defined
+ and __sap_software_download_mp_transaction_results.download_basket | length > 0
+
+- name: SAP Software Download - Download - Files in sap_software_download_files
+ ansible.builtin.include_tasks:
+ file: download_files.yml
+
+
+- name: SAP Software Download - Post-Steps - Cleanup Python environment
+ ansible.builtin.include_tasks:
+ file: post_steps/01_cleanup_python_environment.yml
diff --git a/roles/sap_software_download/tasks/post_steps/01_cleanup_python_environment.yml b/roles/sap_software_download/tasks/post_steps/01_cleanup_python_environment.yml
new file mode 100644
index 0000000..e3d303b
--- /dev/null
+++ b/roles/sap_software_download/tasks/post_steps/01_cleanup_python_environment.yml
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Post-Steps - Remove temporary Python Virtual Environment
+ ansible.builtin.file:
+ path: "{{ __sap_software_download_venv.path }}"
+ state: absent
+ when: sap_software_download_use_venv | d(true)
diff --git a/roles/sap_software_download/tasks/pre_steps/01_include_variables.yml b/roles/sap_software_download/tasks/pre_steps/01_include_variables.yml
new file mode 100644
index 0000000..edfec13
--- /dev/null
+++ b/roles/sap_software_download/tasks/pre_steps/01_include_variables.yml
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# Example of files loading order:
+# 1. Suse.yml / RedHat.yml - Specific to OS family.
+# 2. SLES_15.yml / RedHat_9.yml - Specific to distribution (SLES, SLES_SAP or RedHat) and major release.
+# 3. SLES_15.6.yml / RedHat_9.2 - Specific to distribution (SLES, SLES_SAP or RedHat) and minor release.
+# 4. SLES_SAP_15.yml - Specific to distribution SLES_SAP and major release.
+# 5. SLES_SAP_15.6.yml - Specific to distribution SLES_SAP and minor release.
+- name: Pre-Steps - Include OS specific variables
+ ansible.builtin.include_vars: "{{ __vars_file }}"
+ loop: "{{ __var_files }}"
+ vars:
+ __vars_file: "{{ role_path }}/vars/{{ item }}"
+ __distribution_major: "{{ ansible_distribution ~ '_' ~ ansible_distribution_major_version }}"
+ __distribution_minor: "{{ ansible_distribution ~ '_' ~ ansible_distribution_version }}"
+ # Enables loading of shared vars between SLES and SLES_SAP
+ __distribution_major_split: "{{ ansible_distribution.split('_')[0] ~ '_' ~ ansible_distribution_major_version }}"
+ __distribution_minor_split: "{{ ansible_distribution.split('_')[0] ~ '_' ~ ansible_distribution_version }}"
+ __var_files: >-
+ {{
+ [
+ ansible_os_family ~ '.yml',
+ (ansible_distribution ~ '.yml') if ansible_distribution != ansible_os_family else None,
+ (__distribution_major_split ~ '.yml') if __distribution_major_split != __distribution_major else None,
+ (__distribution_minor_split ~ '.yml') if __distribution_minor_split != __distribution_minor else None,
+ __distribution_major ~ '.yml',
+ __distribution_minor ~ '.yml'
+ ] | select('defined') | select('string') | list
+ }}
+ when: __vars_file is file
+
+
+- name: Pre-Steps - Assert that SAP Software files were specified
+ ansible.builtin.assert:
+ that:
+ - (sap_software_download_files is defined and sap_software_download_files | type_debug == 'list' and sap_software_download_files | length > 0)
+ or (sap_software_download_mp_transaction is defined and sap_software_download_mp_transaction | length > 0)
+ fail_msg: |
+ Neither `sap_software_download_files` or `sap_software_download_mp_transaction` are valid.
+ Ensure that it at least one of them is valid.
+
+- name: "Pre-Steps - Assert that download directory was provided"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_directory is defined
+ - sap_software_download_directory | length > 0
+ fail_msg: |
+ Empty variable `sap_software_download_directory`.
+
+
+- name: "Pre-Steps - Verify variable: sap_software_download_use_venv"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_use_venv is boolean
+ fail_msg: |
+ Variable `sap_software_download_use_venv` is not boolean.
+ when: sap_software_download_use_venv is defined
+
+- name: "Pre-Steps - Verify variable: sap_software_download_python_interpreter"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_python_interpreter is defined
+ - sap_software_download_python_interpreter | length > 0
+ fail_msg: |
+ Empty variable `sap_software_download_python_interpreter`.
+ Ensure that it contains correct Python interpreter or revert back to defaults.
+ when: sap_software_download_use_venv
+
+- name: "Pre-Steps - Verify variable: sap_software_download_python_package"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_python_package is defined
+ - sap_software_download_python_package | length > 0
+ fail_msg: |
+ Empty variable `sap_software_download_python_package`.
+ Ensure that it contains correct Python package name or revert back to defaults.
+
+- name: "Pre-Steps - Verify variable: sap_software_download_python_module_packages"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_python_module_packages is defined
+ - sap_software_download_python_module_packages | type_debug == 'list'
+ - sap_software_download_python_module_packages | length > 0
+ fail_msg: |
+ Empty variable `sap_software_download_python_module_packages`.
+ Ensure that it contains correct list of Python module package names or revert back to defaults.
+
+- name: "Pre-Steps - Verify variable: sap_software_download_suser_id"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_suser_id is defined
+ - sap_software_download_suser_id | length > 0
+ fail_msg: |
+ Empty variable `sap_software_download_suser_id`.
+ Enter valid S-User ID with download authorizations.
+
+- name: "Pre-Steps - Verify variable: sap_software_download_suser_password"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_suser_password is defined
+ - sap_software_download_suser_password | length > 0
+ fail_msg: |
+ Empty variable `sap_software_download_suser_password`.
+ Enter valid S-User password.
+ no_log: true
+
+- name: "Pre-Steps - Verify variable: sap_software_download_find_alternatives"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_find_alternatives is boolean
+ fail_msg: |
+ Variable `sap_software_download_find_alternatives` is not boolean.
+ when: sap_software_download_find_alternatives is defined
+
+- name: "Pre-Steps - Verify variable: sap_software_download_deduplicate"
+ ansible.builtin.assert:
+ that:
+ - sap_software_download_deduplicate in ['first', 'last']
+ fail_msg: |
+ Enter valid option for `sap_software_download_deduplicate` variable.
+ Options: first, last
+ when: sap_software_download_deduplicate is defined
diff --git a/roles/sap_software_download/tasks/pre_steps/02_prepare_python_environment.yml b/roles/sap_software_download/tasks/pre_steps/02_prepare_python_environment.yml
new file mode 100644
index 0000000..2be5480
--- /dev/null
+++ b/roles/sap_software_download/tasks/pre_steps/02_prepare_python_environment.yml
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Pre-Steps - Install Python and Python package manager pip
+ ansible.builtin.package:
+ name:
+ - "{{ sap_software_download_python_package }}"
+ - "{{ sap_software_download_python_package }}-pip"
+ state: present
+
+
+- name: Pre-Steps - Create download directory {{ sap_software_download_directory }}
+ ansible.builtin.file:
+ path: "{{ sap_software_download_directory }}"
+ state: directory
+ mode: '0755'
+
+
+- name: Pre-Steps - Block for Python venv preparation
+ when: sap_software_download_use_venv | d(true)
+ block:
+ - name: Pre-Steps - Create temporary directory for venv
+ ansible.builtin.tempfile:
+ state: directory
+ suffix: __sap_software_download_venv
+ register: __sap_software_download_venv
+
+ - name: Pre-Steps - Install Python modules to Python venv
+ ansible.builtin.pip:
+ name: "{{ __sap_software_download_python_modules }}"
+ virtualenv: "{{ __sap_software_download_venv.path }}"
+ virtualenv_command: "{{ sap_software_download_python_interpreter }} -m venv"
+
+
+- name: Pre-Steps - Block for default Python preparation
+ when: not sap_software_download_use_venv | d(true)
+ block:
+ # Packages with python modules are installed instead of modules to avoid error:
+ # `externally-managed-environment` which requires `--break-system-packages`
+ - name: Pre-Steps - Install Python module packages
+ ansible.builtin.package:
+ name: "{{ __sap_software_download_python_module_packages }}"
+ state: present
diff --git a/roles/sap_software_download/tasks/pre_steps/03_validate_credentials.yml b/roles/sap_software_download/tasks/pre_steps/03_validate_credentials.yml
new file mode 100644
index 0000000..8154b72
--- /dev/null
+++ b/roles/sap_software_download/tasks/pre_steps/03_validate_credentials.yml
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+# This task attempts dry run to check SAPCAR file in order to validate provided credentials.
+
+- name: Validate Credentials - Dry run check user credentials and download privilege with Python venv
+ when: sap_software_download_use_venv | d(true)
+ community.sap_launchpad.software_center_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ search_query: "SAPCAR"
+ dest: "{{ sap_software_download_directory }}"
+ search_alternatives: true
+ deduplicate: "last"
+ dry_run: true
+ register: __sap_software_download_validate_credentials_venv
+ retries: 1
+ delay: 5
+ environment:
+ PATH: "{{ __sap_software_download_venv.path }}/bin:{{ ansible_env.PATH }}"
+ PYTHONPATH: "{{ __sap_software_download_venv.path }}/lib/{{ sap_software_download_python_interpreter }}/site-packages"
+ VIRTUAL_ENV: "{{ __sap_software_download_venv.path }}"
+ vars:
+ ansible_python_interpreter: "{{ __sap_software_download_venv.path ~ '/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+- name: Validate Credentials - Dry run check user credentials and download privilege with Python system default
+ when: not sap_software_download_use_venv | d(true)
+ community.sap_launchpad.software_center_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ search_query: "SAPCAR"
+ dest: "{{ sap_software_download_directory }}"
+ search_alternatives: true
+ deduplicate: "last"
+ dry_run: true
+ register: __sap_software_download_validate_credentials_default
+ retries: 1
+ delay: 5
+ vars:
+ ansible_python_interpreter: "{{ '/usr/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+- name: Validate Credentials - Show failed results
+ ansible.builtin.fail:
+ msg: |
+ Validation of provided S-User credentials failed.
+ Ensure correct S-User credentials are provided in:
+ `sap_software_download_suser_id` and `sap_software_download_suser_password`
+ when:
+ - (sap_software_download_use_venv | d(true)
+ and __sap_software_download_validate_credentials_venv.failed is defined and __sap_software_download_validate_credentials_venv.failed)
+ or (not sap_software_download_use_venv | d(true)
+ and __sap_software_download_validate_credentials_default is defined and __sap_software_download_validate_credentials_default.failed)
diff --git a/roles/sap_software_download/tasks/pre_steps/04_get_plan_files.yml b/roles/sap_software_download/tasks/pre_steps/04_get_plan_files.yml
new file mode 100644
index 0000000..a4baef1
--- /dev/null
+++ b/roles/sap_software_download/tasks/pre_steps/04_get_plan_files.yml
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Maintenance Plan - Get list of files with Python venv
+ when: sap_software_download_use_venv | d(true)
+ community.sap_launchpad.maintenance_planner_files:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ transaction_name: "{{ sap_software_download_mp_transaction }}"
+ register: __sap_software_download_mp_transaction_results_venv
+ retries: 1
+ environment:
+ PATH: "{{ __sap_software_download_venv.path }}/bin:{{ ansible_env.PATH }}"
+ PYTHONPATH: "{{ __sap_software_download_venv.path }}/lib/{{ sap_software_download_python_interpreter }}/site-packages"
+ VIRTUAL_ENV: "{{ __sap_software_download_venv.path }}"
+ vars:
+ ansible_python_interpreter: "{{ __sap_software_download_venv.path ~ '/bin/' ~ sap_software_download_python_interpreter }}"
+ changed_when: false # Getting list of files does not change anything
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+- name: Maintenance Plan - Get list of files with default Python
+ when: not sap_software_download_use_venv | d(true)
+ community.sap_launchpad.maintenance_planner_files:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ transaction_name: "{{ sap_software_download_mp_transaction }}"
+ register: __sap_software_download_mp_transaction_results_default
+ retries: 1
+ vars:
+ ansible_python_interpreter: "{{ '/usr/bin/' ~ sap_software_download_python_interpreter }}"
+ changed_when: false # Getting list of files does not change anything
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+- name: Maintenance Plan - Set fact with maintenance_planner_files output
+ ansible.builtin.set_fact:
+ __sap_software_download_mp_transaction_results: "{{ __sap_software_download_mp_transaction_results_venv
+ if sap_software_download_use_venv | d(true) else __sap_software_download_mp_transaction_results_default }}"
+
+
+- name: Maintenance Plan - Show failed results
+ ansible.builtin.fail:
+ msg: |
+ Maintenance Plan was not found.
+ Either ensure correct value in `sap_software_download_mp_transaction`
+ or ignore this error with `sap_software_download_ignore_plan_not_found` set to `true`.
+ when:
+ - not sap_software_download_ignore_plan_not_found | d(false)
+ - __sap_software_download_mp_transaction_results.failed
+
+- name: Maintenance Plan - Show ignored failed results
+ ansible.builtin.debug:
+ msg: |
+ Maintenance Plan was not found.
+ Error was ignored with `sap_software_download_ignore_plan_not_found` set to `true`.
+ when:
+ - sap_software_download_ignore_plan_not_found | d(false)
+ - __sap_software_download_mp_transaction_results.failed
diff --git a/roles/sap_software_download/tasks/pre_steps/05_validate_relations.yml b/roles/sap_software_download/tasks/pre_steps/05_validate_relations.yml
new file mode 100644
index 0000000..39c9687
--- /dev/null
+++ b/roles/sap_software_download/tasks/pre_steps/05_validate_relations.yml
@@ -0,0 +1,98 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Block - Relationship Validation - Check availability of files (with alternatives)
+ when: sap_software_download_find_alternatives
+ block:
+
+ - name: Relationship Validation - Dry run to check availability of files with Python venv
+ community.sap_launchpad.software_center_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ search_query: "{{ item }}"
+ dest: "{{ sap_software_download_directory }}"
+ search_alternatives: "{{ sap_software_download_find_alternatives | d(true) }}"
+ deduplicate: "{{ sap_software_download_deduplicate | d('') }}"
+ dry_run: true
+ # Loop condition acts as when conditional
+ loop: "{{ sap_software_download_files if sap_software_download_use_venv | d(true) else [] }}"
+ loop_control:
+ label: "{{ item }} : {{ __sap_software_download_files_results_dryrun_venv.msg | d('') }}"
+ register: __sap_software_download_files_results_dryrun_venv
+ retries: 1
+ until: __sap_software_download_files_results_dryrun_venv is not failed
+ environment:
+ PATH: "{{ __sap_software_download_venv.path }}/bin:{{ ansible_env.PATH }}"
+ PYTHONPATH: "{{ __sap_software_download_venv.path }}/lib/{{ sap_software_download_python_interpreter }}/site-packages"
+ VIRTUAL_ENV: "{{ __sap_software_download_venv.path }}"
+ vars:
+ ansible_python_interpreter: "{{ __sap_software_download_venv.path ~ '/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+
+ - name: Relationship Validation - Dry run to check availability of files with Python system default
+ community.sap_launchpad.software_center_download:
+ suser_id: "{{ sap_software_download_suser_id }}"
+ suser_password: "{{ sap_software_download_suser_password }}"
+ search_query: "{{ item }}"
+ dest: "{{ sap_software_download_directory }}"
+ search_alternatives: "{{ sap_software_download_find_alternatives | d(true) }}"
+ deduplicate: "{{ sap_software_download_deduplicate | d('') }}"
+ dry_run: true
+ # Loop condition acts as when conditional
+ loop: "{{ sap_software_download_files if not sap_software_download_use_venv | d(true) else [] }}"
+ loop_control:
+ label: "{{ item }} : {{ __sap_software_download_files_results_dryrun_default.msg | d('') }}"
+ register: __sap_software_download_files_results_dryrun_default
+ retries: 1
+ until: __sap_software_download_files_results_dryrun_default is not failed
+ vars:
+ ansible_python_interpreter: "{{ '/usr/bin/' ~ sap_software_download_python_interpreter }}"
+ ignore_errors: true # Errors are ignored and validated afterwards
+
+
+ - name: Relationship Validation - Set fact with software_center_download output
+ ansible.builtin.set_fact:
+ __sap_software_download_files_results_dryrun: "{{ __sap_software_download_files_results_dryrun_venv
+ if sap_software_download_use_venv | d(true) else __sap_software_download_files_results_dryrun_default }}"
+
+ - name: Relationship Validation - Show failed results
+ ansible.builtin.fail:
+ msg: |
+ Relationship validation failed because following files were not found: {{ __failed_items | map(attribute='item') | list | join(', ') }}
+ Either ensure correct list of files in `sap_software_download_files`
+ or ignore this error with `sap_software_download_ignore_file_not_found` set to `true`.
+ vars:
+ __failed_items: "{{ __sap_software_download_files_results_dryrun.results | selectattr('failed', 'defined') | selectattr('failed', 'true') }}"
+ when:
+ - not sap_software_download_ignore_file_not_found | d(false)
+ - __failed_items | length > 0
+
+
+- name: Relationship Validation - Define list of files
+ ansible.builtin.set_fact:
+ __sap_software_download_files: "{{ sap_software_download_files if not sap_software_download_find_alternatives
+ else __sap_software_download_files_results_dryrun.results | selectattr('failed', 'false')| map(attribute='filename') | list | unique | d([])}}"
+
+
+- name: Relationship Validation - SAP HANA - Multiple IMDB_SERVER files found
+ ansible.builtin.debug:
+ msg: Warning - Multiple SAP HANA Database (IMDB_SERVER) found, which can result in inaccurate relationship validation!
+ when: __sap_software_download_files | select('match', '^IMDB_SERVER.*') | list | length > 1
+
+
+- name: Relationship Validation - Include tasks for SAP HANA 1.0 relationship validation
+ when: __sap_software_download_files | select('match', '^IMDB_SERVER1.*') | list | length > 0
+ ansible.builtin.include_tasks:
+ file: pre_steps/06_validate_sap_hana_1.yml
+ loop: "{{ __sap_software_download_files | select('match', '^IMDB_SERVER1.*') | list }}"
+ loop_control:
+ loop_var: __sap_software_download_sap_hana
+
+- name: Relationship Validation - Include tasks for SAP HANA 2.0 relationship validation
+ when: __sap_software_download_files | select('match', '^IMDB_SERVER2.*') | list | length > 0
+ ansible.builtin.include_tasks:
+ file: pre_steps/07_validate_sap_hana_2.yml
+ loop: "{{ __sap_software_download_files | select('match', '^IMDB_SERVER2.*') | list }}"
+ loop_control:
+ loop_var: __sap_software_download_sap_hana
diff --git a/roles/sap_software_download/tasks/pre_steps/06_validate_sap_hana_1.yml b/roles/sap_software_download/tasks/pre_steps/06_validate_sap_hana_1.yml
new file mode 100644
index 0000000..93ac892
--- /dev/null
+++ b/roles/sap_software_download/tasks/pre_steps/06_validate_sap_hana_1.yml
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Relationship Validation - SAP HANA 1.00 - Set HANA variables
+ ansible.builtin.set_fact:
+ # 122 for IMDB_SERVER100_122_35-10009569.SAR
+ __sap_software_download_sap_hana_1_version:
+ "{{ __sap_software_download_sap_hana.split('_')[2] | regex_replace('[^0-9]', '') }}"
+
+ # 35 for IMDB_SERVER100_122_35-10009569.SAR
+ __sap_software_download_sap_hana_1_revision:
+ "{{ __sap_software_download_sap_hana.split('_')[3].split('-')[0] | regex_replace('[^0-9]', '') }}"
+
+
+# Warning conditions:
+# - At least one IMDB_LCAPPS_1 was found
+# - No IMDB_LCAPPS__ was found
+# Examples:
+# - IMDB_SERVER100_122_35-10009569.SAR - Maintenance Revision 122.35 (SPS12) for HANA DB 1.00
+# - IMDB_LCAPPS_122P_3500-20010426.SAR - LCAPPS for HANA 1.00.122.35 Build 100.47 PL 017
+- name: Relationship Validation - SAP HANA 1.00 - IMDB_SERVER and IMDB_LCAPPS
+ ansible.builtin.fail:
+ msg: |
+ Warning: Incompatible SAP HANA component LCAPPS files were found for detected SAP HANA DATABASE 1.0 SPS {{
+ __sap_software_download_sap_hana_1_version[:2] }} Revision {{ __sap_software_download_sap_hana_1_version
+ }}.{{ __sap_software_download_sap_hana_1_revision }}.
+
+ Expected file pattern: IMDB_LCAPPS_{{
+ __sap_software_download_sap_hana_1_version }}*_{{__sap_software_download_sap_hana_1_revision }}*
+ Actual files detected: {{ __sap_software_download_sap_hana_1_lcapps_list | unique | join(', ') }}
+ vars:
+ __sap_software_download_sap_hana_1_lcapps_list:
+ "{{ __sap_software_download_files | select('match', '^IMDB_LCAPPS_1.*') | list }}"
+ __sap_software_download_sap_hana_1_lcapps_list_filtered:
+ "{{ sap_software_download_files | select('match', '^IMDB_LCAPPS_.*'
+ ~ __sap_software_download_sap_hana_1_version ~ '.*_' ~ __sap_software_download_sap_hana_1_revision) | list }}"
+ ignore_errors: "{{ sap_software_download_ignore_relationship_warning | d(false) }}"
+ when:
+ - __sap_software_download_sap_hana_1_lcapps_list | length > 0
+ - __sap_software_download_sap_hana_1_lcapps_list_filtered | length == 0
+
+
+# Warning conditions:
+# - At least one IMDB_AFL100 was found
+# - No IMDB_AFL100__ was found
+# Examples:
+# - IMDB_SERVER100_122_35-10009569.SAR - Maintenance Revision 122.35 (SPS12) for HANA DB 1.00
+# - IMDB_AFL100_122P_3500-10012328.SAR - SAP HANA AFL 1.0 Revision 122.3500 only for HANA DB 122.35
+- name: Relationship Validation - SAP HANA 1.00 - IMDB_SERVER and IMDB_AFL
+ ansible.builtin.fail:
+ msg: |
+ Warning: Incompatible SAP HANA component AFL files were found for detected SAP HANA DATABASE 1.0 SPS {{
+ __sap_software_download_sap_hana_1_version[:2] }} Revision {{ __sap_software_download_sap_hana_1_version
+ }}.{{ __sap_software_download_sap_hana_1_revision }}.
+
+ Expected file pattern: IMDB_AFL100_{{ __sap_software_download_sap_hana_1_version }}*_{{ __sap_software_download_sap_hana_1_revision }}*
+ Actual files detected: {{ __sap_software_download_sap_hana_1_afl_list | unique | join(', ') }}
+ vars:
+ __sap_software_download_sap_hana_1_afl_list:
+ "{{ __sap_software_download_files | select('match', '^IMDB_AFL100.*') | list }}"
+ __sap_software_download_sap_hana_1_afl_list_filtered:
+ "{{ sap_software_download_files | select('match', '^IMDB_AFL100_.*'
+ ~ __sap_software_download_sap_hana_1_version ~ '.*_' ~ __sap_software_download_sap_hana_1_revision) | list }}"
+ ignore_errors: "{{ sap_software_download_ignore_relationship_warning | d(false) }}"
+ when:
+ - __sap_software_download_sap_hana_1_afl_list | length > 0
+ - __sap_software_download_sap_hana_1_afl_list_filtered | length == 0
+
+
+# Warning conditions:
+# - At least one IMDB_CLIENT was found
+# - No IMDB_CLIENT1* was found
+# Examples:
+ # - IMDB_SERVER100_122_35-10009569.SAR - Maintenance Revision 122.35 (SPS12) for HANA DB 1.00
+ # - IMDB_CLIENT100_120_140-10009663.SAR - Revision 120 for SAP HANA CLIENT 1.00
+- name: Relationship Validation - SAP HANA 1.00 - IMDB_SERVER and IMDB_CLIENT
+ ansible.builtin.fail:
+ msg: |
+ Warning: Incompatible SAP HANA client files were found for detected SAP HANA DATABASE 1.0.
+
+ Expected file pattern: IMDB_CLIENT100_*
+ Actual files detected: {{ __sap_software_download_sap_hana_client_list | unique | join(', ') }}
+ vars:
+ __sap_software_download_sap_hana_client_list:
+ "{{ __sap_software_download_files | select('match', '^IMDB_CLIENT.*') | list }}"
+ ignore_errors: "{{ sap_software_download_ignore_relationship_warning | d(false) }}"
+ when:
+ - __sap_software_download_sap_hana_client_list | length > 0
+ - __sap_software_download_sap_hana_client_list | select('match', '^IMDB_CLIENT1.*') | length == 0
diff --git a/roles/sap_software_download/tasks/pre_steps/07_validate_sap_hana_2.yml b/roles/sap_software_download/tasks/pre_steps/07_validate_sap_hana_2.yml
new file mode 100644
index 0000000..094fca7
--- /dev/null
+++ b/roles/sap_software_download/tasks/pre_steps/07_validate_sap_hana_2.yml
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+- name: Relationship Validation - SAP HANA 2.00 - Set HANA variables
+ ansible.builtin.set_fact:
+ # 084 for IMDB_SERVER20_084_0-80002031.SAR
+ __sap_software_download_sap_hana_2_version:
+ "{{ __sap_software_download_sap_hana.split('_')[2] }}"
+
+
+# Warning conditions:
+# - At least one IMDB_LCAPPS_2 was found
+# - No IMDB_LCAPPS_2 was found
+# Examples:
+ # - IMDB_SERVER20_084_0-80002031.SAR - Revision 2.00.084.0 (SPS08) for HANA DB 2.0
+ # - IMDB_LCAPPS_2084_0-20010426.SAR - LCAPPS for HANA 2.0 Rev 84 Build 101.19 PL 007
+- name: Relationship Validation - SAP HANA 2.00 - IMDB_SERVER and IMDB_LCAPPS
+ ansible.builtin.fail:
+ msg: |
+ Warning: Incompatible SAP HANA component LCAPPS files were found for detected SAP HANA DATABASE 2.0 SPS {{
+ __sap_software_download_sap_hana_2_version[:2] }} Revision {{ __sap_software_download_sap_hana_2_version }}.
+
+ Expected file pattern: IMDB_LCAPPS_2{{ __sap_software_download_sap_hana_2_version }}*
+ Actual files detected: {{ __sap_software_download_sap_hana_2_lcapps_list | unique | join(', ') }}
+ vars:
+ __sap_software_download_sap_hana_2_lcapps_list:
+ "{{ __sap_software_download_files | select('match', '^IMDB_LCAPPS_2.*') | list }}"
+ __sap_software_download_sap_hana_2_lcapps_list_filtered:
+ "{{ sap_software_download_files | select('match', '^IMDB_LCAPPS_2' ~ __sap_software_download_sap_hana_2_version) | list }}"
+ ignore_errors: "{{ sap_software_download_ignore_relationship_warning | d(false) }}"
+ when:
+ - __sap_software_download_sap_hana_2_lcapps_list | length > 0
+ - __sap_software_download_sap_hana_2_lcapps_list_filtered | length == 0
+
+
+# Warning conditions:
+# - At least one IMDB_AFL20 was found
+# - No IMDB_AFL20_ was found
+# Examples:
+ # - IMDB_SERVER20_084_0-80002031.SAR - Revision 2.00.084.0 (SPS08) for HANA DB 2.0
+ # - IMDB_AFL20_084_1-80001894.SAR - SAP HANA AFL Rev 84.1 only for HANA 2.0 Rev 84
+- name: Relationship Validation - SAP HANA 2.00 - IMDB_SERVER and IMDB_AFL
+ ansible.builtin.fail:
+ msg: |
+ Warning: Incompatible SAP HANA component AFL files were found for detected SAP HANA DATABASE 2.0 SPS {{
+ __sap_software_download_sap_hana_2_version[:2] }} Revision {{ __sap_software_download_sap_hana_2_version }}.
+
+ Expected file pattern: IMDB_AFL20_{{ __sap_software_download_sap_hana_2_version }}*
+ Actual files detected: {{ __sap_software_download_sap_hana_2_afl_list | unique | join(', ') }}
+ vars:
+ __sap_software_download_sap_hana_2_afl_list:
+ "{{ __sap_software_download_files | select('match', '^IMDB_AFL20.*') | list }}"
+ __sap_software_download_sap_hana_2_afl_list_filtered:
+ "{{ sap_software_download_files | select('match', '^IMDB_AFL20_.*' ~ __sap_software_download_sap_hana_2_version) | list }}"
+ ignore_errors: "{{ sap_software_download_ignore_relationship_warning | d(false) }}"
+ when:
+ - __sap_software_download_sap_hana_2_afl_list | length > 0
+ - __sap_software_download_sap_hana_2_afl_list_filtered | length == 0
+
+
+# Warning conditions:
+# - At least one IMDB_CLIENT was found
+# - No IMDB_CLIENT2* was found
+# Examples:
+ # - IMDB_SERVER20_084_0-80002031.SAR - Revision 2.00.084.0 (SPS08) for HANA DB 2.0
+ # - IMDB_CLIENT20_024_21-80002082.SAR - SAP HANA CLIENT Version 2.24
+- name: Relationship Validation - SAP HANA 2.00 - IMDB_SERVER and IMDB_CLIENT
+ ansible.builtin.fail:
+ msg: |
+ Warning: Incompatible SAP HANA client files were found for detected SAP HANA DATABASE 2.0.
+
+ Expected file pattern: IMDB_CLIENT20_*
+ Actual files detected: {{ __sap_software_download_sap_hana_client_list | unique | join(', ') }}
+ vars:
+ __sap_software_download_sap_hana_client_list:
+ "{{ __sap_software_download_files | select('match', '^IMDB_CLIENT.*') | list }}"
+ ignore_errors: "{{ sap_software_download_ignore_relationship_warning | d(false) }}"
+ when:
+ - __sap_software_download_sap_hana_client_list | length > 0
+ - __sap_software_download_sap_hana_client_list | select('match', '^IMDB_CLIENT2.*') | length == 0
diff --git a/roles/sap_software_download/vars/RedHat_10.yml b/roles/sap_software_download/vars/RedHat_10.yml
new file mode 100644
index 0000000..13cb27b
--- /dev/null
+++ b/roles/sap_software_download/vars/RedHat_10.yml
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# Variables specific to following versions:
+# - Red Hat Linux Enterprise Server 10
+
+# Set which Python version will be used on destination node.
+# This is python executable name, which can differ from python package name.
+__sap_software_download_python_interpreter: 'python3.12'
+
+# Set which Python package will be installed on destination node.
+__sap_software_download_python_package: 'python3.12'
+
+# The list of required Python Modules in packages
+# This is required in order to avoid externally-managed-environment error.
+__sap_software_download_python_module_packages:
+ - python3.12-wheel
+ - python3.12-urllib3
+ - python3.12-requests
+ - python3.12-beautifulsoup4
+ - python3.12-lxml
diff --git a/roles/sap_software_download/vars/RedHat_8.yml b/roles/sap_software_download/vars/RedHat_8.yml
new file mode 100644
index 0000000..453b5ce
--- /dev/null
+++ b/roles/sap_software_download/vars/RedHat_8.yml
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# Variables specific to following versions:
+# - Red Hat Linux Enterprise Server 8
+
+# Set which Python version will be used on destination node.
+# This is python executable name, which can differ from python package name.
+__sap_software_download_python_interpreter: 'python3.9'
+
+# Set which Python package will be installed on destination node.
+__sap_software_download_python_package: 'python39'
+
+# The list of required Python Modules in packages
+# This is required in order to avoid externally-managed-environment error.
+__sap_software_download_python_module_packages:
+ - python39-wheel
+ - python39-urllib3
+ - python39-requests
+ - python39-beautifulsoup4
+ - python39-lxml
diff --git a/roles/sap_software_download/vars/RedHat_9.yml b/roles/sap_software_download/vars/RedHat_9.yml
new file mode 100644
index 0000000..f472adf
--- /dev/null
+++ b/roles/sap_software_download/vars/RedHat_9.yml
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# Variables specific to following versions:
+# - Red Hat Linux Enterprise Server 9
+
+# Set which Python version will be used on destination node.
+# This is python executable name, which can differ from python package name.
+__sap_software_download_python_interpreter: 'python3.9'
+
+# Set which Python package will be installed on destination node.
+__sap_software_download_python_package: 'python3.9'
+
+# The list of required Python Modules in packages
+# This is required in order to avoid externally-managed-environment error.
+__sap_software_download_python_module_packages:
+ - python3.9-wheel
+ - python3.9-urllib3
+ - python3.9-requests
+ - python3.9-beautifulsoup4
+ - python3.9-lxml
diff --git a/roles/sap_software_download/vars/Suse.yml b/roles/sap_software_download/vars/Suse.yml
new file mode 100644
index 0000000..f540820
--- /dev/null
+++ b/roles/sap_software_download/vars/Suse.yml
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# Variables specific to following versions:
+# - SUSE Linux Enterprise Server 15
+# - SUSE Linux Enterprise Server 16
+
+# NOTE: SLES 15 SP5 introduced Python 3.11.
+
+# Set which Python version will be used on destination node.
+# This is python executable name, which can differ from python package name.
+__sap_software_download_python_interpreter: >-
+ {%- if ansible_distribution_major_version == '15' and ansible_distribution_version.split('.')[1] | int < 5 -%}
+ python3
+ {%- else -%}
+ python3.11
+ {%- endif -%}
+
+# Set which Python package will be installed on destination node.
+__sap_software_download_python_package: >-
+ {%- if ansible_distribution_major_version == '15' and ansible_distribution_version.split('.')[1] | int < 5 -%}
+ python3
+ {%- else -%}
+ python311
+ {%- endif -%}
+
+# The list of required Python Modules in packages
+# This is required in order to avoid externally-managed-environment error.
+__sap_software_download_python_module_packages: >-
+ {%- if ansible_distribution_major_version == '15' and ansible_distribution_version.split('.')[1] | int < 5 -%}
+ [
+ "python3-wheel",
+ "python3-urllib3",
+ "python3-requests",
+ "python3-beautifulsoup4",
+ "python3-lxml"
+ ]
+ {%- else -%}
+ [
+ "python311-wheel",
+ "python311-urllib3",
+ "python311-requests",
+ "python311-beautifulsoup4",
+ "python311-lxml"
+ ]
+ {%- endif -%}
diff --git a/roles/sap_software_download/vars/main.yml b/roles/sap_software_download/vars/main.yml
new file mode 100644
index 0000000..e40f28e
--- /dev/null
+++ b/roles/sap_software_download/vars/main.yml
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: Apache-2.0
+---
+
+# The list of required Python Modules for download module
+__sap_software_download_python_modules:
+ - wheel
+ - urllib3
+ - requests
+ - beautifulsoup4
+ - lxml
+
+# The list of required Python Modules in packages
+# This is required in order to avoid externally-managed-environment error.
+# Specific packages are available in OS specific var files
+__sap_software_download_python_module_packages:
+ - python3-wheel
+ - python3-urllib3
+ - python3-requests
+ - python3-beautifulsoup4
+ - python3-lxml