diff --git a/.github/workflows/ansible-lint.yml b/.github/workflows/ansible-lint.yml new file mode 100644 index 0000000..b78d9d8 --- /dev/null +++ b/.github/workflows/ansible-lint.yml @@ -0,0 +1,24 @@ +name: Ansible Lint + +on: [push, pull_request] + +jobs: + ansible-lint: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Lint Ansible Playbook + uses: ansible/ansible-lint-action@master + with: + targets: | + ./ + ./roles + override-deps: | + ansible==4.8.0 + ansible-core==2.11.6 + ansible-lint==5.2.1 + +# Static: use Ansible Community Edition 4.8.0, with lowest compatible Ansible Core 2.11.6 and use Ansible-lint 5.2.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4938417 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# .gitignore defines files to ignore and remain untracked in Git +# Each line in a gitignore file specifies a pattern, eg. directory or file extension + +# Git should not track binary artifacts such as images, libraries, executables, archive files etc. +# Until the team has mature processes, a Binary Artifacts Repository Manager is not in use. + +# Therefore some binary artifacts are tracked in this Git repository + + +# Further .gitignore templates available at: +# https://github.com/github/gitignore + + +# macOS OS generated files +.DS_Store +._* +.Spotlight-V100 +.Trashes + +# Windows OS generated files # +ehthumbs.db +Thumbs.db + +# Compressed Archives +# git has built-in compression +# *.7z +# *.dmg +# *.gz +# *.iso +# *.jar +# *.rar +# *.tar +# *.zip + +# Binaries / Compiled source +# *.com +# *.class +# *.dll +# *.exe +# *.o +# *.so + +# Logs and databases +# *.log +# *.sqlite + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# VSCode +.vscode + diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..578e3b9 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# community.sap_operations Ansible Collection ![Ansible Lint](https://github.com/infrasap/community.sap_operations/workflows/Ansible%20Lint/badge.svg?branch=main) + +This Ansible Collection executes various SAP Systems operational tasks, which can be used day-to-day individually or combined for more complex regular maintainance automation + +## Functionality + +This Ansible Collection executes various SAP Systems operational tasks, including: + +- **OS configuration Post-install of SAP Software** + - Create ansible user for managing systems + - Update /etc/hosts file + - Update SSH authorized known hosts file + - Update fapolicy entries based on SAP System instance numbers + - Update firewall port entries based on SAP System instance numbers + - License registration and refresh for RHEL subscription manager +- **SAP administration tasks** + - Start/Stop of SAP HANA and SAP NetWeaver (in any configuration) + - Update SAP profile files + +## Contents + +An Ansible Playbook can call either an Ansible Role, or the individual Ansible Modules: +- **Ansible Roles** (runs multiple Ansible Modules) +- **Ansible Modules** (and adjoining Python/Bash Functions) + +For further information regarding the development, code structure and execution workflow please read the [Development documentation](./docs/DEVELOPMENT.md). + +Within this Ansible Collection, there are various Ansible Roles and Ansible Modules. + +#### Ansible Roles + +| Name                    | Summary | +| :-- | :-- | +| [os_ansible_user](/roles/os_ansible_user) | creates Ansible user `ansadm` with ssh key | +| [os_etchosts](/roles/os_etchosts) | updates `/etc/hosts` | +| [os_knownhosts](/roles/os_knownhosts) | updates known hosts file `/.ssh/known_hosts` | +| [sap_control](/roles/sap_control) | starting and stopping SAP systems | +| [sap_fapolicy](/roles/sap_fapolicy) | update service `fapolicyd` for generic / sap nw / sap hana related uids | +| [sap_firewall](/roles/sap_firewall) | update service `firewalld` for generic / sap nw / sap hana related ports | +| [sap_profile_update](/roles/sap_profile_update) | update default and instance profiles | +| [sap_rhsm](/roles/sap_rhsm) | Red Hat subscription manager registration | + +#### Ansible Modules + +| Name                    | Summary | +| :-- | :-- | +| [sap_operations.sap_facts](/docs/module_sap_facts.md) | gather SAP facts in a host (e.g. SAP System IDs and SAP System Instance Numbers of either SAP HANA database server or SAP NetWeaver application server) | +| [sap_operations.sap_monitor_hana_status](/docs/module_sap_monitor.md) | check status of running SAP HANA database server | +| [sap_operations.sap_monitor_nw_status](/docs/module_sap_monitor.md) | check status of running SAP NetWeaver application server | +| [sap_operations.sap_monitor_nw_perf](/docs/module_sap_monitor.md) | check host performance metrics from SAP NetWeaver Primary Application Server (PAS) instance | +| [sap_operations.sap_monitor_nw_response](/docs/module_sap_monitor.md) | check system response time metrics from SAP NetWeaver Primary Application Server (PAS) instance | + +## Execution examples + +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 | + +## Requirements, Dependencies and Testing + +### Operating System requirements + +Designed for Linux operating systems, e.g. RHEL and SLES. + +This role has not been tested and amended for SAP NetWeaver Application Server instantiations on IBM AIX or Windows Server. + +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) + +### Python requirements + +Python 3 from the execution/controller host. + +### Testing on execution/controller host + +**Tests with Ansible Core release versions:** +- Ansible Core 2.11.5 community edition + +**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 + +### Testing on target/remote host + +**Tests with Operating System release versions:** +- RHEL 8.2 for SAP + +## License + +- [Apache 2.0](./LICENSE) + +## Contributors + +Contributors to the Ansible Roles within this Ansible Collection, are shown within [/docs/contributors](./docs/CONTRIBUTORS.md). diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/CONTRIBUTORS.md b/docs/CONTRIBUTORS.md new file mode 100644 index 0000000..5950b9f --- /dev/null +++ b/docs/CONTRIBUTORS.md @@ -0,0 +1,8 @@ +# Development contributors + +- **IBM Lab for SAP Solutions** + - IBM Consulting + - **Jason Masipiquena** - Developer of Ansible Collection constructs and Ansible Modules, and project owner + - **Sherard Guico** - Sponsor and technical review + - IBM Cloud + - **Sean Freeman** - Reviewer diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..1ba4920 --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,142 @@ + +# Development of community.sap_operations Ansible Collection + +This Ansible Collection is developed with several design principles and code practices. + +## Code structure + +This Ansible Collection directory tree structure is shown below: +```code +collection/ +├── docs/ +├── meta/ +├── plugins/ +│ └── modules/ +│ ├── sap_facts.sh +│ ├── sap_monitor_hana_status.sh +│ ├── sap_monitor_nw_perf.sh +│ ├── sap_monitor_nw_response.sh +│ └── sap_monitor_nw_status.sh +├── roles/ +│ ├── os_ansible_user +│ │ ├── defaults +│ │ │ └── main.yml +│ │ └── tasks +│ │ └── main.yml +│ ├── os_etchosts +│ │ ├── defaults +│ │ │ └── main.yml +│ │ └── tasks +│ │ ├── main.yml +│ │ └── update_etchosts.yml +│ ├── os_knownhosts +│ │ ├── defaults +│ │ │ └── main.yml +│ │ └── tasks +│ │ └── main.yml +│ ├── sap_control +│ │ ├── defaults +│ │ │ └── main.yml +│ │ └── tasks +│ │ ├── functions +│ │ │ ├── cleanipc.yml +│ │ │ ├── restart_sapstartsrv.yml +│ │ │ └── sapstartsrv.yml +│ │ ├── main.yml +│ │ ├── prepare.yml +│ │ └── sapcontrol.yml +│ ├── sap_fapolicy +│ │ ├── defaults +│ │ │ └── main.yml +│ │ └── tasks +│ │ ├── enable_fapolicy.yml +│ │ ├── get_sidadm_user.yml +│ │ ├── get_user_uid.yml +│ │ ├── main.yml +│ │ └── update_fapolicy.yml +│ ├── sap_firewall +│ │ ├── defaults +│ │ │ └── main.yml +│ │ └── tasks +│ │ ├── enable_firewall.yml +│ │ ├── generate_ports_generic.yml +│ │ ├── generate_ports_hana.yml +│ │ ├── generate_ports_nw.yml +│ │ ├── main.yml +│ │ └── update_firewall.yml +│ ├── sap_profile_update +│ │ ├── defaults +│ │ │ └── main.yml +│ │ └── tasks +│ │ ├── main.yml +│ │ └── update_parameter.yml +│ └── sap_rhsm +│ ├── defaults +│ │ └── main.yml +│ └── tasks +│ ├── main.yml +│ ├── rhsm_refresh.yml +│ └── rhsm_register.yml +├── playbooks/ +│ ├── sample-os-yum-update.yml +│ ├── sample-sap-control-all-restart-nw.yml +│ ├── sample-sap-control-all-restart.yml +│ ├── sample-sap-control-single-restart.yml +│ ├── sample-sap-etchosts-update.yml +│ ├── sample-sap-facts.yml +│ ├── sample-sap-fapolicy-all-update.yml +│ ├── sample-sap-firewall-all-update.yml +│ ├── sample-sap-firewall-update.yml +│ ├── sample-sap-hana-status.yml +│ ├── sample-sap-knownhosts-update.yml +│ ├── sample-sap-nw-perf.yml +│ ├── sample-sap-nw-resp.yml +│ ├── sample-sap-nw-status.yml +│ └── sample-sap-profile-update.yml +├── tests/ +├── galaxy.yml +└── README.md +``` + +## Execution logic + +This Ansible Collection is designed to be heavily re-usable for various scenarios where SAP System operational tasks are performed, 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/EXEC_EXAMPLES.md b/docs/EXEC_EXAMPLES.md new file mode 100644 index 0000000..8b71497 --- /dev/null +++ b/docs/EXEC_EXAMPLES.md @@ -0,0 +1,128 @@ +# Execution examples + +## Sample execution workflows + +![](../docs/diagrams/workflow_sample_exec_sap_operations_sample.svg) + +## Execution example with Ansible Playbook calling Ansible Module + +**Ansible Playbook YAML, execute Ansible Module** +```yaml +--- +- hosts: all + become: true + +# Prompt for Ansible Variables + vars_prompt: + - name: sap_facts_param + prompt: "Choose: all, hana, nw" + private: no + + tasks: + - name: Execute sap_facts Ansible Module to gather SAP System facts for the host + community.sap_operations.sap_facts: + param: "{{ sap_facts_param }}" + register: sap_facts_register + + - debug: + msg: "{{ sap_facts_register.sap_facts }}" + + - debug: + msg: "{{ sap_facts_register.sap_hana_sid }}" + + - debug: + msg: "{{ sap_facts_register.sap_nw_sid }}" +``` + +**Execution of Ansible Playbook, with in-line Ansible Inventory of target/remote hosts** + +```shell +# Install from local source directory for Ansible 2.11+ +ansible-galaxy collection install ./community.sap_operations + +# Workaround install from local source directory for Ansible 2.9.x +# mv ./community.sap_operations ~/.ansible/collections/ansible_collections/community + +# SSH Connection details +target_private_key_file="$PWD/vs_rsa" +target_host="10.0.50.5" +target_user="root" + +# Run Ansible Collection to 1 target/remote host +ansible-playbook --timeout 60 ./community.sap_operations/playbooks/sample-sap-facts.yml \ +--connection 'ssh' --user "$target_user" --inventory "$target_host," --private-key "$target_private_key_file" +``` + + +## Execution example with Ansible Playbook calling Ansible Role + +**Ansible Playbook YAML, execute Ansible Role on target/remote host** +```yaml +--- +- hosts: all + become: true + +# Prompt for Ansible Variables + vars_prompt: + - name: sap_sid + prompt: Please enter target SAP System ID (SID) + private: no + +# Define Ansible Variables + vars: + sap_control_function: "restart_sap_nw" + +# Option 1: Use roles declaration + roles: + - { role: community.sap_operations.sap_control } + +# Option 2: Use sequential parse/execution, by using include_role inside Task block + tasks: + - name: Execute Ansible Role to start/stop SAP Systems + include_role: + name: { role: community.sap_operations.sap_control } + +# Option 3: Use task block with import_roles + tasks: + - name: Execute Ansible Role to start/stop SAP Systems + import_roles: + name: { role: community.sap_operations.sap_control } +``` + +**Execution of Ansible Playbook, with in-line Ansible Inventory of target/remote hosts with Proxy/Bastion** + +```shell +# Install from local source directory for Ansible 2.11+ +ansible-galaxy collection install ./community.sap_operations + +# Workaround install from local source directory for Ansible 2.9.x +# mv ./community.sap_operations ~/.ansible/collections/ansible_collections/community + +# SSH Connection details +bastion_private_key_file="$PWD/bastion_rsa" +bastion_host="169.0.40.4" +bastion_port="50222" +bastion_user="bastionuser" + +target_private_key_file="$PWD/vs_rsa" +target_host="10.0.50.5" +target_user="root" + +# Run Ansible Collection to target/remote hosts via Proxy/Bastion +ansible-playbook --timeout 60 ./community.sap_operations/playbooks/sample-sap-control-single-restart.yml \ +--connection 'ssh' --user "$target_user" --inventory "$target_host," --private-key "$target_private_key_file" \ +--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 Bash Shell environment + +**Execute Bash Functions** +```shell +# Change directory to Ansible Module (in Bash) source +cd ./plugins/modules + +# Execute Ansible Module, using Arguments in same way passed from Ansible +echo 'param=all' > vars.sh +bash ./sap_facts.sh 'vars.sh' +rm vars.sh +``` diff --git a/docs/diagrams/workflow_module_sap_facts.svg b/docs/diagrams/workflow_module_sap_facts.svg new file mode 100644 index 0000000..391ff2a --- /dev/null +++ b/docs/diagrams/workflow_module_sap_facts.svg @@ -0,0 +1,3 @@ + + +
sap_facts
sap_facts
return_json
return_json
hana
hana
nw
nw
SID
SID
all
all
get_all_hana_sid
get_all_hana_sid
get_all_nw_sid
get_all_nw_sid
get_sid_type
get_sid_type
get_all_nw_nr
get_all_nw_nr
get_all_hana_nr
get_all_hana_nr
get_instance_type
get_instance_type
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/diagrams/workflow_role_sap_control.svg b/docs/diagrams/workflow_role_sap_control.svg new file mode 100644 index 0000000..12d942f --- /dev/null +++ b/docs/diagrams/workflow_role_sap_control.svg @@ -0,0 +1,3 @@ + + +
Execute function, loop for each SAP system
Execute function, loop for each SAP system
Execute function for single SAP system
Execute function for single SAP system
restart_all_sap
restart_all_sap
start_all_sap
start_all_sap
stop_all_sap
stop_all_sap
start_all_hana
start_all_hana
start_all_nw
start_all_nw
stop_all_nw
stop_all_nw
stop_all_hana
stop_all_hana
restart_all_hana
restart_all_hana
restart_all_nw
restart_all_nw
restart_sap_nw
restart_sap_nw
restart_sap_hana
restart_sap_hana
start_sap_hana
start_sap_hana
start_sap_nw
start_sap_nw
stop_sap_nw
stop_sap_nw
stop_sap_hana
stop_sap_hana
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/diagrams/workflow_role_sap_firewall.svg b/docs/diagrams/workflow_role_sap_firewall.svg new file mode 100644 index 0000000..a9043e1 --- /dev/null +++ b/docs/diagrams/workflow_role_sap_firewall.svg @@ -0,0 +1,3 @@ + + +
sap_firewall
sap_firewall
generic
generic
hana
hana
nw
nw
generate_ports
generate_ports
update_firewall
update_firewall
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/diagrams/workflow_sample_exec_sap_operations_sample.svg b/docs/diagrams/workflow_sample_exec_sap_operations_sample.svg new file mode 100644 index 0000000..f4f9f47 --- /dev/null +++ b/docs/diagrams/workflow_sample_exec_sap_operations_sample.svg @@ -0,0 +1,3 @@ + + +
Workflow samples within this collection
community.sap_operations
Workflow samples within this collection...
Workflow sample in conjunction with other collection
community.sap_launchpad
Workflow sample in conjunction with other collection...
sap_facts
sap_facts
sap_profile_update
sap_profile_update
sap_control (restart)
sap_control (restart)
sap_kernel_update
sap_kernel_update
sap_control (restart)
sap_control (restart)
sap_software_download
sap_software_download
sap_firewall
sap_firewall
sap_control
sap_control
sap_monitoring
sap_monitoring
sap_monitor_nw_status
sap_monitor_nw_status
sap_monitor_nw_perf
sap_monitor_nw_perf
sap_...
sap_...
Workflow sample in conjunction with 2 other collections
community.sap_launchpad
community.sap_install
Workflow sample in conjunction with 2 other collections...
sap_hana_install
sap_hana_install
sap_firewall
sap_firewall
os_etchosts
os_etchosts
sap_software_download
sap_software_download
sap_general_preconfigure
sap_general_preconfigure
sap_storage
sap_storage
sap_rhsm
sap_rhsm
sap_swpm
sap_swpm
sap_firewall
sap_firewall
os_etchosts
os_etchosts
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/module_sap_facts.md b/docs/module_sap_facts.md new file mode 100644 index 0000000..20f42a6 --- /dev/null +++ b/docs/module_sap_facts.md @@ -0,0 +1,46 @@ +# sap_facts Ansible Module + +- The equivalent of Ansible's `gather_facts`, but for gathering SAP facts +- Scans the host and populates a dictionary list `sap_facts` containing `SID` `Type` `InstanceNumber` `InstanceType` +- This is the foundation of every modules / roles / tasks not just for this Ansbile collection but for any other collections related to SAP +- ![](/docs/diagrams/workflow_module_sap_facts.svg) +- Full list of outputs: + | **Output** | **Info** | **Return Variable** | + | :--- | :--- | :--- | + | sap_nw_sid | List of all SAP NW SIDs | `.sap_nw_sid` | + | sap_hana_sid | List of all SAP HANA SIDs | `.sap_hana_sid` | + | sap_nw_nr | List of all SAP NW instance numbers | `.sap_nw_nr` | + | sap_hana_nr | List of all SAP HANA instance numbers | `.sap_hana_nr` | + | **sap_facts** | Dictionary list of all the details | `.sap_facts` | + +- Sample `sap_facts` dictionary list generated: + + ```yaml + [ + { + "InstanceNumber": "00", + "InstanceType": "HANA", + "SID": "H20", + "Type": "hana" + }, + { + "InstanceNumber": "02", + "InstanceType": "ASCS", + "SID": "S4H", + "Type": "nw" + }, + { + "InstanceNumber": "01", + "InstanceType": "PAS", + "SID": "S4H", + "Type": "nw" + }, + { + "InstanceNumber": "03", + "InstanceType": "WebDisp", + "SID": "WD1", + "Type": "nw" + } + ] + ``` + diff --git a/docs/module_sap_monitor.md b/docs/module_sap_monitor.md new file mode 100644 index 0000000..df5156f --- /dev/null +++ b/docs/module_sap_monitor.md @@ -0,0 +1,133 @@ +# sap_monitor Ansible Module + +Ansible modules for SAP healthcheck / monitoring + +- Modules for SAP healthcheck / monitoring +- Please check the documentaion here [SAP Check Documentation](/docs/sap_monitor) +- For technical details, please check the individual `.sh` files here [SAP Check](/plugins/modules) + + +- **sap_monitor_hana_status** + - Checks the status of SAP HANA system + - Returns 'GREEN' 'YELLOW' 'RED' 'GRAY' as returned by `sapcontrol` + - Full list of outputs: + | **Output** | **Info** | **Return Variable** | + | :--- | :--- | :--- | + | sap_status | 'GREEN' 'YELLOW' 'RED' 'GRAY' as returned by `sapcontrol` | `.sap_status` | + - Sample output: + ```yaml + "changed": false, + "sap_status": "GREEN", + "failed": false, + "item": { + "InstanceNumber": "90", + "InstanceType": "HANA", + "SID": "HDX", + "Type": "hana" + }, + "msg": "SAP Check Successful" + ``` + +- **sap_monitor_nw_status** + - Checks the status of SAP Netweaver system + - Returns 'GREEN' 'YELLOW' 'RED' 'GRAY' as returned by `sapcontrol` + - Full list of outputs: + | **Output** | **Info** | **Return Variable** | + | :--- | :--- | :--- | + | sap_status | 'GREEN' 'YELLOW' 'RED' 'GRAY' as returned by `sapcontrol` | `.sap_status` | + - Sample output: + ```yaml + { + "ansible_loop_var": "item", + "changed": false, + "sap_status": "GREEN", + "failed": false, + "item": { + "InstanceNumber": "92", + "InstanceType": "ASCS", + "SID": "S4X", + "Type": "nw" + }, + "msg": "SAP Check Successful" + }, + { + "ansible_loop_var": "item", + "changed": false, + "sap_status": "GREEN", + "failed": false, + "item": { + "InstanceNumber": "91", + "InstanceType": "PAS", + "SID": "S4X", + "Type": "nw" + }, + "msg": "SAP Check Successful" + } + ``` + + +- **sap_monitor_nw_perf** + - Checks performance metrics of an SAP Netweaver ABAP system + - > **_Note:_** The current checklist only contains 4 items at this early stage of development but can be easily improved later + - Current scope: + - Heap Memory + - Extended Memory + - CPU Utilization + - Program Buffer Swap + - (more can be added) + - Full list of outputs: + | **Output** | **Info** | **Return Variable** | + | :--- | :--- | :--- | + | heap_memory | Heap memory | `.heap_memory` | + | extended_memory | Extended memory | `.extended_memory` | + | cpu_util | CPU utilization | `.cpu_util` | + | program_swap | Program buffer swap | `.program_swap` | + - Sample output: + ```yaml + "changed": false, + "cpu_util": "7", + "extended_memory": "19", + "failed": false, + "heap_memory": "0", + "item": { + "InstanceNumber": "91", + "InstanceType": "PAS", + "SID": "S4X", + "Type": "nw" + }, + "msg": "SAP Check Successful", + "program_swap": "0.0 + ``` + +- **sap_monitor_nw_response** + - Checks response times of an SAP Netweaver ABAP system + - > **_Note:_** The current checklist only contains 4 items at this early stage of development but can be easily improved later + - Current scope: + - Dialog Response Time + - Database Response Time + - Front End Response Time + - Program Buffer Swap + - (more can be added) + - Full list of outputs: + | **Output** | **Info** | **Return Variable** | + | :--- | :--- | :--- | + | dialog_response_time | Dialog response time | `.dialog_response_time` | + | database_response_time | Database respone time | `.database_response_time` | + | frontend_response_time | Front end response time | `.frontend_response_time` | + | number_users | Current number of logged in users | `.number_users` | + - Sample output: + ```yaml + "changed": false, + "database_response_time": "177", + "dialog_response_time": "542", + "failed": false, + "frontend_response_time": "211", + "item": { + "InstanceNumber": "91", + "InstanceType": "PAS", + "SID": "S4X", + "Type": "nw" + }, + "msg": "SAP Check Successful", + "number_users": "19" + ``` \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..6326c43 --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,61 @@ +### REQUIRED +# The namespace of the collection. This can be a company/brand/organization or product namespace under which all +# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with +# underscores or numbers and cannot contain consecutive underscores +namespace: community + +# The name of the collection. Has the same character restrictions as 'namespace' +name: sap_operations + +# The version of the collection. Must be compatible with semantic versioning +version: 1.0.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 + +### OPTIONAL but strongly recommended +# A short summary description of the collection +description: Collection of Ansible Modules and Ansible Roles for SAP system operations + +# 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 + +# 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' +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: [] + +# 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 +# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version +# range specifiers can be set and are separated by ',' +dependencies: {} + +# The URL of the originating SCM repository +repository: http://example.com/repository + +# The URL to any online docs +documentation: http://docs.example.com + +# The URL to the homepage of the collection/project +homepage: http://example.com + +# The URL to the collection issue tracker +issues: http://example.com/issue/tracker + +# A list of file glob-like patterns used to filter any files or directories that should not be included in the build +# 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'] diff --git a/meta/.gitkeep b/meta/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/playbooks/sample-os-yum-update.yml b/playbooks/sample-os-yum-update.yml new file mode 100644 index 0000000..a1be9e9 --- /dev/null +++ b/playbooks/sample-os-yum-update.yml @@ -0,0 +1,19 @@ +# Ansible playbook for yum update +--- + +- name: Yum Update All + hosts: all + become: true + + tasks: + + - name: Yum Update All + block: + + - name: Subscription Manager - Lock Release + command: 'subscription-manager release --set={{ ansible_distribution_version }}' + + - name: Yum update all + command: 'yum --assumeyes update' + + when: ansible_facts['distribution'] == 'RedHat' \ No newline at end of file diff --git a/playbooks/sample-sap-control-all-restart-nw.yml b/playbooks/sample-sap-control-all-restart-nw.yml new file mode 100644 index 0000000..8488e94 --- /dev/null +++ b/playbooks/sample-sap-control-all-restart-nw.yml @@ -0,0 +1,8 @@ +--- +- hosts: all + become: true + vars: + sap_control_function: "restart_all_nw" + roles: + - { role: community.sap_operations.sap_control } + diff --git a/playbooks/sample-sap-control-all-restart.yml b/playbooks/sample-sap-control-all-restart.yml new file mode 100644 index 0000000..9bb8599 --- /dev/null +++ b/playbooks/sample-sap-control-all-restart.yml @@ -0,0 +1,7 @@ +--- +- hosts: all + become: true + vars: + sap_control_function: "restart_all_sap" + roles: + - { role: community.sap_operations.sap_control } diff --git a/playbooks/sample-sap-control-single-restart.yml b/playbooks/sample-sap-control-single-restart.yml new file mode 100644 index 0000000..ab0d5b4 --- /dev/null +++ b/playbooks/sample-sap-control-single-restart.yml @@ -0,0 +1,14 @@ +--- +- hosts: all + become: true + +# Prompt for Ansible Variables + vars_prompt: + - name: sap_sid + prompt: Please enter target SAP System ID (SID) + private: no + + vars: + sap_control_function: "restart_sap_nw" + roles: + - { role: community.sap_operations.sap_control } diff --git a/playbooks/sample-sap-etchosts-update.yml b/playbooks/sample-sap-etchosts-update.yml new file mode 100644 index 0000000..8a0b208 --- /dev/null +++ b/playbooks/sample-sap-etchosts-update.yml @@ -0,0 +1,13 @@ +--- +- hosts: all + become: true + vars: + sap_os_tools_etchosts_entries: + - "10.0.20.4 hana01-lb" + - "10.0.20.5 hana02-lb" + - "10.0.40.8 s4hana01-ascs-pas" + - "10.0.40.9 s4hana01-aas" + sap_os_tools_etchosts_fqdn: "example.com" + roles: + - { role: community.sap_operations.os_etchosts } + diff --git a/playbooks/sample-sap-facts.yml b/playbooks/sample-sap-facts.yml new file mode 100644 index 0000000..dc13a30 --- /dev/null +++ b/playbooks/sample-sap-facts.yml @@ -0,0 +1,24 @@ +--- +- hosts: all + become: true + +# Prompt for Ansible Variables + vars_prompt: + - name: sap_facts_param + prompt: "Choose: all, hana, nw" + private: no + + tasks: + - name: Execute sap_facts Ansible Module to gather SAP System facts for the host + community.sap_operations.sap_facts: + param: "{{ sap_facts_param }}" + register: sap_facts_register + + - debug: + msg: "{{ sap_facts_register.sap_facts }}" + + - debug: + msg: "{{ sap_facts_register.sap_hana_sid }}" + + - debug: + msg: "{{ sap_facts_register.sap_nw_sid }}" \ No newline at end of file diff --git a/playbooks/sample-sap-fapolicy-all-update.yml b/playbooks/sample-sap-fapolicy-all-update.yml new file mode 100644 index 0000000..fac2065 --- /dev/null +++ b/playbooks/sample-sap-fapolicy-all-update.yml @@ -0,0 +1,35 @@ +--- +- hosts: all + become: true + + vars: + sap_fapolicy_user_generic_list: + - "root" + - "sapadm" + - "uuidd" + + tasks: + + # Update fapolicy for generic users + - name: Fapolicy Update - generic + vars: + sap_fapolicy_type: "generic" + include_role: + name: community.sap_operations.sap_fapolicy + loop: "{{ sap_fapolicy_user_generic_list }}" + loop_control: + loop_var: sap_fapolicy_user + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "all" + register: sap_facts_register + + # Update fapolicy for SAP users + - name: Fapolicy Update - SAP Users + vars: + sap_fapolicy_sid: "{{ item.Type }}" + sap_fapolicy_type: "{{ item.Type }}" + include_role: + name: community.sap_operations.sap_fapolicy + loop: "{{ sap_facts_register.sap_facts }}" diff --git a/playbooks/sample-sap-firewall-all-update.yml b/playbooks/sample-sap-firewall-all-update.yml new file mode 100644 index 0000000..dd35440 --- /dev/null +++ b/playbooks/sample-sap-firewall-all-update.yml @@ -0,0 +1,18 @@ +--- +- hosts: all + become: true + + tasks: + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "all" + register: sap_facts_register + + - name: Firewall Update + vars: + sap_firewall_type: "{{ item.Type }}" + sap_firewall_instance_nr: "{{ item.InstanceNumber }}" + include_role: + name: community.sap_operations.sap_firewall + loop: "{{ sap_facts_register.sap_facts }}" diff --git a/playbooks/sample-sap-firewall-update.yml b/playbooks/sample-sap-firewall-update.yml new file mode 100644 index 0000000..0361d9d --- /dev/null +++ b/playbooks/sample-sap-firewall-update.yml @@ -0,0 +1,11 @@ +--- +- hosts: all + become: true + vars: + sap_firewall_ports: + - "1128" + - "1129" + sap_firewall_type: "generic" + roles: + - { role: community.sap_operations.sap_firewall } + diff --git a/playbooks/sample-sap-hana-backint-clean.yml b/playbooks/sample-sap-hana-backint-clean.yml new file mode 100644 index 0000000..41c6919 --- /dev/null +++ b/playbooks/sample-sap-hana-backint-clean.yml @@ -0,0 +1,9 @@ +--- +- hosts: all + become: true + vars: + backup_function: 'clean' + sap_hana_sid: 'H01' + target_platform: 'ibm_cos_s3' + roles: + - { role: ../roles/sap_hana_backint } diff --git a/playbooks/sample-sap-hana-backint-execute.yml b/playbooks/sample-sap-hana-backint-execute.yml new file mode 100644 index 0000000..bd0d1bd --- /dev/null +++ b/playbooks/sample-sap-hana-backint-execute.yml @@ -0,0 +1,9 @@ +--- +- hosts: all + become: true + vars: + backup_function: 'execute' + sap_hana_sid: 'H01' + target_platform: 'ibm_cos_s3' + roles: + - { role: ../roles/sap_hana_backint } diff --git a/playbooks/sample-sap-hana-backint-setup.yml b/playbooks/sample-sap-hana-backint-setup.yml new file mode 100644 index 0000000..fb05d74 --- /dev/null +++ b/playbooks/sample-sap-hana-backint-setup.yml @@ -0,0 +1,9 @@ +--- +- hosts: all + become: true + vars: + backup_function: 'setup' + sap_hana_sid: 'H01' + target_platform: 'ibm_cos_s3' + roles: + - { role: ../roles/sap_hana_backint } diff --git a/playbooks/sample-sap-hana-status.yml b/playbooks/sample-sap-hana-status.yml new file mode 100644 index 0000000..3c7434f --- /dev/null +++ b/playbooks/sample-sap-hana-status.yml @@ -0,0 +1,22 @@ +--- +- hosts: all + become: true + + tasks: + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "hana" + register: sap_facts_register + + - name: Run sap_monitor_hana_status module + community.sap_operations.sap_monitor_hana_status: + hana_sid: "{{ item.SID }}" + hana_instance_number: "{{ item.InstanceNumber }}" + register: sap_monitor_hana_status_register + loop: "{{ sap_facts_register.sap_facts }}" + + - debug: + msg: + - "{{ sap_monitor_hana_status_register }}" + diff --git a/playbooks/sample-sap-knownhosts-update.yml b/playbooks/sample-sap-knownhosts-update.yml new file mode 100644 index 0000000..da38a0f --- /dev/null +++ b/playbooks/sample-sap-knownhosts-update.yml @@ -0,0 +1,12 @@ +# Ansible playbook to update known hosts file - $HOME/.ssh/known_hosts +--- + +- name: Store known hosts of 'all' the hosts in the inventory file + hosts: localhost + connection: local + + vars: + ssh_known_hosts: "{{ groups['all'] }}" + + roles: + - { role: community.sap_operations.os_knownhosts } diff --git a/playbooks/sample-sap-nw-perf.yml b/playbooks/sample-sap-nw-perf.yml new file mode 100644 index 0000000..9318a11 --- /dev/null +++ b/playbooks/sample-sap-nw-perf.yml @@ -0,0 +1,25 @@ +--- +- hosts: all + become: true + + tasks: + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "nw" + register: sap_facts_register + + - name: Run sap_monitor_nw_perf module + community.sap_operations.sap_monitor_nw_perf: + nw_sid: "{{ item.SID }}" + nw_instance_number: "{{ item.InstanceNumber }}" + nw_instance_type: "{{ item.InstanceType }}" + register: sap_monitor_nw_status_register + loop: "{{ sap_facts_register.sap_facts }}" + when: + - "'PAS' in item.InstanceType" + + - debug: + msg: + - "{{ sap_monitor_nw_status_register }}" + diff --git a/playbooks/sample-sap-nw-resp.yml b/playbooks/sample-sap-nw-resp.yml new file mode 100644 index 0000000..25f9b0e --- /dev/null +++ b/playbooks/sample-sap-nw-resp.yml @@ -0,0 +1,25 @@ +--- +- hosts: all + become: true + + tasks: + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "nw" + register: sap_facts_register + + - name: Run sap_monitor_nw_response module + community.sap_operations.sap_monitor_nw_response: + nw_sid: "{{ item.SID }}" + nw_instance_number: "{{ item.InstanceNumber }}" + nw_instance_type: "{{ item.InstanceType }}" + register: sap_monitor_nw_status_register + loop: "{{ sap_facts_register.sap_facts }}" + when: + - "'PAS' in item.InstanceType" + + - debug: + msg: + - "{{ sap_monitor_nw_status_register }}" + diff --git a/playbooks/sample-sap-nw-status.yml b/playbooks/sample-sap-nw-status.yml new file mode 100644 index 0000000..f878067 --- /dev/null +++ b/playbooks/sample-sap-nw-status.yml @@ -0,0 +1,23 @@ +--- +- hosts: all + become: true + + tasks: + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "nw" + register: sap_facts_register + + - name: Run sap_monitor_nw_status module + community.sap_operations.sap_monitor_nw_status: + nw_sid: "{{ item.SID }}" + nw_instance_number: "{{ item.InstanceNumber }}" + nw_instance_type: "{{ item.InstanceType }}" + register: sap_monitor_nw_status_register + loop: "{{ sap_facts_register.sap_facts }}" + + - debug: + msg: + - "{{ sap_monitor_nw_status_register }}" + diff --git a/playbooks/sample-sap-profile-update.yml b/playbooks/sample-sap-profile-update.yml new file mode 100644 index 0000000..95adb93 --- /dev/null +++ b/playbooks/sample-sap-profile-update.yml @@ -0,0 +1,37 @@ +--- +- hosts: all + become: true + +# Prompt for Ansible Variables + vars_prompt: + - name: sap_sid + prompt: Please enter target SAP System ID (SID) + private: no + + vars: + sap_update_profile_default_profile_params: + - sapgui/user_scripting = TRUE + - ssl/ciphersuites = 135:PFS:HIGH::EC_P256:EC_HIGH + - ssl/client_ciphersuites = 150:PFS:HIGH::EC_P256:EC_HIGH + sap_update_profile_instance_profile_params: + - PHYS_MEMSIZE = 32768 + - icm/server_port_0 = PROT=HTTP,PORT=80$$,PROCTIMEOUT=600,TIMEOUT=3600 + - icm/server_port_1 = PROT=HTTPS,PORT=443$$,PROCTIMEOUT=600,TIMEOUT=3600 + - icm/server_port_2 = PROT=SMTP,PORT=25$$,PROCTIMEOUT=120,TIMEOUT=120 + + + tasks: + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "{{ sap_sid }}" + register: sap_facts_register + + # SAP Profile Update + - name: SAP Profile Update + vars: + sap_update_profile_sid: "{{ item.SID }}" + sap_update_profile_instance_nr: "{{ item.InstanceNumber }}" + include_role: + name: community.sap_operations.sap_profile_update + loop: "{{ sap_facts_register.sap_facts }}" diff --git a/playbooks/sample-sap-pyrfc.yml b/playbooks/sample-sap-pyrfc.yml new file mode 100644 index 0000000..6dd4b10 --- /dev/null +++ b/playbooks/sample-sap-pyrfc.yml @@ -0,0 +1,56 @@ +--- +- hosts: all + become: true + + vars: + suser_id: 'S00000000' + suser_password: 'password' + sap_nwrfc_sdk: nwrfc750P_8-70002752.zip + reuse_connection: + ashost: s4hana.poc.cloud + sysid: TDT + sysnr: "01" + client: "400" + user: DDIC + passwd: Password1 + lang: EN +# trace: 3 +# saprouter: /H/111.22.33.44/S/3299/W/e5ngxs/H/555.66.777.888/H/ +# gwhost: gateway.poc.cloud +# ghserv: gateway.poc.cloud + + tasks: + + - name: Run SAP RFC - STFC_CONNECTION + vars: + pyrfc_first_run: yes + target_function: STFC_CONNECTION + target_parameters: + REQUTEXT: 'Hello SAP!' + target_connection: "{{ reuse_connection }}" + include_role: + name: community.sap_operations.sap_pyrfc + register: sap_rfc_output1 + + - name: DEBUG - Output of STFC_CONNECTION + debug: + msg: "{{ sap_rfc_output1 }}" + + - name: Run SAP RFC - STFC_STRUCTURE + vars: + pyrfc_first_run: no + target_function: STFC_STRUCTURE + target_parameters: + IMPORTSTRUCT: + RFCINT1: 128 + RFCTABLE: + - COLUMN0: test + target_connection: "{{ reuse_connection }}" + include_role: + name: community.sap_operations.sap_pyrfc + register: sap_rfc_output2 + + - name: DEBUG - Output of STFC_STRUCTURE + debug: + msg: "{{ sap_rfc_output2 }}" + diff --git a/plugins/.gitkeep b/plugins/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugins/inventory/.gitkeep b/plugins/inventory/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/.gitkeep b/plugins/module_utils/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/pyrfc_handler.py b/plugins/module_utils/pyrfc_handler.py new file mode 100644 index 0000000..5534479 --- /dev/null +++ b/plugins/module_utils/pyrfc_handler.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +import pyrfc + + +def get_connection(module, conn_params): + module.warn('Connecting ... %s' % conn_params['ashost']) + if "saprouter" in conn_params: + module.warn("...via SAPRouter to SAP System") + elif "gwhost" in conn_params: + module.warn("...via Gateway to SAP System") + else: + module.warn("...direct to SAP System") + + conn = pyrfc.Connection(**conn_params) + + module.warn("Verifying connection is open/alive: %s" % conn.alive) + return conn diff --git a/plugins/modules/.gitkeep b/plugins/modules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugins/modules/README.md b/plugins/modules/README.md new file mode 100644 index 0000000..7401ca3 --- /dev/null +++ b/plugins/modules/README.md @@ -0,0 +1,3 @@ +# Ansible Modules documentation + +Each Ansible Module has documentation underneath `/docs`. \ No newline at end of file diff --git a/plugins/modules/sap_facts.sh b/plugins/modules/sap_facts.sh new file mode 100755 index 0000000..ff59341 --- /dev/null +++ b/plugins/modules/sap_facts.sh @@ -0,0 +1,522 @@ +#!/bin/bash +# ===================================================================== +# +# sap_facts.sh +# Authored by - Jason Masipiquena +# - IBM - Lab for SAP Solutions +# +# Bash script designed to be used as an Ansible module +# Gathers SAP facts in a host +# - SAP HANA +# - SIDs +# - Instance Numbers (one per SID) +# - SAP NW +# - SIDs +# - Instance Numbers (can be more than one per SID) +# - Instance Types (ASCS, PAS, ERS, etc) +# Input: +# - params (all | nw | ) +# Output: +# - sap_nw_sid - list of all SAP NW SIDs - access from Ansible via .sap_nw_sid +# - sap_hana_sid - list of all SAP HANA SIDs - access from Ansible via .sap_hana_sid +# - sap_nw_nr - list of all SAP NW instance numbers - access from Ansible via .sap_nw_nr +# - sap_hana_nr - list of all SAP HANA instance numbers - access from Ansible via .sap_hana_nr +# - sap_facts - dictionary list of all the details - access from Ansible via .sap_facts +# +# ===================================================================== + +# ===================================================================== +# Functions +# ===================================================================== +# get_all_hana_sid +# get_all_nw_sid +# get_sid_type +# get_all_nw_nr +# get_hana_nr +# get_all_hana_nr +# get_instance_type +# check_sapstartsrv +# start_sapstartsrv +# check_sapcontrol +# array_to_json +# arrays_to_dictionary +# process_final_results +# return_ansible + +# Loops through /hana/shared for all SIDs and exports results to global array HANA_SID_ARRAY +function get_all_hana_sid(){ + + unset HANA_SID_ARRAY + export HANA_SID_ARRAY + + if [ -d /hana/shared ]; then + # /hana/shared directory exists + while read LINE + do + ### Check to see if /usr/sap/SID exists. If not then its probably not used anymore / its not a SID so ignore + if [ -d /usr/sap/$LINE ]; then + # $LINE is an HDB system + HANA_SID_ARRAY+=( "$LINE" ) + else + # $LINE not in /usr/sap + : + fi + done < <(ls -1 /hana/shared) + ## process substitution otherwise the variable values wont appear outside loop + else + # /hana/shared directory doesnt exist + unset HANA_SID_ARRAY + fi + + # Check if HANA SID detected + if [[ -z "${HANA_SID_ARRAY[@]}" ]]; then + # HANA_SID_ARRAY is empty, no HANA SID found + RETURN_MESSAGE="${RETURN_MESSAGE} - ""HANA SID not found" + else + # Proceed with getting instance numbers + RETURN_MESSAGE="${RETURN_MESSAGE} - ""HANA SID found" + get_all_hana_nr + fi +} + +# Loops through /sapmnt for all SIDs and exports results to global array NW_SID_ARRAY +function get_all_nw_sid(){ + + unset NW_SID_ARRAY + export NW_SID_ARRAY + + if [ -d /sapmnt ]; then + # /sapmnt directory exists + while read LINE + do + ### Check to see if /usr/sap/SID exists. If not then its probably not used anymore / its not a SID so ignore + if [ -d /usr/sap/$LINE ]; then + # "$LINE is a SAP system" + NW_SID_ARRAY+=( "$LINE" ) + else + ### Check to see if /sapmnt/SID/sap_bobj exists to see if its a bobj system + if [ -d /sapmnt/$LINE/sap_bobj ]; then + # $LINE is a bobj system + NW_SID_ARRAY+=( "$LINE" ) + else + # $LINE not a SID" + : + fi + fi + done < <(ls -1 /sapmnt) + ## process substitution otherwise the variable values wont appear outside loop + else + # /sapmnt directory doesnt exist + unset NW_SID_ARRAY + fi + + if [[ -z "${NW_SID_ARRAY[@]}" ]]; then + # NW_SID_ARRAY is empty, no NW SID found + RETURN_MESSAGE="${RETURN_MESSAGE} - ""NW SID not found" + else + # Proceed with getting instance numbers + RETURN_MESSAGE="${RETURN_MESSAGE} - ""NW SID found" + get_all_nw_nr + fi +} + +# Check SID if it's NW or HANA +function get_sid_type() { + local PARAM=$1 + + ### Check to see if /usr/sap/SID exists. If not then its probably not used anymore / its not a SID so ignore + if [ -d /hana/shared/$PARAM ]; then + # $SID is an HDB system + HANA_SID_ARRAY+=( "$PARAM" ) + RETURN_MESSAGE="- SID: $PARAM found - hana" + get_all_hana_nr + elif [ -d /sapmnt/$PARAM ]; then + NW_SID_ARRAY+=( "$PARAM" ) + RETURN_MESSAGE="- SID: $PARAM found - nw" + get_all_nw_nr + else + RETURN_MESSAGE="- SID: $PARAM not found" + return_ansible failed + fi + +} + +# Loops through NW_SID_ARRAY populated by function get_all_nw_sid and get NW instance numbers +function get_all_nw_nr(){ + + unset NW_NR_ARRAY + export NW_NR_ARRAY + + for i in "${NW_SID_ARRAY[@]}" + do + SID=$(echo $i) + SIDADM=${SID,,}adm + + while read LINE + do + + ## Get the first character and the last two characters + LASTTWO=$(echo ${LINE: -2}) + FIRST=$(echo ${LINE:0:1}) + + if [[ $LASTTWO =~ ^[0-9]+$ ]];then + NR=$LASTTWO + check_sapstartsrv $SIDADM $SID $NR + sapcontrol_test=`check_sapcontrol $SIDADM $SID $NR` + if [[ $sapcontrol_test == "fail" ]]; then + # sapcontrol not working + # invalid SAP system + # ignore this instance number + : + else + # sapcontrol is working + # valid SAP system + # add this instance number in NW_NR_ARRAY + NW_NR_ARRAY+=( "$LASTTWO" ) + fi + else + : + fi + done < <(ls -1 /usr/sap/$SID) + done + +} + +# Returns instance number of SAP HANA +function get_hana_nr(){ + # $1 - SID + local NR=$(ls -1 /usr/sap/$1 | grep HDB | sed 's/...//' | head -1) + HANA_NR_ARRAY+=( "$NR" ) +} + + +# Loops through HANA_SID_ARRAY populated by function get_all_hana_sid and get HANA instance numbers +function get_all_hana_nr(){ + + unset HANA_NR_ARRAY + export HANA_NR_ARRAY + + for i in "${HANA_SID_ARRAY[@]}" + do + local SID=$(echo $i) + local SIDADM=${SID,,}adm + local NR=$(ls -1 /usr/sap/$SID | grep HDB | sed 's/...//' | head -1) + + check_sapstartsrv $SIDADM $SID $NR + sapcontrol_test=`check_sapcontrol $SIDADM $SID $NR` + if [[ $sapcontrol_test == "fail" ]]; then + # sapcontrol not working + # invalid SAP system + # ignore this instance number + : + else + # sapcontrol is working + # valid SAP system + # add this instance number in HANA_NR_ARRAY + HANA_NR_ARRAY+=( "$NR" ) + fi + done +} + +# remove_failed_sids() { + +# for target in "${NW_SID_ARRAY_DELETE[@]}"; do +# for i in "${!NW_SID_ARRAY[@]}"; do +# if [[ ${NW_SID_ARRAY[i]} = $target ]]; then +# unset 'NW_SID_ARRAY[i]' +# fi +# done +# done + +# for target in "${HANA_SID_ARRAY_DELETE[@]}"; do +# for i in "${!HANA_SID_ARRAY[@]}"; do +# if [[ ${HANA_SID_ARRAY[i]} = $target ]]; then +# unset 'HANA_SID_ARRAY[i]' +# fi +# done +# done + +# echo ${HANA_SID_ARRAY[@]} +# echo ${HANA_SID_ARRAY[@]} + +# } + +# Returns instance type of passed instance number +get_instance_type() { + + local INS=$1 + local FIRST=$(echo ${INS:0:1}) + local NR="" + local INS_TYPE="" + + if [[ $FIRST = "D" ]]; then + # It's a PAS + INS_TYPE="PAS" + elif [[ $FIRST = "A" ]]; then + # It's an ASCS + INS_TYPE="ASCS" + elif [[ $FIRST = "W" ]]; then + # It's a Webdisp + INS_TYPE="WebDisp" + elif [[ $FIRST = "J" ]]; then + # It's a Java + INS_TYPE="Java" + elif [[ $FIRST = "S" ]]; then + # It's an SCS + INS_TYPE="SCS" + elif [[ $FIRST = "E" ]]; then + # It's an ERS + INS_TYPE="ERS" + elif [[ $FIRST = "H" ]]; then + # It's a HANA system + INS_TYPE="HANA" + else + # Unknown instance type + INS_TYPE="XXX" + fi + + echo $INS_TYPE + +} + +# Check if sapstartsrv is running +function check_sapstartsrv(){ + # $1 - SIDADM + # $2 - SID + # $3 - NR + + ## Count the number of sapstartsrv processes + SAPSTARTSRV=$(ps -ef | grep $2 | grep $3 | grep sapstartsrv | wc -l) + + if [[ $SAPSTARTSRV = 0 ]]; then + ## No sapstartsrv process running - attempt to start + start_sapstartsrv $1 $2 $3 + elif [[ $SAPSTARTSRV -gt 1 ]]; then + # Multiple sapstartsrv processes running for a given instance number + # Stop all corresponding sapstartsrv processes + for i in $SAPSTARTSRV + do + su - $1 -c "sapcontrol -nr $3 -function StopService $2" + done + # Start sapstartsrv + start_sapstartsrv $1 $2 $3 + else + # sapstartsrv is ok + : + fi +} + +# Start sapstartsrv for given SID and NR +function start_sapstartsrv(){ + # $1 - SIDADM + # $2 - SID + # $3 - NR + su - $1 -c "sapcontrol -nr $3 -function StartService $2" +} + +# Check sapcontrol on an instance number +function check_sapcontrol(){ + # $1 - SIDADM + # $2 - SID + # $3 - NR + local sapcontrol_test=`su - $1 -c "sapcontrol -nr $3 -function GetInstanceProperties"` + if [[ $sapcontrol_test == *"FAIL"* ]]; then + # sapcontrol not working + echo "fail" + else + # sapcontrol is working + echo "ok" + fi + +} + +# Convert array to json format +function array_to_json() { + echo -n '[' + while [ $# -gt 0 ]; do + x=${1//\\/\\\\} + echo -n \"${x//\"/\\\"}\" + [ $# -gt 1 ] && echo -n ', ' + shift + done + echo ']' +} + +# Convert all arrays to json dictionary format +function arrays_to_dictionary() { + i=0 + echo -n '[' + + while [ $# -gt 0 ]; do + + echo -n '{' + + x=${1//\\/\\\\} + echo -n \"InstanceNumber\": \"${x//\"/\\\"}\" + echo -n ', ' + echo -n \"InstanceType\": \"${DICT_ALL_INS_TYPE_ARRAY[i]//\"/\\\"}\" + echo -n ', ' + echo -n \"SID\": \"${DICT_ALL_SID_ARRAY[i]//\"/\\\"}\" + echo -n ', ' + echo -n \"Type\": \"${DICT_ALL_TYPE_ARRAY[i]//\"/\\\"}\" + echo -n '}' + + [ $# -gt 1 ] && echo -n ', ' + + shift + + i=$((i+1)) + done + + echo ']' + +} + +# Process results for Ansible +function process_final_results(){ + + # Declare dictionary variables + unset DICT_ALL_NR_ARRAY + unset DICT_ALL_TYPE_ARRAY + unset DICT_ALL_INS_TYPE_ARRAY + unset DICT_ALL_SID_ARRAY + unset DICT_ALL_NW_SID_ARRAY + unset DICT_ALL_HANA_SID_ARRAY + export DICT_ALL_NR_ARRAY + export DICT_ALL_TYPE_ARRAY + export DICT_ALL_INS_TYPE_ARRAY + export DICT_ALL_SID_ARRAY + export DICT_ALL_NW_SID_ARRAY + export DICT_ALL_HANA_SID_ARRAY + + # Check NW and HANA arrays if empty + if [[ -z "${HANA_NR_ARRAY[@]}" ]]; then + RETURN_MESSAGE="${RETURN_MESSAGE} - ""No valid HANA Systems found" + else + : + fi + + if [[ -z "${NW_NR_ARRAY[@]}" ]]; then + RETURN_MESSAGE="${RETURN_MESSAGE} - ""No valid NW Systems found" + else + : + fi + + if [[ -z "${NW_NR_ARRAY[@]}" ]] && [[ -z "${HANA_NR_ARRAY[@]}" ]]; then + RETURN_MESSAGE="${RETURN_MESSAGE} - ""No valid Systems found" + else + : + fi + + # Append all NW NR to final NR dictionary array + for index in "${!NW_NR_ARRAY[@]}" + do + DICT_ALL_NR_ARRAY+=( "${NW_NR_ARRAY[index]}" ) + DICT_ALL_TYPE_ARRAY+=( "nw" ) + done + + # Append all HANA NR to final NR dictionary array + for index in "${!HANA_NR_ARRAY[@]}" + do + DICT_ALL_NR_ARRAY+=( "${HANA_NR_ARRAY[index]}" ) + DICT_ALL_TYPE_ARRAY+=( "hana" ) + done + + # Get instance information of all instance numbers + for index in "${!DICT_ALL_NR_ARRAY[@]}" + do + + local NR=$(echo ${DICT_ALL_NR_ARRAY[index]}) + local SID=$(/usr/sap/hostctrl/exe/sapcontrol -nr $NR -function GetInstanceProperties | grep SAPSYSTEMNAME | awk '{ print $3 }') + local INS=$(/usr/sap/hostctrl/exe/sapcontrol -nr $NR -function GetInstanceProperties | grep INSTANCE_NAME | awk '{ print $3 }') + + # Get instance type + local INS_TYPE=`get_instance_type $INS` + + DICT_ALL_INS_TYPE_ARRAY+=( "${INS_TYPE}" ) + + if [[ ${INS_TYPE} == "HANA" ]]; then + # Append all HANA SID + DICT_ALL_HANA_SID_ARRAY+=( "${SID}" ) + else + # Append all NW SID + DICT_ALL_NW_SID_ARRAY+=( "${SID}" ) + fi + + # Append all SID + DICT_ALL_SID_ARRAY+=( "${SID}" ) + + done + + # Trim SID Arrays to get only unique SIDs + IFS=" " read -r -a DICT_ALL_NW_SID_ARRAY <<< "$(tr ' ' '\n' <<< "${DICT_ALL_NW_SID_ARRAY[@]}" | sort -u | tr '\n' ' ')" + IFS=" " read -r -a DICT_ALL_HANA_SID_ARRAY <<< "$(tr ' ' '\n' <<< "${DICT_ALL_HANA_SID_ARRAY[@]}" | sort -u | tr '\n' ' ')" + + # Process lists for all SIDs + DICT_ALL_NW_SID_JSON=`array_to_json "${DICT_ALL_NW_SID_ARRAY[@]}"` + DICT_ALL_HANA_SID_JSON=`array_to_json "${DICT_ALL_HANA_SID_ARRAY[@]}"` + + # Process lists for all NRs + DICT_ALL_NW_NR_JSON=`array_to_json "${NW_NR_ARRAY[@]}"` + DICT_ALL_HANA_NR_JSON=`array_to_json "${HANA_NR_ARRAY[@]}"` + + # Process dictionaries for sap_facts + DICT_ALL_NR_JSON=`array_to_json "${DICT_ALL_NR_ARRAY[@]}"` + DICT_ALL_SID_JSON=`array_to_json "${DICT_ALL_SID_ARRAY[@]}"` + DICT_ALL_INS_TYPE_JSON=`array_to_json "${DICT_ALL_INS_TYPE_ARRAY[@]}"` + DICT_ALL_TYPE_JSON=`array_to_json "${DICT_ALL_TYPE_ARRAY[@]}"` + + # Process all arrays for final json dictionary + SAP_FACTS_DICTIONARY=`arrays_to_dictionary ${DICT_ALL_NR_ARRAY[@]}` + + # Return values for Ansible + return_ansible success + +} + +# Return values for Ansible +function return_ansible(){ + + local result=$1 + + if [ $result = "success" ]; then + printf '{"changed": %s, "failed": %s, "msg": "%s", "sap_nw_sid": %s, "sap_hana_sid": %s, "sap_nw_nr": %s, "sap_hana_nr": %s, "sap_facts": %s}' \ + false false "SAP Information Gathering Successful $RETURN_MESSAGE" "$DICT_ALL_NW_SID_JSON" "$DICT_ALL_HANA_SID_JSON" "$DICT_ALL_NW_NR_JSON" "$DICT_ALL_HANA_NR_JSON" "$SAP_FACTS_DICTIONARY" + else + printf '{"changed": %s, "failed": %s, "msg": "%s", "sap_nw_sid": %s, "sap_hana_sid": %s, "sap_nw_nr": %s, "sap_hana_nr": %s, "sap_facts": %s}' \ + false true "SAP Information Gathering Failed $RETURN_MESSAGE" "$DICT_ALL_NW_SID_JSON" "$DICT_ALL_HANA_SID_JSON" "$DICT_ALL_NW_NR_JSON" "$DICT_ALL_HANA_NR_JSON" "$SAP_FACTS_DICTIONARY" + fi + + exit + +} + +# ===================================================================== +# Main +# ===================================================================== +main () { + + # For blank input, default param="all" + if [ -z "$param" ]; then + param="all" + fi + + if [ $param = "all" ]; then + get_all_hana_sid + get_all_nw_sid + elif [ $param = "hana" ]; then + get_all_hana_sid + elif [ $param = "nw" ]; then + get_all_nw_sid + else + # It must be a SID + get_sid_type $param + fi + + process_final_results + +} + +# For Ansible module, source $1 will take all the input parameters +source $1 +main diff --git a/plugins/modules/sap_monitor_hana_status.sh b/plugins/modules/sap_monitor_hana_status.sh new file mode 100644 index 0000000..6ced1cb --- /dev/null +++ b/plugins/modules/sap_monitor_hana_status.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# ===================================================================== +# +# sap_monitor_hana_status.sh +# Authored by - Jason Masipiquena +# - IBM - Lab for SAP Solutions +# +# Bash script designed to be used as an Ansible module +# Check status of SAP HANA system +# Input: +# - hana_sid +# - hana_instance_number +# Output: +# - GREEN YELLOW RED GRAY (based on the status from sapcontrol) +# - Access the result from Ansible via .sap_status +# +# ===================================================================== + +# ===================================================================== +# Functions +# ===================================================================== +# check_is_hdb_up +# return_ansible + +# Checks if SAP HANA system is up +function check_is_hdb_up(){ + # $1 - SIDADM + # $2 - SID + # $3 - NR + local STATUS=$(su - $1 -c "sapcontrol -nr $3 -function GetSystemInstanceList | grep HDB") + STATUS=$(echo $STATUS | awk '{ print $7 }') + echo $STATUS +} + + +# Return values for Ansible +function return_ansible(){ + + local result=$1 + + if [ $result = "success" ]; then + printf '{"changed": %s, "failed": %s, "msg": "%s", "sap_status": "%s"}' \ + false false "SAP Check $RETURN_MESSAGE" "$STATUS" + else + printf '{"changed": %s, "failed": %s, "msg": "%s", "sap_status": "%s"}' \ + false true "SAP Check Failed" "$STATUS" + fi + + exit + +} + + +# ===================================================================== +# Main +# ===================================================================== +main () { + + export RETURN_MESSAGE="Successful" + + # These values are from Ansible + export SID=$(echo ${hana_sid}) + export NR=$(echo ${hana_instance_number}) + + export SIDADM=$(echo ${SID,,}adm) + export HOSTNAME=$(uname -n) + + export STATUS=$(check_is_hdb_up $SIDADM $SID $NR) + + return_ansible success + +} + +# For Ansible module, source $1 will take all the input parameters +source $1 +main diff --git a/plugins/modules/sap_monitor_nw_perf.sh b/plugins/modules/sap_monitor_nw_perf.sh new file mode 100644 index 0000000..1e6696e --- /dev/null +++ b/plugins/modules/sap_monitor_nw_perf.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# ===================================================================== +# +# sap_monitor_nw_perf.sh +# Authored by - Jason Masipiquena +# - IBM - Lab for SAP Solutions +# +# Bash script designed to be used as an Ansible module +# Checks SAP NW (PAS) performance metrics +# Input: +# - nw_sid +# - nw_instance_number +# - nw_instance_type +# Output: +# - heap_memory - Heap memory - access from Ansible via .heap_memory +# - extended_memory - Extended memory - access from Ansible via .extended_memory +# - cpu_util - CPU utilization - access from Ansible via .cpu_util +# - program_swap - Program buffer swap - access from Ansible via .program_swap +# +# ===================================================================== + +# ===================================================================== +# Functions +# ===================================================================== +# get_heap_mem +# get_ext_mem +# get_cpu_util +# get_program_buffer_swap +# result_to_dictionary +# return_ansible + +# Returns SAP NW (PAS) heap memory +function get_heap_mem(){ + # $1 - NR + local HEAPMEM=$(/usr/sap/hostctrl/exe/sapcontrol -nr $1 -function GetAlertTree | grep HeapAct | head -1 | awk '{ print $4 }') + echo $HEAPMEM +} + +# Returns SAP NW (PAS) extended memory +function get_ext_mem(){ + # $1 - NR + local EXTMEM=$(/usr/sap/hostctrl/exe/sapcontrol -nr $1 -function GetAlertTree | grep EsAct | head -1 | awk '{ print $4 }') + echo $EXTMEM +} + +# Returns SAP NW (PAS) CPU utilization +function get_cpu_util(){ + # $1 - NR + local CPUUTIL=$(/usr/sap/hostctrl/exe/sapcontrol -nr $1 -function GetAlertTree | grep CPU_Utilization | head -1 | awk '{ print $4 }') + echo $CPUUTIL +} + +# Returns SAP NW (PAS) program buffer swap +function get_program_buffer_swap(){ + # $1 - NR + local PROGRAMSWAP=$(/usr/sap/hostctrl/exe/sapcontrol -nr $1 -function GetAlertTree | grep Program | grep Swap | head -1 | awk '{ print $4 }') + echo $PROGRAMSWAP +} + +# Process results to json format +function result_to_dictionary() { + + echo -n '{' + + echo -n \"HeapMemory\": \"${HEAPMEM//\"/\\\"}\" + echo -n ', ' + echo -n \"ExtendedMemory\": \"${EXTMEM//\"/\\\"}\" + echo -n ', ' + echo -n \"CpuUtil\": \"${CPUUTIL//\"/\\\"}\" + echo -n ', ' + echo -n \"ProgramSwap\": \"${PROGRAMSWAP//\"/\\\"}\" + + echo -n '}' + +} + +# Return values for Ansible +function return_ansible(){ + + local result=$1 + + if [ $result = "success" ]; then + printf '{"changed": %s, "failed": %s, "msg": "%s", "heap_memory": "%s", "extended_memory": "%s", "cpu_util": "%s", "program_swap": "%s"}' \ + false false "SAP Check $RETURN_MESSAGE" "$HEAPMEM" "$EXTMEM" "$CPUUTIL" "$PROGRAMSWAP" + else + printf '{"changed": %s, "failed": %s, "msg": "%s", "heap_memory": "%s", "extended_memory": "%s", "cpu_util": "%s", "program_swap": "%s"}' \ + false true "SAP Check Failed" "$HEAPMEM" "$EXTMEM" "$CPUUTIL" "$PROGRAMSWAP" + fi + + exit + +} + + +# ===================================================================== +# Main +# ===================================================================== +main () { + + export RETURN_MESSAGE="Successful" + + # These values are from Ansible + export SID=$(echo ${nw_sid}) + export NR=$(echo ${nw_instance_number}) + export TYPE=$(echo ${nw_instance_type}) + + export SIDADM=$(echo ${SID,,}adm) + export HOSTNAME=$(uname -n) + + export CPUUTIL=$(get_cpu_util $NR) + export HEAPMEM=$(get_heap_mem $NR) + export EXTMEM=$(get_ext_mem $NR) + export PROGRAMSWAP=$(get_program_buffer_swap $NR) + + RESULTS_DICTIONARY=`result_to_dictionary` + return_ansible success + +} + +# For Ansible module, source $1 will take all the input parameters +source $1 +main diff --git a/plugins/modules/sap_monitor_nw_response.sh b/plugins/modules/sap_monitor_nw_response.sh new file mode 100644 index 0000000..1552623 --- /dev/null +++ b/plugins/modules/sap_monitor_nw_response.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# ===================================================================== +# +# sap_monitor_nw_response.sh +# Authored by - Jason Masipiquena +# - IBM - Lab for SAP Solutions +# +# Bash script designed to be used as an Ansible module +# Checks SAP NW (PAS) response times +# Input: +# - nw_sid +# - nw_instance_number +# - nw_instance_type +# Output: +# - dialog_response_time - Dialog response time - access from Ansible via .dialog_response_time +# - database_response_time - Database response time - access from Ansible via .database_response_time +# - frontend_response_time - Front end response time - access from Ansible via .frontend_response_time +# - number_users - Current number of users logged in - access from Ansible via .number_users +# +# ===================================================================== + +# ===================================================================== +# Functions +# ===================================================================== +# get_dia_resp +# get_db_resp +# get_front_resp +# get_num_users +# result_to_dictionary +# return_ansible + +# Returns SAP NW (PAS) dialog response time +function get_dia_resp(){ + # $1 - NR + local DIARESP=$(/usr/sap/hostctrl/exe/sapcontrol -nr $1 -function GetAlertTree | grep ResponseTimeDialog | head -1 | awk '{ print $4 }') + echo $DIARESP +} + +# Returns SAP NW (PAS) database response time +function get_db_resp(){ + # $1 - NR + local DBRESP=$(/usr/sap/hostctrl/exe/sapcontrol -nr $1 -function GetAlertTree | grep DBRequestTime | head -1 | awk '{ print $4 }') + echo $DBRESP +} + +# Returns SAP NW (PAS) front end response time +function get_front_resp(){ + # $1 - NR + local FRONTRESP=$(/usr/sap/hostctrl/exe/sapcontrol -nr $1 -function GetAlertTree | grep FrontendResponseTime | head -1 | awk '{ print $4 }') + echo $FRONTRESP +} + +# Returns SAP NW (PAS) current number of logged in users +function get_num_users(){ + # $1 - NR + local NUMUSERS=$(/usr/sap/hostctrl/exe/sapcontrol -nr $1 -function GetAlertTree | grep UsersLoggedIn | head -1 | awk '{ print $4 }') + echo $NUMUSERS +} + +# Process results to json format +function result_to_dictionary() { + + echo -n '{' + + echo -n \"DialogResponseTime\": \"${DIARESP//\"/\\\"}\" + echo -n ', ' + echo -n \"DatabaseResponseTime\": \"${DBRESP//\"/\\\"}\" + echo -n ', ' + echo -n \"FrontEndResponseTime\": \"${FRONTRESP//\"/\\\"}\" + echo -n ', ' + echo -n \"NumberUsers\": \"${NUMUSERS//\"/\\\"}\" + + echo -n '}' + +} + +# Return values for Ansible +function return_ansible(){ + + local result=$1 + + if [ $result = "success" ]; then + printf '{"changed": %s, "failed": %s, "msg": "%s", "dialog_response_time": "%s", "database_response_time": "%s", "frontend_response_time": "%s", "number_users": "%s"}' \ + false false "SAP Check $RETURN_MESSAGE" "$DIARESP" "$DBRESP" "$FRONTRESP" "$NUMUSERS" + else + printf '{"changed": %s, "failed": %s, "msg": "%s", "dialog_response_time": "%s", "database_response_time": "%s", "frontend_response_time": "%s", "number_users": "%s"}' \ + false true "SAP Check Failed" "$DIARESP" "$DBRESP" "$FRONTRESP" "$NUMUSERS" + fi + + exit + +} + + +# ===================================================================== +# Main +# ===================================================================== +main () { + + export RETURN_MESSAGE="Successful" + + # These values are from Ansible + export SID=$(echo ${nw_sid}) + export NR=$(echo ${nw_instance_number}) + export TYPE=$(echo ${nw_instance_type}) + + export SIDADM=$(echo ${SID,,}adm) + export HOSTNAME=$(uname -n) + + export DIARESP=$(get_dia_resp $NR) + export DBRESP=$(get_db_resp $NR) + export FRONTRESP=$(get_front_resp $NR) + export NUMUSERS=$(get_num_users $NR) + + RESULTS_DICTIONARY=`result_to_dictionary` + return_ansible success + +} + +# For Ansible module, source $1 will take all the input parameters +source $1 +main diff --git a/plugins/modules/sap_monitor_nw_status.sh b/plugins/modules/sap_monitor_nw_status.sh new file mode 100644 index 0000000..888d85f --- /dev/null +++ b/plugins/modules/sap_monitor_nw_status.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# ===================================================================== +# +# sap_monitor_nw_status.sh +# Authored by - Jason Masipiquena +# - IBM - Lab for SAP Solutions +# +# Bash script designed to be used as an Ansible module +# Check status of SAP NW system +# Input: +# - nw_sid +# - nw_instance_number +# - nw_instance_type +# Output: +# - GREEN YELLOW RED GRAY (based on the status from sapcontrol) +# - Access the result from Ansible via .sap_status +# +# ===================================================================== + +# ===================================================================== +# Functions +# ===================================================================== +# check_is_nw_up +# return_ansible + +# Checks if SAP NW system is up +function check_is_nw_up(){ + # $1 - SIDADM + # $2 - NR + # $3 - Type of instance + INS_TYPE=$3 + INS_GREP="" + + if [[ $INS_TYPE = "PAS" ]]; then + INS_GREP="ABAP" + elif [[ $INS_TYPE = "ASCS" ]]; then + INS_GREP="MESSAGE" + elif [[ $INS_TYPE = "WebDisp" ]]; then + INS_GREP="WEBDISP" + elif [[ $INS_TYPE = "Java" ]]; then + INS_GREP="J2EE" + elif [[ $INS_TYPE = "SCS" ]]; then + INS_GREP="MESSAGE" + elif [[ $INS_TYPE = "ERS" ]]; then + INS_GREP="ENQUE" + else + INS_GREP="XXX" + fi + + local STATUS=$(su - $1 -c "sapcontrol -nr $2 -function GetSystemInstanceList | grep $INS_GREP") + STATUS=$(echo $STATUS | awk '{ print $7 }') + echo $STATUS +} + +# Return values for Ansible +function return_ansible(){ + + local result=$1 + + if [ $result = "success" ]; then + printf '{"changed": %s, "failed": %s, "msg": "%s", "sap_status": "%s"}' \ + false false "SAP Check $RETURN_MESSAGE" "$STATUS" + else + printf '{"changed": %s, "failed": %s, "msg": "%s", "sap_status": "%s"}' \ + false true "SAP Check Failed" "$STATUS" + fi + + exit + +} + + +# ===================================================================== +# Main +# ===================================================================== +main () { + + export RETURN_MESSAGE="Successful" + + # These values are from Ansible + export SID=$(echo ${nw_sid}) + export NR=$(echo ${nw_instance_number}) + export TYPE=$(echo ${nw_instance_type}) + + export SIDADM=$(echo ${SID,,}adm) + export HOSTNAME=$(uname -n) + + export STATUS=$(check_is_nw_up $SIDADM $NR $TYPE) + + return_ansible success + +} + +# For Ansible module, source $1 will take all the input parameters +source $1 +main diff --git a/plugins/modules/sap_pyrfc.py b/plugins/modules/sap_pyrfc.py new file mode 100644 index 0000000..d2d6a8d --- /dev/null +++ b/plugins/modules/sap_pyrfc.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +from __future__ import absolute_import, division, print_function + +from ansible.module_utils.basic import AnsibleModule +from pyrfc import (ABAPApplicationError, ABAPRuntimeError, CommunicationError, + Connection, LogonError) + +from ..module_utils.pyrfc_handler import get_connection + +__metaclass__ = type + +EXAMPLES = ''' +sap_pyrfc: + function: STFC_CONNECTION + parameters: + REQUTEXT: "Hello SAP!" + connection: + ashost: s4hana.poc.cloud + sysid: TDT + sysnr: "01" + client: "400" + user: DDIC + passwd: Password1 + lang: EN +''' + + +def main(): + argument_spec = dict(function=dict(required=True, type='str'), + parameters=dict(required=True, type='dict'), + connection=dict(required=True, type='dict'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + function = module.params.get('function') + func_params = module.params.get('parameters') + conn_params = module.params.get('connection') + + # Validate connection fields + required_conn_fields = ['ashost', 'sysnr', 'client', 'user', 'passwd'] + missing = [f for f in required_conn_fields if f not in conn_params] + if missing: + msg = 'Missing required login fields: %s' % ', '.join(missing) + module.fail_json(msg=msg) + + # Check mode + if module.check_mode: + msg = "function: %s; params: %s; login: %s" % (function, func_params, conn_params) + module.exit_json(msg=msg, changed=True) + + try: + conn = get_connection(module, conn_params) + result = conn.call(function, **func_params) + module.exit_json(changed=True, result=result) + except CommunicationError as e: + msg = "Could not connect to server: %s" % e.message + module.exit_json(failed=True, msg=msg) + except LogonError as e: + msg = "Could not log in: %s" % e.message + module.exit_json(failed=True, msg=msg) + except (ABAPApplicationError, ABAPRuntimeError) as e: + msg = "ABAP error occurred: %s" % e.message + module.exit_json(failed=True, msg=msg) + + module.exit_json(failed=False) + + +if __name__ == '__main__': + main() diff --git a/roles/os_ansible_user/README.md b/roles/os_ansible_user/README.md new file mode 100644 index 0000000..82cec4e --- /dev/null +++ b/roles/os_ansible_user/README.md @@ -0,0 +1,56 @@ +# os_ansible_user Ansible Role + +Ansible role for creating an ansible user for your managed systems + +## Prerequisites + +- Create your Ansible user in your Ansible command host +- Generate a key via `ssh-keygen` +- > **_Note:_** Highly recommended that you do this manually and not be part of the automation for security reasons +- A userid that has sudo privileges (or direct root) to create the Ansible user + - Provide the user in your `vars` + ```yaml + ansible_user: admin_user + ansible_password: your_password + ansible_sudo_pass: your_password + ``` + +## Overview + +### Variables + +| **Variable** | **Info** | **Default** | **Required** | +| :--- | :--- | :--- | :--- | +| os_ansible_user_userid | Ansible user to be created | | yes | +| os_ansible_user_password | Password of the Ansible user to be created | | yes | +| os_ansible_user_uid | Ansible user Unix user id | | yes | +| os_ansible_user_gid | Ansible user Unix group id | | yes | +| os_ansible_user_keyfile | Key filename found in `~./ssh/` | "id_ecdsa.pub" | yes | +| os_ansible_user_force_recreate | Forcefully recreate user by deleting existing user first | "yes" | no | + +### Input and Execution + +- Sample execution: + + ```bash + ansible-playbook --connection=local --limit localhost -i "localhost," os-create-ansible-user.yml" + ``` + +- Sample playbook + + ```yaml + --- + - hosts: all + become: true + vars: + ansible_user: admin_user + ansible_password: your_password + ansible_sudo_pass: your_password + os_ansible_user_userid: ansadm + os_ansible_user_password: 'my_password' + os_ansible_user_uid: 1010 + os_ansible_user_gid: 1010 + os_ansible_user_keyfile: id_ecdsa.pub + roles: + - { role: community.sap_operations.os_ansible_user } + ``` diff --git a/roles/os_ansible_user/defaults/main.yml b/roles/os_ansible_user/defaults/main.yml new file mode 100644 index 0000000..9468464 --- /dev/null +++ b/roles/os_ansible_user/defaults/main.yml @@ -0,0 +1,7 @@ +os_ansible_user_userid: ansadm +os_ansible_user_password: "" +os_ansible_user_uid: "" +os_ansible_user_gid: "" +os_ansible_user_keyfile: id_ecdsa.pub + +os_ansible_user_force_recreate: yes \ No newline at end of file diff --git a/roles/os_ansible_user/tasks/main.yml b/roles/os_ansible_user/tasks/main.yml new file mode 100644 index 0000000..800daba --- /dev/null +++ b/roles/os_ansible_user/tasks/main.yml @@ -0,0 +1,47 @@ +- name: Remove existing {{ os_ansible_user_userid }} + block: + - name: Kill all processes under {{ os_ansible_user_userid }} + ignore_errors: yes + shell: | + killall -u {{ os_ansible_user_userid }} + + - name: Remove {{ os_ansible_user_userid }} + user: + name: '{{ os_ansible_user_userid }}' + state: absent + remove: yes + force: yes + + - name: Remove {{ os_ansible_user_userid }} group + group: + name: '{{ os_ansible_user_userid }}' + state: absent + when: os_ansible_user_force_recreate + +- name: Create {{ os_ansible_user_userid }} group + group: + name: '{{ os_ansible_user_userid }}' + gid: '{{ os_ansible_user_gid }}' + state: present + +- name: Create {{ os_ansible_user_userid }} + user: + name: '{{ os_ansible_user_userid }}' + comment: "Ansible User" + uid: '{{ os_ansible_user_uid }}' + group: '{{ os_ansible_user_userid }}' + groups: "{{ os_ansible_user_userid }},wheel" + +- name: Add to sudoers NOPASSWD + shell: | + echo '{{ os_ansible_user_password }}' | passwd --stdin {{ os_ansible_user_userid }} + echo -e '{{ os_ansible_user_userid }}\tALL=(ALL)\tNOPASSWD: ALL' > /etc/sudoers.d/{{ os_ansible_user_userid }} + +- name: Add key + authorized_key: + user: '{{ os_ansible_user_userid }}' + state: present + manage_dir: yes + key: "{{ lookup('file', item) }}" + with_fileglob: + - /home/{{ os_ansible_user_userid }}/.ssh/{{ os_ansible_user_keyfile }} diff --git a/roles/os_etchosts/README.md b/roles/os_etchosts/README.md new file mode 100644 index 0000000..51c5260 --- /dev/null +++ b/roles/os_etchosts/README.md @@ -0,0 +1,48 @@ +# os_etchosts Ansible Role + +Ansible role for updating /etc/hosts + +## Overview + +### Variables + +| **Variable** | **Info** | **Default** | **Required** | +| :--- | :--- | :--- | :--- | +| os_etchosts_entries | List of ip addresses and hostnames (please see sample) | | yes | +| os_etchosts_fqdn | Fully qualified domain name | | yes | +| os_etchosts_delimiter | Delimiter between the hosts entries | "\t" | no | + +### Input and Execution + +- Sample execution: + + ```bash + ansible-playbook --connection=local --limit localhost -i "localhost," sap-etchosts-update.yml" + ``` + +- Sample playbook + + ```yaml + --- + - hosts: all + become: true + vars: + sap_os_tools_etchosts_entries: + - "10.0.0.1 hana01-lb" + - "10.0.0.2 hana02-lb" + - "10.0.1.1 s4hana01-ci" + - "10.0.1.2 s4hana01-app" + sap_os_tools_etchosts_fqdn: "poc.cloud" + roles: + - { role: community.sap_operations.os_etchosts } + ``` + +- Sample result + + ```console + cat /etc/hosts + 10.0.0.1 hana01-lb.poc.cloud hana01-lb + 10.0.0.2 hana02-lb.poc.cloud hana02-lb + 10.0.1.1 s4hana01-ci.poc.cloud s4hana01-ci + 10.0.1.2 s4hana01-app.poc.cloud s4hana01-app + ``` diff --git a/roles/os_etchosts/defaults/main.yml b/roles/os_etchosts/defaults/main.yml new file mode 100644 index 0000000..01774bb --- /dev/null +++ b/roles/os_etchosts/defaults/main.yml @@ -0,0 +1,3 @@ +os_etchosts_delimiter: "\t" +os_etchosts_fqdn: "" +os_etchosts_entries: [] diff --git a/roles/os_etchosts/tasks/main.yml b/roles/os_etchosts/tasks/main.yml new file mode 100644 index 0000000..13b7542 --- /dev/null +++ b/roles/os_etchosts/tasks/main.yml @@ -0,0 +1,7 @@ +# SAP OS Tools - /etc/hosts + +- name: SAP OS Tools - /etc/hosts + include_tasks: update_etchosts.yml + loop: "{{ os_etchosts_entries }}" + loop_control: + loop_var: passed_entry diff --git a/roles/os_etchosts/tasks/update_etchosts.yml b/roles/os_etchosts/tasks/update_etchosts.yml new file mode 100644 index 0000000..357837a --- /dev/null +++ b/roles/os_etchosts/tasks/update_etchosts.yml @@ -0,0 +1,16 @@ +- name: SAP OS Tools - /etc/hosts - Split passed entry + set_fact: + passed_ip: "{{ passed_entry.split()[0] }}" + passed_host: "{{ passed_entry.split()[1] }}" + +- name: SAP OS Tools - /etc/hosts - Deduplicate values from /etc/hosts + lineinfile: + path: /etc/hosts + create: false + regexp: (?i)^\s*{{ passed_ip }}\s+ + state: absent + +- name: SAP OS Tools - /etc/hosts - Update + lineinfile: + path: /etc/hosts + line: "{{ passed_ip }}{{ os_etchosts_delimiter }}{{ passed_host }}.{{ os_etchosts_fqdn }}{{ os_etchosts_delimiter }}{{ passed_host }}" diff --git a/roles/os_knownhosts/README.md b/roles/os_knownhosts/README.md new file mode 100644 index 0000000..8fcb788 --- /dev/null +++ b/roles/os_knownhosts/README.md @@ -0,0 +1,29 @@ +# os_knownhosts Ansible Role + +Ansible role for updating known hosts file `/.ssh/known_hosts`. This is usually used on the Ansible control / central node. + +## Overview + + +### Input and Execution + +Just execute the role, no need to set vafriable inputs. Put the target host(s) in the inventory `-i` argument + +- Sample execution: + + ```bash + ansible-playbook -i "host_you_want_to_update," sap-knownhosts-update.yml" + ``` + +- Sample playbook + + ```yaml + --- + + - name: Store known hosts of 'all' the hosts in the inventory file + hosts: localhost + connection: local + roles: + - { role: community.sap_operations.os_knownhosts } + + ``` diff --git a/roles/os_knownhosts/defaults/main.yml b/roles/os_knownhosts/defaults/main.yml new file mode 100644 index 0000000..bf5ac98 --- /dev/null +++ b/roles/os_knownhosts/defaults/main.yml @@ -0,0 +1,3 @@ +ssh_known_hosts_command: "ssh-keyscan -T 10" +ssh_known_hosts_file: "{{ lookup('env','HOME') + '/.ssh/known_hosts' }}" +ssh_known_hosts: "{{ groups['all'] }}" \ No newline at end of file diff --git a/roles/os_knownhosts/tasks/main.yml b/roles/os_knownhosts/tasks/main.yml new file mode 100644 index 0000000..295c2d9 --- /dev/null +++ b/roles/os_knownhosts/tasks/main.yml @@ -0,0 +1,15 @@ +# Update known hosts file - $HOME/.ssh/known_hosts +--- + +- name: Scan ssh public key for each host + shell: "ssh-keyscan {{ item }},`dig +short {{ item }}`" + with_items: "{{ ssh_known_hosts }}" + register: ssh_known_host_results + ignore_errors: true + +- name: Add/update public key in the '{{ ssh_known_hosts_file }}' + known_hosts: + name: "{{ item.item }}" + key: "{{ item.stdout }}" + path: "{{ ssh_known_hosts_file }}" + with_items: "{{ ssh_known_host_results.results }}" diff --git a/roles/sap_control/README.md b/roles/sap_control/README.md new file mode 100644 index 0000000..0409cef --- /dev/null +++ b/roles/sap_control/README.md @@ -0,0 +1,71 @@ +# sap_control Ansible Role + +This Ansible Role executes basic SAP administration tasks on Linux operating systems. + +## Ansible Role Overview + +This Ansible Role executes basic SAP administration tasks on Linux operating systems, including: +- Start/Stop/Restart of SAP HANA Database Server +- Start/Stop/Restart of SAP NetWeaver Application Server +- Multiple Automatic discovery and Start/Stop/Restart of SAP HANA Database Server or SAP NetWeaver Application Server + +## Example execution + +### Example Playbook + +```yaml +- hosts: sap_servers + roles: + - { role: community.sap_operations.sap_control } +``` + +### Example Inputs + +- Using restart all + ```yaml + sap_control_function: "restart_all_sap" + ``` +- Using a specific SAP SID + ```yaml + sap_control_function: "stop_sap_hana" + sap_sid: "HDB" + ``` + +## Ansible Role Requirements and Dependencies + +### Operating System + +This role has been tested with RHEL, and is designed for Linux operating systems. + +This role has not been tested and amended for SAP NetWeaver Application Server instantiations on IBM AIX or Windows Server. + +Assumptions for executing this role include: +- Instances of either SAP HANA Database Server or SAP NetWeaver Application Server are installed to the target host +- Relevent OS Packages for SAP are installed +- Registered OS License and OS Package repositories are available (from the relevant content delivery network of the OS vendor) + +### SAP software instances + +This role has been tested with SAP NetWeaver Application Server 7.53 and SAP HANA Database Server 2.0 SPS04 rev 20. + +This role has not been tested with other versions of SAP NetWeaver Application Server, however it should work for all versions above SAP NetWeaver Application Server 7.50. + +This role has not been tested with other versions of SAP HANA Database Server, however it should work for all versions above SAP HANA 2.0 SPS00. + +Assumptions for executing this role include: +- Installations used default `/usr/sap` and `/sapmnt` directories +- Installations for SAP HANA used default `/hana/shared` directory + +## Ansible Role Variables + +| **variable** | **info** | **required** | +| :--- |:--- | :--- | +| `SID` | SAP system SID | no, only if you are targetting a single SAP system| +| `nowait` | Default: `false` | no, use only when absolutely sure! This will bypass all waiting and ignore all necessary steps for a graceful stop / start| +| `sap_control_function` | Function to execute:
  • `restart_all_sap`
  • `restart_all_nw`
  • `restart_all_hana`
  • `restart_sap_nw`
  • `restart_sap_hana`
  • `stop_all_sap`
  • `start_all_sap`
  • `stop_all_nw`
  • `start_all_nw`
  • `stop_all_hana`
  • `start_all_hana`
  • `stop_sap_nw`
  • `start_sap_nw`
  • `stop_sap_hana`
  • `start_sap_hana`
| yes, only this is required to detect the Instance Number which is used with SAP Host Agent `sapcontrol` CLI


_Note: Executions using `all` will automatically detect any System IDs and corresponding Instance Numbers_ | + +## Ansible Role workflow and structure + +The following diagram demonstrates the Ansible Role workflow, where the variable `sap_control_function` is set with different values (such as **"restart_all"**) and will perform different sequence of SAP Host Agent `sapcontrol` CLI functions. + +![sap_control](/docs/diagrams/workflow_role_sap_control.svg) diff --git a/roles/sap_control/defaults/main.yml b/roles/sap_control/defaults/main.yml new file mode 100644 index 0000000..55cd1fb --- /dev/null +++ b/roles/sap_control/defaults/main.yml @@ -0,0 +1,85 @@ +--- +sap_sid: "initial" +sap_control_function: "initial" +sap_control_name_header: "initial" + +nowait: false +sap_control_start: "StartWait 180 2" +sap_control_stop: "StopWait 180 2" + +# get_all_sap_sid_dir_nw: "/sapmnt" +# get_all_sap_sid_dir_hana: "/hana/shared" + +# Functions + +sap_control_functions_list: + - restart_all_sap + - stop_all_sap + - start_all_sap + - restart_all_nw + - restart_all_hana + - stop_all_nw + - start_all_nw + - stop_all_hana + - start_all_hana + - restart_sap_nw + - restart_sap_hana + - stop_sap_nw + - start_sap_nw + - stop_sap_hana + - start_sap_hana + +# Functions flow +restart_all_sap_list: + - sap_control_function_current: "nw_stop" + - sap_control_function_current: "hana_stop" + - sap_control_function_current: "hana_start" + - sap_control_function_current: "nw_start" + +stop_all_sap_list: + - sap_control_function_current: "nw_stop" + - sap_control_function_current: "hana_stop" + +start_all_sap_list: + - sap_control_function_current: "hana_start" + - sap_control_function_current: "nw_start" + +restart_all_nw_list: + - sap_control_function_current: "nw_stop" + - sap_control_function_current: "nw_start" + +restart_all_hana_list: + - sap_control_function_current: "hana_stop" + - sap_control_function_current: "hana_start" + +stop_all_nw_list: + - sap_control_function_current: "nw_stop" + +start_all_nw_list: + - sap_control_function_current: "nw_start" + +stop_all_hana_list: + - sap_control_function_current: "hana_stop" + +start_all_hana_list: + - sap_control_function_current: "hana_start" + +restart_sap_nw_list: + - sap_control_function_current: "nw_stop" + - sap_control_function_current: "nw_start" + +restart_sap_hana_list: + - sap_control_function_current: "hana_stop" + - sap_control_function_current: "hana_start" + +stop_sap_nw_list: + - sap_control_function_current: "nw_stop" + +start_sap_nw_list: + - sap_control_function_current: "nw_start" + +stop_sap_hana_list: + - sap_control_function_current: "hana_stop" + +start_sap_hana_list: + - sap_control_function_current: "hana_start" \ No newline at end of file diff --git a/roles/sap_control/tasks/functions/cleanipc.yml b/roles/sap_control/tasks/functions/cleanipc.yml new file mode 100644 index 0000000..25194c2 --- /dev/null +++ b/roles/sap_control/tasks/functions/cleanipc.yml @@ -0,0 +1,11 @@ +- name: SAP {{ sap_control_name_header }} - Cleanipc {{ passed_sap_nr }} + shell: | + source ~/.profile && cleanipc {{ passed_sap_nr }} remove + args: + executable: /bin/bash + become: yes + become_method: sudo + become_user: "{{ passed_sap_sid | lower }}adm" + register: cleanipc + changed_when: + - "'Number of IPC-Objects...........: 0' not in cleanipc.stdout" \ No newline at end of file diff --git a/roles/sap_control/tasks/functions/restart_sapstartsrv.yml b/roles/sap_control/tasks/functions/restart_sapstartsrv.yml new file mode 100644 index 0000000..592a41f --- /dev/null +++ b/roles/sap_control/tasks/functions/restart_sapstartsrv.yml @@ -0,0 +1,38 @@ +# Restart sapstartsrv + + +# Get number of sapstartsrv processes running for {{ passed_sap_sid }}-{{ passed_sap_nr }} +- name: SAPstartsrv - Get number of sapstartsrv processes running for {{ passed_sap_sid }}-{{ passed_sap_nr }} + shell: | + ps -ef | grep {{ passed_sap_sid }} | grep {{ passed_sap_nr }} | grep sapstartsrv | awk '{ print $8 }' + register: num_of_sapstartsrv_reg + +- name: SAPstartsrv - Set fact for number of sapstartsrv + set_fact: + num_of_sapstartsrv: "{{ num_of_sapstartsrv_reg.stdout.split() }}" + +# Stop sapstartsrv +- name: SAPstartsrv - Stop sapstartsrv {{ passed_sap_sid }}-{{ passed_sap_nr }} + shell: | + source ~/.profile && cdexe; sapcontrol -nr {{ passed_sap_nr }} -function StopService {{ passed_sap_sid }} + args: + executable: /bin/bash + become: yes + become_user: "{{ passed_sap_sid | lower }}adm" + register: sap_stop_sapstartsrv + loop: "{{ num_of_sapstartsrv }}" + delay: 2 + +# Start sapstartsrv +- name: SAPstartsrv - Start sapstartsrv {{ passed_sap_sid }}-{{ passed_sap_nr }} + shell: | + source ~/.profile && cdexe; sapcontrol -nr {{ passed_sap_nr }} -function StartService {{ passed_sap_sid }} + args: + executable: /bin/bash + become: yes + become_user: "{{ passed_sap_sid | lower }}adm" + register: sap_start_sapstartsrv + +- name: SAPstartsrv - Wait for 10 seconds for sapstartsrv to initialize + pause: + seconds: 10 diff --git a/roles/sap_control/tasks/functions/sapstartsrv.yml b/roles/sap_control/tasks/functions/sapstartsrv.yml new file mode 100644 index 0000000..f25b952 --- /dev/null +++ b/roles/sap_control/tasks/functions/sapstartsrv.yml @@ -0,0 +1,23 @@ +--- +# This task requires the variables +# passed_sap_sid +# passed_sap_nr + +# Check sapstartsrv +- name: SAPstartsrv - Check sapstartsrv + shell: | + source ~/.profile && cdexe; sapcontrol -nr {{ passed_sap_nr }} -function GetSystemInstanceList + args: + executable: /bin/bash + become: yes + become_user: "{{ passed_sap_sid | lower }}adm" + ignore_errors: yes + register: check_sapstartsrv + changed_when: + - ('FAIL' in check_sapstartsrv.stdout) + +# Check sapstartsrv +- name: SAPstartsrv - Restart sapstartsrv + include_tasks: restart_sapstartsrv.yml + when: + - ('FAIL' in check_sapstartsrv.stdout) diff --git a/roles/sap_control/tasks/main.yml b/roles/sap_control/tasks/main.yml new file mode 100644 index 0000000..ba3c5ce --- /dev/null +++ b/roles/sap_control/tasks/main.yml @@ -0,0 +1,113 @@ +--- +# Lowercase input +- name: Lowercase input + set_fact: + sap_control_function: "{{ sap_control_function | lower }}" + +# Check inputs +- name: Check function validity + fail: msg="Function {{ sap_control_function }} is invalid" + when: + - "sap_control_function not in sap_control_functions_list" + +- name: Check function if defined + fail: msg="No sap_control_function defined" + when: + - "sap_control_function == 'initial'" + +# - name: Check function - all and SID +# fail: msg="An 'all' function and a 'SID' cant be used at the same time" +# when: +# - (sap_sid != 'initial') and ('all' in sap_control_function) + +- name: Check function - all and SID + fail: msg="Parameter 'sap_sid' is required when not using an 'all' function" + when: + - (sap_sid == 'initial') and ('all' not in sap_control_function) + +# Set facts when nowait is true +- name: Set facts when nowait is true + set_fact: + sap_control_start: "Start" + sap_control_stop: "Stop" + when: nowait | bool + +# sap_control_functions_list: +# - restart_all_sap +# - stop_all_sap +# - start_all_sap +# - restart_all_nw +# - restart_all_hana +# - stop_all_nw +# - start_all_nw +# - stop_all_hana +# - start_all_hana +# - restart_sap_nw +# - restart_sap_hana +# - stop_sap_nw +# - start_sap_nw +# - stop_sap_hana +# - start_sap_hana + +- name: Set function list facts + set_fact: + sap_control_act_type: "{{ sap_control_function.split('_')[0] | lower }}" + sap_control_get_type: "{{ sap_control_function.split('_')[1] | lower }}" + sap_control_sap_type: "{{ sap_control_function.split('_')[2] | lower }}" + +- name: Set sap_facts_param + set_fact: + sap_facts_param: "{{ sap_control_get_type }}" + +- name: Set sap_facts_param + set_fact: + sap_facts_param: "{{ sap_control_sap_type }}" + when: + - "'all' in sap_control_function" + - "'sap' not in sap_control_sap_type" + +# When not all +- name: Set sap_facts_param + set_fact: + sap_facts_param: "{{ sap_sid }}" + when: + - "'all' not in sap_control_function" + +# # Get SAP Info +# - name: Get SAP Info +# vars: +# sap_info_get_function: "get_{{ sap_control_get_type }}_{{ sap_control_sap_type }}" +# include_role: +# name: roles/sap_info + +# Get SAP Facts +- name: Run sap_facts module to gather SAP facts + sap_facts: + param: "{{ sap_facts_param }}" + register: sap_facts_register + +- debug: + msg: "{{ sap_facts_register.sap_facts }}" + +- pause: + seconds: 10 + +# Debugging stuff +- name: Display parameters for runtime + debug: + msg: + - "Starting sap_control with the following parameters: " + - "{{ sap_control_function }}" + - "{{ sap_control_start }}" + - "{{ sap_control_stop }}" + - "{{ nowait }}" + +# Start SAP Control +- name: SAP Control + include_tasks: prepare.yml + loop: "{{ vars[sap_control_function + '_list'] }}" + loop_control: + loop_var: function_list + + + diff --git a/roles/sap_control/tasks/prepare.yml b/roles/sap_control/tasks/prepare.yml new file mode 100644 index 0000000..0847280 --- /dev/null +++ b/roles/sap_control/tasks/prepare.yml @@ -0,0 +1,21 @@ +--- + +- name: Prepare - Set function control facts + set_fact: + sap_type: "{{ function_list.sap_control_function_current.split('_')[0] | lower }}" + funct_type: "{{ function_list.sap_control_function_current.split('_')[1] | lower }}" + +- name: Prepare - Set header + set_fact: + sap_control_name_header: "{{ sap_type | upper }} {{ funct_type | capitalize }}" + +- name: SAP Control + vars: + sap_control_execute_sid: "{{ item.SID }}" + sap_control_execute_type: "{{ item.Type }}" + sap_control_execute_instance_nr: "{{ item.InstanceNumber }}" + sap_control_execute_instance_type: "{{ item.InstanceType }}" + include_tasks: "sapcontrol.yml" + loop: "{{ sap_facts_register.sap_facts }}" + when: + - "item.Type == sap_type" diff --git a/roles/sap_control/tasks/sapcontrol.yml b/roles/sap_control/tasks/sapcontrol.yml new file mode 100644 index 0000000..fe215d0 --- /dev/null +++ b/roles/sap_control/tasks/sapcontrol.yml @@ -0,0 +1,35 @@ +# sap_control_execute_sid: "{{ item.SID }}" +# sap_control_execute_type: "{{ item.Type }}" +# sap_control_execute_instance_nr: "{{ item.InstanceNumber }}" +# sap_control_execute_instance_type: "{{ item.InstanceType }}" +- set_fact: + passed_sap_nr: "{{ sap_control_execute_instance_nr }}" + passed_sap_sid: "{{ sap_control_execute_sid }}" + +# Check sapstartsrv +- name: SAP {{ sap_control_name_header }} - sapstartsrv + include_tasks: functions/sapstartsrv.yml + +# Execute sapcontrol +- name: SAP {{ sap_control_name_header }} - Executing sapcontrol -nr {{ passed_sap_nr }} -function {{ vars['sap_control_' + funct_type] }} + shell: | + source ~/.profile && sapcontrol -nr {{ passed_sap_nr }} -function {{ vars['sap_control_' + funct_type] }} + args: + executable: /bin/bash + warn: false + become: yes + become_user: "{{ passed_sap_sid | lower }}adm" + register: sapcontrol_status + failed_when: + - "'FAIL' in sapcontrol_status.stdout" + +# Cleanipc +- name: SAP {{ sap_control_name_header }} - Cleanipc + include_tasks: functions/cleanipc.yml + when: + - "'nw' in sap_type" + - "'stop' in funct_type" + + + + diff --git a/roles/sap_fapolicy/README.md b/roles/sap_fapolicy/README.md new file mode 100644 index 0000000..35a23c9 --- /dev/null +++ b/roles/sap_fapolicy/README.md @@ -0,0 +1,85 @@ +# sap_fapolicy Ansible Role + +Ansible role for updating fapolicy entries based on SAP instance numbers + +- **Generic** - use the `generic` option to update entries directly by providing a list of users +- **SAP NW** - use the `nw` option to update SAP NW entries +- **SAP HANA** - use the `hana` option to update SAP HANA entries + +## Overview + +Fapolicy entries will be updated to allow access to the following directories + - "/hana/" + - "/sapmnt/" + - "/usr/sap/" + - "/software/" + - "/var/tmp/" + - "/tmp/" + +![](/docs/diagrams/sap_fapolicy_workflow.svg) + +### Variables + +| **Variable** | **Info** | **Default** | **Required** | +| :--- | :--- | :--- | :--- | +| sap_fapolicy_type | 'generic' / 'nw' / 'hana' | 'generic' | yes | +| sap_fapolicy_user | Unix user to include in fapolicy entries | | if 'generic' | +| sap_fapolicy_sid | SAP system SID | | if 'nw' / 'hana' | + +### Input and Execution + +- Sample execution: + + ```bash + ansible-playbook --connection=local --limit localhost -i "localhost," sap-fapolicy-update.yml" + ``` + +- Sample playbook using `generic` option + + ```yaml + --- + - hosts: all + become: true + + vars: + sap_fapolicy_user_generic_list: + - "root" + - "sapadm" + - "uuidd" + + tasks: + + # Update fapolicy for generic users + - name: Fapolicy Update - generic + vars: + sap_fapolicy_type: "generic" + include_role: + name: community.sap_operations.sap_fapolicy + loop: "{{ sap_fapolicy_user_generic_list }}" + loop_control: + loop_var: sap_fapolicy_user + ``` + +- Sample playbook using `sap_facts` module to get all SAP systems in the host + + ```yaml + --- + - hosts: all + become: true + + tasks: + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "all" + register: sap_facts_register + + # Update fapolicy for SAP users + - name: Fapolicy Update - SAP Users + vars: + sap_fapolicy_sid: "{{ item.Type }}" + sap_fapolicy_type: "{{ item.Type }}" + include_role: + name: community.sap_operations.sap_fapolicy + loop: "{{ sap_facts_register.sap_facts }}" + ``` diff --git a/roles/sap_fapolicy/defaults/main.yml b/roles/sap_fapolicy/defaults/main.yml new file mode 100644 index 0000000..5944d80 --- /dev/null +++ b/roles/sap_fapolicy/defaults/main.yml @@ -0,0 +1,25 @@ + +# https://access.redhat.com/discussions/3936201 + +sap_fapolicy_sid: +sap_fapolicy_type: "generic" + +sap_fapolicy_user: +sap_fapolicy_uid: + +# sap_fapolicy_user_list: +# - "root" +# - "sapadm" +# - "uuidd" + +# sap_fapolicy_uid_list: [] + +sap_fapolicy_directory_list: + - "/hana/" + - "/sapmnt/" + - "/usr/sap/" + - "/software/" + - "/var/tmp/" + - "/tmp/" + +sap_fapolicy_rules_header: "# Allow rules for SAP directories" \ No newline at end of file diff --git a/roles/sap_fapolicy/tasks/enable_fapolicy.yml b/roles/sap_fapolicy/tasks/enable_fapolicy.yml new file mode 100644 index 0000000..6d5b6c3 --- /dev/null +++ b/roles/sap_fapolicy/tasks/enable_fapolicy.yml @@ -0,0 +1,5 @@ +- name: SAP Faplocy - Service fapolicy state - enable + command: "systemctl enable fapolicyd" + +- name: SAP Faplocy - Service fapolicy state - start + command: "systemctl start fapolicyd" \ No newline at end of file diff --git a/roles/sap_fapolicy/tasks/get_sidadm_user.yml b/roles/sap_fapolicy/tasks/get_sidadm_user.yml new file mode 100644 index 0000000..02d1a3e --- /dev/null +++ b/roles/sap_fapolicy/tasks/get_sidadm_user.yml @@ -0,0 +1,16 @@ +--- + +# This task requires the variable sap_fapolicy_sid + +- name: Get sidadm user of "{{ sap_fapolicy_sid }}" + set_fact: + sidadm_user: "{{ sap_fapolicy_sid | lower }}adm" + register: get_sidadm_user_register + +# Return value +- set_fact: + sap_fapolicy_user: "{{ sidadm_user.split() }}" + +# - name: Merge the sap_fapolicy_user_list with result +# set_fact: +# sap_fapolicy_user_list: "{{ sap_fapolicy_user_list + get_sidadm_user_return }}" diff --git a/roles/sap_fapolicy/tasks/get_user_uid.yml b/roles/sap_fapolicy/tasks/get_user_uid.yml new file mode 100644 index 0000000..aa0fc4a --- /dev/null +++ b/roles/sap_fapolicy/tasks/get_user_uid.yml @@ -0,0 +1,18 @@ +--- + +# This task requires the variable passed_user + +- name: Get UID "{{ passed_user }}" + shell: | + id -u {{ passed_user }} + args: + executable: /bin/bash + register: get_user_uid_register + +# Return value +- set_fact: + sap_fapolicy_uid: "{{ get_user_uid_register.stdout.split() }}" + +# - name: Merge the sap_fapolicy_uid_list with result +# set_fact: +# sap_fapolicy_uid_list: "{{ sap_fapolicy_uid_list + get_user_uid_return }}" diff --git a/roles/sap_fapolicy/tasks/main.yml b/roles/sap_fapolicy/tasks/main.yml new file mode 100644 index 0000000..148c04a --- /dev/null +++ b/roles/sap_fapolicy/tasks/main.yml @@ -0,0 +1,40 @@ +- name: SAP Fapolicy - Gathering Package Facts + package_facts: + manager: auto + +- name: SAP Fapolicy - Setup + block: + + - name: Get sidadm user of SID + include_tasks: get_sidadm_user.yml + when: + - "'generic' not in sap_fapolicy_type" + - sap_fapolicy_user is defined + + # # At this point, we should have all sidadm user + the default users + # - debug: + # msg: + # - "{{ sap_fapolicy_user_list }}" + + # Get UIDs of users + # List is stored in sap_fapolicy_uid_list + - name: Get UID of user + vars: + passed_user: "{{ sap_fapolicy_user | lower }}" + include_tasks: get_user_uid.yml + + # # At this point, we should have all uids + # - debug: + # msg: + # - "{{ sap_fapolicy_uid_list }}" + + - name: SAP Fapolicy - Enable Fapolicy + include_tasks: "enable_fapolicy.yml" + + - name: SAP Fapolicy - Update Fapolicy + include_tasks: "update_fapolicy.yml" + + when: + - ansible_virtualization_role != "guest" or ansible_virtualization_type != "docker" + - '"fapolicyd" in ansible_facts.packages' + diff --git a/roles/sap_fapolicy/tasks/update_fapolicy.yml b/roles/sap_fapolicy/tasks/update_fapolicy.yml new file mode 100644 index 0000000..0ca2bfe --- /dev/null +++ b/roles/sap_fapolicy/tasks/update_fapolicy.yml @@ -0,0 +1,50 @@ +# Update fapolicy rules +- name: Update fapolicy rules + block: + - name: check fapolicy service + stat: + path: /run/fapolicyd/fapolicyd.fifo + register: fa_fifo + + - name: check fapolicy rules file + stat: + path: /etc/fapolicyd/fapolicyd.rules + register: fa_rules + + # Add header line + - name: Add header line + lineinfile: + path: /etc/fapolicyd/fapolicyd.rules + line: "{{ rules_header }}" + insertafter: '# or anything else applications access that is not a computer langauge.' #Hardcoded. + when: fa_rules.stat.exists + + # Allow permission-any for SAP directories + + - name: Add lines of fapolicy rules for User {{ sap_fapolicy_user }} - UID {{ sap_fapolicy_user }} + lineinfile: + path: /etc/fapolicyd/fapolicyd.rules + line: "allow perm=any uid={{ sap_fapolicy_user }} : dir='{{ item }}'" + insertafter: "{{ rules_header }}" + when: fa_rules.stat.exists + register: fapolicy_res + loop: "{{ sap_fapolicy_directory_list }}" + + # Allow trust + - name: Add trust=1 for User {{ sap_fapolicy_user }} - UID {{ sap_fapolicy_user }} + lineinfile: + path: /etc/fapolicyd/fapolicyd.rules + line: "allow perm=any uid={{ sap_fapolicy_user }} trust=1 : all" + insertafter: "{{ rules_header }}" + when: fa_rules.stat.exists + register: fapolicy_res + + + - name: update fapolicy db + shell: /usr/sbin/fapolicyd-cli --update + when: fa_fifo.stat.exists + +# - debug: +# msg: +# - "User: {{ item.0 }} UID: {{ item.1 }}" +# loop: "{{ user_list|zip(sap_fapolicy_uid_list)|list }}" \ No newline at end of file diff --git a/roles/sap_firewall/README.md b/roles/sap_firewall/README.md new file mode 100644 index 0000000..ecb0947 --- /dev/null +++ b/roles/sap_firewall/README.md @@ -0,0 +1,66 @@ +# sap_firewall Ansible Role + +Ansible role for updating firewall port entries based on SAP instance numbers + +- **Generic** - use the `generic` option to update the ports directly by providing a list of ports +- **SAP NW** - use the `nw` option to update SAP NW ports +- **SAP HANA** - use the `hana` option to update SAP HANA ports + +## Overview + +![](/docs/diagrams/workflow_role_sap_firewall.svg) + +### Variables + +| **Variable** | **Info** | **Default** | **Required** | +| :--- | :--- | :--- | :--- | +| sap_firewall_type | 'generic' / 'nw' / 'hana' | 'generic' | yes | +| sap_firewall_ports | List of ports to update when using the option 'generic' | | no, only if 'generic' | +| sap_firewall_instance_nr | SAP instance number when using options 'hana' / 'nw' | | no, only if 'nw' / 'hana' | +| sap_firewall_disable_end | Set this parameter to true to disable firewall at the end of execution | | no | + +### Input and Execution + +- Sample execution: + + ```bash + ansible-playbook --connection=local --limit localhost -i "localhost," sap-firewall-update.yml -e "@input_file.yml" + ``` + +- Sample playbook using `generic` option + + ```yaml + --- + - hosts: all + become: true + vars: + sap_firewall_ports: + - "1128" + - "1129" + sap_firewall_type: "generic" + roles: + - { role: community.sap_operations.sap_firewall } + ``` + +- Sample playbook using `sap_facts` module to get all SAP systems in the host + + ```yaml + --- + - hosts: all + become: true + + tasks: + + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "all" + register: sap_facts_register + + - name: Firewall Update + vars: + sap_firewall_type: "{{ item.Type }}" + sap_firewall_instance_nr: "{{ item.InstanceNumber }}" + include_role: + name: community.sap_operations.sap_firewall + loop: "{{ sap_facts_register.sap_facts }}" + ``` diff --git a/roles/sap_firewall/defaults/main.yml b/roles/sap_firewall/defaults/main.yml new file mode 100644 index 0000000..f03a92b --- /dev/null +++ b/roles/sap_firewall/defaults/main.yml @@ -0,0 +1,13 @@ +# Firewall type +sap_firewall_type: "generic" +# generic | hana | nw + +# sap_firewall_type - set to 'generic' will go thru all the ports defined in this list +sap_firewall_ports: [] + +# sap_firewall_type - set to 'nw' or 'hana' to make use of instance_nr +sap_firewall_instance_nr: + +# Set this parameter to true to disable firewall at the end of execution +sap_firewall_disable_end: 'false' + diff --git a/roles/sap_firewall/tasks/enable_firewall.yml b/roles/sap_firewall/tasks/enable_firewall.yml new file mode 100644 index 0000000..6e05722 --- /dev/null +++ b/roles/sap_firewall/tasks/enable_firewall.yml @@ -0,0 +1,5 @@ +- name: SAP Firewall - Service firewalld state - enable + command: "systemctl enable firewalld" + +- name: SAP Firewall - Service firewalld state - start + command: "systemctl start firewalld" \ No newline at end of file diff --git a/roles/sap_firewall/tasks/generate_ports_generic.yml b/roles/sap_firewall/tasks/generate_ports_generic.yml new file mode 100644 index 0000000..4472252 --- /dev/null +++ b/roles/sap_firewall/tasks/generate_ports_generic.yml @@ -0,0 +1,5 @@ +- name: SAP Firewall - Generate Ports - Generic + set_fact: + sap_firewall_ports: "{{ sap_firewall_ports }}" + when: + - sap_firewall_ports is defined \ No newline at end of file diff --git a/roles/sap_firewall/tasks/generate_ports_hana.yml b/roles/sap_firewall/tasks/generate_ports_hana.yml new file mode 100644 index 0000000..74f03d9 --- /dev/null +++ b/roles/sap_firewall/tasks/generate_ports_hana.yml @@ -0,0 +1,26 @@ +- name: SAP Firewall - Generate Ports - SAP HANA - {{ sap_firewall_instance_nr }} + set_fact: + sap_firewall_ports: + - "1128" + - "1129" + - "43{{ sap_firewall_instance_nr }}" + - "5050" + - "9090" + - "9091" + - "9092" + - "9093" + - "3{{ sap_firewall_instance_nr }}00-3{{ sap_firewall_instance_nr }}90" + - "30105" + - "30107" + - "30140" + - "4{{ sap_firewall_instance_nr }}01" + - "4{{ sap_firewall_instance_nr }}02" + - "4{{ sap_firewall_instance_nr }}06" + - "4{{ sap_firewall_instance_nr }}12" + - "4{{ sap_firewall_instance_nr }}14" + - "4{{ sap_firewall_instance_nr }}40" + - "5{{ sap_firewall_instance_nr }}00" + - "5{{ sap_firewall_instance_nr }}13" + - "5{{ sap_firewall_instance_nr }}14" + - "51000" + - "64997" \ No newline at end of file diff --git a/roles/sap_firewall/tasks/generate_ports_nw.yml b/roles/sap_firewall/tasks/generate_ports_nw.yml new file mode 100644 index 0000000..ffed21a --- /dev/null +++ b/roles/sap_firewall/tasks/generate_ports_nw.yml @@ -0,0 +1,15 @@ +- name: SAP Firewall - Generate Ports - SAP NW - {{ sap_firewall_instance_nr }} + set_fact: + sap_firewall_ports: + - "3200-3399" + - "36{{ sap_firewall_instance_nr }}" + - "80{{ sap_firewall_instance_nr }}" + - "81{{ sap_firewall_instance_nr }}" + - "443{{ sap_firewall_instance_nr }}" + - "620{{ sap_firewall_instance_nr }}" + - "621{{ sap_firewall_instance_nr }}" + - "5{{ sap_firewall_instance_nr }}13" + - "5{{ sap_firewall_instance_nr }}14" + - "5{{ sap_firewall_instance_nr }}16" + - "44390" + - "8090" \ No newline at end of file diff --git a/roles/sap_firewall/tasks/main.yml b/roles/sap_firewall/tasks/main.yml new file mode 100644 index 0000000..395407a --- /dev/null +++ b/roles/sap_firewall/tasks/main.yml @@ -0,0 +1,39 @@ +--- + +- name: SAP Firewall + block: + + - name: SAP Firewall - Gathering Package Facts + package_facts: + manager: auto + + - name: SAP Firewall - Setup + block: + + - name: SAP Firewall - Enable Firewall + include_tasks: "enable_firewall.yml" + + - name: SAP Firewall - Generate Ports + include_tasks: "generate_ports_{{ sap_firewall_type }}.yml" + + - name: SAP Firewall - Add ports + block: + + - name: SAP Firewall - Add Ports + include_tasks: update_firewall.yml + loop: "{{ sap_firewall_ports }}" + loop_control: + loop_var: passed_port + + when: + - sap_firewall_ports is defined + + - name: SAP Firewall - Reload Firewall + shell: | + firewall-cmd --reload + + when: + - '"firewalld" in ansible_facts.packages' + + when: ansible_virtualization_role != "guest" or ansible_virtualization_type != "docker" + diff --git a/roles/sap_firewall/tasks/update_firewall.yml b/roles/sap_firewall/tasks/update_firewall.yml new file mode 100644 index 0000000..c72cd37 --- /dev/null +++ b/roles/sap_firewall/tasks/update_firewall.yml @@ -0,0 +1,10 @@ +--- +# This task requires the variable passed_port + +- name: SAP Firewall - Update Firewall - {{ passed_port }}/tcp + firewalld: + zone: public + port: "{{ passed_port }}/tcp" + permanent: yes + immediate: yes + state: enabled diff --git a/roles/sap_hana_backint/README.md b/roles/sap_hana_backint/README.md new file mode 100644 index 0000000..2ee07fd --- /dev/null +++ b/roles/sap_hana_backint/README.md @@ -0,0 +1,82 @@ +# sap_hana_backint Ansible Role + +`NOTE: Work in Progress` + +Collection of Ansible roles to perform SAP HANA database server backups - the setup, execute, and cleanup - using an SAP HANA Backint Agent. + +This Ansible Role works with: +- Amazon Simple Storage Service (S3). +- IBM Cloud Object Storage (COS), S3 protocol compatible +- Microsoft Azure Backup to Microsoft Azure Recovery Services Vault (RSV) [powered by Azure Blob Storage] +- SAP HANA 2.0 SPS06 and above + +This Ansible Role uses the following: +- **SAP HANA Backint Agents:** + - [SAP HANA Backint Agent for Amazon S3 - SAP Note 2935898](https://launchpad.support.sap.com/#/notes/2935898), supports AWS S3 and IBM COS. For CPU architectures x86_64 and ppc64le. See [SAP Note 2935898](https://launchpad.support.sap.com/#/notes/2935898). + - [Microsoft Azure Backup Plugin for SAP HANA](https://docs.microsoft.com/en-us/azure/backup/sap-hana-db-about), to store backups with Microsoft Azure Recovery Services Vault (RSV) powered by Azure Blob Storage. For CPU architectures x86_64 only. See [SAP Note 2756788](https://launchpad.support.sap.com/#/notes/2756788). + - [Google Cloud Storage Backint agent for SAP HANA](https://cloud.google.com/solutions/sap/docs/sap-hana-backint-overview), using Google Cloud Storage (GCS) Buckets. For CPU architectures x86_64 only. See [SAP Note 2705632](https://launchpad.support.sap.com/#/notes/2705632). +- **Scripts:** + - [SAP Note 2399996 - How-To: Configuring automatic SAP HANA Cleanup with SAP HANACleaner](https://launchpad.support.sap.com/#/notes/2399996). An open-source project - https://github.com/chriselswede/hanachecker + +## Functionality + +The following functions are available: +- `backup_function: 'setup'`: setup of SAP HANA Backint for all SAP HANA SIDs and Tenants on the host, or for specified SID. This includes: + - create hdbuserstore user and keys + - create configuration files and install the agent + - amend SAP HANA parameters in global.ini +- `backup_function: 'execute'`: run backup from SAP HANA Backint for all SAP HANA SIDs and Tenants on the host, or for specified SID. +- `backup_function: 'clean'`: run cleanup of SAP HANA Backint for all SAP HANA SIDs and Tenants on the host, or for specified SID. + +The following target platforms are available: +- `target_platform: 'aws_s3'` +- `target_platform: 'azure_backup_rsv'` +- `target_platform: 'ibm_cos_s3'` +- `target_platform: 'gcs'` + +## Requirements + +- **SAP HANA Backint Agents:** + - SAP HANA Backint Agent for Amazon S3, version 1.2.17 and above (bundled with SAP HANA 2.0 SPS06 and above) + - Python 3.7, enabled with SSL. Cannot use System Python (default: Python 3.6), avoid SAP HANA Python (default: Python 3.7) +- **Object Storage platforms:** + - AWS Simple Storage Service (S3); with object versioning enabled. + - IBM Cloud Object Storage (COS); with object versioning enabled. Uses S3 protocol. + +## Setup detailed information + +### hdbuserstore keys + +These users and keys will be created in hdbusertstore: +- SystemDB: `HDB_SYSTEM_BACKUP_USER` +- TenantDB: `HDB_TENANT_{SID}_BACKUP_USER` + +### Directories and Files + +- Backint Directory: `/usr/sap/{SID}/SYS/global/hdb/opt` +- Executable File: `/usr/sap/{SID}/SYS/global/hdb/opt/hdbbackint` +- Configuration File: `/usr/sap/{SID}/SYS/global/hdb/opt/hdbbackint.cfg` +- Cleaner Script: `/usr/sap/{SID}/SYS/global/hdb/opt/hanacleaner.py` + +### SAP HANA parameters + +| Description | SAP HANA global.ini parameter | default value set | +| --- | --- | --- | +| Data backup agent file | `data_backup_parameter_file` | hdbbackint agent path | +| - | | | +| Catalog backup using backint | `catalog_backup_using_backint` | `true` | +| Catalog backup agent file | `catalog_backup_parameter_file` | hdbbackint agent path | +| - | | | +| ***OPTIONAL, IF ENABLED:***
Log backup using backint | `log_backup_using_backint` | ***OPTIONAL, IF ENABLED:***
`true` | +| ***OPTIONAL, IF ENABLED:***
Log backup agent file | `log_backup_parameter_file` | ***OPTIONAL, IF ENABLED:***
hdbbackint agent path | + +## Known Issues and Errors + +### Microsoft Azure Backup agent + +**Preregistration script:** +- RHEL 8.x with MS Azure Recovery Services Vault (RSV) attempts to run `yum install python-azure-agent` instead of `yum install WALinuxAgent`. Error appears to be due to `Package_WaAgent_RHEL="python-azure-agent"`. +- The Shell Function `Package.RequirePython()` does not have a case for RHEL, which means RHEL will always use Python 2.x. For RHEL 8.x, this will install Python 2.x (which was removed as the default of the OS Distribution). +- The Shell Function `Check.Waagent()` does not operate correctly because a command is commented out `#systemctl restart waagent.service`. +- The Shell Function `Check.Wireserver()`, `Check.IMDS()` and `Check.AadConnectivity()` assumes the host is running on Microsoft Azure platform, it blocks usage of Microsoft Azure Recovery Services Vault from on-premise hosts. +- The Shell Function `Plugin.CreateUser()` results in `Failed to create BACKUP_KEY_NAME: 'Operation succeed.'` diff --git a/roles/sap_hana_backint/defaults/main.yml b/roles/sap_hana_backint/defaults/main.yml new file mode 100644 index 0000000..6829515 --- /dev/null +++ b/roles/sap_hana_backint/defaults/main.yml @@ -0,0 +1,41 @@ +# setup, execute, clean +backup_function: + +# aws_s3, ibm_cos_s3, azure_backup_rsv, gcs +target_platform: + +sap_hana_sid: all + +sap_hana_backint_setup_system_passwd: NewPass$321 +sap_hana_backint_setup_system_tenant_passwd: NewPass$321 + + +# Variables for Setup function +log_enable: false +#sap_hana_backint_setup_backint_directory: "/usr/sap/hana/backint" +sap_hana_backint_setup_hostname: "{{ ansible_hostname }}" +system_backup_user: "HDB_SYSTEM_BACKUP_USER" + + +# Variables for Clean function +backup_retention: "4" +backint_directory: + + +# IBM Cloud Object Storage (COS) S3 details, reference to ibm_cos_s3_hdbbackint.cfg +#sap_hana_backint_setup_provider: "ibm" +#sap_hana_backint_setup_auth: "key" +#sap_hana_backint_setup_bucket: "bucket-name" +#sap_hana_backint_setup_region: "us-east" +#sap_hana_backint_setup_api_key: "xxxxxx" +#sap_hana_backint_setup_resource_instance_id: "crn:v1:bluemix:public:cloud-object-storage:global:a/xxxxxxxx:xxxx-xxxx-xxxx-xxxx-xxxxxxxx:bucket:xxxx" +#sap_hana_backint_setup_endpoint_url: "https://s3.direct.us-east.cloud-object-storage.appdomain.cloud" + + +# AWS S3 object storage details, reference to aws_s3_hdbbackint.cfg +#sap_hana_backint_setup_provider: "aws" +#sap_hana_backint_setup_auth: "key" +#sap_hana_backint_setup_bucket: "bucket-name" +#sap_hana_backint_setup_region: "us-east-1" +#sap_hana_backint_setup_access_key: "xxxxxx" +#sap_hana_backint_setup_secret_access_key: "xxxxxx" diff --git a/roles/sap_hana_backint/tasks/clean/clean.yml b/roles/sap_hana_backint/tasks/clean/clean.yml new file mode 100644 index 0000000..4138ad0 --- /dev/null +++ b/roles/sap_hana_backint/tasks/clean/clean.yml @@ -0,0 +1,20 @@ + +# Clean backups for single SID +- name: SAP HANA Backint - Clean - sap_hana_sid is provided + block: + - name: SAP HANA Backint - Clean - Clean backups for {{ sap_hana_sid }} + include_tasks: clean_backups.yml + when: sap_hana_sid != "all" + + +# Clean backups for each SID - loop through {{ sap_hana_sid_all }} +# Loop is equivalent to with_list, and must not be a string +- name: SAP HANA Backint - Clean - All HDB SIDs in hostname {{ ansible_hostname }} + block: + - name: SAP HANA Backint - Clean - Clean backups for each SID + include_tasks: clean_backups.yml + register: clean_backups_reg + loop: "{{ sap_hana_sid_all }}" + loop_control: + loop_var: passed_sap_hana_sid + when: sap_hana_sid == "all" diff --git a/roles/sap_hana_backint/tasks/clean/clean_backups.yml b/roles/sap_hana_backint/tasks/clean/clean_backups.yml new file mode 100644 index 0000000..3bee6d1 --- /dev/null +++ b/roles/sap_hana_backint/tasks/clean/clean_backups.yml @@ -0,0 +1,28 @@ +- name: SAP HANA Backint - Execute - Set fact for SID + set_fact: + passed_sap_hana_sid: "{{ sap_hana_sid }}" + when: sap_hana_sid != "all" + +# Set hdbuserstore users +- name: SAP HANA Backint - Execute - Set facts for hdbuserstore users + set_fact: + tenant_backup_user: "HDB_TENANT_{{ passed_sap_hana_sid }}_BACKUP_USER" + +- name: SAP HANA Backint - Clean - Output cleaning information + debug: + msg: + - "Cleaning backups for: " + - "SAP HANA SID - {{ passed_sap_hana_sid }} " + - "Hostname - {{ ansible_hostname }} " + +# Clean backup for SYSTEMDB +- name: SAP HANA Backint - Clean - SYSTEMDB - {{ passed_sap_hana_sid }} + include_tasks: clean_backups_systemdb.yml + +# Clean backup for Tenant DBs - loop through {{ sap_hana_tenant }} +- name: SAP HANA Backint - Clean - TENANTDB - {{ passed_sap_hana_sid }} + include_tasks: clean_backups_tenants.yml + register: clean_hana_tenant + loop: "{{ sap_hana_tenant }}" + loop_control: + loop_var: passed_sap_hana_tenant diff --git a/roles/sap_hana_backint/tasks/clean/clean_backups_systemdb.yml b/roles/sap_hana_backint/tasks/clean/clean_backups_systemdb.yml new file mode 100644 index 0000000..99ca2b4 --- /dev/null +++ b/roles/sap_hana_backint/tasks/clean/clean_backups_systemdb.yml @@ -0,0 +1,13 @@ + +- name: SAP HANA Backint - Clean - Execute hanacleaner.py for SYSTEMDB - {{ passed_sap_hana_sid }} + shell: | + source ~/.profile && python {{ backint_directory }}/hanacleaner.py -k {{ system_backup_user }} -be {{ backup_retention }} -bb true + args: + executable: /bin/bash + become: yes + become_user: "{{ passed_sap_hana_sid | lower }}adm" + register: sap_hana_backint_clean_execute_hanacleaner + +- name: SAP HANA Backint - Clean - Output hanacleaner execution + debug: + msg: "{{ sap_hana_backint_clean_execute_hanacleaner }}.stdout_lines" diff --git a/roles/sap_hana_backint/tasks/clean/clean_backups_tenants.yml b/roles/sap_hana_backint/tasks/clean/clean_backups_tenants.yml new file mode 100644 index 0000000..f49e974 --- /dev/null +++ b/roles/sap_hana_backint/tasks/clean/clean_backups_tenants.yml @@ -0,0 +1,13 @@ + +- name: SAP HANA Backint - Clean - Execute hanacleaner.py for TENANTDB - {{ passed_sap_hana_tenant }} + shell: | + source ~/.profile && python {{ backint_directory }}/hanacleaner.py -k {{ tenant_backup_user }} -be {{ backup_retention }} -bb true -fs + args: + executable: /bin/bash + become: yes + become_user: "{{ passed_sap_hana_sid | lower }}adm" + register: sap_hana_backint_clean_execute_hanacleaner + +- name: SAP HANA Backint - Clean - Output hanacleaner execution + debug: + msg: "{{ sap_hana_backint_clean_execute_hanacleaner }}.stdout_lines" diff --git a/roles/sap_hana_backint/tasks/execute/execute.yml b/roles/sap_hana_backint/tasks/execute/execute.yml new file mode 100644 index 0000000..c697ae4 --- /dev/null +++ b/roles/sap_hana_backint/tasks/execute/execute.yml @@ -0,0 +1,26 @@ + +# Execute backups for single SID +- name: SAP HANA Backint - Execute - sap_hana_sid is provided + block: + - name: SAP HANA Backint - Execute - Execute backups for {{ sap_hana_sid }} + include_tasks: execute_backup.yml + when: sap_hana_sid != "all" + + +# Execute backups for each SID - loop through list {{ sap_hana_sid_all }} +# Loop is equivalent to with_list, and must not be a string +- name: SAP HANA Backint - Execute - All HDB SIDs in hostname {{ ansible_hostname }} + block: + - name: SAP HANA Backint - Execute - Execute backups for each SID + include_tasks: execute_backup.yml + register: execute_backup_reg + loop: "{{ sap_hana_sid_all }}" + loop_control: + loop_var: passed_sap_hana_sid + when: sap_hana_sid == "all" + + +- name: SAP HANA Backint - Output NOTICE + debug: + msg: + - "Please note that backups were triggered in the background" diff --git a/roles/sap_hana_backint/tasks/execute/execute_backup.yml b/roles/sap_hana_backint/tasks/execute/execute_backup.yml new file mode 100644 index 0000000..2719674 --- /dev/null +++ b/roles/sap_hana_backint/tasks/execute/execute_backup.yml @@ -0,0 +1,28 @@ +- name: SAP HANA Backint - Execute - Set fact for SID + set_fact: + passed_sap_hana_sid: "{{ sap_hana_sid }}" + when: sap_hana_sid != "all" + +# Set hdbuserstore users +- name: SAP HANA Backint - Execute - Set facts for hdbuserstore users + set_fact: + tenant_backup_user: "HDB_TENANT_{{ passed_sap_hana_sid }}_BACKUP_USER" + +- name: SAP HANA Backint - Output executing information + debug: + msg: + - "Executing backup for: " + - "SAP HANA SID - {{ passed_sap_hana_sid }} " + - "Hostname - {{ ansible_hostname }} " + +# Execute backup for SYSTEMDB +- name: SAP HANA Backint - Execute - SYSTEMDB - {{ passed_sap_hana_sid }} + include_tasks: execute_backup_systemdb.yml + +# Execute backup for Tenant DBs - loop through {{ sap_hana_tenant }} +- name: SAP HANA Backint - Execute - TENANTDB - {{ passed_sap_hana_sid }} + include_tasks: execute_backup_tenants.yml + register: backup_hana_tenant + loop: "{{ sap_hana_tenant }}" + loop_control: + loop_var: passed_sap_hana_tenant diff --git a/roles/sap_hana_backint/tasks/execute/execute_backup_systemdb.yml b/roles/sap_hana_backint/tasks/execute/execute_backup_systemdb.yml new file mode 100644 index 0000000..4cf1543 --- /dev/null +++ b/roles/sap_hana_backint/tasks/execute/execute_backup_systemdb.yml @@ -0,0 +1,8 @@ + +- name: SAP HANA Backint - Execute - SYSTEMDB - Run backup in the background + shell: | + nohup /usr/sap/{{ passed_sap_hana_sid }}/SYS/exe/hdb/hdbsql -U {{ system_backup_user }} "BACKUP DATA FOR SYSTEMDB USING BACKINT ('COMPLETE_DATA_BACKUP');" & + args: + executable: /bin/bash + become: yes + become_user: "{{ passed_sap_hana_sid | lower }}adm" diff --git a/roles/sap_hana_backint/tasks/execute/execute_backup_tenants.yml b/roles/sap_hana_backint/tasks/execute/execute_backup_tenants.yml new file mode 100644 index 0000000..b8f2981 --- /dev/null +++ b/roles/sap_hana_backint/tasks/execute/execute_backup_tenants.yml @@ -0,0 +1,8 @@ + +- name: SAP HANA Backint - Execute - TENANTDB - {{ passed_sap_hana_tenant }} - Run backup in the background + shell: | + nohup /usr/sap/{{ passed_sap_hana_sid }}/SYS/exe/hdb/hdbsql -U {{ system_backup_user }} "BACKUP DATA FOR {{ passed_sap_hana_tenant }} USING BACKINT ('COMPLETE_DATA_BACKUP');" & + args: + executable: /bin/bash + become: yes + become_user: "{{ passed_sap_hana_sid | lower }}adm" diff --git a/roles/sap_hana_backint/tasks/get_all_sap_hana_sid.yml b/roles/sap_hana_backint/tasks/get_all_sap_hana_sid.yml new file mode 100644 index 0000000..126d8d0 --- /dev/null +++ b/roles/sap_hana_backint/tasks/get_all_sap_hana_sid.yml @@ -0,0 +1,27 @@ +--- + +# This task requires the variable sap_hana_sid + +- name: SAP HANA Backint - Get info - Get Tenant DBs of SAP HANA Database Server {{ sap_hana_sid }} + shell: | + while read LINE + do + ### Check to see if /usr/sap/SID exists. If not then its probably not used anymore / its not a SID so ignore + if [ -d /usr/sap/$LINE ]; then + # $LINE is an HDB system + SAP_HDB_SID_ARRAY+=( "$LINE" ) + else + # $LINE not in /usr/sap + : + fi + done < <(ls -1 /hana/shared) + + echo "${SAP_HDB_SID_ARRAY[@]}" + args: + executable: /bin/bash + register: get_all_sap_hana_sid + +# Return value - sap_hana_sid_all +- name: SAP HANA Backint - Get info - Set facts for all SAP HANA System IDs (SIDs) + set_fact: + sap_hana_sid_all: "{{ get_all_sap_hana_sid.stdout.split() }}" diff --git a/roles/sap_hana_backint/tasks/get_all_sap_hana_tenant.yml b/roles/sap_hana_backint/tasks/get_all_sap_hana_tenant.yml new file mode 100644 index 0000000..2384624 --- /dev/null +++ b/roles/sap_hana_backint/tasks/get_all_sap_hana_tenant.yml @@ -0,0 +1,23 @@ +--- + +# This task requires the variable sap_hana_sid and hdbuserstore system_backup_user + +- name: SAP HANA Backint - Get info - Get Tenant DBs of SAP HANA Database Server {{ sap_hana_sid }} + shell: | + /usr/sap/{{ sap_hana_sid }}/SYS/exe/hdb/hdbsql -x -a -U {{ system_backup_user }} "select DATABASE_NAME from SYS.M_DATABASES WHERE DATABASE_NAME != 'SYSTEMDB'" | sed 's/\"//g' + args: + executable: /bin/bash + become: yes + become_user: "{{ sap_hana_sid | lower }}adm" + register: get_all_sap_hana_tenant + +# Code below doesnt work when running in crontab +#- name: Get Tenant DBs of SAP HANA Database Server {{ sap_hana_sid }} +# shell: | +# su - {{ sap_hana_sid | lower }}adm -c "hdbsql -x -a -U {{ system_backup_user }} \"select DATABASE_NAME from SYS.M_DATABASES WHERE DATABASE_NAME != 'SYSTEMDB'\" | sed 's/\"//g'" +# register: get_all_sap_hana_tenant + +# Return value - sap_hana_tenant +- name: SAP HANA Backint - Get info - Set facts for all SAP HANA Tenants + set_fact: + sap_hana_tenant: "{{ get_all_sap_hana_tenant.stdout.split() }}" diff --git a/roles/sap_hana_backint/tasks/get_sap_hana_instance_nr.yml b/roles/sap_hana_backint/tasks/get_sap_hana_instance_nr.yml new file mode 100644 index 0000000..1601f38 --- /dev/null +++ b/roles/sap_hana_backint/tasks/get_sap_hana_instance_nr.yml @@ -0,0 +1,15 @@ +--- + +# This task requires the variable sap_hana_sid + +- name: SAP HANA Backint - Get info - Get instance number of SAP HANA Database Server {{ sap_hana_sid }} + shell: | + ls -1 /usr/sap/{{ sap_hana_sid }} | grep HDB | sed 's/...//' | head -1 + args: + executable: /bin/bash + register: get_sap_hana_instance_nr + +# Return value - sap_hana_instance_nr_one +- name: SAP HANA Backint - Get info - Set facts for SAP HANA Instance Number + set_fact: + sap_hana_instance_nr_one: "{{ get_sap_hana_instance_nr.stdout.split() }}" diff --git a/roles/sap_hana_backint/tasks/main.yml b/roles/sap_hana_backint/tasks/main.yml new file mode 100644 index 0000000..e23c2c4 --- /dev/null +++ b/roles/sap_hana_backint/tasks/main.yml @@ -0,0 +1,33 @@ +# Set facts +- name: SAP HANA Backint - Set fact for Ansible Role actions + set_fact: + sap_control_function: "{{ backup_function | lower }}" + python_version: "3.7.9" + python_build_path: "/usr/sap/python_env_build" + python_altinstall_path: "/usr/sap/python_env" + +- name: SAP HANA Backint - Set fact if sap_hana_sid is provided + set_fact: + backint_directory: "/usr/sap/{{ sap_hana_sid }}/SYS/global/hdb/opt" +# tenant_backup_user: "HDB_TENANT_{{ passed_sap_hana_tenant }}_BACKUP_USER" + when: sap_hana_sid != "all" + +# List is stored in sap_hana_sid_all +- name: SAP HANA Backint - Get all SAP HANA System IDs (SIDs) + include_tasks: get_all_sap_hana_sid.yml + +# List is stored in sap_hana_tenant +- name: SAP HANA Backint - Get all SAP HANA Tenants of {{ sap_hana_sid }} + include_tasks: get_all_sap_hana_tenant.yml + +# List is stored in sap_hana_instance_nr_one +- name: SAP HANA Backint - Get SAP HANA instance number + include_tasks: get_sap_hana_instance_nr.yml + +# Install Python 3.7 +- name: SAP HANA Backint - Python 3.7 + include_tasks: "python37.yml" + when: (backup_function == 'setup' and target_platform == 'aws_s3') or (backup_function == 'setup' and target_platform == 'ibm_cos_s3') + +- name: SAP HANA Backint - {{ backup_function }} + include_tasks: "{{ backup_function }}/{{ backup_function }}.yml" diff --git a/roles/sap_hana_backint/tasks/python37.yml b/roles/sap_hana_backint/tasks/python37.yml new file mode 100644 index 0000000..9b00966 --- /dev/null +++ b/roles/sap_hana_backint/tasks/python37.yml @@ -0,0 +1,79 @@ +- name: Python (System) - Install Python package manager pip3, git, make, compiler/library for C and C++, and compilers for zlib, openssl, libssl + yum: + name: + - python3-pip + - gcc + - glibc + - gcc-c++ + - libstdc++ + - make + - zlib-devel + - openssl-devel + - git + state: present + +- name: Python (System) - Install virtualenv to System Python 3.x + pip: + name: + - virtualenv + +- name: Python altinstall - Create directories for Python build + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: 0755 + owner: "{{ sap_hana_sid | lower }}adm" + group: sapsys + loop: + - "{{ python_build_path }}" + - "{{ python_altinstall_path }}" + - "{{ python_altinstall_path }}/python_backint_local" + - "{{ python_altinstall_path }}/python_backint" + +- name: Python altinstall - Download and Extract Python build + ansible.builtin.unarchive: + src: "https://www.python.org/ftp/python/{{ python_version }}/Python-{{ python_version }}.tgz" + dest: "{{ python_build_path }}" + remote_src: yes + +- name: Python altinstall - Configure Python build +# become_user: "{{ sap_hana_sid | lower }}adm" + command: ./configure --enable-share=no --prefix={{ python_altinstall_path }}/python_backint_local + args: + chdir: "{{ python_build_path }}/Python-{{ python_version }}" + register: python_configured + +# This executes the build, and creates /usr/local/lib +- name: Python altinstall - Make Python build +# become_user: "{{ sap_hana_sid | lower }}adm" +# shell: make && make altinstall + shell: make altinstall + args: + chdir: "{{ python_build_path }}/Python-{{ python_version }}" + become: yes + when: python_configured.changed + +- name: Python altinstall - Create virtualenv for Python build, install baseline dependencies to virtualenv, and set variable. Do not inherit system Python modules + pip: + name: + - wheel + - requests + - pip + virtualenv: "{{ python_altinstall_path }}/python_backint" + virtualenv_command: virtualenv --download + virtualenv_python: "{{ python_build_path }}/Python-{{ python_version }}/python" + +- name: Python altinstall - Change owner and group permission for directories, subdirectories and files + ansible.builtin.file: + path: "{{ item }}" + recurse: true + mode: 0755 + owner: "{{ sap_hana_sid | lower }}adm" + group: sapsys + loop: + - "{{ python_altinstall_path }}/python_backint_local" + - "{{ python_altinstall_path }}/python_backint" + +- name: Python altinstall - Set fact for backint file + set_fact: + python_37_binary: "{{ python_altinstall_path }}/python_backint/bin/python3" diff --git a/roles/sap_hana_backint/tasks/setup/setup.yml b/roles/sap_hana_backint/tasks/setup/setup.yml new file mode 100644 index 0000000..230f45f --- /dev/null +++ b/roles/sap_hana_backint/tasks/setup/setup.yml @@ -0,0 +1,55 @@ + +# 1. Prepare backint + +- name: SAP HANA Backint - Setup - Create temporary directory + tempfile: + state: directory + suffix: sap_hana_backint_setup_temp_dir + register: sap_hana_backint_setup_temp_dir + + +# 2. Setup the selected backint agent + +# Setup backint for single SID +- name: SAP HANA Backint - Setup - sap_hana_sid is provided + block: + - name: SAP HANA Backint - Setup - Setup Backint for {{ sap_hana_sid }} + include_tasks: "setup_backint_{{ target_platform }}.yml" + when: sap_hana_sid != "all" + + +# Setup backint for each SID - loop through list {{ sap_hana_sid_all }} +# Loop is equivalent to with_list, and must not be a string +- name: SAP HANA Backint - Setup - All HDB SIDs in hostname {{ ansible_hostname }} + block: + - name: SAP HANA Backint - Setup - Setup Backint for each SID + include_tasks: "setup_backint_{{ target_platform }}.yml" + loop: "{{ sap_hana_sid_all }}" + loop_control: + loop_var: passed_sap_hana_sid + register: setup_backint_reg + when: sap_hana_sid == "all" + + +# 3. Define backups to execute + +# Setup SYSTEMDB +- name: SAP HANA Backint - Setup - Setup SYSTEMDB of {{ sap_hana_sid }} + include_tasks: setup_systemdb.yml + register: setup_hana_systemdb + +# Setup Tenant DBs - loop through {{ sap_hana_tenant }} +- name: SAP HANA Backint - Setup - Setup Tenant DBs of {{ sap_hana_sid }} + include_tasks: setup_tenants.yml + register: setup_hana_tenant + loop: "{{ sap_hana_tenant }}" + loop_control: + loop_var: passed_sap_hana_tenant + + +# 4. Cleanup + +- name: SAP HANA Backint - Setup - Clean up + file: + path: "{{ sap_hana_backint_setup_temp_dir.path }}" + state: absent diff --git a/roles/sap_hana_backint/tasks/setup/setup_backint_aws_s3.yml b/roles/sap_hana_backint/tasks/setup/setup_backint_aws_s3.yml new file mode 100644 index 0000000..994cc02 --- /dev/null +++ b/roles/sap_hana_backint/tasks/setup/setup_backint_aws_s3.yml @@ -0,0 +1,138 @@ +# Amazon Simple Storage Service (S3) + +- name: SAP HANA Backint - Execute - Set fact for SID + set_fact: + passed_sap_hana_sid: "{{ sap_hana_sid }}" + when: sap_hana_sid != "all" + +# Set initial facts +- name: SAP HANA Backint - Setup - AWS S3 - Set facts + set_fact: + sap_hana_backint_setup_backint_directory: "/usr/sap/{{ passed_sap_hana_sid }}/SYS/global/hdb/opt" + passed_sap_hana_instance_nr: "{{ sap_hana_instance_nr_one[0] }}" + +- name: SAP HANA Backint - Setup - AWS S3 - Discover tar.gz of SAP HANA Backint Agent for Amazon S3 + find: + paths: "/" + recurse: yes + patterns: "aws-s3-backint-*.tar.gz" + register: __sap_hana_backint_agent_s3_targz + +- name: SAP HANA Backint - Setup - AWS S3 - Output tar.gz path of SAP HANA Backint Agent for Amazon S3 + debug: + msg: "{{ __sap_hana_backint_agent_s3_targz.files[0].path }}" + +- name: SAP HANA Backint - Setup - AWS S3 - Extract tar.gz of SAP HANA Backint Agent for Amazon S3 + ansible.builtin.unarchive: + src: "{{ __sap_hana_backint_agent_s3_targz.files[0].path }}" + dest: "{{ sap_hana_backint_setup_temp_dir.path }}" + remote_src: yes + +- name: SAP HANA Backint - Setup - AWS S3 - Discover binary of SAP HANA Backint Agent for Amazon S3 + find: + paths: "{{ sap_hana_backint_setup_temp_dir.path }}" + recurse: yes + file_type: directory + patterns: "aws-s3-backint" + register: __sap_hana_backint_agent_s3 + +- name: SAP HANA Backint - Setup - AWS S3 - Output binary path of SAP HANA Backint Agent for Amazon S3 + debug: + msg: "{{ __sap_hana_backint_agent_s3.files[0].path }}" + +# src must end in forward-slash / to copy directory contents - https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html#parameter-src +- name: SAP HANA Backint - Setup - AWS S3 - Copy temp directory contents to SAP HANA Backint directory + ansible.builtin.copy: + src: "{{ __sap_hana_backint_agent_s3.files[0].path }}/" + dest: "{{ sap_hana_backint_setup_backint_directory }}" + mode: '0755' + directory_mode: yes + remote_src: yes + +# Change directory owner and permission +- name: SAP HANA Backint - Setup - AWS S3 - Change directory owner and permission + ansible.builtin.file: + path: "{{ sap_hana_backint_setup_backint_directory }}" + state: directory + recurse: yes + mode: '0755' + owner: "{{ passed_sap_hana_sid | lower }}adm" + group: sapsys + +- name: SAP HANA Backint - Setup - AWS S3 - Set fact for backint file + set_fact: + sap_hana_backint_setup_backint_file: "{{ sap_hana_backint_setup_backint_directory }}/hdbbackint" + + +# Setup + +- name: SAP HANA Backint - Setup - AWS S3 - Clone hanacleaner.py + ansible.builtin.git: + repo: https://github.com/chriselswede/hanacleaner.git + dest: "$HOME/.ansible/tmp/git_hanacleaner" + single_branch: yes + version: master + +# Copy hanacleaner.py +- name: SAP HANA Backint - Setup - AWS S3 - Copy hanacleaner.py to SAP HANA backint directory + ansible.builtin.copy: + src: "$HOME/.ansible/tmp/git_hanacleaner/hanacleaner.py" + dest: "{{ sap_hana_backint_setup_backint_directory }}/hanacleaner.py" + mode: '0755' + remote_src: yes + register: hdbbackint_hanacleaner + +# Softlink Python +- name: SAP HANA Backint - Setup - AWS S3 - Create softlink of {{ python_37_binary }} into {{ sap_hana_backint_setup_backint_directory }} + ansible.builtin.file: + src: "{{ python_37_binary }}" + dest: "{{ sap_hana_backint_setup_backint_directory }}/python37_alt" + owner: "{{ passed_sap_hana_sid | lower }}adm" + group: sapsys + state: link + register: python_37_binary_hana_dir + +- name: SAP HANA Backint - Setup - AWS S3 - Set fact for Python altinstall + set_fact: + python_37_binary_hana_softlink: "{{ python_37_binary_hana_dir.dest }}" + +- name: SAP HANA Backint - Setup - AWS S3 - Replace hdbbackint launch script + template: + src: "{{ role_path }}/templates/hdbbackint.j2" + dest: "{{ sap_hana_backint_setup_backint_file }}" + mode: '0755' + +# Softlink hdbbackint +#- name: SAP HANA Backint - Setup - AWS S3 - {{ sap_hana_backint_setup_backint_file }} {{ sap_hana_backint_setup_backint_directory }}/hdbbackint +# ansible.builtin.file: +# src: "{{ sap_hana_backint_setup_backint_file }}" +# dest: "{{ sap_hana_backint_setup_backint_directory }}/hdbbackint" +# owner: "{{ passed_sap_hana_sid | lower }}adm" +# group: sapsys +# state: link + +# Process hdbbackint.cfg +- name: SAP HANA Backint - Setup - AWS S3 - Process hdbbackint.cfg + template: + src: "{{ role_path }}/templates/aws_s3_hdbbackint.cfg" + dest: "{{ sap_hana_backint_setup_backint_directory }}/hdbbackint.cfg" + mode: '0644' + register: hdbbackint_template + +# Change directory owner and permission +- name: SAP HANA Backint - Setup - AWS S3 - Change directory owner and permission + ansible.builtin.file: + path: "{{ sap_hana_backint_setup_backint_directory }}" + state: directory + recurse: yes + mode: '0755' + owner: "{{ passed_sap_hana_sid | lower }}adm" + group: sapsys + +- name: SAP HANA Backint - Setup - AWS S3 - Output backint configuration information + debug: + msg: + - "backint directory - {{ sap_hana_backint_setup_backint_directory }}" + - "backint file - {{ sap_hana_backint_setup_backint_file }}" + - "backint cfg - {{ sap_hana_backint_setup_backint_directory }}/hdbbackint.cfg" + - "backint link - {{ sap_hana_backint_setup_backint_directory }}/hdbbackint" diff --git a/roles/sap_hana_backint/tasks/setup/setup_backint_azure_backup_rsv.yml b/roles/sap_hana_backint/tasks/setup/setup_backint_azure_backup_rsv.yml new file mode 100644 index 0000000..c4208c2 --- /dev/null +++ b/roles/sap_hana_backint/tasks/setup/setup_backint_azure_backup_rsv.yml @@ -0,0 +1,68 @@ +# Microsoft Azure + +- name: SAP HANA Backint - Execute - Set fact for SID + set_fact: + passed_sap_hana_sid: "{{ sap_hana_sid }}" + when: sap_hana_sid != "all" + +# Set initial facts +- name: SAP HANA Backint - Setup - MS Azure Backup to MS Azure RSV - Set facts + set_fact: + sap_hana_backint_setup_backint_directory: "/usr/sap/{{ passed_sap_hana_sid }}/SYS/global/hdb/opt" + passed_sap_hana_instance_nr: "{{ sap_hana_instance_nr_one[0] }}" + + +- name: SAP HANA Backint - Setup - MS Azure Backup to MS Azure RSV - Download backint pre-registration script from constant script source url + get_url: + url: https://aka.ms/scriptforpermsonhana + dest: "{{ sap_hana_backint_setup_temp_dir.path }}/msawb-plugin-config-com-sap-hana.sh" + mode: '0775' + + +# Change directory owner and permission +- name: SAP HANA Backint - Setup - MS Azure Backup to MS Azure RSV - Change directory owner and permission + ansible.builtin.file: + path: "{{ sap_hana_backint_setup_backint_directory }}" + state: directory + recurse: yes + mode: '0755' + owner: "{{ passed_sap_hana_sid | lower }}adm" + group: sapsys + + +# Setup + +- name: SAP HANA Backint - Setup - MS Azure Backup to MS Azure RSV - Clone hanacleaner.py + ansible.builtin.git: + repo: https://github.com/chriselswede/hanacleaner.git + dest: "$HOME/.ansible/tmp/git_hanacleaner" + single_branch: yes + version: master + +# Copy hanacleaner.py +- name: SAP HANA Backint - Setup - MS Azure Backup to MS Azure RSV - Copy hanacleaner.py to SAP HANA backint directory + ansible.builtin.copy: + src: "$HOME/.ansible/tmp/git_hanacleaner/hanacleaner.py" + dest: "{{ sap_hana_backint_setup_backint_directory }}/hanacleaner.py" + mode: '0755' + remote_src: yes + register: hdbbackint_hanacleaner + +# Change directory owner and permission +- name: SAP HANA Backint - Setup - MS Azure Backup to MS Azure RSV - Change directory owner and permission + ansible.builtin.file: + path: "{{ sap_hana_backint_setup_backint_directory }}" + state: directory + recurse: yes + mode: '0755' + owner: "{{ passed_sap_hana_sid | lower }}adm" + group: sapsys + +- name: SAP HANA Backint - Setup - MS Azure Backup to MS Azure RSV - Execute msawb-plugin-config-com-sap-hana.sh + shell: | + bash "{{ sap_hana_backint_setup_temp_dir.path }}/msawb-plugin-config-com-sap-hana.sh" --sid {{ passed_sap_hana_sid }} + +- name: SAP HANA Backint - Setup - MS Azure Backup to MS Azure RSV - Output backint configuration information + debug: + msg: + - "backint directory - {{ sap_hana_backint_setup_backint_directory }}" diff --git a/roles/sap_hana_backint/tasks/setup/setup_backint_gcs.yml b/roles/sap_hana_backint/tasks/setup/setup_backint_gcs.yml new file mode 100644 index 0000000..0b4d707 --- /dev/null +++ b/roles/sap_hana_backint/tasks/setup/setup_backint_gcs.yml @@ -0,0 +1,60 @@ +# Google Cloud Storage (GCS) Bucket + +- name: SAP HANA Backint - Execute - Set fact for SID + set_fact: + passed_sap_hana_sid: "{{ sap_hana_sid }}" + when: sap_hana_sid != "all" + +# Set initial facts +- name: SAP HANA Backint - Setup - GCS - Set facts + set_fact: + sap_hana_backint_setup_backint_directory: "/usr/sap/{{ passed_sap_hana_sid }}/SYS/global/hdb/opt" + passed_sap_hana_instance_nr: "{{ sap_hana_instance_nr_one[0] }}" + + +- name: SAP HANA Backint - Setup - GCS - Clone hanacleaner.py + ansible.builtin.git: + repo: https://github.com/chriselswede/hanacleaner.git + dest: "$HOME/.ansible/tmp/git_hanacleaner" + single_branch: yes + version: master + +# Copy hanacleaner.py +- name: SAP HANA Backint - Setup - GCS - Copy hanacleaner.py to SAP HANA backint directory + ansible.builtin.copy: + src: "$HOME/.ansible/tmp/git_hanacleaner/hanacleaner.py" + dest: "{{ sap_hana_backint_setup_backint_directory }}/hanacleaner.py" + mode: '0755' + remote_src: yes + register: hdbbackint_hanacleaner + + +- name: SAP HANA Backint - Setup - GCS - Download temp dir + tempfile: + state: directory + suffix: gcs_download + register: gcs_download + become: yes + become_user: "{{ sap_hana_sid | lower }}adm" + +- name: SAP HANA Backint - Setup - GCS - Download Google Cloud Storage Backint agent for SAP HANA + get_url: + url: https://storage.googleapis.com/cloudsapdeploy/backint-gcs/install.sh + dest: "{{ gcs_download.path }}/gcs_backint_install.sh" + mode: '0775' + become_user: "{{ sap_hana_sid | lower }}adm" + +- name: SAP HANA Backint - Setup - GCS - Execute install.sh (silent) + shell: "{{ gcs_download.path }}/gcs_backint_install.sh" + args: + executable: /bin/bash + become: yes + become_user: "{{ sap_hana_sid | lower }}adm" + environment: + SAPSYSTEMNAME: "{{ passed_sap_hana_sid }}" + +# Add task to process /usr/sap/SID/SYS/global/hdb/opt/backint/backint-gcs/parameters.txt +# Use /templates/gcs_config.cfg +#catalog_backup_parameter_file /usr/sap/SID/SYS/global/hdb/opt/backint/backint-gcs/parameters-catalog.txt +#data_backup_parameter_file /usr/sap/SID/SYS/global/hdb/opt/backint/backint-gcs/parameters-data.txt +#log_backup_parameter_file /usr/sap/SID/SYS/global/hdb/opt/backint/backint-gcs/parameters-log.txt diff --git a/roles/sap_hana_backint/tasks/setup/setup_backint_ibm_cos_s3.yml b/roles/sap_hana_backint/tasks/setup/setup_backint_ibm_cos_s3.yml new file mode 100644 index 0000000..d32104f --- /dev/null +++ b/roles/sap_hana_backint/tasks/setup/setup_backint_ibm_cos_s3.yml @@ -0,0 +1,138 @@ +# IBM Cloud Object Storage (COS), S3 compatible + +- name: SAP HANA Backint - Execute - Set fact for SID + set_fact: + passed_sap_hana_sid: "{{ sap_hana_sid }}" + when: sap_hana_sid != "all" + +# Set initial facts +- name: SAP HANA Backint - Setup - IBM COS - Set facts + set_fact: + sap_hana_backint_setup_backint_directory: "/usr/sap/{{ passed_sap_hana_sid }}/SYS/global/hdb/opt" + passed_sap_hana_instance_nr: "{{ sap_hana_instance_nr_one[0] }}" + +- name: SAP HANA Backint - Setup - IBM COS - Discover tar.gz of SAP HANA Backint Agent for Amazon S3 + find: + paths: "/" + recurse: yes + patterns: "aws-s3-backint-*.tar.gz" + register: __sap_hana_backint_agent_s3_targz + +- name: SAP HANA Backint - Setup - IBM COS - Output tar.gz path of SAP HANA Backint Agent for Amazon S3 + debug: + msg: "{{ __sap_hana_backint_agent_s3_targz.files[0].path }}" + +- name: SAP HANA Backint - Setup - IBM COS - Extract tar.gz of SAP HANA Backint Agent for Amazon S3 + ansible.builtin.unarchive: + src: "{{ __sap_hana_backint_agent_s3_targz.files[0].path }}" + dest: "{{ sap_hana_backint_setup_temp_dir.path }}" + remote_src: yes + +- name: SAP HANA Backint - Setup - IBM COS - Discover binary of SAP HANA Backint Agent for Amazon S3 + find: + paths: "{{ sap_hana_backint_setup_temp_dir.path }}" + recurse: yes + file_type: directory + patterns: "aws-s3-backint" + register: __sap_hana_backint_agent_s3 + +- name: SAP HANA Backint - Setup - IBM COS - Output binary path of SAP HANA Backint Agent for Amazon S3 + debug: + msg: "{{ __sap_hana_backint_agent_s3.files[0].path }}" + +# src must end in forward-slash / to copy directory contents - https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html#parameter-src +- name: SAP HANA Backint - Setup - IBM COS - Copy temp directory contents to SAP HANA Backint directory + ansible.builtin.copy: + src: "{{ __sap_hana_backint_agent_s3.files[0].path }}/" + dest: "{{ sap_hana_backint_setup_backint_directory }}" + mode: '0755' + directory_mode: yes + remote_src: yes + +# Change directory owner and permission +- name: SAP HANA Backint - Setup - IBM COS - Change directory owner and permission + ansible.builtin.file: + path: "{{ sap_hana_backint_setup_backint_directory }}" + state: directory + recurse: yes + mode: '0755' + owner: "{{ passed_sap_hana_sid | lower }}adm" + group: sapsys + +- name: SAP HANA Backint - Setup - IBM COS - Set fact for backint file + set_fact: + sap_hana_backint_setup_backint_file: "{{ sap_hana_backint_setup_backint_directory }}/hdbbackint" + + +# Setup + +- name: SAP HANA Backint - Setup - IBM COS - Clone hanacleaner.py + ansible.builtin.git: + repo: https://github.com/chriselswede/hanacleaner.git + dest: "$HOME/.ansible/tmp/git_hanacleaner" + single_branch: yes + version: master + +# Copy hanacleaner.py +- name: SAP HANA Backint - Setup - IBM COS - Copy hanacleaner.py to SAP HANA backint directory + ansible.builtin.copy: + src: "$HOME/.ansible/tmp/git_hanacleaner/hanacleaner.py" + dest: "{{ sap_hana_backint_setup_backint_directory }}/hanacleaner.py" + mode: '0755' + remote_src: yes + register: hdbbackint_hanacleaner + +# Softlink Python +- name: SAP HANA Backint - Setup - IBM COS - Create softlink of {{ python_37_binary }} into {{ sap_hana_backint_setup_backint_directory }} + ansible.builtin.file: + src: "{{ python_37_binary }}" + dest: "{{ sap_hana_backint_setup_backint_directory }}/python37_alt" + owner: "{{ passed_sap_hana_sid | lower }}adm" + group: sapsys + state: link + register: python_37_binary_hana_dir + +- name: SAP HANA Backint - Setup - IBM COS - Set fact for Python altinstall + set_fact: + python_37_binary_hana_softlink: "{{ python_37_binary_hana_dir.dest }}" + +- name: SAP HANA Backint - Setup - IBM COS - Replace hdbbackint launch script + template: + src: "{{ role_path }}/templates/hdbbackint.j2" + dest: "{{ sap_hana_backint_setup_backint_file }}" + mode: '0755' + +# Softlink hdbbackint +#- name: SAP HANA Backint - Setup - IBM COS - {{ sap_hana_backint_setup_backint_file }} {{ sap_hana_backint_setup_backint_directory }}/hdbbackint +# ansible.builtin.file: +# src: "{{ sap_hana_backint_setup_backint_file }}" +# dest: "{{ sap_hana_backint_setup_backint_directory }}/hdbbackint" +# owner: "{{ passed_sap_hana_sid | lower }}adm" +# group: sapsys +# state: link + +# Process hdbbackint.cfg +- name: SAP HANA Backint - Setup - IBM COS - Process hdbbackint.cfg + template: + src: "{{ role_path }}/templates/ibm_cos_s3_hdbbackint.cfg" + dest: "{{ sap_hana_backint_setup_backint_directory }}/hdbbackint.cfg" + mode: '0644' + register: hdbbackint_template + +# Change directory owner and permission +- name: SAP HANA Backint - Setup - IBM COS - Change directory owner and permission + ansible.builtin.file: + path: "{{ sap_hana_backint_setup_backint_directory }}" + state: directory + recurse: yes + mode: '0755' + owner: "{{ passed_sap_hana_sid | lower }}adm" + group: sapsys + +- name: SAP HANA Backint - Setup - IBM COS - Output backint configuration information + debug: + msg: + - "backint directory - {{ sap_hana_backint_setup_backint_directory }}" + - "backint file - {{ sap_hana_backint_setup_backint_file }}" + - "backint cfg - {{ sap_hana_backint_setup_backint_directory }}/hdbbackint.cfg" + - "backint link - {{ sap_hana_backint_setup_backint_directory }}/hdbbackint" diff --git a/roles/sap_hana_backint/tasks/setup/setup_systemdb.yml b/roles/sap_hana_backint/tasks/setup/setup_systemdb.yml new file mode 100644 index 0000000..40e4ab9 --- /dev/null +++ b/roles/sap_hana_backint/tasks/setup/setup_systemdb.yml @@ -0,0 +1,102 @@ +# hdbuserstore + +# Return value +- name: SAP HANA Backint - Setup - SYSTEMDB - Set facts + set_fact: + sap_hana_backint_setup_hdbuserstore_system_backup_user: "HDB_SYSTEM_BACKUP_USER" + +# Create and Store Connection Info in hdbuserstore for SYSTEMDB +- name: SAP HANA Backint - Setup - SYSTEMDB - Create and Store Connection Info in hdbuserstore + shell: | + /usr/sap/{{ sap_hana_sid}}/SYS/exe/hdb/hdbuserstore \ + SET {{ sap_hana_backint_setup_hdbuserstore_system_backup_user }} {{ ansible_hostname }}:3{{ passed_sap_hana_instance_nr }}13 SYSTEM '{{ sap_hana_backint_setup_system_passwd }}' + args: + executable: /bin/bash + become: yes + become_user: "{{ sap_hana_sid | lower }}adm" + register: sap_hana_backint_setup_hdbuserstore + + +# Set SAP HANA parameters + +# Set log parameters if log_enable is true +- name: SAP HANA Backint - Setup - SYSTEMDB - Set log_mode parameters if log_enable is set to true + block: + # Set parameter log_mode + - name: SAP HANA Backint - Setup - SYSTEMDB - Set log_mode to normal + shell: | + /usr/sap/{{ sap_hana_sid }}/SYS/exe/hdb/hdbsql \ + -U {{ sap_hana_backint_setup_hdbuserstore_system_backup_user }} \ + -m < | yes | +| sap_update_profile_instance_nr | SAP system instance number | | yes | +| sap_update_profile_default_profile_params | List of parameters to update in the default profile | | no | +| sap_update_profile_instance_profile_params | List of parameters to update in the instance profile | | no | + +### Input and Execution + +- Sample execution: + + ```bash + ansible-playbook --connection=local --limit localhost -i "localhost," sap-profile-update.yml -e "@input_file.yml" + ``` + +- Sample direct input + + ```yaml + sap_update_profile_sid: "S20" + sap_update_profile_instance_nr: "00" + sap_update_profile_default_profile_params: + - sapgui/user_scripting = TRUE + - ssl/ciphersuites = 135:PFS:HIGH::EC_P256:EC_HIGH + - ssl/client_ciphersuites = 150:PFS:HIGH::EC_P256:EC_HIGH + sap_update_profile_instance_profile_params: + - PHYS_MEMSIZE = 32768 + - icm/server_port_0 = PROT=HTTP,PORT=80$$,PROCTIMEOUT=600,TIMEOUT=3600 + - icm/server_port_1 = PROT=HTTPS,PORT=443$$,PROCTIMEOUT=600,TIMEOUT=3600 + - icm/server_port_2 = PROT=SMTP,PORT=25$$,PROCTIMEOUT=120,TIMEOUT=120 + ``` + +- Sample playbook using `sap_facts` module to get all SAP systems in the host + + ```yaml + --- + - hosts: all + become: true + + vars: + + sap_update_profile_default_profile_params: + - sapgui/user_scripting = TRUE + - ssl/ciphersuites = 135:PFS:HIGH::EC_P256:EC_HIGH + - ssl/client_ciphersuites = 150:PFS:HIGH::EC_P256:EC_HIGH + sap_update_profile_instance_profile_params: + - PHYS_MEMSIZE = 32768 + - icm/server_port_0 = PROT=HTTP,PORT=80$$,PROCTIMEOUT=600,TIMEOUT=3600 + - icm/server_port_1 = PROT=HTTPS,PORT=443$$,PROCTIMEOUT=600,TIMEOUT=3600 + - icm/server_port_2 = PROT=SMTP,PORT=25$$,PROCTIMEOUT=120,TIMEOUT=120 + + tasks: + + # Gather SAP facts of the host + - name: Run sap_facts module to gather SAP facts + community.sap_operations.sap_facts: + param: "all" + register: sap_facts_register + + # Update all the profiles of the SAP systems in the host + - name: Update all the profiles of the SAP systems in the host + vars: + sap_update_profile_sid: "{{ item.SID }}" + sap_update_profile_instance_nr: "{{ item.InstanceNumber }}" + include_role: + name: community.sap_operations.sap_profile_update + loop: "{{ sap_facts_register.sap_facts }}" + when: + - item.Type == 'nw' + ``` diff --git a/roles/sap_profile_update/defaults/main.yml b/roles/sap_profile_update/defaults/main.yml new file mode 100644 index 0000000..442d640 --- /dev/null +++ b/roles/sap_profile_update/defaults/main.yml @@ -0,0 +1,20 @@ +# SAP system details +sap_update_profile_sid: +sap_update_profile_instance_nr: + +# List of profile parameters to be updated in the DEFAULT profile +sap_update_profile_default_profile_params: [] +# Sample list +# sap_update_profile_default_profile_params: +# - sapgui/user_scripting = TRUE +# - ssl/ciphersuites = 135:PFS:HIGH::EC_P256:EC_HIGH +# - ssl/client_ciphersuites = 150:PFS:HIGH::EC_P256:EC_HIGH + +# List of profile parameters to be updated in the instance profile +sap_update_profile_instance_profile_params: [] +# Sample list +# sap_update_profile_instance_profile_params: +# - PHYS_MEMSIZE = 32768 +# - icm/server_port_0 = PROT=HTTP,PORT=80$$,PROCTIMEOUT=600,TIMEOUT=3600 +# - icm/server_port_1 = PROT=HTTPS,PORT=443$$,PROCTIMEOUT=600,TIMEOUT=3600 +# - icm/server_port_2 = PROT=SMTP,PORT=25$$,PROCTIMEOUT=120,TIMEOUT=120 diff --git a/roles/sap_profile_update/tasks/main.yml b/roles/sap_profile_update/tasks/main.yml new file mode 100644 index 0000000..31ae248 --- /dev/null +++ b/roles/sap_profile_update/tasks/main.yml @@ -0,0 +1,37 @@ +--- + +# Get instance profile path using sapcontrol +- name: SAP Profile Update - Get Instance Profile + shell: | + source ~/.profile && cdexe; sapcontrol -nr {{ sap_update_profile_instance_nr }} -function ParameterValue SAPPROFILE | grep profile + args: + executable: /bin/bash + become: yes + become_user: "{{ sap_update_profile_sid | lower }}adm" + register: get_instance_profile + +- set_fact: + sap_update_profile_default_profile_file_path : "/sapmnt/{{ sap_update_profile_sid }}/profile/DEFAULT.PFL" + sap_update_profile_instance_profile_file_path : "{{ get_instance_profile.stdout }}" + +# Update default profile +- name: SAP Profile Update - Updating DEFAULT.PFL + include_tasks : update_parameter.yml + vars: + passed_parameter_path: "{{ sap_update_profile_default_profile_file_path }}" + loop: "{{ sap_update_profile_default_profile_params }}" + loop_control: + loop_var: passed_parameter + when: + - sap_update_profile_default_profile_params is defined + +# Update instance profile +- name: SAP Profile Update - Updating instance profile + include_tasks : update_parameter.yml + vars: + passed_parameter_path: "{{ sap_update_profile_instance_profile_file_path }}" + loop: "{{ sap_update_profile_instance_profile_params }}" + loop_control: + loop_var: passed_parameter + when: + - sap_update_profile_default_profile_params is defined \ No newline at end of file diff --git a/roles/sap_profile_update/tasks/update_parameter.yml b/roles/sap_profile_update/tasks/update_parameter.yml new file mode 100644 index 0000000..5905c35 --- /dev/null +++ b/roles/sap_profile_update/tasks/update_parameter.yml @@ -0,0 +1,13 @@ +--- +# passed_parameter.split()[0] = first word of passed_parameter used as regex +# Add comment using: +# line: "# Updated by sap_profile_update - {{ lookup('pipe','date \"+%Y-%m-%d %H:%M\"') }}\n{{ passed_parameter }}" + +- name: Update "{{ passed_parameter }}" + lineinfile: + path: "{{ passed_parameter_path }}" + regexp: '^{{ passed_parameter.split()[0] }}' + line: "{{ passed_parameter }}" + owner: "{{ sap_update_profile_sid | lower }}adm" + group: sapsys + mode: '0644' diff --git a/roles/sap_pyrfc/README.md b/roles/sap_pyrfc/README.md new file mode 100644 index 0000000..8ed092c --- /dev/null +++ b/roles/sap_pyrfc/README.md @@ -0,0 +1,74 @@ +# sap_pyrfc Ansible Role + +The sap_pyrfc Ansible Role uses the open-source [`PyRFC`](https://github.com/SAP/PyRFC) bindings for the proprietary [`SAP NWRFC SDK`](https://support.sap.com/en/product/connectors/nwrfcsdk.html). + +## Ansible Role Overview + +This Ansible Role executes SAP RFCs, by downloading the SAP NWRFC SDK and using PyRFC. + +Appropriate SAP User ID with download privileges, and target SAP System user authorizations are required. + +Examples are given, however the Ansible Role does not contain any system-altering RFCs by default when executed. The actions must be defined in the Ansible Task variables. + +## Ansible Role Requirements and Dependencies + +### Dependencies on other Ansible Roles + +To execute successfully, this Ansible Role is dependant on the Ansible Collection `community.sap_launchpad` on first run. Subsequent runs on the same host will re-use the Python altinstall with PyRFC enabled. + +### Operating System + +This role has been tested with RHEL, and is designed for Linux operating systems. + +This role has not been tested and amended for SAP NetWeaver Application Server instantiations on IBM AIX or Windows Server. + +Assumptions for executing this role include: +- Instances of SAP NetWeaver Application Server are installed to the target host +- Relevent OS Packages for SAP are installed +- Registered OS License and OS Package repositories are available (from the relevant content delivery network of the OS vendor) + +## Ansible Tasks using PyRFC for different RFC parameter’s data types + +RFC parameters _(IMPORTING, EXPORTING, CHANGING)_ can accept the following data types: +- Data elements (string, integer etc.) +- ABAP Structure +- ABAP Table + +### Examples of Ansible Task variables for different RFC parameters + +**RFC parameter requires a data element:** +- Commonly string or integer +- For this input type the PyRFC module requires a Python string or integer +- Ansible Task must declare the parameter name and use a YAML variable - `see example format below:` +```yaml +parameters: + VAR: 'ECHO' +``` + +**RFC parameter requires an ABAP Structure:** +- For this input type the PyRFC module requires a Python Dictionary. +- Ansible Task must declare the parameter name and use a YAML dictionary (key:value) - `see example format below:` +```yaml +parameters: + IMPORTSTRUCT: + RFCFLOAT: 1.1 + RFCCHAR1: 'A' +``` + +**RFC requires an ABAP Table:** +- For this input type the PyRFC module requires a Python List. +- The Ansible Task must declare the parameter name and use a YAML list - `see example format below:` +```yaml +parameters: + RFCTABLE: + - COLUMN0: SAP + - COLUMN1: 1.23 +``` + +## Supplementary information + +The [Supplementary information](./SUPPLEMENTARY.md) provides: +- Context of SAP NWRFC SDK and PyRFC + - Explanation of samples from SAP NWRFC SDK +- Brief explanation of SAP RFCs +- Security concerns with RFCs and SAP Systems diff --git a/roles/sap_pyrfc/SUPPLEMENTARY.md b/roles/sap_pyrfc/SUPPLEMENTARY.md new file mode 100644 index 0000000..9bdf2ad --- /dev/null +++ b/roles/sap_pyrfc/SUPPLEMENTARY.md @@ -0,0 +1,66 @@ +# Supplementary information for sap_pyrfc Ansible Role + +## Brief explanation of SAP RFCs + +ABAP Function Modules interface parameters have different types: +- **Exporting parameters** = call the function module with parameter and data payload, the function module will use the data and provide a response (to the calling source/program). Always optional. +- **Importing parameters** = call the function module with parameter and data payload, the function module will use the data. Cannot be amended, may be required or optional. +- **Changing parameters** = call the function module with parameter and data payload, the function module will process the data and provide an amended response (to the calling source/program). May be required or optional. +- **Tables parameters** = call the function module with parameter and data payload with internal ABAP table data structures. + +RFC parameters _(IMPORTING, EXPORTING, CHANGING)_ can accept the following data types: +- Data elements (string, integer) +- ABAP Structure +- ABAP Table + +RFC parameters (TABLE) is deprecated and used for backwards compatibility, this can only accept one data type: +- ABAP Table + +For futher information, refer to [ABAP Programming Language - Built-In Data Types - SAP NetWeaver AS ABAP 7.56](https://help.sap.com/doc/abapdocu_756_index_htm/7.56/en-us/abenbuilt_in_types_complete.htm). + +## Context of SAP NWRFC SDK and PyRFC + +For clarity, there are two SDKs for calling RFCs in SAP Systems which have been released by SAP: +1. [SAP NetWeaver Remote Function Call (RFC) Software Development Kit (SDK) (aka. "SAP NWRFC SDK")](https://support.sap.com/en/product/connectors/nwrfcsdk.html). `Supported by SAP`. +2. Classical "RFC SDK", previously bundled with the SAP Kernel and using ASCII ILE, EBCDIC ILE, UNICODE ILE, or PASE Unicode. `Deprecated as of SAP Kernel 7.41, on 2016-03-31`. + +The SAP NetWeaver RFC SDK is a proprietary set of compiled binaries which provides a C/C++ interface for connecting to SAP Systems, enabling development of programs which call ABAP functionality (RFC clients) and programs which can be called from ABAP (RFC servers). + +More details including a Programming Guide and Doxygen documentation can be found on [sap.com - connectors - nwrfcsdk](https://support.sap.com/en/product/connectors/nwrfcsdk.html) + +Alternatives to the SAP NWRFC SDK include the SAP Java Connector (JCo), and SAP Connector for Microsoft .NET (NCo). + +Optionally, the SAP NetWeaver RFC SDK can be called by multiple connectors/bindings for different runtime environments; which are released to open-source by SAP: +- Python: [PyRFC](https://github.com/SAP/PyRFC) +- Node.js: [node-rfc](https://github.com/SAP/node-rfc) +- Go: [gorfc](https://github.com/SAP/gorfc) + +Other connectors/bindings to have previously existed but are infrequently used with the SAP NetWeaver RFC SDK are: +- R: [RSAP](https://github.com/piersharding/RSAP) +- Ruby: [ruby-sapnwrfc](https://github.com/piersharding/ruby-sapnwrfc) +- PHP 7/8: [php7-sapnwrfc](https://github.com/gkralik/php7-sapnwrfc) + +### Samples from SAP NWRFC SDK + +This additional context is to avoid confusion. By default the SAP NetWeaver RFC SDK includes two sample ALE programs (rfcexec or startrfc) as reference implementations which support the SAP IDoc scenario. + +Neither of these sample programs are used by sap_pyrfc Ansible Role or the underlying PyRFC bindings. + +These sample ALE programs appear in documentation available on help.sap.com under the IDoc Interface/ALE section: +- Using startrfc, [SAP NetWeaver AS - IDoc Interface/ALE - Inbound: Triggering the SAP System](https://help.sap.com/viewer/8f3819b0c24149b5959ab31070b64058/7.52.3/en-US/4b4c43dd3b71265ce10000000a421937.html) +- Using rfcexec, [SAP NetWeaver AS - IDoc Interface/ALE - Outbound: Triggering the Receiving System](https://help.sap.com/viewer/8f3819b0c24149b5959ab31070b64058/7.52.3/en-US/4b403cbe6f820a93e10000000a421937.html) + - The rfcexec program is a generic RFC server and requires a forbidden/negative list for the operating system states which cannot be executed. A sample file 'rfcexec.sec' demonstrates those security configurations and is covered further in SAP Note 618516 - Security-related enhancement of RFCEXEC program. + +## Security concerns with RFCs and SAP Systems + +RFCs are very powerful, and with the wrong user privileges can be a security concern. + +A useful RFC used by technical administrators is `RFC_ABAP_INSTALL_AND_RUN` which temporarily stores and executes an ABAP Report, which helps to demonstrate potential security concerns. This is now deprecated as of SAP S/4HANA 1809, as it could be used for arbitrary code execution onto SAP Systems. + +**RFC_ABAP_INSTALL_AND_RUN workflow:** +- Call RFC with the PROGRAMNAME Importing Parameter (e.g. Z$$$XRFC), MODE Importing Parameter and ABAP Report to the PROGRAM Tables Parameter (with multiple Table Component called LINE and maximum 72 characters) +- Create temporary ABAP Report "Z$$$XRFC" +- Execute temporary ABAP Report "Z$$$XRFC" +- Store result of the executed ABAP Report into the WRITES Table Parameter +- Remove the temporary ABAP Report "Z$$$XRFC" +- Respond to the calling source/program with the WRITES Table Parameter or the ERRORMESSAGE Importing Parameter diff --git a/roles/sap_pyrfc/defaults/main.yml b/roles/sap_pyrfc/defaults/main.yml new file mode 100644 index 0000000..3073aa8 --- /dev/null +++ b/roles/sap_pyrfc/defaults/main.yml @@ -0,0 +1,22 @@ + +pyrfc_first_run: yes + +sap_nwrfc_sdk: nwrfc750P_8-70002752.zip + +target_function: STFC_CONNECTION + +target_parameters: + REQUTEXT: 'Hello SAP!' + +target_connection: + ashost: s4hana.poc.cloud + sysid: TDT + sysnr: "01" + client: "400" + user: DDIC + passwd: Password1 + lang: EN +# trace: 3 +# saprouter: /H/111.22.33.44/S/3299/W/e5ngxs/H/555.66.777.888/H/ +# gwhost: gateway.poc.cloud +# ghserv: gateway.poc.cloud diff --git a/roles/sap_pyrfc/tasks/main.yml b/roles/sap_pyrfc/tasks/main.yml new file mode 100644 index 0000000..dffc140 --- /dev/null +++ b/roles/sap_pyrfc/tasks/main.yml @@ -0,0 +1,18 @@ +--- + +- name: Initial install of Python altinstall for PyRFC + include_tasks: python_pyrfc_init.yml + when: pyrfc_first_run == yes + +- name: Discover Python altinstall for PyRFC + include_tasks: python_pyrfc_discover.yml + when: pyrfc_first_run == no + +# RFC call using variables set by the Ansible Playbook when calling this Ansible Role +- name: Execute Ansible Module to call PyRFC using the virtualenv Python3 + sap_pyrfc: + function: "{{ target_function }}" + parameters: "{{ target_parameters }}" + connection: "{{ target_connection }}" + vars: + ansible_python_interpreter: ~/env/pyrfc_env/bin/python3 diff --git a/roles/sap_pyrfc/tasks/python_pyrfc_discover.yml b/roles/sap_pyrfc/tasks/python_pyrfc_discover.yml new file mode 100644 index 0000000..6b1435b --- /dev/null +++ b/roles/sap_pyrfc/tasks/python_pyrfc_discover.yml @@ -0,0 +1,2 @@ + +# ? \ No newline at end of file diff --git a/roles/sap_pyrfc/tasks/python_pyrfc_init.yml b/roles/sap_pyrfc/tasks/python_pyrfc_init.yml new file mode 100644 index 0000000..26a4e4b --- /dev/null +++ b/roles/sap_pyrfc/tasks/python_pyrfc_init.yml @@ -0,0 +1,69 @@ +# Dependency on Ansible Role for SAP Launchpad +#- collections: +# - community.sap_launchpad + +- name: Create directories if does not exist + file: + path: "{{ item }}" + state: directory + mode: '0755' + with_items: + - "/software" + - "/usr/local/sap" + +- name: Install Python package manager pip3, and compiler/library for C and C++ + yum: + name: + - python3-pip + - gcc + - glibc + - gcc-c++ + - libstdc++ + state: present + +- name: Install virtualenv to system Python + pip: + name: + - virtualenv + +- name: Create virtualenv for python3, install dependencies to virtualenv. Do not inherit system Python modules + pip: + name: + - cython + - wheel + - pytest + - sphinx + virtualenv: ~/env/pyrfc_env + virtualenv_command: virtualenv + virtualenv_python: python3 + +# Use task block to call Ansible Module in the Ansible Collection for sap_operations +- 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: "{{ sap_nwrfc_sdk }}" + dest: "/software" + +- name: Discover zip of the SAP NWRFC SDK on target host + find: + paths: "/software" + recurse: yes + patterns: "nwrfc*.zip" + register: __sap_nwrfc_sdk + +- name: Extract zip of the SAP NWRFC SDK on target host + ansible.builtin.unarchive: + remote_src: yes + src: "{{ __sap_nwrfc_sdk.files[0].path }}" + dest: /usr/local/sap + +- name: Install pyrfc to virtualenv on target host. Do not inherit system Python modules + environment: + SAPNWRFC_HOME: /usr/local/sap/nwrfcsdk + pip: + name: + - pyrfc + virtualenv: ~/env/pyrfc_env + virtualenv_command: virtualenv + virtualenv_python: python3 diff --git a/roles/sap_rhsm/README.md b/roles/sap_rhsm/README.md new file mode 100644 index 0000000..829b243 --- /dev/null +++ b/roles/sap_rhsm/README.md @@ -0,0 +1,68 @@ +# sap_rhsm Ansible Role + +Ansible role for RHEL registration / refresh + +## Overview + +### Variables + +| **Variable** | **Info** | **Default** | **Required** | +| :--- | :--- | :--- | :--- | +| sap_rhsm_function | 'register' or 'refresh' | 'register' | yes | +| sap_rhsm_username | RHEL User for access.redhat.com | | yes | +| sap_rhsm_password | Password for access.redhat.com | | yes | +| sap_rhsm_pool_id | Subscription pool id | | yes | +| sap_rhsm_repos | List of repositories to enable | | yes | +| sap_rhsm_packages | List of packages to install | | yes | + +### Input and Execution + +- Sample execution: + + ```bash + ansible-playbook --connection=local --limit localhost -i "localhost," sap-rhsm-register.yml" + ``` + +- Sample playbook + - Register + ```yaml + --- + - hosts: all + become: true + vars: + sap_rhsm_function: "register" + sap_rhsm_username: "my_rhel_user" + sap_rhsm_password: "my_rhel_password" + sap_rhsm_pools_id: "8x8x8x8x8x88x8x8x8x8x8x8x8x" + sap_rhsm_repos: + - rhel-8-for-x86_64-baseos-e4s-rpms + - rhel-8-for-x86_64-appstream-e4s-rpms + - rhel-8-for-x86_64-sap-solutions-e4s-rpms + - rhel-8-for-x86_64-sap-netweaver-e4s-rpms + - rhel-8-for-x86_64-highavailability-e4s-rpms + - ansible-2-for-rhel-8-x86_64-rpms + sap_rhsm_packages: + - yum-utils + - nfs-utils + roles: + - { role: community.sap_operations.sap_rhsm } + ``` + - Refresh + ```yaml + --- + - hosts: all + become: true + vars: + sap_rhsm_function: "refresh" + roles: + - { role: community.sap_operations.sap_rhsm } + ``` +- Sample result + + ```console + cat /etc/hosts + 10.0.0.1 hana01-lb.poc.cloud hana01-lb + 10.0.0.2 hana02-lb.poc.cloud hana02-lb + 10.0.1.1 s4hana01-ci.poc.cloud s4hana01-ci + 10.0.1.2 s4hana01-app.poc.cloud s4hana01-app + ``` diff --git a/roles/sap_rhsm/defaults/main.yml b/roles/sap_rhsm/defaults/main.yml new file mode 100644 index 0000000..15843e5 --- /dev/null +++ b/roles/sap_rhsm/defaults/main.yml @@ -0,0 +1,7 @@ +sap_rhsm_function: "register" + +sap_rhsm_username: +sap_rhsm_password: +sap_rhsm_pool_id: +sap_rhsm_repos: [] +sap_rhsm_packages: [] \ No newline at end of file diff --git a/roles/sap_rhsm/tasks/main.yml b/roles/sap_rhsm/tasks/main.yml new file mode 100644 index 0000000..a568b3c --- /dev/null +++ b/roles/sap_rhsm/tasks/main.yml @@ -0,0 +1,2 @@ +- name: SAP OS Tools - RedHat Subscription Manager + include_tasks: "rhsm_{{ sap_rhsm_function }}.yml" \ No newline at end of file diff --git a/roles/sap_rhsm/tasks/rhsm_refresh.yml b/roles/sap_rhsm/tasks/rhsm_refresh.yml new file mode 100644 index 0000000..6a90be5 --- /dev/null +++ b/roles/sap_rhsm/tasks/rhsm_refresh.yml @@ -0,0 +1,31 @@ +- name: SAP OS Tools - RedHat Subscription Manager - Refresh - {{ ansible_distribution }} - {{ ansible_distribution_version }} + block: + + - name: Subscription Manager - Refresh + command: 'subscription-manager refresh' + + - name: Subscription Manager - Identity Regenerate + command: 'subscription-manager identity --regenerate' + + - name: Subscription Manager - Lock Release + command: 'subscription-manager release --set={{ ansible_distribution_version }}' + + - name: Yum clean all + command: 'yum clean all' + + - name: Clear dnf cache + file: + path: /var/cache/dnf + state: absent + + - name: Clear rhsm packages + file: + path: /var/lib/rhsm/packages/packages.json + state: absent + + - name: Restart service rhsmcertd + service: + name: rhsmcertd + state: restarted + + when: ansible_facts['distribution'] == 'RedHat' diff --git a/roles/sap_rhsm/tasks/rhsm_register.yml b/roles/sap_rhsm/tasks/rhsm_register.yml new file mode 100644 index 0000000..f261dfe --- /dev/null +++ b/roles/sap_rhsm/tasks/rhsm_register.yml @@ -0,0 +1,80 @@ +- name: SAP OS Tools - RedHat Subscription Manager - Register - {{ ansible_distribution }} - {{ ansible_distribution_version }} + block: + + # - name: Subscription Manager - Initial Commands + # command: 'subscription-manager {{ item }}' + # loop: + # - "clean" + # - "remove --all" + # - "register --force --username={{ sap_rhsm_username }} --password='{{ sap_rhsm_password }}'" + # - "identity --regenerate --force --username={{ sap_rhsm_username }} --password='{{ sap_rhsm_password }}'" + # - "auto-attach" + # - "attach --pool='{{ sap_rhsm_pool_id }}'" + # - "release --set={{ ansible_distribution_version }}" + # - "repos --disable='*'" + + - name: Subscription Manager - Clean + command: 'subscription-manager clean' + + - name: Subscription Manager - Remove + command: 'subscription-manager remove --all' + + - name: Subscription Manager - Register + command: 'subscription-manager register --force --username={{ sap_rhsm_username }} --password="{{ sap_rhsm_password }}"' + + - name: Subscription Manager - Identity Regenerate + command: 'subscription-manager identity --regenerate --force --username={{ sap_rhsm_username }} --password="{{ sap_rhsm_password }}"' + + - name: Subscription Manager - Auto Attach + command: 'subscription-manager auto-attach' + + - name: Subscription Manager - Attach Pool + command: 'subscription-manager attach --pool="{{ sap_rhsm_pool_id }}"' + + - name: Subscription Manager - Lock Release + command: 'subscription-manager release --set={{ ansible_distribution_version }}' + + - name: Subscription Manager - Disable Repos + command: 'subscription-manager repos --disable="*"' + + - name: Subscription Manager - Enable Repos + command: 'subscription-manager repos --enable="{{ item }}"' + loop: "{{ sap_rhsm_repos }}" + + - name: Yum clean all + command: 'yum clean all' + args: + warn: false + + - name: Yum search sap- + command: 'yum -y search sap-' + + # - name: Yum install yum-utils + # command: 'yum -y install yum-utils' + + # - name: Yum install nfs-utils + # command: 'yum -y install nfs-utils' + + - name: Yum install + command: 'yum -y install {{ item }}' + loop: "{{ sap_rhsm_packages }}" + + - name: Clear dnf cache + file: + path: /var/cache/dnf + state: absent + + - name: Clear rhsm packages + file: + path: /var/lib/rhsm/packages/packages.json + state: absent + + - name: Restart service rhsmcertd + service: + name: rhsmcertd + state: restarted + + #- name: Register the system to Red Hat Insights + # command: 'insights-client --register' + + when: ansible_facts['distribution'] == 'RedHat' diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29