summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/sap_libs
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/sap_libs')
-rw-r--r--ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/bug_report.yml149
-rw-r--r--ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/config.yml27
-rw-r--r--ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/documentation_report.yml125
-rw-r--r--ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/feature_request.yml69
-rw-r--r--ansible_collections/community/sap_libs/.github/workflows/ansible-test.yml122
-rw-r--r--ansible_collections/community/sap_libs/.vscode/extensions.json5
-rw-r--r--ansible_collections/community/sap_libs/CHANGELOG.rst134
-rw-r--r--ansible_collections/community/sap_libs/CODE_OF_CONDUCT.md3
-rw-r--r--ansible_collections/community/sap_libs/CONTRIBUTING.md3
-rw-r--r--ansible_collections/community/sap_libs/FILES.json614
-rw-r--r--ansible_collections/community/sap_libs/LICENSE201
-rw-r--r--ansible_collections/community/sap_libs/MAINTAINERS2
-rw-r--r--ansible_collections/community/sap_libs/MAINTAINING.md3
-rw-r--r--ansible_collections/community/sap_libs/MANIFEST.json32
-rw-r--r--ansible_collections/community/sap_libs/README.md182
-rw-r--r--ansible_collections/community/sap_libs/REVIEW_CHECKLIST.md3
-rw-r--r--ansible_collections/community/sap_libs/changelogs/changelog.yaml116
-rw-r--r--ansible_collections/community/sap_libs/changelogs/config.yaml30
-rw-r--r--ansible_collections/community/sap_libs/changelogs/fragments/.keep0
-rw-r--r--ansible_collections/community/sap_libs/codecov.yml2
-rw-r--r--ansible_collections/community/sap_libs/meta/runtime.yml15
-rw-r--r--ansible_collections/community/sap_libs/plugins/doc_fragments/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/plugins/module_utils/pyrfc_handler.py48
-rwxr-xr-xansible_collections/community/sap_libs/plugins/module_utils/swpm2_parameters_inifile_generate.py271
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sap_company.py335
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sap_control_exec.py405
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sap_hdbsql.py246
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sap_pyrfc.py187
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sap_snote.py267
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sap_system_facts.py213
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sap_task_list_execute.py350
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sap_user.py508
-rw-r--r--ansible_collections/community/sap_libs/plugins/modules/sapcar_extract.py228
-rw-r--r--ansible_collections/community/sap_libs/tests/integration/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/tests/integration/targets/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/tests/sanity/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/tests/sanity/ignore-2.10.txt9
-rw-r--r--ansible_collections/community/sap_libs/tests/sanity/ignore-2.11.txt9
-rw-r--r--ansible_collections/community/sap_libs/tests/sanity/ignore-2.12.txt9
-rw-r--r--ansible_collections/community/sap_libs/tests/sanity/ignore-2.13.txt9
-rw-r--r--ansible_collections/community/sap_libs/tests/sanity/ignore-2.14.txt9
-rw-r--r--ansible_collections/community/sap_libs/tests/sanity/ignore-2.15.txt9
-rw-r--r--ansible_collections/community/sap_libs/tests/sanity/ignore-2.9.txt9
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/compat/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/compat/builtins.py24
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/compat/mock.py122
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/compat/unittest.py38
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/mock/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/mock/loader.py102
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/mock/path.py8
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/mock/procenv.py76
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/mock/vault_helper.py27
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/mock/yaml_helper.py126
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_company.py136
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_control_exec.py129
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_hdbsql.py102
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_pyrfc.py76
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_snote.py181
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_system_facts.py105
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_task_list_execute.py89
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_user.py189
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sapcar_extract.py53
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/plugins/modules/utils.py52
-rw-r--r--ansible_collections/community/sap_libs/tests/unit/requirements.txt7
68 files changed, 6600 insertions, 0 deletions
diff --git a/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/bug_report.yml b/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 000000000..e6108e4b0
--- /dev/null
+++ b/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,149 @@
+---
+name: Bug report
+description: Create a report to help us improve
+
+body:
+- type: markdown
+ attributes:
+ value: |
+ ⚠
+ Verify first that your issue is not [already reported on GitHub][issue search].
+ Also test if the latest release and devel branch are affected too.
+ *Complete **all** sections as described, this form is processed automatically.*
+
+ [issue search]: https://github.com/sap-linuxlab/community.sap_libs/search?q=is%3Aissue&type=issues
+
+
+- type: textarea
+ attributes:
+ label: Summary
+ description: Explain the problem briefly below.
+ placeholder: >-
+ When I try to do X with the collection from the main branch on GitHub, Y
+ breaks in a way Z under the env E. Here are all the details I know
+ about this problem...
+ validations:
+ required: true
+
+- type: dropdown
+ attributes:
+ label: Issue Type
+ # FIXME: Once GitHub allows defining the default choice, update this
+ options:
+ - Bug Report
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ # For smaller collections we could use a multi-select and hardcode the list
+ # May generate this list via GitHub action and walking files under https://github.com/sap-linuxlab/community.sap_libs/tree/main/plugins
+ # Select from list, filter as you type (`mysql` would only show the 3 mysql components)
+ # OR freeform - doesn't seem to be supported in adaptivecards
+ label: Component Name
+ description: >-
+ Write the short name of the module, plugin, task or feature below,
+ *use your best guess if unsure*.
+ placeholder: dnf, apt, yum, pip, user etc.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Ansible Version
+ description: >-
+ Paste verbatim output from `ansible --version` between
+ tripple backticks.
+ value: |
+ ```console (paste below)
+ $ ansible --version
+
+ ```
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: community.sap_libs Version
+ description: >-
+ Paste verbatim output from "ansible-galaxy collection list community.sap_libs"
+ between tripple backticks.
+ value: |
+ ```console (paste below)
+ $ ansible-galaxy collection list community.sap_libs
+
+ ```
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Configuration
+ description: >-
+ If this issue has an example piece of YAML that can help to reproduce this problem, please provide it.
+ This can be a piece of YAML from, e.g., an automation, script, scene or configuration.
+ Paste verbatim output from `ansible-config dump --only-changed` between quotes
+ value: |
+ ```console (paste below)
+ $ ansible-config dump --only-changed
+
+ ```
+
+
+- type: textarea
+ attributes:
+ label: OS / Environment
+ description: >-
+ Provide all relevant information below, e.g. target OS versions,
+ network device firmware, etc.
+ placeholder: RHEL 8, SLES
+ validations:
+ required: false
+
+
+- type: textarea
+ attributes:
+ label: Steps to Reproduce
+ description: |
+ Describe exactly how to reproduce the problem, using a minimal test-case. It would *really* help us understand your problem if you could also pased any playbooks, configs and commands you used.
+
+ **HINT:** You can paste https://gist.github.com links for larger files.
+ value: |
+ <!--- Paste example playbooks or commands between quotes below -->
+ ```yaml (paste below)
+
+ ```
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Expected Results
+ description: >-
+ Describe what you expected to happen when running the steps above.
+ placeholder: >-
+ I expected X to happen because I assumed Y.
+ that it did not.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Actual Results
+ description: |
+ Describe what actually happened. If possible run with extra verbosity (`-vvvv`).
+
+ Paste verbatim command output between quotes.
+ value: |
+ ```console (paste below)
+
+ ```
+- type: checkboxes
+ attributes:
+ label: Code of Conduct
+ description: |
+ Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--sap-linuxlab) first.
+ options:
+ - label: I agree to follow the Ansible Code of Conduct
+ required: true
+...
diff --git a/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/config.yml b/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..f90bd1ad8
--- /dev/null
+++ b/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,27 @@
+---
+# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
+blank_issues_enabled: false # default: true
+contact_links:
+- name: Security bug report
+ url: https://docs.ansible.com/ansible-core/devel/community/reporting_bugs_and_features.html?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
+ about: |
+ Please learn how to report security vulnerabilities here.
+
+ For all security related bugs, email security@ansible.com
+ instead of using this issue tracker and you will receive
+ a prompt response.
+
+ For more information, see
+ https://docs.ansible.com/ansible/latest/community/reporting_bugs_and_features.html
+- name: Ansible Code of Conduct
+ url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
+ about: Be nice to other members of the community.
+- name: Talks to the community
+ url: https://docs.ansible.com/ansible/latest/community/communication.html?utm_medium=github&utm_source=issue_template_chooser#mailing-list-information
+ about: Please ask and answer usage questions here
+- name: Working groups
+ url: https://github.com/ansible/community/wiki
+ about: Interested in improving a specific area? Become a part of a working group!
+- name: For Enterprise
+ url: https://www.ansible.com/products/engine?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
+ about: Red Hat offers support for the Ansible Automation Platform
diff --git a/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/documentation_report.yml b/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/documentation_report.yml
new file mode 100644
index 000000000..e690c7df3
--- /dev/null
+++ b/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/documentation_report.yml
@@ -0,0 +1,125 @@
+---
+name: Documentation Report
+description: Ask us about docs
+# NOTE: issue body is enabled to allow screenshots
+
+body:
+- type: markdown
+ attributes:
+ value: |
+ ⚠
+ Verify first that your issue is not [already reported on GitHub][issue search].
+ Also test if the latest release and devel branch are affected too.
+ *Complete **all** sections as described, this form is processed automatically.*
+
+ [issue search]: https://github.com/sap-linuxlab/community.sap_libs/search?q=is%3Aissue&type=issues
+
+
+- type: textarea
+ attributes:
+ label: Summary
+ description: |
+ Explain the problem briefly below, add suggestions to wording or structure.
+
+ **HINT:** Did you know the documentation has an `Edit on GitHub` link on every page?
+ placeholder: >-
+ I was reading the Collection documentation of version X and I'm having
+ problems understanding Y. It would be very helpful if that got
+ rephrased as Z.
+ validations:
+ required: true
+
+- type: dropdown
+ attributes:
+ label: Issue Type
+ # FIXME: Once GitHub allows defining the default choice, update this
+ options:
+ - Documentation Report
+ validations:
+ required: true
+
+- type: input
+ attributes:
+ label: Component Name
+ description: >-
+ Write the short name of the rst file, module, plugin, task or
+ feature below, *use your best guess if unsure*.
+ placeholder: mysql_user
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Ansible Version
+ description: >-
+ Paste verbatim output from `ansible --version` between
+ tripple backticks.
+ value: |
+ ```console (paste below)
+ $ ansible --version
+
+ ```
+ validations:
+ required: false
+
+- type: textarea
+ attributes:
+ label: Community.sap_libs Version
+ description: >-
+ Paste verbatim output from "ansible-galaxy collection list community.sap_libs"
+ between tripple backticks.
+ value: |
+ ```console (paste below)
+ $ ansible-galaxy collection list community.sap_libs
+
+ ```
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Configuration
+ description: >-
+ Paste verbatim output from `ansible-config dump --only-changed` between quotes.
+ value: |
+ ```console (paste below)
+ $ ansible-config dump --only-changed
+
+ ```
+ validations:
+ required: false
+
+- type: textarea
+ attributes:
+ label: OS / Environment
+ description: >-
+ Provide all relevant information below, e.g. OS version,
+ browser, etc.
+ placeholder: SLES, RHEL
+ validations:
+ required: false
+
+- type: textarea
+ attributes:
+ label: Additional Information
+ description: |
+ Describe how this improves the documentation, e.g. before/after situation or screenshots.
+
+ **Tip:** It's not possible to upload the screenshot via this field directly but you can use the last textarea in this form to attach them.
+
+ **HINT:** You can paste https://gist.github.com links for larger files.
+ placeholder: >-
+ When the improvement is applied, it makes it more straightforward
+ to understand X.
+ validations:
+ required: false
+
+- type: checkboxes
+ attributes:
+ label: Code of Conduct
+ description: |
+ Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--sap-linuxlab) first.
+ options:
+ - label: I agree to follow the Ansible Code of Conduct
+ required: true
+...
diff --git a/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/feature_request.yml b/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 000000000..1b88ccacf
--- /dev/null
+++ b/ansible_collections/community/sap_libs/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,69 @@
+---
+name: Feature request
+description: Suggest an idea for this project
+
+body:
+- type: markdown
+ attributes:
+ value: |
+ ⚠
+ Verify first that your issue is not [already reported on GitHub][issue search].
+ Also test if the latest release and devel branch are affected too.
+ *Complete **all** sections as described, this form is processed automatically.*
+
+ [issue search]: https://github.com/sap-linuxlab/community.sap_libs/search?q=is%3Aissue&type=issues
+
+
+- type: textarea
+ attributes:
+ label: Summary
+ description: Describe the new feature/improvement briefly below.
+ placeholder: >-
+ I am trying to do X with the collection from the main branch on GitHub and
+ I think that implementing a feature Y would be very helpful for me and
+ every other user of community.sap_libs because of Z.
+ validations:
+ required: true
+
+- type: dropdown
+ attributes:
+ label: Issue Type
+ # FIXME: Once GitHub allows defining the default choice, update this
+ options:
+ - Feature Idea
+ validations:
+ required: true
+
+- type: input
+ attributes:
+ label: Component Name
+ description: >-
+ Write the short name of the module, plugin, task or feature below,
+ *use your best guess if unsure*.
+ placeholder: dnf, apt, yum, pip, user etc.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Additional Information
+ description: |
+ Describe how the feature would be used, why it is needed and what it would solve.
+
+ **HINT:** You can paste https://gist.github.com links for larger files.
+ value: |
+ <!--- Paste example playbooks or commands between quotes below -->
+ ```yaml (paste below)
+
+ ```
+ validations:
+ required: false
+- type: checkboxes
+ attributes:
+ label: Code of Conduct
+ description: |
+ Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--sap-linuxlab) first.
+ options:
+ - label: I agree to follow the Ansible Code of Conduct
+ required: true
+...
diff --git a/ansible_collections/community/sap_libs/.github/workflows/ansible-test.yml b/ansible_collections/community/sap_libs/.github/workflows/ansible-test.yml
new file mode 100644
index 000000000..e0fc592fb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/.github/workflows/ansible-test.yml
@@ -0,0 +1,122 @@
+name: CI
+on:
+ # Run CI against all pushes (direct commits, also merged PRs), Pull Requests
+ push:
+ pull_request:
+ # Run CI once per day (at 06:00 UTC)
+ # This ensures that even if there haven't been commits that we are still testing against latest version of ansible-test for each ansible-base version
+ schedule:
+ - cron: '0 6 * * *'
+env:
+ NAMESPACE: community
+ COLLECTION_NAME: sap_libs
+
+jobs:
+ sanity:
+ name: Sanity (Ⓐ${{ matrix.ansible }})
+ strategy:
+ matrix:
+ ansible:
+ - stable-2.9
+ - stable-2.10
+ - stable-2.11
+ - stable-2.12
+ - stable-2.13
+ - stable-2.14
+ - devel
+
+ runs-on: >-
+ ${{ contains(fromJson(
+ '["stable-2.9", "stable-2.10", "stable-2.11", "stable-2.12", "stable-2.13", "stable-2.14"]'
+ ), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
+ steps:
+
+ - name: Check out code
+ uses: actions/checkout@v3
+
+ - name: Perform sanity testing with ansible-test
+ uses: ansible-community/ansible-test-gh-action@release/v1
+ with:
+ ansible-core-version: ${{ matrix.ansible }}
+ testing-type: sanity
+
+
+ units:
+ runs-on: >-
+ ${{ contains(fromJson(
+ '["stable-2.9", "stable-2.10", "stable-2.11", "stable-2.12", "stable-2.13", "stable-2.14"]'
+ ), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
+ name: Units (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }})
+ strategy:
+ # As soon as the first unit test fails, cancel the others to free up the CI queue
+ fail-fast: true
+ matrix:
+ ansible:
+ - stable-2.9
+ - stable-2.10
+ - stable-2.11
+ - stable-2.12
+ - stable-2.13
+ - stable-2.14
+ - devel
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v3
+
+ - name: Perform unit testing with ansible-test
+ uses: ansible-community/ansible-test-gh-action@release/v1
+ with:
+ ansible-core-version: ${{ matrix.ansible }}
+ testing-type: units
+ test-deps: >-
+ ansible.netcommon
+ ansible.utils
+
+# Please consult the Readme for information on why we disabled integration tests temporarily.
+
+ # integration:
+ # runs-on: ubuntu-latest
+ # name: I (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }})
+ # strategy:
+ # fail-fast: false
+ # matrix:
+ # ansible:
+ # - stable-2.9 # Only if your collection supports Ansible 2.9
+ # - stable-2.10
+ # - stable-2.11
+ # - stable-2.12
+ # - stable-2.13
+ # - devel
+ # python:
+ # - 2.6
+ # - 2.7
+ # - 3.5
+ # - 3.6
+ # - 3.7
+ # - 3.8
+ # - 3.9
+ # exclude:
+ # # Because ansible-test doesn't support python3.9 for Ansible 2.9
+ # - ansible: stable-2.9
+ # python: 3.9
+ # - ansible: devel
+ # python: 2.6
+
+ # steps:
+ # - name: Check out code
+ # uses: actions/checkout@v3
+
+ # - name: Perform integration testing with ansible-test
+ # uses: ansible-community/ansible-test-gh-action@release/v1
+ # with:
+ # ansible-core-version: ${{ matrix.ansible }}
+ # python-version: 3.8
+ # pre-test-cmd: >-
+ # mkdir -p tests/output/
+ # touch tests/output/coverage
+ # target-python-version: ${{ matrix.python }}
+ # testing-type: integration
+ # test-deps: >-
+ # ansible.netcommon
+ # ansible.utils \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/.vscode/extensions.json b/ansible_collections/community/sap_libs/.vscode/extensions.json
new file mode 100644
index 000000000..1450d869d
--- /dev/null
+++ b/ansible_collections/community/sap_libs/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "redhat.ansible"
+ ]
+}
diff --git a/ansible_collections/community/sap_libs/CHANGELOG.rst b/ansible_collections/community/sap_libs/CHANGELOG.rst
new file mode 100644
index 000000000..28c3d4f3d
--- /dev/null
+++ b/ansible_collections/community/sap_libs/CHANGELOG.rst
@@ -0,0 +1,134 @@
+===========================
+Community SAP Release Notes
+===========================
+
+.. contents:: Topics
+
+
+v1.4.1
+======
+
+Release Summary
+---------------
+
+This is the 1.4.1 patch release of the ``community.sap_libs`` collection.
+This changelog contains all changes to the modules and plugins in this collection
+that have been made after the previous release.
+
+Bugfixes
+--------
+
+- fixes failures in sanity test for plugins/modules/sap_pyrfc.py
+- fixes failures in sanity test for tests/unit/compat/builtins.py
+- fixes failures in sanity test for tests/unit/plugins/modules/test_sap_system_facts.py
+- fixes failures in sanity test for tests/unit/plugins/modules/test_sap_system_facts.py
+- fixes pipeline warnings
+- sapcontrol_exec - This pr fixes problems on c(StartSystem), c(StopSystem), c(RestartSystem) which needs parameters they ca not provided by the parameters argument because of special format like c(waittimeout=1) without string quotes. This is caused by the suds module itself.
+
+v1.4.0
+======
+
+Release Summary
+---------------
+
+This is the 1.3.0 minor release of the ``community.sap_libs`` collection.
+This changelog contains all changes to the modules and plugins in this collection
+that have been made after the previous release.
+
+Bugfixes
+--------
+
+- fix a bug where some commands produces no output which cause to crash the module.
+- modules - fix a "variable used before assignment" that cannot be reached but causes sanity test failures.
+
+v1.3.0
+======
+
+Release Summary
+---------------
+
+This is the 1.3.0 minor release of the ``community.sap_libs`` collection. This changelog contains all changes to the modules and plugins in this collection that have been made after the previous release.
+
+Minor Changes
+-------------
+
+- License requirements are updated.
+- The modules purposes are described clearer.
+- The namespaces of the modules are removed to provide a flatter design.
+- hana_query - module is moved to sap_hdbsql.
+- sapcontrol - module is moved to sap_control_exec to have a clearer separation to other roles and references.
+
+v1.2.0
+======
+
+Release Summary
+---------------
+
+This is the minor release of the ``community.sap_libs`` collection.
+This changelog contains all changes to the modules and plugins in this collection
+that have been made after the previous release.
+
+Bugfixes
+--------
+
+- syp_system_facts - fix a typo in the usage example which lead to an error if it used as supposed.
+
+New Modules
+-----------
+
+- sap_pyrfc - Ansible Module for use of SAP PyRFC to execute SAP RFCs (Remote Function Calls) to SAP remote-enabled function modules
+
+v1.1.0
+======
+
+Release Summary
+---------------
+
+This is the minor release of the ``community.sap_libs`` collection.
+This changelog contains all changes to the modules and plugins in this collection
+that have been made after the previous release.
+
+New Modules
+-----------
+
+System
+~~~~~~
+
+- sapcontrol - Ansible Module to execute SAPCONTROL
+
+v1.0.0
+======
+
+Release Summary
+---------------
+
+This is the minor release of the ``community.sap`` collection. It is the initial relase for the ``community.sap`` collection
+
+New Modules
+-----------
+
+Database
+~~~~~~~~
+
+saphana
+^^^^^^^
+
+- hana_query - Ansible Module to execute SQL on SAP HANA
+
+Files
+~~~~~
+
+- sapcar_extract - Manages SAP SAPCAR archives
+
+Identity
+~~~~~~~~
+
+- sap_company - This module will manage a company entities in a SAP S4HANA environment
+- sap_user - This module will manage a user entities in a SAP S4/HANA environment
+
+System
+~~~~~~
+
+- sap_snote - This module will upload and (de)implements C(SNOTES) in a SAP S4HANA environment.
+- sap_system_facts - Gathers SAP facts in a host
+- sap_task_list_execute - Perform SAP Task list execution
diff --git a/ansible_collections/community/sap_libs/CODE_OF_CONDUCT.md b/ansible_collections/community/sap_libs/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..0164155b8
--- /dev/null
+++ b/ansible_collections/community/sap_libs/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+# Community Code of Conduct
+
+Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
diff --git a/ansible_collections/community/sap_libs/CONTRIBUTING.md b/ansible_collections/community/sap_libs/CONTRIBUTING.md
new file mode 100644
index 000000000..b4a17dec0
--- /dev/null
+++ b/ansible_collections/community/sap_libs/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# Contributing
+
+Refer to the [Contributing guidelines](https://github.com/ansible/community-docs/blob/main/contributing.rst).
diff --git a/ansible_collections/community/sap_libs/FILES.json b/ansible_collections/community/sap_libs/FILES.json
new file mode 100644
index 000000000..d16215449
--- /dev/null
+++ b/ansible_collections/community/sap_libs/FILES.json
@@ -0,0 +1,614 @@
+{
+ "files": [
+ {
+ "name": ".",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE/bug_report.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "575804259b5332b40427800e6e11bd676b392c2267f569c713f4ebbe29cfbbe0",
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE/config.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2e5f08c57601d637ec507daec616f993993d16f51892ca62214932b4fad0dcd9",
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE/documentation_report.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6d775ce8659e9e8b73b442ba1b64c406d9ab022c181e7de40967a9ad17c8943b",
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE/feature_request.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b441852aab4cb82226dd1e17a34d78b1ddd32e394e1daac91b8fd758e200123c",
+ "format": 1
+ },
+ {
+ "name": ".github/workflows",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/workflows/ansible-test.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "58507c49766179b58c1e6b550f15668c01b5dad25d88da745e1e140457c4583e",
+ "format": 1
+ },
+ {
+ "name": ".vscode",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".vscode/extensions.json",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f72b83e1aa1301adb18ce0fe71ce6613d3cfb148f881b3e39c55359d41d3277f",
+ "format": 1
+ },
+ {
+ "name": "changelogs",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/fragments/.keep",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "changelogs/changelog.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0131d36807f4d8b8ff8572729792c11bdbf5dafec7554e19ecd00b95945f08b8",
+ "format": 1
+ },
+ {
+ "name": "changelogs/config.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5f253a9432287fcdfe3100b556bf39d442a5e8b5a5fd3ec4a971f37f954ebac0",
+ "format": 1
+ },
+ {
+ "name": "meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "meta/runtime.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "592bc6961db54832e62eeebe83912aa3041c6bcfaf3df3a1a7a867cfbc27c55d",
+ "format": 1
+ },
+ {
+ "name": "plugins",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/pyrfc_handler.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ab0ba29a768b4478f3fa606a530712b845d3f68fb3e9b1bdf6a3988e0ce283cc",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/swpm2_parameters_inifile_generate.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4320fb963ddfb7d2434da4426e09c57feded076c241ba44a3de18c57eee043c3",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sap_company.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a3641687701245c9fbadb1fba2ce4bfbcdda33b3ce64c134d3e27916688ab37b",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sap_control_exec.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d4c4d2106cccbb5b25b3b3523f7e896f83b7e5d5a6e99ba0c7eab01e8dad5314",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sap_hdbsql.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "21686c309fcdcc1be98f802112ade272e136875f6143ab8844495142ea3b58d2",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sap_pyrfc.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "34db9ed3eb0bbe7ed6e692d39a95837150f45b1cc0b38304728c5afc472bf274",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sap_snote.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "598913d72aece5f981b343789704f799daa0be0fee21b6b6865c5721c4730bce",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sap_system_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4dbf86ef447a3a021c832addcb2c001521d837ec1e13c4d4f3725b1be046a514",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sap_task_list_execute.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a6a6f58647718e88edf7d7bb852053b4cf297ec8afc0fd330a3e9792378cb420",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sap_user.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3ed1c980af0df1b349171ecb830a3596d44f7875094c7b1db9356635fae56cd4",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/sapcar_extract.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9e96b03379bdd6c96b7246753233758703bfe383b3f935d56d14f84ddfdb5ead",
+ "format": 1
+ },
+ {
+ "name": "tests",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.10.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d3912e2808d50e5b6d67317db83cd5ab3b253d6a08aabcdb08c895a72f0ae163",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.11.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d3912e2808d50e5b6d67317db83cd5ab3b253d6a08aabcdb08c895a72f0ae163",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.12.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4b96e6fd9dd5ffdf1d5a0860ea5407249eecc1fcd4505693e040ef70ba6b2d97",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.13.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4b96e6fd9dd5ffdf1d5a0860ea5407249eecc1fcd4505693e040ef70ba6b2d97",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.14.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4b96e6fd9dd5ffdf1d5a0860ea5407249eecc1fcd4505693e040ef70ba6b2d97",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.15.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4b96e6fd9dd5ffdf1d5a0860ea5407249eecc1fcd4505693e040ef70ba6b2d97",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.9.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d3912e2808d50e5b6d67317db83cd5ab3b253d6a08aabcdb08c895a72f0ae163",
+ "format": 1
+ },
+ {
+ "name": "tests/unit",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/compat",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/compat/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/compat/builtins.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "05f6116032fac49bc912b73f3bc4689ebf658fbc20bd088c8833f22a6e1f59c1",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/compat/mock.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0af958450cf6de3fbafe94b1111eae8ba5a8dbe1d785ffbb9df81f26e4946d99",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/compat/unittest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5401a046e5ce71fa19b6d905abd0f9bdf816c0c635f7bdda6730b3ef06e67096",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/mock",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/mock/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/mock/loader.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3452ac615f89c99a76d1df4ab1ad84d1aff546e5b5fde18034a241239690d05a",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/mock/path.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f048a12629a6297a676ea56529ecf766cff30bcaa873c6659ac5b7f6e29472b1",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/mock/procenv.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e79b2fe520af92318da175c231296e16bf047842a93b1bfa4e1a5afc453baa03",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/mock/vault_helper.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0562db7b9972e6378701e3492c623e5f881732c4792e096032b72c2e54d22298",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/mock/yaml_helper.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cd95a4807e52e9123a8d40132a5f52b75cbc1496e1a32b104b2655bf631cfee4",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sap_company.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "41198ca748183d2c0539e35332b5649e55a12010a8ce5b0abe77cfd6ec0611c4",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sap_control_exec.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "10881167f5b5993f99b21eefc7984d91b7ed1104dd61b69a3f4b461c4129264a",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sap_hdbsql.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "dc0de20c35c780cb80163b0de97fd5887068f1abde7f70037ffa7606d2c9a5fb",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sap_pyrfc.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b96dd15fee5fd6ff51e84fd428a767d98b2698cd07d7789fd21ce134154f3029",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sap_snote.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f520fcc506ed73656432d0428d1de39c28e6207840c41c5aa735e4ebed72b247",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sap_system_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f416397df10811f97d53a5f1cec147406d594dfdfc33588827c3cd663ee3d97c",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sap_task_list_execute.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f8deb1ca8741c89271c26fb97f208b95dabac2e5f1d42cbfe763c43658ca29fd",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sap_user.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6e5cea8169a2a94a179f4b79760514377480d5c432debc215878eaa9ae05ce98",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/test_sapcar_extract.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9993318f661724e663e5bb0c9cea1b96ffc8dc4379ac80b8b32475fb90bfc011",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/modules/utils.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "da523bad72795557d39c3dbc0527ebeca4c4b27e1f5009c88c81add458ebdfd2",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "57ca135f60a45a67968cae026a738c74d287bd1a69cf99b20e6c0b02ebc72b35",
+ "format": 1
+ },
+ {
+ "name": "CHANGELOG.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "507e66971cef73bd854440f20a3ac07289be0d31229a4a8afc9d09f13d657a72",
+ "format": 1
+ },
+ {
+ "name": "CODE_OF_CONDUCT.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "14ec928234a7ed52bf8b458d31e4862335111e477e4dbe7fb543686c24115140",
+ "format": 1
+ },
+ {
+ "name": "CONTRIBUTING.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eec219dbc09446ed3e9938c8f66378621a0548b056426df33e3ea32e26bb4dc8",
+ "format": 1
+ },
+ {
+ "name": "LICENSE",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
+ "format": 1
+ },
+ {
+ "name": "MAINTAINERS",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bc751d90416fb1652ef9a5d35d8f60f5e050bc9735eb2fe26378d1695fd1ac46",
+ "format": 1
+ },
+ {
+ "name": "MAINTAINING.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2435665a6562d5f3841fff1631970f95f0466c498e949d2b8579ccc2a0b810ad",
+ "format": 1
+ },
+ {
+ "name": "README.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "feb05211584ede33d61c331bfe223f9bbb19579cb73068ca1624c17bde8e9069",
+ "format": 1
+ },
+ {
+ "name": "REVIEW_CHECKLIST.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "91ad4aff2cc14b98f81fbe2d90609c5a69ed96b6d836387a9c697c1112e603c0",
+ "format": 1
+ },
+ {
+ "name": "codecov.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9745c317e22e0efb4d13c8240bfd094dad77784004d3564310209b8fc037ed5a",
+ "format": 1
+ }
+ ],
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/LICENSE b/ansible_collections/community/sap_libs/LICENSE
new file mode 100644
index 000000000..261eeb9e9
--- /dev/null
+++ b/ansible_collections/community/sap_libs/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/ansible_collections/community/sap_libs/MAINTAINERS b/ansible_collections/community/sap_libs/MAINTAINERS
new file mode 100644
index 000000000..6e30606b6
--- /dev/null
+++ b/ansible_collections/community/sap_libs/MAINTAINERS
@@ -0,0 +1,2 @@
+rainerleber
+rkpobe \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/MAINTAINING.md b/ansible_collections/community/sap_libs/MAINTAINING.md
new file mode 100644
index 000000000..9fad0d343
--- /dev/null
+++ b/ansible_collections/community/sap_libs/MAINTAINING.md
@@ -0,0 +1,3 @@
+# Maintaining this collection
+
+Refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst).
diff --git a/ansible_collections/community/sap_libs/MANIFEST.json b/ansible_collections/community/sap_libs/MANIFEST.json
new file mode 100644
index 000000000..d3cfbb838
--- /dev/null
+++ b/ansible_collections/community/sap_libs/MANIFEST.json
@@ -0,0 +1,32 @@
+{
+ "collection_info": {
+ "namespace": "community",
+ "name": "sap_libs",
+ "version": "1.4.1",
+ "authors": [
+ "Rainer Leber (github.com/rainerleber)",
+ "Robert Kraemer (github.com/rkpobe)",
+ "Sean Freeman (github.com/sean-freeman)"
+ ],
+ "readme": "README.md",
+ "tags": [
+ "sap"
+ ],
+ "description": "SAP Module community collection for Ansible",
+ "license": [],
+ "license_file": "LICENSE",
+ "dependencies": {},
+ "repository": "https://github.com/sap-linuxlab/community.sap_libs",
+ "documentation": "https://github.com/sap-linuxlab/community.sap_libs",
+ "homepage": "https://github.com/sap-linuxlab/community.sap_libs",
+ "issues": "https://github.com/sap-linuxlab/community.sap_libs"
+ },
+ "file_manifest_file": {
+ "name": "FILES.json",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "296f2cdd0cd68d42c4dcfa4b9678ecdc575b8dffc9aa91064b75beab0199046b",
+ "format": 1
+ },
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/README.md b/ansible_collections/community/sap_libs/README.md
new file mode 100644
index 000000000..61a89551e
--- /dev/null
+++ b/ansible_collections/community/sap_libs/README.md
@@ -0,0 +1,182 @@
+# Community SAP_LIBS Collection
+
+This repository contains the community.sap_libs Ansible Collection. The collection includes modules and plugins supported by the Ansible SAP community to help SAP landscape management.
+
+**This collection is migrated from ansbile-collections/community.sap to sap-linuxlab/community.sap_libs.**
+
+# SAP Module Collection for Ansible
+<!-- Add CI and code coverage badges here. Samples included below. -->
+[![CI](https://github.com/sap-linuxlab/community.sap_libs/workflows/CI/badge.svg)](https://github.com/sap-linuxlab/community.sap_libs/actions) [![Codecov](https://img.shields.io/codecov/c/github/sap-linuxlab/community.sap_libs)](https://codecov.io/gh/sap-linuxlab/community.sap_libs)
+
+<!-- Describe the collection and why a user would want to use it. What does the collection do? -->
+
+## Code of Conduct
+
+We follow the [Ansible Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html) in all our interactions within this project.
+
+If you encounter abusive behavior, please refer to the [policy violations](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html#policy-violations) section of the Code for information on how to raise a complaint.
+
+## Communication
+
+<!--List available communication channels. In addition to channels specific to your collection, we also recommend to use the following ones.-->
+
+We announce releases and important changes through Ansible's [The Bullhorn newsletter](https://github.com/ansible/community/wiki/News#the-bullhorn). Be sure you are [subscribed](https://eepurl.com/gZmiEP).
+
+Join us in the `#ansible` (general use questions and support), `#ansible-community` (community and collection development questions), and other [Matrix/LiberaChat IRC channels](https://docs.ansible.com/ansible/devel/community/communication.html#real-time-chat).
+
+We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track [The Bullhorn newsletter](https://eepurl.com/gZmiEP) and join us.
+
+For more information about communication, refer to the [Ansible Communication guide](https://docs.ansible.com/ansible/devel/community/communication.html).
+
+## Contributing to this collection
+
+<!--Describe how the community can contribute to your collection. At a minimum, fill up and include the CONTRIBUTING.md file containing how and where users can create issues to report problems or request features for this collection. List contribution requirements, including preferred workflows and necessary testing, so you can benefit from community PRs. If you are following general Ansible contributor guidelines, you can link to - [Ansible Community Guide](https://docs.ansible.com/ansible/devel/community/index.html). List the current maintainers (contributors with write or higher access to the repository). The following can be included:-->
+
+The content of this collection is made by people like you, a community of individuals collaborating on making the world better through developing automation software.
+
+We are actively accepting new contributors.
+
+Any kind of contribution is very welcome.
+
+You don't know how to start? Refer to our [contribution guide](CONTRIBUTING.md)!
+
+We use the following guidelines:
+
+* [CONTRIBUTING.md](CONTRIBUTING.md)
+* [REVIEW_CHECKLIST.md](REVIEW_CHECKLIST.md)
+* [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html)
+* [Ansible Development Guide](https://docs.ansible.com/ansible/devel/dev_guide/index.html)
+* [Ansible Collection Development Guide](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections)
+
+## Collection maintenance
+
+The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. If you have questions or need help, feel free to mention them in the proposals.
+
+To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](MAINTAINING.md).
+
+## Governance
+
+<!--Describe how the collection is governed. Here can be the following text:-->
+
+The process of decision making in this collection is based on discussing and finding consensus among participants.
+
+Every voice is important. If you have something on your mind, create an issue or dedicated discussion and let's discuss it!
+
+## Tested with Ansible and the following Python versions
+
+Tested Ansible versions:
+- 2.9
+- 2.10
+- 2.11
+- 2.12
+- 2.13
+- 2.14
+- devel
+
+Tested Python versions:
+- 2.6
+- 2.7
+- 3.5
+- 3.6
+- 3.7
+- 3.8
+- 3.9
+- 3.10
+- 3.11
+
+Due to SAP licensing and hardware requirements, integration tests are momentarily not feasible.
+The modules are tested manually against SAP systems until we found a solution or have some
+modules where we are able to execute integration test we decided to disable these tests.
+
+## External requirements
+
+For some modules the below requirements are needed on the host that executes a module.
+
+- pyrfc >= 2.4.0
+- SAPCAR
+- SAPCONTROL
+
+### Supported connections
+<!-- Optional. If your collection supports only specific connection types (such as HTTPAPI, netconf, or others), list them here. -->
+
+## Included content
+
+- **Modules**:
+ - [sap_hdbsql](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_hdbsql.html)
+ - [sap_task_list_execute](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_task_list_execute.html)
+ - [sapcar_extract](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sapcar_extract.html)
+ - [sap_company](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_company.html)
+ - [sap_snote](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_snote.html)
+ - [sap_user](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_user.html)
+ - [sap_system_facts](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_system_facts.html)
+ - [sap_control_exec](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_control_exec.html)
+
+## Using this collection
+
+<!--Include some quick examples that cover the most common use cases for your collection content. It can include the following examples of installation and upgrade (change NAMESPACE.COLLECTION_NAME correspondingly):-->
+
+### Installing the Collection from Ansible Galaxy
+
+Before using this collection, you need to install it with the Ansible Galaxy command-line tool:
+```bash
+ansible-galaxy collection install community.sap_libs
+```
+
+You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format:
+```yaml
+---
+collections:
+ - name: community.sap_libs
+```
+
+Note that if you install the collection from Ansible Galaxy, it will not be upgraded automatically when you upgrade the `ansible` package. To upgrade the collection to the latest available version, run the following command:
+```bash
+ansible-galaxy collection install community.sap_libs --upgrade
+```
+
+You can also install a specific version of the collection, for example, if you need to downgrade when something is broken in the latest version (please report an issue in this repository). Use the following syntax to install version `1.0.0`:
+
+```bash
+ansible-galaxy collection install community.sap_libs:==1.0.0
+```
+
+See [Ansible Using collections](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html) for more details.
+
+## Release notes
+
+See the [changelog](https://github.com/sap-linuxlab/community.sap_libs/tree/main/CHANGELOG.rst).
+
+## Releasing, Versioning and Deprecation
+This collection follows Semantic Versioning. More details on versioning can be found in the Ansible docs.
+
+We plan to regularly release new minor or bugfix versions once new features or bugfixes have been implemented.
+
+Releasing the current major version happens from the main branch. We will create a stable-1 branch for 1.x.y versions once we start working on a 2.0.0 release, to allow backporting bugfixes and features from the 2.0.0 branch (main) to stable-1.
+
+For reference have a look at the issue [Releasing, Versioning and Deprecation](https://github.com/sap-linuxlab/community.sap_libs/issues/1).
+
+
+
+## Roadmap
+
+Please have a look at the project board.
+
+## More information
+
+<!-- List out where the user can find additional information, such as working group meeting times, slack/IRC channels, or documentation for the product this collection automates. At a minimum, link to: -->
+
+- [Ansible Collection overview](https://github.com/ansible-collections/overview)
+- [Ansible User guide](https://docs.ansible.com/ansible/devel/user_guide/index.html)
+- [Ansible Developer guide](https://docs.ansible.com/ansible/devel/dev_guide/index.html)
+- [Ansible Collections Checklist](https://github.com/ansible-collections/overview/blob/master/collection_requirements.rst)
+- [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html)
+- [The Bullhorn (the Ansible Contributor newsletter)](https://us19.campaign-archive.com/home/?u=56d874e027110e35dea0e03c1&id=d6635f5420)
+- [News for Maintainers](https://github.com/ansible-collections/news-for-maintainers)
+
+## Licensing
+
+<!-- Include the appropriate license information here and a pointer to the full licensing details. If the collection contains modules migrated from the ansible/ansible repo, you must use the same license that existed in the ansible/ansible repo. See the GNU license example below. -->
+
+Apache License, Version 2.0
+
+See [LICENSE](http://www.apache.org/licenses/LICENSE-2.0) to see the full text. \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/REVIEW_CHECKLIST.md b/ansible_collections/community/sap_libs/REVIEW_CHECKLIST.md
new file mode 100644
index 000000000..9dccf7ef1
--- /dev/null
+++ b/ansible_collections/community/sap_libs/REVIEW_CHECKLIST.md
@@ -0,0 +1,3 @@
+# Review Checklist
+
+Refer to the [Collection review checklist](https://github.com/ansible/community-docs/blob/main/review_checklist.rst).
diff --git a/ansible_collections/community/sap_libs/changelogs/changelog.yaml b/ansible_collections/community/sap_libs/changelogs/changelog.yaml
new file mode 100644
index 000000000..e29a18049
--- /dev/null
+++ b/ansible_collections/community/sap_libs/changelogs/changelog.yaml
@@ -0,0 +1,116 @@
+ancestor: null
+releases:
+ 1.0.0:
+ changes:
+ release_summary: This is the minor release of the ``community.sap`` collection.
+ It is the initial relase for the ``community.sap`` collection
+ fragments:
+ - 1.0.0.yml
+ modules:
+ - description: Ansible Module to execute SQL on SAP HANA
+ name: hana_query
+ namespace: database.saphana
+ - description: This module will manage a company entities in a SAP S4HANA environment
+ name: sap_company
+ namespace: identity
+ - description: This module will upload and (de)implements C(SNOTES) in a SAP S4HANA
+ environment.
+ name: sap_snote
+ namespace: system
+ - description: Gathers SAP facts in a host
+ name: sap_system_facts
+ namespace: system
+ - description: Perform SAP Task list execution
+ name: sap_task_list_execute
+ namespace: system
+ - description: This module will manage a user entities in a SAP S4/HANA environment
+ name: sap_user
+ namespace: identity
+ - description: Manages SAP SAPCAR archives
+ name: sapcar_extract
+ namespace: files
+ 1.1.0:
+ changes:
+ release_summary: 'This is the minor release of the ``community.sap_libs`` collection.
+
+ This changelog contains all changes to the modules and plugins in this collection
+
+ that have been made after the previous release.'
+ fragments:
+ - 1.1.0.yml
+ modules:
+ - description: Ansible Module to execute SAPCONTROL
+ name: sapcontrol
+ namespace: system
+ release_date: '2022-05-17'
+ 1.2.0:
+ changes:
+ bugfixes:
+ - syp_system_facts - fix a typo in the usage example which lead to an error
+ if it used as supposed.
+ release_summary: 'This is the minor release of the ``community.sap_libs`` collection.
+
+ This changelog contains all changes to the modules and plugins in this collection
+
+ that have been made after the previous release.'
+ fragments:
+ - 1.2.0.yml
+ - 11-sap_system_facts_fix_typo.yml
+ modules:
+ - description: Ansible Module for use of SAP PyRFC to execute SAP RFCs (Remote
+ Function Calls) to SAP remote-enabled function modules
+ name: sap_pyrfc
+ namespace: ''
+ release_date: '2022-07-18'
+ 1.3.0:
+ changes:
+ minor_changes:
+ - License requirements are updated.
+ - The modules purposes are described clearer.
+ - The namespaces of the modules are removed to provide a flatter design.
+ - hana_query - module is moved to sap_hdbsql.
+ - sapcontrol - module is moved to sap_control_exec to have a clearer separation
+ to other roles and references.
+ release_summary: This is the 1.3.0 minor release of the ``community.sap_libs``
+ collection. This changelog contains all changes to the modules and plugins
+ in this collection that have been made after the previous release.
+ fragments:
+ - 1.3.0.yml
+ release_date: '2022-09-09'
+ 1.4.0:
+ changes:
+ bugfixes:
+ - fix a bug where some commands produces no output which cause to crash the
+ module.
+ - modules - fix a "variable used before assignment" that cannot be reached but
+ causes sanity test failures.
+ release_summary: 'This is the 1.3.0 minor release of the ``community.sap_libs``
+ collection.
+
+ This changelog contains all changes to the modules and plugins in this collection
+
+ that have been made after the previous release.'
+ fragments:
+ - 0020-fix_sap_control_exec.yml
+ - 1.4.0.yml
+ - 22-use-before-assignment.yml
+ release_date: '2022-12-05'
+ 1.4.1:
+ changes:
+ bugfixes:
+ - fixes failures in sanity test for plugins/modules/sap_pyrfc.py
+ - fixes failures in sanity test for tests/unit/compat/builtins.py
+ - fixes failures in sanity test for tests/unit/plugins/modules/test_sap_system_facts.py
+ - fixes failures in sanity test for tests/unit/plugins/modules/test_sap_system_facts.py
+ - fixes pipeline warnings
+ - sapcontrol_exec - This pr fixes problems on c(StartSystem), c(StopSystem),
+ c(RestartSystem) which needs parameters they ca not provided by the parameters
+ argument because of special format like c(waittimeout=1) without string quotes.
+ This is caused by the suds module itself.
+ release_summary: This is the 1.4.1 patch release of the ``community.sap_libs``
+ collection. This changelog contains all changes to the modules and plugins
+ in this collection that have been made after the previous release.
+ fragments:
+ - 28-fix-sapcontrol-issue.yml
+ - 29-fix-lint-issues.yml
+ release_date: '2023-03-09'
diff --git a/ansible_collections/community/sap_libs/changelogs/config.yaml b/ansible_collections/community/sap_libs/changelogs/config.yaml
new file mode 100644
index 000000000..db443763c
--- /dev/null
+++ b/ansible_collections/community/sap_libs/changelogs/config.yaml
@@ -0,0 +1,30 @@
+changelog_filename_template: ../CHANGELOG.rst
+changelog_filename_version_depth: 0
+changes_file: changelog.yaml
+changes_format: combined
+keep_fragments: false
+mention_ancestor: true
+flatmap: true
+new_plugins_after_name: removed_features
+notesdir: fragments
+prelude_section_name: release_summary
+prelude_section_title: Release Summary
+sections:
+- - major_changes
+ - Major Changes
+- - minor_changes
+ - Minor Changes
+- - breaking_changes
+ - Breaking Changes / Porting Guide
+- - deprecated_features
+ - Deprecated Features
+- - removed_features
+ - Removed Features (previously deprecated)
+- - security_fixes
+ - Security Fixes
+- - bugfixes
+ - Bugfixes
+- - known_issues
+ - Known Issues
+title: Community SAP
+trivial_section_name: trivial
diff --git a/ansible_collections/community/sap_libs/changelogs/fragments/.keep b/ansible_collections/community/sap_libs/changelogs/fragments/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/changelogs/fragments/.keep
diff --git a/ansible_collections/community/sap_libs/codecov.yml b/ansible_collections/community/sap_libs/codecov.yml
new file mode 100644
index 000000000..31b6a86ac
--- /dev/null
+++ b/ansible_collections/community/sap_libs/codecov.yml
@@ -0,0 +1,2 @@
+fixes:
+ - "/sap-linuxlab/community/sap_libs/::"
diff --git a/ansible_collections/community/sap_libs/meta/runtime.yml b/ansible_collections/community/sap_libs/meta/runtime.yml
new file mode 100644
index 000000000..db097ee98
--- /dev/null
+++ b/ansible_collections/community/sap_libs/meta/runtime.yml
@@ -0,0 +1,15 @@
+---
+requires_ansible: ">=2.9.10"
+
+plugin_routing:
+ modules:
+ hana_query:
+ redirect: community.sap_libs.sap_hdbsql
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: Use community.sap_libs.sap_hdbsql instead.
+ sapcontrol:
+ redirect: community.sap_libs.sap_control_exec
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: Use community.sap_libs.sap_control_exec instead.
diff --git a/ansible_collections/community/sap_libs/plugins/doc_fragments/__init__.py b/ansible_collections/community/sap_libs/plugins/doc_fragments/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/doc_fragments/__init__.py
diff --git a/ansible_collections/community/sap_libs/plugins/module_utils/__init__.py b/ansible_collections/community/sap_libs/plugins/module_utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/module_utils/__init__.py
diff --git a/ansible_collections/community/sap_libs/plugins/module_utils/pyrfc_handler.py b/ansible_collections/community/sap_libs/plugins/module_utils/pyrfc_handler.py
new file mode 100644
index 000000000..4f4e5b4e9
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/module_utils/pyrfc_handler.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Sean Freeman ,
+# Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# 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.
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible.module_utils.basic import missing_required_lib
+
+import traceback
+
+PYRFC_LIBRARY_IMPORT_ERROR = None
+try:
+ import pyrfc
+except ImportError:
+ PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+ HAS_PYRFC_LIBRARY = False
+else:
+ HAS_PYRFC_LIBRARY = True
+
+
+def get_connection(module, conn_params):
+ if not HAS_PYRFC_LIBRARY:
+ module.fail_json(msg=missing_required_lib(
+ "pyrfc"), exception=PYRFC_LIBRARY_IMPORT_ERROR)
+
+ 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/ansible_collections/community/sap_libs/plugins/module_utils/swpm2_parameters_inifile_generate.py b/ansible_collections/community/sap_libs/plugins/module_utils/swpm2_parameters_inifile_generate.py
new file mode 100755
index 000000000..f7b72d52a
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/module_utils/swpm2_parameters_inifile_generate.py
@@ -0,0 +1,271 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Sean Freeman ,
+# Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# 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.
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible.module_utils.basic import missing_required_lib
+import traceback
+import sys
+import os
+
+
+BS4_LIBRARY_IMPORT_ERROR = None
+try:
+ from bs4 import BeautifulSoup
+except ImportError:
+ BS4_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+ HAS_BS4_LIBRARY = False
+else:
+ HAS_BS4_LIBRARY = True
+
+LXML_LIBRARY_IMPORT_ERROR = None
+try:
+ from lxml import etree
+except ImportError:
+ LXML_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+ HAS_LXML_LIBRARY = False
+else:
+ HAS_LXML_LIBRARY = True
+
+
+def debug_bs4(module):
+ # Diagnose XML file parsing errors in Beautiful Soup
+ # https://stackoverflow.com/questions/56942892/cannot-parse-iso-8859-15-encoded-xml-with-bs4/56947172#56947172
+ if not HAS_BS4_LIBRARY:
+ module.fail_json(msg=missing_required_lib(
+ "bs4"), exception=BS4_LIBRARY_IMPORT_ERROR)
+ from bs4.diagnose import diagnose
+ with open('control.xml', 'rb') as f:
+ diagnose(f)
+
+
+# SWPM2 control.xml conversion to utf8
+def control_xml_utf8(filepath, module):
+ if not HAS_LXML_LIBRARY:
+ module.fail_json(msg=missing_required_lib(
+ "lxml"), exception=LXML_LIBRARY_IMPORT_ERROR)
+ source = filepath + "/control.xml"
+
+ # Convert control.xml from iso-8859-1 to UTF-8, so it can be used with Beautiful Soup lxml-xml parser
+ # https://stackoverflow.com/questions/64629600/how-can-you-convert-a-xml-iso-8859-1-to-utf-8-using-python-3-7-7/64634454#64634454
+ with open(source, 'rb') as source:
+ parser = etree.XMLParser(encoding="iso-8859-1", strip_cdata=False)
+ root = etree.parse(source, parser)
+
+ string = etree.tostring(root, xml_declaration=True, encoding="UTF-8",
+ pretty_print=True).decode('utf8').encode('iso-8859-1')
+
+# string1 = etree.tostring(root, xml_declaration=True, encoding="UTF-8",
+# pretty_print=True).decode('utf8').encode('utf-8').strip()
+
+ with open('control_utf8.xml', 'wb') as target:
+ target.write(string)
+
+
+# SWPM2 Component and Parameters extract all as CSV
+def control_xml_to_csv(filepath, module):
+ if not HAS_BS4_LIBRARY:
+ module.fail_json(msg=missing_required_lib(
+ "bs4"), exception=BS4_LIBRARY_IMPORT_ERROR)
+
+ infile = open(filepath + "/control_utf8.xml", "r")
+ contents = infile.read()
+
+ soup = BeautifulSoup(markup=contents, features='lxml-xml')
+ space = soup.find('components')
+
+ component_list = space.findChildren("component", recursive=False)
+
+ csv_output = open('control_output.csv', 'w')
+ csv_header = '"' + 'Component Name' + '","' + 'Component Display Name' + '","' + 'Parameter Name' + '","' + 'Parameter Inifile Key' + \
+ '","' + 'Parameter Access' + '","' + 'Parameter Encode' + '","' + \
+ 'Parameter Default Value' + '","' + 'Parameter Inifile description' + '"'
+ csv_output.write("%s\n" % csv_header)
+
+ for component in component_list:
+ for parameter in component.findChildren("parameter"):
+ component_key = parameter.findParent("component")
+ component_key_name_text = component_key["name"]
+ for child in component_key.findChildren("display-name"):
+ component_key_display_name_text = child.get_text().replace('\n', '')
+ component_parameter_key_name = parameter["name"]
+ component_parameter_key_inifile_name = parameter.get(
+ "defval-for-inifile-generation", "")
+ component_parameter_key_access = parameter.get("access", "")
+ component_parameter_key_encode = parameter.get("encode", "")
+ component_parameter_key_defval = parameter.get("defval", "")
+ component_parameter_contents_doclong_text = parameter.get_text().replace('\n', '')
+ component_parameter_contents_doclong_text_quote_replacement = component_parameter_contents_doclong_text.replace(
+ '"', '\'')
+ csv_string = '"' + component_key_name_text + '","' + component_key_display_name_text + '","' + \
+ component_parameter_key_name + '","' + component_parameter_key_inifile_name + '","' + \
+ component_parameter_key_access + '","' + component_parameter_key_encode + '","' + \
+ component_parameter_key_defval + '","' + \
+ component_parameter_contents_doclong_text_quote_replacement + '"'
+ csv_output.write("%s\n" % csv_string)
+
+ csv_output.close()
+
+
+# SWPM2 Component and Parameters extract all and generate template inifile.params
+def control_xml_to_inifile_params(filepath, module):
+ if not HAS_BS4_LIBRARY:
+ module.fail_json(msg=missing_required_lib(
+ "bs4"), exception=BS4_LIBRARY_IMPORT_ERROR)
+
+ infile = open(filepath + "/control_utf8.xml", "r")
+ contents = infile.read()
+
+ soup = BeautifulSoup(markup=contents, features='lxml-xml')
+ space = soup.find('components')
+
+ component_list = space.findChildren("component", recursive=False)
+
+ inifile_output = open('generated_inifile_params', 'w')
+
+ inifile_params_header = """############
+ # SWPM Unattended Parameters inifile.params generated export
+ #
+ #
+ # Export of all SWPM Component and the SWPM Unattended Parameters. Not all components have SWPM Unattended Parameters.
+ #
+ # All parameters are commented-out, each hash # before the parameter is removed to activate the parameter.
+ # When running SWPM in Unattended Mode, the activated parameters will create a new SWPM file in the sapinst directory.
+ # If any parameter is marked as 'encode', the plaintext value will be coverted to DES hash
+ # for this parameter in the new SWPM file (in the sapinst directory).
+ #
+ # An inifile.params is otherwise obtained after running SWPM as GUI or Unattended install,
+ # and will be generated for a specific Product ID (such as 'NW_ABAP_OneHost:S4HANA1809.CORE.HDB.CP').
+ ############
+
+
+
+ ############
+ # MANUAL
+ ############
+
+ # The folder containing all archives that have been downloaded from http://support.sap.com/swdc and are supposed to be used in this procedure
+ # archives.downloadBasket =
+ """
+
+ inifile_output.write(inifile_params_header)
+
+ for component in component_list:
+ component_key_name_text = component["name"]
+ component_key_display_name = component.find("display-name")
+ if component_key_display_name is not None:
+ component_key_display_name_text = component_key_display_name.get_text()
+ inifile_output.write("\n\n\n\n############\n# Component: %s\n# Component Display Name: %s\n############\n" % (
+ component_key_name_text, component_key_display_name_text))
+ for parameter in component.findChildren("parameter"):
+ # component_key=parameter.findParent("component")
+ component_parameter_key_encode = parameter.get("encode", None)
+ component_parameter_key_inifile_name = parameter.get(
+ "defval-for-inifile-generation", None)
+ component_parameter_key_defval = parameter.get("defval", "")
+ component_parameter_contents_doclong_text = parameter.get_text().replace('\n', '')
+# component_parameter_contents_doclong_text_quote_replacement=component_parameter_contents_doclong_text.replace('"','\'')
+ if component_parameter_key_inifile_name is not None:
+ inifile_output.write("\n# %s" % (
+ component_parameter_contents_doclong_text))
+ if component_parameter_key_encode == "true":
+ inifile_output.write(
+ "\n# Encoded parameter. Plaintext values will be coverted to DES hash")
+ inifile_output.write("\n# %s = %s\n" % (
+ component_parameter_key_inifile_name, component_parameter_key_defval))
+
+ inifile_output.close()
+
+# SWPM2 product.catalog conversion to utf8
+
+
+def product_catalog_xml_utf8(filepath, module):
+ if not HAS_LXML_LIBRARY:
+ module.fail_json(msg=missing_required_lib(
+ "lxml"), exception=LXML_LIBRARY_IMPORT_ERROR)
+
+ source = filepath + "/product.catalog"
+
+ # Convert control.xml from iso-8859-1 to UTF-8, so it can be used with Beautiful Soup lxml-xml parser
+ # https://stackoverflow.com/questions/64629600/how-can-you-convert-a-xml-iso-8859-1-to-utf-8-using-python-3-7-7/64634454#64634454
+ with open(source, 'rb') as source:
+ parser = etree.XMLParser(encoding="iso-8859-1", strip_cdata=False)
+ root = etree.parse(source, parser)
+
+ string = etree.tostring(root, xml_declaration=True, encoding="UTF-8",
+ pretty_print=True).decode('utf8').encode('iso-8859-1')
+
+ with open('product_catalog_utf8.xml', 'wb') as target:
+ target.write(string)
+
+# SWPM2 Product Catalog entries to CSV
+# Each Product Catalog entry is part of a components group, which may have attributes:
+# output-dir, control-file, product-dir (link to SWPM directory of param file etc)
+# Attributes possible for each entry = control-file, db, id, name, os, os-type, output-dir,
+# ppms-component, ppms-component-release, product, product-dir, release, table
+
+
+def product_catalog_xml_to_csv(filepath, module):
+ if not HAS_BS4_LIBRARY:
+ module.fail_json(msg=missing_required_lib(
+ "bs4"), exception=BS4_LIBRARY_IMPORT_ERROR)
+
+ infile = open(filepath + "/product_catalog_utf8.xml", "r")
+ contents = infile.read()
+
+ soup = BeautifulSoup(markup=contents, features='lxml-xml')
+ space = soup.find_all('component')
+
+ csv_output = open('product_catalog_output.csv', 'w')
+ csv_header = '"' + 'Product Catalog Component Name' + '","' + 'Product Catalog Component ID' + '","' + 'Product Catalog Component Table' + '","' + \
+ 'Product Catalog Component Output Dir' + '","' + 'Product Catalog Component Display Name' + \
+ '","' + 'Product Catalog Component UserInfo' + '"'
+ csv_output.write("%s\n" % csv_header)
+
+ for component in space:
+ component_name = component.get("name", "")
+ component_id = component.get("id", "")
+ component_table = component.get("table", "")
+ component_output_dir = component.get("output-dir", "")
+ for displayname in component.findChildren("display-name"):
+ component_displayname = displayname.get_text().strip()
+ for userinfo in component.findChildren("user-info"):
+ html_raw = userinfo.get_text().strip()
+ html_parsed = BeautifulSoup(html_raw, 'html.parser')
+ component_userinfo = html_parsed.get_text().replace('"', '\'')
+ csv_string = '"' + component_name + '","' + component_id + '","' + component_table + '","' + \
+ component_output_dir + '","' + component_displayname + \
+ '","' + component_userinfo + '"'
+ csv_output.write("%s\n" % csv_string)
+
+ csv_output.close()
+
+
+# Get arguments passed to Python script session
+# Define path to control.xml, else assume in /tmp directory
+
+if len(sys.argv) > 1:
+ control_xml_path = sys.argv[1]
+else:
+ control_xml_path = "/tmp"
+
+if control_xml_path == "":
+ control_xml_path = os.getcwd()
+
+if os.path.exists(control_xml_path + '/control.xml'):
+ control_xml_utf8(control_xml_path, '')
+ control_xml_to_csv(control_xml_path, '')
+ control_xml_to_inifile_params(control_xml_path, '')
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sap_company.py b/ansible_collections/community/sap_libs/plugins/modules/sap_company.py
new file mode 100644
index 000000000..29b214f68
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sap_company.py
@@ -0,0 +1,335 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# 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.
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_company
+
+short_description: This module will manage a company entities in a SAP S4HANA environment
+
+version_added: "1.0.0"
+
+description:
+ - The M(community.sap_libs.sap_user) module depends on C(pyrfc) Python library (version 2.4.0 and upwards).
+ Depending on distribution you are using, you may need to install additional packages to
+ have these available.
+ - This module will use the company BAPIs C(BAPI_COMPANY_CLONE) and C(BAPI_COMPANY_DELETE) to manage company entities.
+
+options:
+ state:
+ description:
+ - The decision what to do with the company.
+ default: 'present'
+ choices:
+ - 'present'
+ - 'absent'
+ required: false
+ type: str
+ conn_username:
+ description: The required username for the SAP system.
+ required: true
+ type: str
+ conn_password:
+ description: The required password for the SAP system.
+ required: true
+ type: str
+ host:
+ description: The required host for the SAP system. Can be either an FQDN or IP Address.
+ required: true
+ type: str
+ sysnr:
+ description:
+ - The system number of the SAP system.
+ - You must quote the value to ensure retaining the leading zeros.
+ required: false
+ default: '01'
+ type: str
+ client:
+ description:
+ - The client number to connect to.
+ - You must quote the value to ensure retaining the leading zeros.
+ required: false
+ default: '000'
+ type: str
+ company_id:
+ description: The company id.
+ required: true
+ type: str
+ name:
+ description: The company name.
+ required: false
+ type: str
+ name_2:
+ description: Additional company name.
+ required: false
+ type: str
+ country:
+ description: The country code for the company. For example, C('DE').
+ required: false
+ type: str
+ time_zone:
+ description: The timezone.
+ required: false
+ type: str
+ city:
+ description: The city where the company is located.
+ required: false
+ type: str
+ post_code:
+ description: The post code from the city.
+ required: false
+ type: str
+ street:
+ description: Street where the company is located.
+ required: false
+ type: str
+ street_no:
+ description: Street number.
+ required: false
+ type: str
+ e_mail:
+ description: General E-Mail address.
+ required: false
+ type: str
+
+requirements:
+ - pyrfc >= 2.4.0
+
+author:
+ - Rainer Leber (@rainerleber)
+
+notes:
+ - Does not support C(check_mode).
+'''
+
+EXAMPLES = r'''
+- name: Create SAP Company
+ community.sap_libs.sap_company:
+ conn_username: 'DDIC'
+ conn_password: 'HECtna2021#'
+ host: 100.0.201.20
+ sysnr: '01'
+ client: '000'
+ state: present
+ company_id: "Comp_ID"
+ name: "Test_comp"
+ name_2: "LTD"
+ country: "DE"
+ time_zone: "UTC"
+ city: "City"
+ post_code: "12345"
+ street: "test_street"
+ street_no: "1"
+ e_mail: "test@test.de"
+
+# pass in a message and have changed true
+- name: Delete SAP Company
+ community.sap_libs.sap_company:
+ conn_username: 'DDIC'
+ conn_password: 'HECtna2021#'
+ host: 100.0.201.20
+ sysnr: '01'
+ client: '000'
+ state: absent
+ company_id: "Comp_ID"
+ name: "Test_comp"
+ name_2: "LTD"
+ country: "DE"
+ time_zone: "UTC"
+ city: "City"
+ post_code: "12345"
+ street: "test_street"
+ street_no: "1"
+ e_mail: "test@test.de"
+'''
+
+RETURN = r'''
+# These are examples of possible return values, and in general should use other names for return values.
+msg:
+ description: A small execution description.
+ type: str
+ returned: always
+ sample: 'Company address COMP_ID created'
+out:
+ description: A complete description of the executed tasks. If this is available.
+ type: list
+ elements: dict
+ returned: always
+ sample: '{
+ "RETURN": [
+ {
+ "FIELD": "",
+ "ID": "01",
+ "LOG_MSG_NO": "000000",
+ "LOG_NO": "",
+ "MESSAGE": "Company address COMP_ID created",
+ "MESSAGE_V1": "COMP_ID",
+ "MESSAGE_V2": "",
+ "MESSAGE_V3": "",
+ "MESSAGE_V4": "",
+ "NUMBER": "078",
+ "PARAMETER": "",
+ "ROW": 0,
+ "SYSTEM": "",
+ "TYPE": "S"
+ }
+ ]
+ }
+ }'
+'''
+
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+import traceback
+try:
+ from pyrfc import Connection
+except ImportError:
+ HAS_PYRFC_LIBRARY = False
+ ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ ANOTHER_LIBRARY_IMPORT_ERROR = None
+ HAS_PYRFC_LIBRARY = True
+
+
+def call_rfc_method(connection, method_name, kwargs):
+ # PyRFC call function
+ return connection.call(method_name, **kwargs)
+
+
+def build_company_params(name, name_2, country, time_zone, city, post_code, street, street_no, e_mail):
+ # Creates RFC parameters for creating organizations
+ # define dicts in batch
+ params = dict()
+ # define company name
+ params['NAME'] = name
+ params['NAME_2'] = name_2
+ # define location
+ params['COUNTRY'] = country
+ params['TIME_ZONE'] = time_zone
+ params['CITY'] = city
+ params['POSTL_COD1'] = post_code
+ params['STREET'] = street
+ params['STREET_NO'] = street_no
+ # define communication
+ params['E_MAIL'] = e_mail
+ # return dict
+ return params
+
+
+def return_analysis(raw):
+ change = False
+ failed = False
+ msg = raw['RETURN'][0]['MESSAGE']
+ for state in raw['RETURN']:
+ if state['TYPE'] == "E":
+ if state['NUMBER'] == '081':
+ change = False
+ else:
+ failed = True
+ if state['TYPE'] == "S":
+ if state['NUMBER'] != '079':
+ change = True
+ else:
+ msg = "No changes where made."
+ return [{"change": change}, {"failed": failed}, {"msg": msg}]
+
+
+def run_module():
+ module = AnsibleModule(
+ argument_spec=dict(
+ state=dict(default='present', choices=['absent', 'present']),
+ conn_username=dict(type='str', required=True),
+ conn_password=dict(type='str', required=True, no_log=True),
+ host=dict(type='str', required=True),
+ sysnr=dict(type='str', default="01"),
+ client=dict(type='str', default="000"),
+ company_id=dict(type='str', required=True),
+ name=dict(type='str', required=False),
+ name_2=dict(type='str', required=False),
+ country=dict(type='str', required=False),
+ time_zone=dict(type='str', required=False),
+ city=dict(type='str', required=False),
+ post_code=dict(type='str', required=False),
+ street=dict(type='str', required=False),
+ street_no=dict(type='str', required=False),
+ e_mail=dict(type='str', required=False),
+ ),
+ supports_check_mode=False,
+ )
+ result = dict(changed=False, msg='', out={})
+ raw = ""
+
+ params = module.params
+
+ state = params['state']
+ conn_username = (params['conn_username']).upper()
+ conn_password = params['conn_password']
+ host = params['host']
+ sysnr = params['sysnr']
+ client = params['client']
+
+ company_id = (params['company_id']).upper()
+ name = params['name']
+ name_2 = params['name_2']
+ country = params['country']
+ time_zone = params['time_zone']
+ city = params['city']
+ post_code = params['post_code']
+ street = params['street']
+ street_no = params['street_no']
+ e_mail = params['e_mail']
+
+ if not HAS_PYRFC_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('pyrfc'),
+ exception=ANOTHER_LIBRARY_IMPORT_ERROR)
+
+ # basic RFC connection with pyrfc
+ try:
+ conn = Connection(user=conn_username, passwd=conn_password, ashost=host, sysnr=sysnr, client=client)
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'Something went wrong connecting to the SAP system.'
+ module.fail_json(**result)
+
+ # build parameter dict of dict
+ company_params = build_company_params(name, name_2, country, time_zone, city, post_code, street, street_no, e_mail)
+
+ if state == "absent":
+ raw = call_rfc_method(conn, 'BAPI_COMPANY_DELETE', {'COMPANY': company_id})
+
+ if state == "present":
+ raw = call_rfc_method(conn, 'BAPI_COMPANY_CLONE',
+ {'METHOD': {'USMETHOD': 'COMPANY_CLONE'}, 'COMPANY': company_id, 'COMP_DATA': company_params})
+
+ analysed = return_analysis(raw)
+
+ result['out'] = raw
+
+ result['changed'] = analysed[0]['change']
+ result['msg'] = analysed[2]['msg']
+
+ if analysed[1]['failed']:
+ module.fail_json(**result)
+
+ module.exit_json(**result)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sap_control_exec.py b/ansible_collections/community/sap_libs/plugins/modules/sap_control_exec.py
new file mode 100644
index 000000000..ca1490b87
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sap_control_exec.py
@@ -0,0 +1,405 @@
+#!/usr/bin/python
+
+# Copyright: (c) 2022, Rainer Leber rainerleber@gmail.com, rainer.leber@sva.de,
+# Robert Kraemer @rkpobe, robert.kraemer@sva.de
+# 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.
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_control_exec
+
+short_description: Ansible Module to execute SAPCONTROL
+
+version_added: "1.1.0"
+
+description:
+ - Provides support for sapstartsrv formaly known as sapcontrol
+ - A complete information of all functions and the parameters can be found here
+ U(https://www.sap.com/documents/2016/09/0a40e60d-8b7c-0010-82c7-eda71af511fa.html)
+
+options:
+ sysnr:
+ description:
+ - The system number of the instance.
+ required: false
+ type: str
+ port:
+ description:
+ - The port number of the sapstartsrv.
+ required: false
+ type: int
+ username:
+ description:
+ - The username to connect to the sapstartsrv.
+ required: false
+ type: str
+ password:
+ description:
+ - The password to connect to the sapstartsrv.
+ required: false
+ type: str
+ hostname:
+ description:
+ - The hostname to connect to the sapstartsrv.
+ - Could be an IP address, FQDN or hostname.
+ required: false
+ default: localhost
+ type: str
+ function:
+ description:
+ - The function to execute.
+ required: true
+ choices:
+ - Start
+ - Stop
+ - RestartInstance
+ - Shutdown
+ - InstanceStart
+ - GetProcessList
+ - Bootstrap
+ - InstanceStop
+ - StopService
+ - StartService
+ - RestartService
+ - ParameterValue
+ - GetStartProfile
+ - GetTraceFile
+ - GetAlertTree
+ - GetAlerts
+ - GetEnvironment
+ - GetVersionInfo
+ - GetQueueStatistic
+ - GetInstanceProperties
+ - ListDeveloperTraces
+ - ReadDeveloperTrace
+ - ListLogFiles
+ - ReadLogFile
+ - AnalyseLogFiles
+ - ConfigureLogFileList
+ - GetLogFileList
+ - CreateSnapshot
+ - ReadSnapshot
+ - ListSnapshots
+ - DeleteSnapshots
+ - GetAccessPointList
+ - GetProcessParameter
+ - SetProcessParameter
+ - SetProcessParameter2
+ - CheckParameter
+ - OSExecute
+ - SendSignal
+ - GetCallstack
+ - GetSystemInstanceList
+ - StartSystem
+ - StopSystem
+ - RestartSystem
+ - GetSystemUpdateList
+ - UpdateSystem
+ - UpdateSCSInstance
+ - CheckUpdateSystem
+ - AccessCheck
+ - GetSecNetworkId
+ - GetNetworkId
+ - RequestLogonFile
+ - UpdateSystemPKI
+ - UpdateInstancePSE
+ - StorePSE
+ - DeletePSE
+ - CheckPSE
+ - CreatePSECredential
+ - HACheckConfig
+ - HACheckFailoverConfig
+ - HAGetFailoverConfig
+ - HAFailoverToNode
+ - HASetMaintenanceMode
+ - HACheckMaintenanceMode
+ - ABAPReadSyslog
+ - ABAPReadRawSyslog
+ - ABAPGetWPTable
+ - ABAPGetComponentList
+ - ABAPCheckRFCDestinations
+ - ABAPGetSystemWPTable
+ - J2EEControlProcess
+ - J2EEControlCluster
+ - J2EEEnableDbgSession
+ - J2EEDisableDbgSession
+ - J2EEGetProcessList
+ - J2EEGetProcessList2
+ - J2EEGetThreadList
+ - J2EEGetThreadList2
+ - J2EEGetThreadCallStack
+ - J2EEGetThreadTaskStack
+ - J2EEGetSessionList
+ - J2EEGetCacheStatistic
+ - J2EEGetCacheStatistic2
+ - J2EEGetApplicationAliasList
+ - J2EEGetComponentList
+ - J2EEControlComponents
+ - J2EEGetWebSessionList
+ - J2EEGetWebSessionList2
+ - J2EEGetEJBSessionList
+ - J2EEGetRemoteObjectList
+ - J2EEGetVMGCHistory
+ - J2EEGetVMGCHistory2
+ - J2EEGetVMHeapInfo
+ - J2EEGetClusterMsgList
+ - J2EEGetSharedTableInfo
+ - ICMGetThreadList
+ - ICMGetConnectionList
+ - ICMGetProxyConnectionList
+ - ICMGetCacheEntries
+ - WebDispGetServerList
+ - WebDispGetGroupList
+ - WebDispGetVirtHostList
+ - WebDispGetUrlPrefixList
+ - EnqGetStatistic
+ - EnqGetLockTable
+ - EnqRemoveUserLocks
+ - StartWait
+ - StopWait
+ - WaitforStarted
+ - WaitforStopped
+ - RestartServiceWait
+ - WaitforServiceStarted
+ - CheckHostAgent
+ type: str
+ parameter:
+ description:
+ - The parameter to pass to the function.
+ required: false
+ type: str
+ force:
+ description:
+ - Forces the execution of the function C(Stop).
+ required: false
+ default: false
+ type: bool
+author:
+ - Rainer Leber (@RainerLeber)
+ - Robert Kraemer (@rkpobe)
+notes:
+ - Does not support C(check_mode).
+'''
+
+EXAMPLES = r"""
+- name: GetProcessList with sysnr
+ community.sap_libs.sap_control_exec:
+ hostname: 192.168.8.15
+ sysnr: "01"
+ function: GetProcessList
+
+- name: GetProcessList with custom port
+ community.sap_libs.sap_control_exec:
+ hostname: 192.168.8.15
+ function: GetProcessList
+ port: 50113
+
+- name: ParameterValue
+ community.sap_libs.sap_control_exec:
+ hostname: 192.168.8.15
+ sysnr: "01"
+ username: hdbadm
+ password: test1234#
+ function: ParameterValue
+ parameter: ztta
+"""
+
+RETURN = r'''
+msg:
+ description: Success-message with functionname.
+ type: str
+ returned: always
+ sample: 'Succesful execution of: GetProcessList'
+out:
+ description: The full output of the required function.
+ type: list
+ elements: dict
+ returned: always
+ sample: [{
+ "item": [
+ {
+ "description": "MessageServer",
+ "dispstatus": "SAPControl-GREEN",
+ "elapsedtime": "412:30:50",
+ "name": "msg_server",
+ "pid": 70643,
+ "starttime": "2022 03 13 15:22:42",
+ "textstatus": "Running"
+ },
+ {
+ "description": "EnqueueServer",
+ "dispstatus": "SAPControl-GREEN",
+ "elapsedtime": "412:30:50",
+ "name": "enserver",
+ "pid": 70644,
+ "starttime": "2022 03 13 15:22:42",
+ "textstatus": "Running"
+ },
+ {
+ "description": "Gateway",
+ "dispstatus": "SAPControl-GREEN",
+ "elapsedtime": "412:30:50",
+ "name": "gwrd",
+ "pid": 70645,
+ "starttime": "2022 03 13 15:22:42",
+ "textstatus": "Running"
+ }
+ ]
+ }]
+'''
+
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+import traceback
+try:
+ from suds.client import Client
+ from suds.sudsobject import asdict
+except ImportError:
+ HAS_SUDS_LIBRARY = False
+ SUDS_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ SUDS_LIBRARY_IMPORT_ERROR = None
+ HAS_SUDS_LIBRARY = True
+
+
+def choices():
+ retlist = ["Start", "Stop", "RestartInstance", "Shutdown", "InstanceStart", 'GetProcessList',
+ 'Bootstrap', 'InstanceStop', 'StopService', 'StartService', 'RestartService', 'ParameterValue',
+ 'GetStartProfile', 'GetTraceFile', 'GetAlertTree', 'GetAlerts', 'GetEnvironment', 'GetVersionInfo',
+ 'GetQueueStatistic', 'GetInstanceProperties', 'ListDeveloperTraces', 'ReadDeveloperTrace',
+ 'ListLogFiles', 'ReadLogFile', 'AnalyseLogFiles', 'ConfigureLogFileList', 'GetLogFileList', 'CreateSnapshot', 'ReadSnapshot',
+ 'ListSnapshots', 'DeleteSnapshots', 'GetAccessPointList', 'GetProcessParameter', 'SetProcessParameter',
+ 'SetProcessParameter2', 'CheckParameter', 'OSExecute', 'SendSignal', 'GetCallstack', 'GetSystemInstanceList',
+ 'StartSystem', 'StopSystem', 'RestartSystem', 'GetSystemUpdateList', 'UpdateSystem', 'UpdateSCSInstance',
+ 'CheckUpdateSystem', 'AccessCheck', 'GetSecNetworkId', 'GetNetworkId', 'RequestLogonFile',
+ 'UpdateSystemPKI', 'UpdateInstancePSE', 'StorePSE', 'DeletePSE', 'CheckPSE', 'CreatePSECredential',
+ 'HACheckConfig', 'HACheckFailoverConfig', 'HAGetFailoverConfig', 'HAFailoverToNode',
+ 'HASetMaintenanceMode', 'HACheckMaintenanceMode', 'ABAPReadSyslog', 'ABAPReadRawSyslog',
+ 'ABAPGetWPTable', 'ABAPGetComponentList', 'ABAPCheckRFCDestinations',
+ 'ABAPGetSystemWPTable', 'J2EEControlProcess', 'J2EEControlCluster', 'J2EEEnableDbgSession',
+ 'J2EEDisableDbgSession', 'J2EEGetProcessList', 'J2EEGetProcessList2', 'J2EEGetThreadList', 'J2EEGetThreadList2',
+ 'J2EEGetThreadCallStack', 'J2EEGetThreadTaskStack', 'J2EEGetSessionList', 'J2EEGetCacheStatistic',
+ 'J2EEGetCacheStatistic2', 'J2EEGetApplicationAliasList', 'J2EEGetComponentList',
+ 'J2EEControlComponents', 'J2EEGetWebSessionList', 'J2EEGetWebSessionList2', 'J2EEGetEJBSessionList', 'J2EEGetRemoteObjectList',
+ 'J2EEGetVMGCHistory', 'J2EEGetVMGCHistory2', 'J2EEGetVMHeapInfo', 'J2EEGetClusterMsgList', 'J2EEGetSharedTableInfo',
+ 'ICMGetThreadList', 'ICMGetConnectionList', 'ICMGetProxyConnectionList', 'ICMGetCacheEntries', 'WebDispGetServerList',
+ 'WebDispGetGroupList', 'WebDispGetVirtHostList', 'WebDispGetUrlPrefixList', 'EnqGetStatistic', 'EnqGetLockTable',
+ 'EnqRemoveUserLocks', 'StartWait', 'StopWait', 'WaitforStarted', 'WaitforStopped', 'RestartServiceWait',
+ 'WaitforServiceStarted', 'CheckHostAgent']
+ return retlist
+
+
+# converts recursively the suds object to a dictionary e.g. {'item': [{'name': hdbdaemon, 'value': '1'}]}
+def recursive_dict(suds_object):
+ out = {}
+ if isinstance(suds_object, str):
+ return suds_object
+ for k, v in asdict(suds_object).items():
+ if hasattr(v, '__keylist__'):
+ out[k] = recursive_dict(v)
+ elif isinstance(v, list):
+ out[k] = []
+ for item in v:
+ if hasattr(item, '__keylist__'):
+ out[k].append(recursive_dict(item))
+ else:
+ out[k].append(item)
+ else:
+ out[k] = v
+ return out
+
+
+def connection(hostname, port, username, password, function, parameter):
+ url = 'http://{0}:{1}/sapcontrol?wsdl'.format(hostname, port)
+ client = Client(url, username=username, password=password)
+ _function = getattr(client.service, function)
+ if parameter is not None:
+ result = _function(parameter)
+ elif function == "StartSystem":
+ result = _function(waittimeout=0)
+ elif function == "StopSystem" or function == "RestartSystem":
+ result = _function(waittimeout=0, softtimeout=0)
+ else:
+ result = _function()
+
+ return result
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ sysnr=dict(type='str', required=False),
+ port=dict(type='int', required=False),
+ username=dict(type='str', required=False),
+ password=dict(type='str', no_log=True, required=False),
+ hostname=dict(type='str', default="localhost"),
+ function=dict(type='str', required=True, choices=choices()),
+ parameter=dict(type='str', required=False),
+ force=dict(type='bool', default=False),
+ ),
+ required_one_of=[('sysnr', 'port')],
+ mutually_exclusive=[('sysnr', 'port')],
+ supports_check_mode=False,
+ )
+ result = dict(changed=False, msg='', out={}, error='')
+ params = module.params
+
+ sysnr = params['sysnr']
+ port = params['port']
+ username = params['username']
+ password = params['password']
+ hostname = params['hostname']
+ function = params['function']
+ parameter = params['parameter']
+ force = params['force']
+
+ if not HAS_SUDS_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('suds'),
+ exception=SUDS_LIBRARY_IMPORT_ERROR)
+
+ if function == "Stop":
+ if force is False:
+ module.fail_json(msg="Stop function requires force: True")
+
+ if port is None:
+ try:
+ try:
+ conn = connection(hostname, "5{0}14".format((sysnr).zfill(2)), username, password, function, parameter)
+ except Exception:
+ conn = connection(hostname, "5{0}13".format((sysnr).zfill(2)), username, password, function, parameter)
+ except Exception as err:
+ result['error'] = str(err)
+ else:
+ try:
+ conn = connection(hostname, port, username, password, function, parameter)
+ except Exception as err:
+ result['error'] = str(err)
+
+ if result['error'] != '':
+ result['msg'] = 'Something went wrong connecting to the SAPCONTROL SOAP API.'
+ module.fail_json(**result)
+
+ if conn is not None:
+ returned_data = recursive_dict(conn)
+ else:
+ returned_data = conn
+
+ result['changed'] = True
+ result['msg'] = "Succesful execution of: " + function
+ result['out'] = [returned_data]
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sap_hdbsql.py b/ansible_collections/community/sap_libs/plugins/modules/sap_hdbsql.py
new file mode 100644
index 000000000..994db7049
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sap_hdbsql.py
@@ -0,0 +1,246 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# 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.
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_hdbsql
+short_description: Ansible Module to execute SQL on SAP HANA
+version_added: "1.0.0"
+description: This module executes SQL statements on HANA with hdbsql.
+options:
+ sid:
+ description: The system ID.
+ type: str
+ required: false
+ bin_path:
+ description: The path to the hdbsql binary.
+ type: str
+ required: false
+ instance:
+ description: The instance number.
+ type: str
+ required: true
+ user:
+ description: A dedicated username. The user could be also in hdbuserstore.
+ type: str
+ default: SYSTEM
+ userstore:
+ description: If C(true), the user must be in hdbuserstore.
+ type: bool
+ default: false
+ password:
+ description:
+ - The password to connect to the database.
+ - "B(Note:) Since the passwords have to be passed as command line arguments, I(userstore=true) should
+ be used whenever possible, as command line arguments can be seen by other users
+ on the same machine."
+ type: str
+ autocommit:
+ description: Autocommit the statement.
+ type: bool
+ default: true
+ host:
+ description: The Host IP address. The port can be defined as well.
+ type: str
+ database:
+ description: Define the database on which to connect.
+ type: str
+ encrypted:
+ description: Use encrypted connection.
+ type: bool
+ default: false
+ filepath:
+ description:
+ - One or more files each containing one SQL query to run.
+ - Must be a string or list containing strings.
+ type: list
+ elements: path
+ query:
+ description:
+ - SQL query to run.
+ - Must be a string or list containing strings. Please note that if you supply a string, it will be split by commas (C(,)) to a list.
+ It is better to supply a one-element list instead to avoid mangled input.
+ type: list
+ elements: str
+notes:
+ - Does not support C(check_mode). Always reports that the state has changed even if no changes have been made.
+author:
+ - Rainer Leber (@rainerleber)
+'''
+
+EXAMPLES = r'''
+- name: Simple select query
+ community.sap_libs.sap_hdbsql:
+ sid: "hdb"
+ instance: "01"
+ password: "Test123"
+ query: select user_name from users
+
+- name: RUN select query with host port
+ community.sap_libs.sap_hdbsql:
+ sid: "hdb"
+ instance: "01"
+ password: "Test123"
+ host: "10.10.2.4:30001"
+ query: select user_name from users
+
+- name: Run several queries
+ community.sap_libs.sap_hdbsql:
+ sid: "hdb"
+ instance: "01"
+ password: "Test123"
+ query:
+ - select user_name from users
+ - select * from SYSTEM
+ host: "localhost"
+ autocommit: False
+
+- name: Run several queries with path
+ community.sap_libs.sap_hdbsql:
+ bin_path: "/usr/sap/HDB/HDB01/exe/hdbsql"
+ instance: "01"
+ password: "Test123"
+ query:
+ - select user_name from users
+ - select * from users
+ host: "localhost"
+ autocommit: False
+
+- name: Run several queries from file
+ community.sap_libs.sap_hdbsql:
+ sid: "hdb"
+ instance: "01"
+ password: "Test123"
+ filepath:
+ - /tmp/HANA_CPU_UtilizationPerCore_2.00.020+.txt
+ - /tmp/HANA.txt
+ host: "localhost"
+
+- name: Run several queries from user store
+ community.sap_libs.sap_hdbsql:
+ sid: "hdb"
+ instance: "01"
+ user: hdbstoreuser
+ userstore: true
+ query:
+ - select user_name from users
+ - select * from users
+ autocommit: False
+'''
+
+RETURN = r'''
+query_result:
+ description: List containing results of all queries executed (one sublist for every query).
+ returned: on success
+ type: list
+ elements: list
+ sample: [[{"Column": "Value1"}, {"Column": "Value2"}], [{"Column": "Value1"}, {"Column": "Value2"}]]
+'''
+
+import csv
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import StringIO
+from ansible.module_utils.common.text.converters import to_native
+
+
+def csv_to_list(rawcsv):
+ reader_raw = csv.DictReader(StringIO(rawcsv))
+ reader = [dict((k, v.strip()) for k, v in row.items()) for row in reader_raw]
+ return list(reader)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ sid=dict(type='str', required=False),
+ bin_path=dict(type='str', required=False),
+ instance=dict(type='str', required=True),
+ encrypted=dict(type='bool', default=False),
+ host=dict(type='str', required=False),
+ user=dict(type='str', default="SYSTEM"),
+ userstore=dict(type='bool', default=False),
+ password=dict(type='str', no_log=True),
+ database=dict(type='str', required=False),
+ query=dict(type='list', elements='str', required=False),
+ filepath=dict(type='list', elements='path', required=False),
+ autocommit=dict(type='bool', default=True),
+ ),
+ required_one_of=[('query', 'filepath'), ('sid', 'instance')],
+ required_if=[('userstore', False, ['password'])],
+ supports_check_mode=False,
+ )
+ rc, out, err, out_raw = [0, [], "", ""]
+
+ params = module.params
+
+ sid = params['sid']
+ bin_path = params['bin_path']
+ instance = params['instance']
+ user = params['user']
+ userstore = params['userstore']
+ password = params['password']
+ autocommit = params['autocommit']
+ host = params['host']
+ database = params['database']
+ encrypted = params['encrypted']
+
+ filepath = params['filepath']
+ query = params['query']
+
+ if bin_path is None:
+ bin_path = "/usr/sap/{sid}/HDB{instance}/exe/hdbsql".format(sid=sid.upper(), instance=instance)
+
+ try:
+ command = [module.get_bin_path(bin_path, required=True)]
+ except Exception as e:
+ module.fail_json(msg='Failed to find hdbsql at the expected path "{0}".Please check SID and instance number: "{1}"'.format(bin_path, to_native(e)))
+
+ if encrypted is True:
+ command.extend(['-attemptencrypt'])
+ if autocommit is False:
+ command.extend(['-z'])
+ if host is not None:
+ command.extend(['-n', host])
+ if database is not None:
+ command.extend(['-d', database])
+ # -x Suppresses additional output, such as the number of selected rows in a result set.
+ if userstore:
+ command.extend(['-x', '-U', user])
+ else:
+ command.extend(['-x', '-i', instance, '-u', user, '-p', password])
+
+ if filepath is not None:
+ command.extend(['-I'])
+ for p in filepath:
+ # makes a command like hdbsql -i 01 -u SYSTEM -p secret123# -I /tmp/HANA_CPU_UtilizationPerCore_2.00.020+.txt,
+ # iterates through files and append the output to var out.
+ query_command = command + [p]
+ (rc, out_raw, err) = module.run_command(query_command)
+ out.append(csv_to_list(out_raw))
+ if query is not None:
+ for q in query:
+ # makes a command like hdbsql -i 01 -u SYSTEM -p secret123# "select user_name from users",
+ # iterates through multiple commands and append the output to var out.
+ query_command = command + [q]
+ (rc, out_raw, err) = module.run_command(query_command)
+ out.append(csv_to_list(out_raw))
+ changed = True
+
+ module.exit_json(changed=changed, rc=rc, query_result=out, stderr=err)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sap_pyrfc.py b/ansible_collections/community/sap_libs/plugins/modules/sap_pyrfc.py
new file mode 100644
index 000000000..e4a30cd19
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sap_pyrfc.py
@@ -0,0 +1,187 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Sean Freeman ,
+# Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# 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.
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_pyrfc
+
+short_description: Ansible Module for use of SAP PyRFC to execute SAP RFCs (Remote Function Calls) to SAP remote-enabled function modules
+
+version_added: "1.2.0"
+
+description:
+ - This module will executes rfc calls on a sap system.
+ - It is a generic approach to call rfc functions on a SAP System.
+ - This module should be used where no module or role is provided.
+
+options:
+ function:
+ description: The SAP RFC function to call.
+ required: true
+ type: str
+ parameters:
+ description: The parameters which are needed by the function.
+ required: true
+ type: dict
+ connection:
+ description: The required connection details.
+ required: true
+ type: dict
+ suboptions:
+ ashost:
+ description: The required host for the SAP system. Can be either an FQDN or IP Address.
+ type: str
+ required: true
+ sysid:
+ description: The systemid of the SAP system.
+ type: str
+ required: false
+ sysnr:
+ description:
+ - The system number of the SAP system.
+ - You must quote the value to ensure retaining the leading zeros.
+ type: str
+ required: true
+ client:
+ description:
+ - The client number to connect to.
+ - You must quote the value to ensure retaining the leading zeros.
+ type: str
+ required: true
+ user:
+ description: The required username for the SAP system.
+ type: str
+ required: true
+ passwd:
+ description: The required password for the SAP system.
+ type: str
+ required: true
+ lang:
+ description: The used language to execute.
+ type: str
+ required: false
+
+requirements:
+ - pyrfc >= 2.4.0
+
+author:
+ - Sean Freeman (@seanfreeman)
+ - Rainer Leber (@rainerleber)
+'''
+
+EXAMPLES = '''
+- name: test the pyrfc module
+ community.sap_libs.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
+'''
+
+RETURN = r'''
+result:
+ description: The execution description.
+ type: dict
+ returned: always
+ sample: {"ECHOTEXT": "Hello SAP!",
+ "RESPTEXT": "SAP R/3 Rel. 756 Sysid: TST Date: 20220710 Time: 140717 Logon_Data: 000/DDIC/E"}
+'''
+
+import traceback
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+from ..module_utils.pyrfc_handler import get_connection
+
+try:
+ from pyrfc import ABAPApplicationError, ABAPRuntimeError, CommunicationError, LogonError
+except ImportError:
+ HAS_PYRFC_LIBRARY = False
+ PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ PYRFC_LIBRARY_IMPORT_ERROR = None
+ HAS_PYRFC_LIBRARY = True
+
+
+def main():
+ msg = None
+ params_spec = dict(
+ ashost=dict(type='str', required=True),
+ sysid=dict(type='str', required=False),
+ sysnr=dict(type='str', required=True),
+ client=dict(type='str', required=True),
+ user=dict(type='str', required=True),
+ passwd=dict(type='str', required=True, no_log=True),
+ lang=dict(type='str', required=False),
+ )
+
+ argument_spec = dict(function=dict(required=True, type='str'),
+ parameters=dict(required=True, type='dict'),
+ connection=dict(
+ required=True, type='dict', options=params_spec),
+ )
+
+ 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')
+
+ if not HAS_PYRFC_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('pyrfc'),
+ exception=PYRFC_LIBRARY_IMPORT_ERROR)
+
+ # 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)
+ error_msg = None
+ except CommunicationError as err:
+ msg = "Could not connect to server"
+ error_msg = err.message
+ except LogonError as err:
+ msg = "Could not log in"
+ error_msg = err.message
+ except (ABAPApplicationError, ABAPRuntimeError) as err:
+ msg = "ABAP error occurred"
+ error_msg = err.message
+ except Exception as err:
+ msg = "Something went wrong."
+ error_msg = err
+ else:
+ module.exit_json(changed=True, result=result)
+
+ if msg:
+ module.fail_json(msg=msg, exception=error_msg)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sap_snote.py b/ansible_collections/community/sap_libs/plugins/modules/sap_snote.py
new file mode 100644
index 000000000..b97e9a25b
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sap_snote.py
@@ -0,0 +1,267 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# 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.
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_snote
+
+short_description: This module will upload and (de)implements C(SNOTES) in a SAP S4HANA environment.
+
+version_added: "1.0.0"
+
+description:
+ - The C(sap_snote) module depends on C(pyrfc) Python library (version 2.4.0 and upwards).
+ Depending on distribution you are using, you may need to install additional packages to
+ have these available.
+ - This module will use the Function Group C(SCWB_API).
+ - The C(TMS) must be configured at first.
+ - Integrating SNOTES cannot be done via C(DDIC)- or C(SAP*)-User.
+options:
+ state:
+ description:
+ - The decision what to do with the SNOTE.
+ - Could be C('present'), C('absent')
+ default: 'present'
+ choices:
+ - 'present'
+ - 'absent'
+ required: false
+ type: str
+ conn_username:
+ description: The required username for the SAP system.
+ required: true
+ type: str
+ conn_password:
+ description: The required password for the SAP system.
+ required: true
+ type: str
+ host:
+ description: The required host for the SAP system. Can be either an FQDN or IP Address.
+ required: true
+ type: str
+ sysnr:
+ description:
+ - The system number of the SAP system.
+ - You must quote the value to ensure retaining the leading zeros.
+ required: false
+ default: '01'
+ type: str
+ client:
+ description:
+ - The client number to connect to.
+ - You must quote the value to ensure retaining the leading zeros.
+ required: false
+ default: '000'
+ type: str
+ snote_path:
+ description:
+ - The path to the extracted SNOTE txt file.
+ - The File could be extracted from SAR package.
+ - If C(snote_path) is not provided, the C(snote) parameter must be defined.
+ - The SNOTE txt file must be at a place where the SAP System is authorized for. For example C(/usr/sap/trans/files).
+ required: false
+ type: str
+ snote:
+ description:
+ - With the C(snote) paramter only implementation and deimplementation will work.
+ - Upload SNOTES to the System is only available if C(snote_path) is provided.
+ required: false
+ type: str
+
+requirements:
+ - pyrfc >= 2.4.0
+
+author:
+ - Rainer Leber (@rainerleber)
+'''
+
+EXAMPLES = r'''
+- name: test snote module
+ hosts: localhost
+ tasks:
+ - name: implement SNOTE
+ community.sap_libs.sap_snote:
+ conn_username: 'DDIC'
+ conn_password: 'Passwd1234'
+ host: 192.168.1.100
+ sysnr: '01'
+ client: '000'
+ state: present
+ snote_path: /usr/sap/trans/tmp/0002949148.txt
+
+- name: test snote module without path
+ hosts: localhost
+ tasks:
+ - name: deimplement SNOTE
+ community.sap_libs.sap_snote:
+ conn_username: 'DDIC'
+ conn_password: 'Passwd1234'
+ host: 192.168.1.100
+ sysnr: '01'
+ client: '000'
+ state: absent
+ snote: 0002949148
+
+'''
+
+RETURN = r'''
+msg:
+ description: A small execution description.
+ type: str
+ returned: always
+ sample: 'SNOTE 000298026 implemented.'
+out:
+ description: A complete description of the SNOTE implementation. If this is available.
+ type: list
+ elements: dict
+ returned: always
+ sample: '{
+ "RETURN": [{"ES_MSG": { "MSGNO": "000", "MSGTY": "", "MSGTXT": "", "MSGV1": "" },
+ "ET_MSG": [],
+ "EV_RC": 0,
+ "ET_MISSING_NOTES": [],
+ "IT_FILENAME": [{"FILENAME": "/usr/sap/trans/tmp/0002980265.txt"}],
+ "IT_NOTES": [{"NUMM": "0002980265", "VERSNO": "0000"}]
+ }]}'
+'''
+
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+from os import path as os_path
+import traceback
+try:
+ from pyrfc import Connection
+except ImportError:
+ HAS_PYRFC_LIBRARY = False
+ ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ ANOTHER_LIBRARY_IMPORT_ERROR = None
+ HAS_PYRFC_LIBRARY = True
+
+
+def call_rfc_method(connection, method_name, kwargs):
+ # PyRFC call function
+ return connection.call(method_name, **kwargs)
+
+
+def check_implementation(conn, snote):
+ check_implemented = call_rfc_method(conn, 'SCWB_API_GET_NOTES_IMPLEMENTED', {})
+ for snote_list in check_implemented['ET_NOTES_IMPL']:
+ if snote in snote_list['NUMM']:
+ return True
+ return False
+
+
+def run_module():
+ module = AnsibleModule(
+ argument_spec=dict(
+ state=dict(default='present', choices=['absent', 'present']),
+ conn_username=dict(type='str', required=True),
+ conn_password=dict(type='str', required=True, no_log=True),
+ host=dict(type='str', required=True),
+ sysnr=dict(type='str', default="01"),
+ client=dict(type='str', default="000"),
+ snote_path=dict(type='str', required=False),
+ snote=dict(type='str', required=False),
+ ),
+ required_one_of=[('snote_path', 'snote')],
+ supports_check_mode=False,
+ )
+ result = dict(changed=False, msg='', out={}, error='')
+ raw = ""
+ post_check = False
+
+ params = module.params
+
+ state = params['state']
+ conn_username = (params['conn_username']).upper()
+ conn_password = params['conn_password']
+ host = params['host']
+ sysnr = (params['sysnr']).zfill(2)
+ client = params['client']
+
+ path = params['snote_path']
+ snote = params['snote']
+
+ if not HAS_PYRFC_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('pyrfc'),
+ exception=ANOTHER_LIBRARY_IMPORT_ERROR)
+
+ if conn_username == "DDIC" or conn_username == "SAP*":
+ result['msg'] = 'User C(DDIC) or C(SAP*) not allowed for this operation.'
+ module.fail_json(**result)
+
+ # basic RFC connection with pyrfc
+ try:
+ conn = Connection(user=conn_username, passwd=conn_password, ashost=host, sysnr=sysnr, client=client)
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'Something went wrong connecting to the SAP system.'
+ module.fail_json(**result)
+
+ # pre evaluation of parameters
+ if path is not None:
+ if path.endswith('.txt'):
+ # splits snote number from path and txt extension
+ snote = os_path.basename(os_path.normpath(path)).split('.')[0]
+ else:
+ result['msg'] = 'The path must include the extracted snote file and ends with txt.'
+ module.fail_json(**result)
+
+ pre_check = check_implementation(conn, snote)
+
+ if state == "absent" and pre_check:
+ raw = call_rfc_method(conn, 'SCWB_API_NOTES_DEIMPLEMENT', {'IT_NOTES': [snote]})
+
+ if state == "present" and not pre_check:
+ if path:
+ raw_upload = call_rfc_method(conn, 'SCWB_API_UPLOAD_NOTES', {'IT_FILENAME': [path], 'IT_NOTES': [snote]})
+ if raw_upload['EV_RC'] != 0:
+ result['out'] = raw_upload
+ result['msg'] = raw_upload['ES_MSG']['MSGTXT']
+ module.fail_json(**result)
+
+ raw = call_rfc_method(conn, 'SCWB_API_NOTES_IMPLEMENT', {'IT_NOTES': [snote]})
+ queued = call_rfc_method(conn, 'SCWB_API_CINST_QUEUE_GET', {})
+
+ if queued['ET_MANUAL_ACTIVITIES']:
+ raw = call_rfc_method(conn, 'SCWB_API_CONFIRM_MAN_ACTIVITY', {})
+
+ if raw:
+ if raw['EV_RC'] == 0:
+ post_check = check_implementation(conn, snote)
+ if post_check and state == "present":
+ result['changed'] = True
+ result['msg'] = 'SNOTE "{0}" implemented.'.format(snote)
+ if not post_check and state == "absent":
+ result['changed'] = True
+ result['msg'] = 'SNOTE "{0}" deimplemented.'.format(snote)
+ else:
+ result['msg'] = "Something went wrong."
+ module.fail_json(**result)
+ result['out'] = raw
+ else:
+ result['msg'] = "Nothing to do."
+
+ module.exit_json(**result)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sap_system_facts.py b/ansible_collections/community/sap_libs/plugins/modules/sap_system_facts.py
new file mode 100644
index 000000000..82e7c0a8c
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sap_system_facts.py
@@ -0,0 +1,213 @@
+#!/usr/bin/python
+
+# Copyright: (c) 2022, Rainer Leber rainerleber@gmail.com>
+# 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.
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_system_facts
+
+short_description: Gathers SAP facts in a host
+
+version_added: "1.0.0"
+
+description:
+ - This facts module gathers SAP system facts about the running instance.
+
+author:
+ - Rainer Leber (@rainerleber)
+
+notes:
+ - Supports C(check_mode).
+'''
+
+EXAMPLES = r'''
+- name: Return SAP system ansible_facts
+ community.sap_libs.sap_system_facts:
+'''
+
+RETURN = r'''
+# These are examples of possible return values,
+# and in general should use other names for return values.
+ansible_facts:
+ description: Facts about the running SAP systems.
+ returned: always
+ type: dict
+ contains:
+ sap:
+ description: Facts about the running SAP systems.
+ type: list
+ elements: dict
+ returned: When SAP system fact is present
+ sample: [
+ {
+ "InstanceType": "NW",
+ "NR": "00",
+ "SID": "ABC",
+ "TYPE": "ASCS"
+ },
+ {
+ "InstanceType": "NW",
+ "NR": "01",
+ "SID": "ABC",
+ "TYPE": "PAS"
+ },
+ {
+ "InstanceType": "HANA",
+ "NR": "02",
+ "SID": "HDB",
+ "TYPE": "HDB"
+ },
+ {
+ "InstanceType": "NW",
+ "NR": "80",
+ "SID": "WEB",
+ "TYPE": "WebDisp"
+ }
+ ]
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+import os
+import re
+
+
+def get_all_hana_sid():
+ hana_sid = list()
+ if os.path.isdir("/hana/shared"):
+ # /hana/shared directory exists
+ for sid in os.listdir('/hana/shared'):
+ if os.path.isdir("/usr/sap/" + sid):
+ hana_sid = hana_sid + [sid]
+ if hana_sid:
+ return hana_sid
+
+
+def get_all_nw_sid():
+ nw_sid = list()
+ if os.path.isdir("/sapmnt"):
+ # /sapmnt directory exists
+ for sid in os.listdir('/sapmnt'):
+ if os.path.isdir("/usr/sap/" + sid):
+ nw_sid = nw_sid + [sid]
+ else:
+ # Check to see if /sapmnt/SID/sap_bobj exists
+ if os.path.isdir("/sapmnt/" + sid + "/sap_bobj"):
+ # is a bobj system
+ nw_sid = nw_sid + [sid]
+ if nw_sid:
+ return nw_sid
+
+
+def get_hana_nr(sids, module):
+ hana_list = list()
+ for sid in sids:
+ for instance in os.listdir('/usr/sap/' + sid):
+ if 'HDB' in instance:
+ instance_nr = instance[-2:]
+ # check if instance number exists
+ command = [module.get_bin_path('/usr/sap/hostctrl/exe/sapcontrol', required=True)]
+ command.extend(['-nr', instance_nr, '-function', 'GetProcessList'])
+ check_instance = module.run_command(command, check_rc=False)
+ # sapcontrol returns c(0 - 5) exit codes only c(1) is unavailable
+ if check_instance[0] != 1:
+ hana_list.append({'NR': instance_nr, 'SID': sid, 'TYPE': 'HDB', 'InstanceType': 'HANA'})
+ return hana_list
+
+
+def get_nw_nr(sids, module):
+ nw_list = list()
+ type = ""
+ for sid in sids:
+ for instance in os.listdir('/usr/sap/' + sid):
+ instance_nr = instance[-2:]
+ command = [module.get_bin_path('/usr/sap/hostctrl/exe/sapcontrol', required=True)]
+ # check if returned instance_nr is a number because sapcontrol returns all if a random string is provided
+ if instance_nr.isdigit():
+ command.extend(['-nr', instance_nr, '-function', 'GetInstanceProperties'])
+ check_instance = module.run_command(command, check_rc=False)
+ if check_instance[0] != 1:
+ for line in check_instance[1].splitlines():
+ if re.search('INSTANCE_NAME', line):
+ # convert to list and extract last
+ type_raw = (line.strip('][').split(', '))[-1]
+ # split instance number
+ type = type_raw[:-2]
+ nw_list.append({'NR': instance_nr, 'SID': sid, 'TYPE': get_instance_type(type), 'InstanceType': 'NW'})
+ return nw_list
+
+
+def get_instance_type(raw_type):
+ if raw_type[0] == "D":
+ # It's a PAS
+ type = "PAS"
+ elif raw_type[0] == "A":
+ # It's an ASCS
+ type = "ASCS"
+ elif raw_type[0] == "W":
+ # It's a Webdisp
+ type = "WebDisp"
+ elif raw_type[0] == "J":
+ # It's a Java
+ type = "Java"
+ elif raw_type[0] == "S":
+ # It's an SCS
+ type = "SCS"
+ elif raw_type[0] == "E":
+ # It's an ERS
+ type = "ERS"
+ else:
+ # Unknown instance type
+ type = "XXX"
+ return type
+
+
+def run_module():
+ module_args = dict()
+ system_result = list()
+
+ result = dict(
+ changed=False,
+ ansible_facts=dict(),
+ )
+
+ module = AnsibleModule(
+ argument_spec=module_args,
+ supports_check_mode=True,
+ )
+
+ hana_sid = get_all_hana_sid()
+ if hana_sid:
+ system_result = system_result + get_hana_nr(hana_sid, module)
+
+ nw_sid = get_all_nw_sid()
+ if nw_sid:
+ system_result = system_result + get_nw_nr(nw_sid, module)
+
+ if system_result:
+ result['ansible_facts'] = {'sap': system_result}
+ else:
+ result['ansible_facts']
+
+ if module.check_mode:
+ module.exit_json(**result)
+
+ module.exit_json(**result)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sap_task_list_execute.py b/ansible_collections/community/sap_libs/plugins/modules/sap_task_list_execute.py
new file mode 100644
index 000000000..f46a5d6f9
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sap_task_list_execute.py
@@ -0,0 +1,350 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# 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.
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_task_list_execute
+short_description: Perform SAP Task list execution
+version_added: "0.1.0"
+description:
+ - The M(community.sap_libs.sap_task_list_execute) module depends on C(pyrfc) Python library (version 2.4.0 and upwards).
+ Depending on distribution you are using, you may need to install additional packages to
+ have these available.
+ - Tasks in the task list which requires manual activities will be confirmed automatically.
+ - This module will use the RFC package C(STC_TM_API).
+
+requirements:
+ - pyrfc >= 2.4.0
+ - xmltodict
+
+options:
+ conn_username:
+ description: The required username for the SAP system.
+ required: true
+ type: str
+ conn_password:
+ description: The required password for the SAP system.
+ required: true
+ type: str
+ host:
+ description: The required host for the SAP system. Can be either an FQDN or IP Address.
+ required: true
+ type: str
+ sysnr:
+ description:
+ - The system number of the SAP system.
+ - You must quote the value to ensure retaining the leading zeros.
+ default: '00'
+ type: str
+ client:
+ description:
+ - The client number to connect to.
+ - You must quote the value to ensure retaining the leading zeros.
+ default: '000'
+ type: str
+ task_to_execute:
+ description: The task list which will be executed.
+ required: true
+ type: str
+ task_parameters:
+ description:
+ - The tasks and the parameters for execution.
+ - If the task list does not need any parameters, this could be empty.
+ - If only specific tasks from the task list should be executed,
+ the tasks even when no parameter is needed must be provided
+ alongside with the module parameter I(task_skip=true).
+ type: list
+ elements: dict
+ suboptions:
+ TASKNAME:
+ description: The name of the task in the task list.
+ type: str
+ required: true
+ FIELDNAME:
+ description: The name of the field of the task.
+ type: str
+ VALUE:
+ description: The value which have to be set.
+ type: raw
+ task_settings:
+ description:
+ - Setting for the execution of the task list. This can be the following as in TCODE SE80 described.
+ Check Mode C(CHECKRUN), Background Processing Active C(BATCH) (this is the default value),
+ Asynchronous Execution C(ASYNC), Trace Mode C(TRACE), Server Name C(BATCH_TARGET).
+ default: ['BATCH']
+ type: list
+ elements: str
+ task_skip:
+ description:
+ - If this parameter is C(true), not defined tasks in I(task_parameters) are skipped.
+ - This could be the case when only certain tasks should run from the task list.
+ default: false
+ type: bool
+
+notes:
+ - Does not support C(check_mode). Always returns that the state has changed.
+author:
+ - Rainer Leber (@rainerleber)
+'''
+
+EXAMPLES = r'''
+# Pass in a message
+- name: Test task execution
+ community.sap_libs.sap_task_list_execute:
+ conn_username: DDIC
+ conn_password: Passwd1234
+ host: 10.1.8.10
+ sysnr: '01'
+ client: '000'
+ task_to_execute: SAP_BASIS_SSL_CHECK
+ task_settings: batch
+
+- name: Pass in input parameters
+ community.sap_libs.sap_task_list_execute:
+ conn_username: DDIC
+ conn_password: Passwd1234
+ host: 10.1.8.10
+ sysnr: '00'
+ client: '000'
+ task_to_execute: SAP_BASIS_SSL_CHECK
+ task_parameters :
+ - { 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO', 'FIELDNAME': 'P_OPT2', 'VALUE': 'X' }
+ - TASKNAME: CL_STCT_CHECK_SEC_CRYPTO
+ FIELDNAME: P_OPT3
+ VALUE: X
+ task_settings: batch
+
+# Exported environment variables
+- name: Hint if module will fail with error message like ImportError libsapnwrfc.so...
+ community.sap_libs.sap_task_list_execute:
+ conn_username: DDIC
+ conn_password: Passwd1234
+ host: 10.1.8.10
+ sysnr: '00'
+ client: '000'
+ task_to_execute: SAP_BASIS_SSL_CHECK
+ task_settings: batch
+ environment:
+ SAPNWRFC_HOME: /usr/local/sap/nwrfcsdk
+ LD_LIBRARY_PATH: /usr/local/sap/nwrfcsdk/lib
+'''
+
+RETURN = r'''
+msg:
+ description: A small execution description.
+ type: str
+ returned: always
+ sample: 'Successful'
+out:
+ description: A complete description of the executed tasks. If this is available.
+ type: list
+ elements: dict
+ returned: on success
+ sample: [...,{
+ "LOG": {
+ "STCTM_S_LOG": [
+ {
+ "ACTIVITY": "U_CONFIG",
+ "ACTIVITY_DESCR": "Configuration changed",
+ "DETAILS": null,
+ "EXEC_ID": "20210728184903.815739",
+ "FIELD": null,
+ "ID": "STC_TASK",
+ "LOG_MSG_NO": "000000",
+ "LOG_NO": null,
+ "MESSAGE": "For radiobutton group ICM too many options are set; choose only one option",
+ "MESSAGE_V1": "ICM",
+ "MESSAGE_V2": null,
+ "MESSAGE_V3": null,
+ "MESSAGE_V4": null,
+ "NUMBER": "048",
+ "PARAMETER": null,
+ "PERIOD": "M",
+ "PERIOD_DESCR": "Maintenance",
+ "ROW": "0",
+ "SRC_LINE": "170",
+ "SRC_OBJECT": "CL_STCTM_REPORT_UI IF_STCTM_UI_TASK~SET_PARAMETERS",
+ "SYSTEM": null,
+ "TIMESTMP": "20210728184903",
+ "TSTPNM": "DDIC",
+ "TYPE": "E"
+ },...
+ ]}}]
+'''
+
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+import traceback
+try:
+ from pyrfc import Connection
+except ImportError:
+ HAS_PYRFC_LIBRARY = False
+ PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ PYRFC_LIBRARY_IMPORT_ERROR = None
+ HAS_PYRFC_LIBRARY = True
+try:
+ import xmltodict
+except ImportError:
+ HAS_XMLTODICT_LIBRARY = False
+ XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ XMLTODICT_LIBRARY_IMPORT_ERROR = None
+ HAS_XMLTODICT_LIBRARY = True
+
+
+def call_rfc_method(connection, method_name, kwargs):
+ # PyRFC call function
+ return connection.call(method_name, **kwargs)
+
+
+def process_exec_settings(task_settings):
+ # processes task settings to objects
+ exec_settings = {}
+ for settings in task_settings:
+ temp_dict = {settings.upper(): 'X'}
+ for key, value in temp_dict.items():
+ exec_settings[key] = value
+ return exec_settings
+
+
+def xml_to_dict(xml_raw):
+ try:
+ xml_parsed = xmltodict.parse(xml_raw, dict_constructor=dict)
+ xml_dict = xml_parsed['asx:abap']['asx:values']['SESSION']['TASKLIST']
+ except KeyError:
+ xml_dict = "No logs available."
+ return xml_dict
+
+
+def run_module():
+
+ params_spec = dict(
+ TASKNAME=dict(type='str', required=True),
+ FIELDNAME=dict(type='str'),
+ VALUE=dict(type='raw'),
+ )
+
+ # define available arguments/parameters a user can pass to the module
+ module = AnsibleModule(
+ argument_spec=dict(
+ # values for connection
+ conn_username=dict(type='str', required=True),
+ conn_password=dict(type='str', required=True, no_log=True),
+ host=dict(type='str', required=True),
+ sysnr=dict(type='str', default="00"),
+ client=dict(type='str', default="000"),
+ # values for execution tasks
+ task_to_execute=dict(type='str', required=True),
+ task_parameters=dict(type='list', elements='dict', options=params_spec),
+ task_settings=dict(type='list', elements='str', default=['BATCH']),
+ task_skip=dict(type='bool', default=False),
+ ),
+ supports_check_mode=False,
+ )
+ result = dict(changed=False, msg='', out={})
+
+ params = module.params
+
+ username = params['conn_username'].upper()
+ password = params['conn_password']
+ host = params['host']
+ sysnr = params['sysnr']
+ client = params['client']
+
+ task_parameters = params['task_parameters']
+ task_to_execute = params['task_to_execute']
+ task_settings = params['task_settings']
+ task_skip = params['task_skip']
+
+ if not HAS_PYRFC_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('pyrfc'),
+ exception=PYRFC_LIBRARY_IMPORT_ERROR)
+
+ if not HAS_XMLTODICT_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('xmltodict'),
+ exception=XMLTODICT_LIBRARY_IMPORT_ERROR)
+
+ # basic RFC connection with pyrfc
+ try:
+ conn = Connection(user=username, passwd=password, ashost=host, sysnr=sysnr, client=client)
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'Something went wrong connecting to the SAP system.'
+ module.fail_json(**result)
+
+ try:
+ raw_params = call_rfc_method(conn, 'STC_TM_SCENARIO_GET_PARAMETERS',
+ {'I_SCENARIO_ID': task_to_execute})
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'The task list does not exist.'
+ module.fail_json(**result)
+ exec_settings = process_exec_settings(task_settings)
+ # initialize session task
+ session_init = call_rfc_method(conn, 'STC_TM_SESSION_BEGIN',
+ {'I_SCENARIO_ID': task_to_execute,
+ 'I_INIT_ONLY': 'X'})
+ # Confirm Tasks which requires manual activities from Task List Run
+ for task in raw_params['ET_PARAMETER']:
+ call_rfc_method(conn, 'STC_TM_TASK_CONFIRM',
+ {'I_SESSION_ID': session_init['E_SESSION_ID'],
+ 'I_TASKNAME': task['TASKNAME']})
+ if task_skip:
+ for task in raw_params['ET_PARAMETER']:
+ call_rfc_method(conn, 'STC_TM_TASK_SKIP',
+ {'I_SESSION_ID': session_init['E_SESSION_ID'],
+ 'I_TASKNAME': task['TASKNAME'], 'I_SKIP_DEP_TASKS': 'X'})
+ # unskip defined tasks and set parameters
+ if task_parameters is not None:
+ for task in task_parameters:
+ call_rfc_method(conn, 'STC_TM_TASK_UNSKIP',
+ {'I_SESSION_ID': session_init['E_SESSION_ID'],
+ 'I_TASKNAME': task['TASKNAME'], 'I_UNSKIP_DEP_TASKS': 'X'})
+
+ call_rfc_method(conn, 'STC_TM_SESSION_SET_PARAMETERS',
+ {'I_SESSION_ID': session_init['E_SESSION_ID'],
+ 'IT_PARAMETER': task_parameters})
+ # start the task
+ try:
+ session_start = call_rfc_method(conn, 'STC_TM_SESSION_RESUME',
+ {'I_SESSION_ID': session_init['E_SESSION_ID'],
+ 'IS_EXEC_SETTINGS': exec_settings})
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'Something went wrong. See error.'
+ module.fail_json(**result)
+ # get task logs because the execution may successfully but the tasks shows errors or warnings
+ # returned value is ABAPXML https://help.sap.com/doc/abapdocu_755_index_htm/7.55/en-US/abenabap_xslt_asxml_general.htm
+ session_log = call_rfc_method(conn, 'STC_TM_SESSION_GET_LOG',
+ {'I_SESSION_ID': session_init['E_SESSION_ID']})
+
+ task_list = xml_to_dict(session_log['E_LOG'])
+
+ result['changed'] = True
+ result['msg'] = session_start['E_STATUS_DESCR']
+ result['out'] = task_list
+
+ module.exit_json(**result)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sap_user.py b/ansible_collections/community/sap_libs/plugins/modules/sap_user.py
new file mode 100644
index 000000000..93d465b20
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sap_user.py
@@ -0,0 +1,508 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com> <rainer.leber@sva.de>
+# 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.
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sap_user
+short_description: This module will manage a user entities in a SAP S4/HANA environment
+version_added: "1.0.0"
+description:
+ - The M(community.sap_libs.sap_user) module depends on C(pyrfc) Python library (version 2.4.0 and upwards).
+ Depending on distribution you are using, you may need to install additional packages to
+ have these available.
+ - This module will use the following user BAPIs to manage user entities.
+ - C(BAPI_USER_GET_DETAIL)
+ - C(BAPI_USER_DELETE)
+ - C(BAPI_USER_CREATE1)
+ - C(BAPI_USER_CHANGE)
+ - C(BAPI_USER_ACTGROUPS_ASSIGN)
+ - C(BAPI_USER_PROFILES_ASSIGN)
+ - C(BAPI_USER_UNLOCK)
+ - C(BAPI_USER_LOCK)
+options:
+ state:
+ description:
+ - The decision what to do with the user.
+ default: 'present'
+ choices:
+ - 'present'
+ - 'absent'
+ - 'lock'
+ - 'unlock'
+ required: false
+ type: str
+ force:
+ description:
+ - Must be C('True') if the password or type should be overwritten.
+ default: False
+ required: false
+ type: bool
+ conn_username:
+ description: The required username for the SAP system.
+ required: true
+ type: str
+ conn_password:
+ description: The required password for the SAP system.
+ required: true
+ type: str
+ host:
+ description: The required host for the SAP system. Can be either an FQDN or IP Address.
+ required: true
+ type: str
+ sysnr:
+ description:
+ - The system number of the SAP system.
+ - You must quote the value to ensure retaining the leading zeros.
+ default: '00'
+ type: str
+ client:
+ description:
+ - The client number to connect to.
+ - You must quote the value to ensure retaining the leading zeros.
+ default: '000'
+ type: str
+ username:
+ description:
+ - The username.
+ type: str
+ required: true
+ firstname:
+ description:
+ - The Firstname of the user in the SAP system.
+ type: str
+ required: false
+ lastname:
+ description:
+ - The lastname of the user in the SAP system.
+ type: str
+ required: false
+ email:
+ description:
+ - The email address of the user in the SAP system.
+ type: str
+ required: false
+ password:
+ description:
+ - The password for the user in the SAP system.
+ type: str
+ required: false
+ useralias:
+ description:
+ - The alias for the user in the SAP system.
+ type: str
+ required: false
+ user_type:
+ description:
+ - The type for the user in the SAP system.
+ - C('A') Dialog user, C('B') System User, C('C') Communication User,
+ C('S') Service User, C('L') Reference User.
+ - Must be in uppercase.
+ type: str
+ required: false
+ default: 'A'
+ choices: ['A', 'B', 'C', 'S', 'L']
+ company:
+ description:
+ - The specific company the user belongs to.
+ - The company name must be available in the SAP system.
+ type: str
+ required: false
+ profiles:
+ description:
+ - Assign profiles to the user.
+ - Should be in uppercase, for example C('SAP_NEW') or C('SAP_ALL').
+ type: list
+ elements: str
+ default: ['']
+ required: false
+ roles:
+ description:
+ - Assign roles to the user.
+ type: list
+ elements: str
+ default: ['']
+ required: false
+
+requirements:
+ - pyrfc >= 2.4.0
+author:
+ - Rainer Leber (@rainerleber)
+notes:
+ - Does not support C(check_mode).
+'''
+
+EXAMPLES = r'''
+- name: Create SAP User
+ community.sap_libs.sap_user:
+ conn_username: 'DDIC'
+ conn_password: 'Test123'
+ host: 192.168.1.150
+ sysnr: '01'
+ client: '000'
+ state: present
+ username: ADMIN
+ firstname: first_admin
+ lastname: last_admin
+ email: admin@test.de
+ password: Test123456
+ useralias: ADMIN
+ company: DEFAULT_COMPANY
+ roles:
+ - "SAP_ALL"
+
+- name: Force change SAP User
+ community.sap_libs.sap_user:
+ conn_username: 'DDIC'
+ conn_password: 'Test123'
+ host: 192.168.1.150
+ sysnr: '01'
+ client: '000'
+ state: present
+ force: true
+ username: ADMIN
+ firstname: first_admin
+ lastname: last_admin
+ email: admin@test.de
+ password: Test123456
+ useralias: ADMIN
+ company: DEFAULT_COMPANY
+ roles:
+ - "SAP_ALL"
+
+- name: Delete SAP User
+ community.sap_libs.sap_user:
+ conn_username: 'DDIC'
+ conn_password: 'Test123'
+ host: 192.168.1.150
+ sysnr: '01'
+ client: '000'
+ state: absent
+ force: true
+ username: ADMIN
+
+- name: Unlock SAP User
+ community.sap_libs.sap_user:
+ conn_username: 'DDIC'
+ conn_password: 'Test123'
+ host: 192.168.1.150
+ sysnr: '01'
+ client: '000'
+ state: unlock
+ force: true
+ username: ADMIN
+'''
+
+RETURN = r'''
+msg:
+ description: A small execution description about the user action.
+ type: str
+ returned: always
+ sample: 'User ADMIN created'
+out:
+ description: A detailed description about the user action.
+ type: list
+ elements: dict
+ returned: on success
+ sample: [...,{
+ "RETURN": [
+ {
+ "FIELD": "BNAME",
+ "ID": "01",
+ "LOG_MSG_NO": "000000",
+ "LOG_NO": "",
+ "MESSAGE": "User ADMIN created",
+ "MESSAGE_V1": "ADMIN",
+ "MESSAGE_V2": "",
+ "MESSAGE_V3": "",
+ "MESSAGE_V4": "",
+ "NUMBER": "102",
+ "PARAMETER": "",
+ "ROW": 0,
+ "SYSTEM": "",
+ "TYPE": "S"
+ }
+ ],
+ "SAPUSER_UUID_HIST": []}]
+'''
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+import traceback
+import datetime
+try:
+ from pyrfc import Connection
+except ImportError:
+ HAS_PYRFC_LIBRARY = False
+ PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ PYRFC_LIBRARY_IMPORT_ERROR = None
+ HAS_PYRFC_LIBRARY = True
+
+
+def add_to_dict(target_dict, target_key, value):
+ # Adds the given value to a dict as the key
+ # check if the given key is in the given dict yet
+ if target_key in target_dict:
+ return False
+ target_dict[target_key] = value
+ return True
+
+
+def call_rfc_method(connection, method_name, kwargs):
+ # PyRFC call function
+ return connection.call(method_name, **kwargs)
+
+
+def build_rfc_user_params(username, firstname, lastname, email, raw_password,
+ useralias, user_type, raw_company, user_change, force):
+ """Creates RFC parameters for Creating users"""
+ # define dicts in batch
+ params = dict()
+ address = dict()
+ password = dict()
+ alias = dict()
+ logondata = dict()
+ company = dict()
+ # for change parameters
+ addressx = dict()
+ passwordx = dict()
+ logondatax = dict()
+ companyx = dict()
+ # define username
+ add_to_dict(params, 'USERNAME', username)
+ # define Address
+ add_to_dict(address, 'FIRSTNAME', firstname)
+ add_to_dict(address, 'LASTNAME', lastname)
+ add_to_dict(address, 'E_MAIL', email)
+ # define Password
+ add_to_dict(password, 'BAPIPWD', raw_password)
+ # define Alias
+ add_to_dict(alias, 'USERALIAS', useralias)
+ # define LogonData
+ add_to_dict(logondata, 'GLTGV', datetime.date.today())
+ add_to_dict(logondata, 'GLTGB', '20991231')
+ add_to_dict(logondata, 'USTYP', user_type)
+ # define company
+ add_to_dict(company, 'COMPANY', raw_company)
+ params['LOGONDATA'] = logondata
+ params['ADDRESS'] = address
+ params['COMPANY'] = company
+ params['ALIAS'] = alias
+ params['PASSWORD'] = password
+ # add change if user exists
+ if user_change and force:
+ add_to_dict(addressx, 'FIRSTNAME', 'X')
+ add_to_dict(addressx, 'LASTNAME', 'X')
+ add_to_dict(addressx, 'E_MAIL', 'X')
+ # define Password
+ add_to_dict(passwordx, 'BAPIPWD', 'X')
+ # define LogonData
+ add_to_dict(logondatax, 'USTYP', 'X')
+ # define company
+ add_to_dict(companyx, 'COMPANY', 'X')
+ params['LOGONDATAX'] = logondatax
+ params['ADDRESSX'] = addressx
+ params['COMPANYX'] = companyx
+ params['PASSWORDX'] = passwordx
+ return params
+
+
+def user_role_assignment_build_rfc_params(roles, username):
+ rfc_table = []
+
+ for role_name in roles:
+ table_row = {'AGR_NAME': role_name}
+
+ add_to_dict(table_row, 'FROM_DAT', datetime.date.today())
+ add_to_dict(table_row, 'TO_DAT', '20991231')
+
+ rfc_table.append(table_row)
+
+ return {
+ 'USERNAME': username,
+ 'ACTIVITYGROUPS': rfc_table
+ }
+
+
+def user_profile_assignment_build_rfc_params(profiles, username):
+ rfc_table = []
+
+ for profile_name in profiles:
+ table_row = {'BAPIPROF': profile_name}
+ rfc_table.append(table_row)
+
+ return {
+ 'USERNAME': username,
+ 'PROFILES': rfc_table
+ }
+
+
+def check_user(user_detail):
+ if len(user_detail['RETURN']) > 0:
+ for sub in user_detail['RETURN']:
+ if sub['NUMBER'] == '124':
+ return False
+ return True
+
+
+def return_analysis(raw):
+ change = False
+ failed = False
+ for state in raw['RETURN']:
+ if state['TYPE'] == "E":
+ if state['NUMBER'] == '224' or state['NUMBER'] == '124':
+ change = False
+ else:
+ failed = True
+ if state['TYPE'] == "S":
+ if state['NUMBER'] != '029':
+ change = True
+ if state['TYPE'] == "W":
+ if state['NUMBER'] == '049' or state['NUMBER'] == '047':
+ change = True
+ if state['NUMBER'] == '255':
+ change = True
+ return [{"change": change}, {"failed": failed}]
+
+
+def run_module():
+ module = AnsibleModule(
+ argument_spec=dict(
+ # logical values
+ state=dict(default='present', choices=[
+ 'absent', 'present', 'lock', 'unlock']),
+ force=dict(type='bool', default=False),
+ # values for connection
+ conn_username=dict(type='str', required=True),
+ conn_password=dict(type='str', required=True, no_log=True),
+ host=dict(type='str', required=True),
+ sysnr=dict(type='str', default="00"),
+ client=dict(type='str', default="000"),
+ # values for the new or existing user
+ username=dict(type='str', required=True),
+ firstname=dict(type='str', required=False),
+ lastname=dict(type='str', required=False),
+ email=dict(type='str', required=False),
+ password=dict(type='str', required=False, no_log=True),
+ useralias=dict(type='str', required=False),
+ user_type=dict(default="A",
+ choices=['A', 'B', 'C', 'S', 'L']),
+ company=dict(type='str', required=False),
+ # values for profile must a list
+ # Example ["SAP_NEW", "SAP_ALL"]
+ profiles=dict(type='list', elements='str', default=[""]),
+ # values for roles must a list
+ roles=dict(type='list', elements='str', default=[""]),
+ ),
+ supports_check_mode=False,
+ required_if=[('state', 'present', ['useralias', 'company'])]
+ )
+ result = dict(changed=False, msg='', out='')
+ count = 0
+ raw = ""
+
+ params = module.params
+
+ state = params['state']
+ conn_username = (params['conn_username']).upper()
+ conn_password = params['conn_password']
+ host = params['host']
+ sysnr = params['sysnr']
+ client = params['client']
+
+ username = (params['username']).upper()
+ firstname = params['firstname']
+ lastname = params['lastname']
+ email = params['email']
+ password = params['password']
+ force = params['force']
+ if not params['useralias'] is None:
+ useralias = (params['useralias']).upper()
+ user_type = (params['user_type']).upper()
+ company = params['company']
+
+ profiles = params['profiles']
+ roles = params['roles']
+
+ if not HAS_PYRFC_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('pyrfc'),
+ exception=PYRFC_LIBRARY_IMPORT_ERROR)
+
+ # basic RFC connection with pyrfc
+ try:
+ conn = Connection(user=conn_username, passwd=conn_password, ashost=host, sysnr=sysnr, client=client)
+ except Exception as err:
+ result['error'] = str(err)
+ result['msg'] = 'Something went wrong connecting to the SAP system.'
+ module.fail_json(**result)
+
+ # user details
+ user_detail = call_rfc_method(conn, 'BAPI_USER_GET_DETAIL', {'USERNAME': username})
+ user_exists = check_user(user_detail)
+
+ if state == "absent":
+ if user_exists:
+ raw = call_rfc_method(conn, 'BAPI_USER_DELETE', {'USERNAME': username})
+
+ if state == "present":
+ user_params = build_rfc_user_params(username, firstname, lastname, email, password, useralias, user_type, company, user_exists, force)
+ if not user_exists:
+ raw = call_rfc_method(conn, 'BAPI_USER_CREATE1', user_params)
+
+ if user_exists:
+ # check for address changes when user exists
+ user_no_changes = all((user_detail.get('ADDRESS')).get(k) == v for k, v in (user_params.get('ADDRESS')).items())
+ if not user_no_changes or force:
+ raw = call_rfc_method(conn, 'BAPI_USER_CHANGE', user_params)
+
+ call_rfc_method(conn, 'BAPI_USER_ACTGROUPS_ASSIGN', user_role_assignment_build_rfc_params(roles, username))
+
+ call_rfc_method(conn, 'BAPI_USER_PROFILES_ASSIGN', user_profile_assignment_build_rfc_params(profiles, username))
+
+ if state == "unlock":
+ if user_exists:
+ raw = call_rfc_method(conn, 'BAPI_USER_UNLOCK', {'USERNAME': username})
+
+ if state == "lock":
+ if user_exists:
+ raw = call_rfc_method(conn, 'BAPI_USER_LOCK', {'USERNAME': username})
+
+ # analyse return value
+ if raw != '':
+ analysed = return_analysis(raw)
+
+ result['out'] = raw
+
+ result['changed'] = analysed[0]['change']
+ for msgs in raw['RETURN']:
+ if count > 0:
+ result['msg'] = result['msg'] + '\n'
+ result['msg'] = result['msg'] + msgs['MESSAGE']
+ count = count + 1
+
+ if analysed[1]['failed']:
+ module.fail_json(**result)
+ else:
+ result['msg'] = "No changes where made."
+
+ module.exit_json(**result)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/plugins/modules/sapcar_extract.py b/ansible_collections/community/sap_libs/plugins/modules/sapcar_extract.py
new file mode 100644
index 000000000..4a1ed9ba3
--- /dev/null
+++ b/ansible_collections/community/sap_libs/plugins/modules/sapcar_extract.py
@@ -0,0 +1,228 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber <rainerleber@gmail.com>
+# 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.
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: sapcar_extract
+short_description: Manages SAP SAPCAR archives
+version_added: "1.0.0"
+description:
+ - Provides support for unpacking C(sar)/C(car) files with the SAPCAR binary from SAP and pulling
+ information back into Ansible.
+options:
+ path:
+ description: The path to the SAR/CAR file.
+ type: path
+ required: true
+ dest:
+ description:
+ - The destination where SAPCAR extracts the SAR file. Missing folders will be created.
+ If this parameter is not provided, it will unpack in the same folder as the SAR file.
+ type: path
+ binary_path:
+ description:
+ - The path to the SAPCAR binary, for example, C(/home/dummy/sapcar) or C(https://myserver/SAPCAR).
+ If this parameter is not provided, the module will look in C(PATH).
+ type: path
+ signature:
+ description:
+ - If C(true), the signature will be extracted.
+ default: false
+ type: bool
+ security_library:
+ description:
+ - The path to the security library, for example, C(/usr/sap/hostctrl/exe/libsapcrytp.so), for signature operations.
+ type: path
+ manifest:
+ description:
+ - The name of the manifest.
+ default: "SIGNATURE.SMF"
+ type: str
+ remove:
+ description:
+ - If C(true), the SAR/CAR file will be removed. B(This should be used with caution!)
+ default: false
+ type: bool
+author:
+ - Rainer Leber (@RainerLeber)
+notes:
+ - Always returns C(changed=true) in C(check_mode).
+'''
+
+EXAMPLES = r"""
+- name: Extract SAR file
+ community.sap_libs.sapcar_extract:
+ path: "~/source/hana.sar"
+
+- name: Extract SAR file with destination
+ community.sap_libs.sapcar_extract:
+ path: "~/source/hana.sar"
+ dest: "~/test/"
+
+- name: Extract SAR file with destination and download from webserver can be a fileshare as well
+ community.sap_libs.sapcar_extract:
+ path: "~/source/hana.sar"
+ dest: "~/dest/"
+ binary_path: "https://myserver/SAPCAR"
+
+- name: Extract SAR file and delete SAR after extract
+ community.sap_libs.sapcar_extract:
+ path: "~/source/hana.sar"
+ remove: true
+
+- name: Extract SAR file with manifest
+ community.sap_libs.sapcar_extract:
+ path: "~/source/hana.sar"
+ signature: true
+
+- name: Extract SAR file with manifest and rename it
+ community.sap_libs.sapcar_extract:
+ path: "~/source/hana.sar"
+ manifest: "MyNewSignature.SMF"
+ signature: true
+"""
+
+import os
+from tempfile import NamedTemporaryFile
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.urls import open_url
+from ansible.module_utils.common.text.converters import to_native
+
+
+def get_list_of_files(dir_name):
+ # create a list of file and directories
+ # names in the given directory
+ list_of_file = os.listdir(dir_name)
+ allFiles = list()
+ # Iterate over all the entries
+ for entry in list_of_file:
+ # Create full path
+ fullPath = os.path.join(dir_name, entry)
+ # If entry is a directory then get the list of files in this directory
+ if os.path.isdir(fullPath):
+ allFiles = allFiles + [fullPath]
+ allFiles = allFiles + get_list_of_files(fullPath)
+ else:
+ allFiles.append(fullPath)
+ return allFiles
+
+
+def download_SAPCAR(binary_path, module):
+ bin_path = None
+ # download sapcar binary if url is provided otherwise path is returned
+ if binary_path is not None:
+ if binary_path.startswith('https://') or binary_path.startswith('http://'):
+ random_file = NamedTemporaryFile(delete=False)
+ with open_url(binary_path) as response:
+ with random_file as out_file:
+ data = response.read()
+ out_file.write(data)
+ os.chmod(out_file.name, 0o700)
+ bin_path = out_file.name
+ module.add_cleanup_file(bin_path)
+ else:
+ bin_path = binary_path
+ return bin_path
+
+
+def check_if_present(command, path, dest, signature, manifest, module):
+ # manipulating output from SAR file for compare with already extracted files
+ iter_command = [command, '-tvf', path]
+ sar_out = module.run_command(iter_command)[1]
+ sar_raw = sar_out.split("\n")[1:]
+ if dest[-1] != "/":
+ dest = dest + "/"
+ sar_files = [dest + x.split(" ")[-1] for x in sar_raw if x]
+ # remove any SIGNATURE.SMF from list because it will not unpacked if signature is false
+ if not signature:
+ sar_files = [item for item in sar_files if not item.endswith('.SMF')]
+ # if signature is renamed manipulate files in list of sar file for compare.
+ if manifest != "SIGNATURE.SMF":
+ sar_files = [item for item in sar_files if not item.endswith('.SMF')]
+ sar_files = sar_files + [manifest]
+ # get extracted files if present
+ files_extracted = get_list_of_files(dest)
+ # compare extracted files with files in sar file
+ present = all(elem in files_extracted for elem in sar_files)
+ return present
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ path=dict(type='path', required=True),
+ dest=dict(type='path'),
+ binary_path=dict(type='path'),
+ signature=dict(type='bool', default=False),
+ security_library=dict(type='path'),
+ manifest=dict(type='str', default="SIGNATURE.SMF"),
+ remove=dict(type='bool', default=False),
+ ),
+ supports_check_mode=True,
+ )
+ rc, out, err = [0, "", ""]
+ params = module.params
+ check_mode = module.check_mode
+
+ path = params['path']
+ dest = params['dest']
+ signature = params['signature']
+ security_library = params['security_library']
+ manifest = params['manifest']
+ remove = params['remove']
+
+ bin_path = download_SAPCAR(params['binary_path'], module)
+
+ if dest is None:
+ dest_head_tail = os.path.split(path)
+ dest = dest_head_tail[0] + '/'
+ else:
+ if not os.path.exists(dest):
+ os.makedirs(dest, 0o755)
+
+ if bin_path is not None:
+ command = [module.get_bin_path(bin_path, required=True)]
+ else:
+ try:
+ command = [module.get_bin_path('sapcar', required=True)]
+ except Exception as e:
+ module.fail_json(msg='Failed to find SAPCAR at the expected path or URL "{0}". Please check whether it is available: {1}'
+ .format(bin_path, to_native(e)))
+
+ present = check_if_present(command[0], path, dest, signature, manifest, module)
+
+ if not present:
+ command.extend(['-xvf', path, '-R', dest])
+ if security_library:
+ command.extend(['-L', security_library])
+ if signature:
+ command.extend(['-manifest', manifest])
+ if not check_mode:
+ (rc, out, err) = module.run_command(command, check_rc=True)
+ changed = True
+ else:
+ changed = False
+ out = "already unpacked"
+
+ if remove:
+ os.remove(path)
+
+ module.exit_json(changed=changed, message=rc, stdout=out,
+ stderr=err, command=' '.join(command))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/sap_libs/tests/integration/__init__.py b/ansible_collections/community/sap_libs/tests/integration/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/integration/__init__.py
diff --git a/ansible_collections/community/sap_libs/tests/integration/targets/__init__.py b/ansible_collections/community/sap_libs/tests/integration/targets/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/integration/targets/__init__.py
diff --git a/ansible_collections/community/sap_libs/tests/sanity/__init__.py b/ansible_collections/community/sap_libs/tests/sanity/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/sanity/__init__.py
diff --git a/ansible_collections/community/sap_libs/tests/sanity/ignore-2.10.txt b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.10.txt
new file mode 100644
index 000000000..89508ad77
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.10.txt
@@ -0,0 +1,9 @@
+plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
diff --git a/ansible_collections/community/sap_libs/tests/sanity/ignore-2.11.txt b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.11.txt
new file mode 100644
index 000000000..89508ad77
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.11.txt
@@ -0,0 +1,9 @@
+plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
diff --git a/ansible_collections/community/sap_libs/tests/sanity/ignore-2.12.txt b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.12.txt
new file mode 100644
index 000000000..3883757f2
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.12.txt
@@ -0,0 +1,9 @@
+plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/tests/sanity/ignore-2.13.txt b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.13.txt
new file mode 100644
index 000000000..3883757f2
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.13.txt
@@ -0,0 +1,9 @@
+plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/tests/sanity/ignore-2.14.txt b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.14.txt
new file mode 100644
index 000000000..3883757f2
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.14.txt
@@ -0,0 +1,9 @@
+plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/tests/sanity/ignore-2.15.txt b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.15.txt
new file mode 100644
index 000000000..3883757f2
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.15.txt
@@ -0,0 +1,9 @@
+plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file
diff --git a/ansible_collections/community/sap_libs/tests/sanity/ignore-2.9.txt b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.9.txt
new file mode 100644
index 000000000..89508ad77
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/sanity/ignore-2.9.txt
@@ -0,0 +1,9 @@
+plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
+plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0
diff --git a/ansible_collections/community/sap_libs/tests/unit/__init__.py b/ansible_collections/community/sap_libs/tests/unit/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/__init__.py
diff --git a/ansible_collections/community/sap_libs/tests/unit/compat/__init__.py b/ansible_collections/community/sap_libs/tests/unit/compat/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/compat/__init__.py
diff --git a/ansible_collections/community/sap_libs/tests/unit/compat/builtins.py b/ansible_collections/community/sap_libs/tests/unit/compat/builtins.py
new file mode 100644
index 000000000..ced77f875
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/compat/builtins.py
@@ -0,0 +1,24 @@
+# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+#
+# Compat for python2.7
+#
diff --git a/ansible_collections/community/sap_libs/tests/unit/compat/mock.py b/ansible_collections/community/sap_libs/tests/unit/compat/mock.py
new file mode 100644
index 000000000..0972cd2e8
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/compat/mock.py
@@ -0,0 +1,122 @@
+# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+'''
+Compat module for Python3.x's unittest.mock module
+'''
+import sys
+
+# Python 2.7
+
+# Note: Could use the pypi mock library on python3.x as well as python2.x. It
+# is the same as the python3 stdlib mock library
+
+try:
+ # Allow wildcard import because we really do want to import all of mock's
+ # symbols into this compat shim
+ # pylint: disable=wildcard-import,unused-wildcard-import
+ from unittest.mock import *
+except ImportError:
+ # Python 2
+ # pylint: disable=wildcard-import,unused-wildcard-import
+ try:
+ from mock import *
+ except ImportError:
+ print('You need the mock library installed on python2.x to run tests')
+
+
+# Prior to 3.4.4, mock_open cannot handle binary read_data
+if sys.version_info >= (3,) and sys.version_info < (3, 4, 4):
+ file_spec = None
+
+ def _iterate_read_data(read_data):
+ # Helper for mock_open:
+ # Retrieve lines from read_data via a generator so that separate calls to
+ # readline, read, and readlines are properly interleaved
+ sep = b'\n' if isinstance(read_data, bytes) else '\n'
+ data_as_list = [l + sep for l in read_data.split(sep)]
+
+ if data_as_list[-1] == sep:
+ # If the last line ended in a newline, the list comprehension will have an
+ # extra entry that's just a newline. Remove this.
+ data_as_list = data_as_list[:-1]
+ else:
+ # If there wasn't an extra newline by itself, then the file being
+ # emulated doesn't have a newline to end the last line remove the
+ # newline that our naive format() added
+ data_as_list[-1] = data_as_list[-1][:-1]
+
+ for line in data_as_list:
+ yield line
+
+ def mock_open(mock=None, read_data=''):
+ """
+ A helper function to create a mock to replace the use of `open`. It works
+ for `open` called directly or used as a context manager.
+
+ The `mock` argument is the mock object to configure. If `None` (the
+ default) then a `MagicMock` will be created for you, with the API limited
+ to methods or attributes available on standard file handles.
+
+ `read_data` is a string for the `read` methoddline`, and `readlines` of the
+ file handle to return. This is an empty string by default.
+ """
+ def _readlines_side_effect(*args, **kwargs):
+ if handle.readlines.return_value is not None:
+ return handle.readlines.return_value
+ return list(_data)
+
+ def _read_side_effect(*args, **kwargs):
+ if handle.read.return_value is not None:
+ return handle.read.return_value
+ return type(read_data)().join(_data)
+
+ def _readline_side_effect():
+ if handle.readline.return_value is not None:
+ while True:
+ yield handle.readline.return_value
+ for line in _data:
+ yield line
+
+ global file_spec
+ if file_spec is None:
+ import _io
+ file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
+
+ if mock is None:
+ mock = MagicMock(name='open', spec=open)
+
+ handle = MagicMock(spec=file_spec)
+ handle.__enter__.return_value = handle
+
+ _data = _iterate_read_data(read_data)
+
+ handle.write.return_value = None
+ handle.read.return_value = None
+ handle.readline.return_value = None
+ handle.readlines.return_value = None
+
+ handle.read.side_effect = _read_side_effect
+ handle.readline.side_effect = _readline_side_effect()
+ handle.readlines.side_effect = _readlines_side_effect
+
+ mock.return_value = handle
+ return mock
diff --git a/ansible_collections/community/sap_libs/tests/unit/compat/unittest.py b/ansible_collections/community/sap_libs/tests/unit/compat/unittest.py
new file mode 100644
index 000000000..98f08ad6a
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/compat/unittest.py
@@ -0,0 +1,38 @@
+# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+'''
+Compat module for Python2.7's unittest module
+'''
+
+import sys
+
+# Allow wildcard import because we really do want to import all of
+# unittests's symbols into this compat shim
+# pylint: disable=wildcard-import,unused-wildcard-import
+if sys.version_info < (2, 7):
+ try:
+ # Need unittest2 on python2.6
+ from unittest2 import *
+ except ImportError:
+ print('You need unittest2 installed on python2.6.x to run tests')
+else:
+ from unittest import *
diff --git a/ansible_collections/community/sap_libs/tests/unit/mock/__init__.py b/ansible_collections/community/sap_libs/tests/unit/mock/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/mock/__init__.py
diff --git a/ansible_collections/community/sap_libs/tests/unit/mock/loader.py b/ansible_collections/community/sap_libs/tests/unit/mock/loader.py
new file mode 100644
index 000000000..5389bdcb2
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/mock/loader.py
@@ -0,0 +1,102 @@
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+#
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+
+from ansible.errors import AnsibleParserError
+from ansible.parsing.dataloader import DataLoader
+from ansible.module_utils.common.text.converters import to_bytes, to_text
+
+
+class DictDataLoader(DataLoader):
+
+ def __init__(self, file_mapping=None):
+ file_mapping = {} if file_mapping is None else file_mapping
+ assert type(file_mapping) == dict
+
+ super(DictDataLoader, self).__init__()
+
+ self._file_mapping = file_mapping
+ self._build_known_directories()
+ self._vault_secrets = None
+
+ def load_from_file(self, path, cache=True, unsafe=False):
+ path = to_text(path)
+ if path in self._file_mapping:
+ return self.load(self._file_mapping[path], path)
+ return None
+
+ # TODO: the real _get_file_contents returns a bytestring, so we actually convert the
+ # unicode/text it's created with to utf-8
+ def _get_file_contents(self, file_name):
+ path = to_text(file_name)
+ if path in self._file_mapping:
+ return (to_bytes(self._file_mapping[path]), False)
+ else:
+ raise AnsibleParserError("file not found: %s" % path)
+
+ def path_exists(self, path):
+ path = to_text(path)
+ return path in self._file_mapping or path in self._known_directories
+
+ def is_file(self, path):
+ path = to_text(path)
+ return path in self._file_mapping
+
+ def is_directory(self, path):
+ path = to_text(path)
+ return path in self._known_directories
+
+ def list_directory(self, path):
+ ret = []
+ path = to_text(path)
+ for x in (list(self._file_mapping.keys()) + self._known_directories):
+ if x.startswith(path):
+ if os.path.dirname(x) == path:
+ ret.append(os.path.basename(x))
+ return ret
+
+ def is_executable(self, path):
+ # FIXME: figure out a way to make paths return true for this
+ return False
+
+ def _add_known_directory(self, directory):
+ if directory not in self._known_directories:
+ self._known_directories.append(directory)
+
+ def _build_known_directories(self):
+ self._known_directories = []
+ for path in self._file_mapping:
+ dirname = os.path.dirname(path)
+ while dirname not in ('/', ''):
+ self._add_known_directory(dirname)
+ dirname = os.path.dirname(dirname)
+
+ def push(self, path, content):
+ rebuild_dirs = False
+ if path not in self._file_mapping:
+ rebuild_dirs = True
+
+ self._file_mapping[path] = content
+
+ if rebuild_dirs:
+ self._build_known_directories()
+
+ def pop(self, path):
+ if path in self._file_mapping:
+ del self._file_mapping[path]
+ self._build_known_directories()
+
+ def clear(self):
+ self._file_mapping = dict()
+ self._known_directories = []
+
+ def get_basedir(self):
+ return os.getcwd()
+
+ def set_vault_secrets(self, vault_secrets):
+ self._vault_secrets = vault_secrets
diff --git a/ansible_collections/community/sap_libs/tests/unit/mock/path.py b/ansible_collections/community/sap_libs/tests/unit/mock/path.py
new file mode 100644
index 000000000..c1c075bc0
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/mock/path.py
@@ -0,0 +1,8 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible_collections.community.general.tests.unit.compat.mock import MagicMock
+from ansible.utils.path import unfrackpath
+
+
+mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x)
diff --git a/ansible_collections/community/sap_libs/tests/unit/mock/procenv.py b/ansible_collections/community/sap_libs/tests/unit/mock/procenv.py
new file mode 100644
index 000000000..5673863e1
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/mock/procenv.py
@@ -0,0 +1,76 @@
+# (c) 2016, Matt Davis <mdavis@ansible.com>
+# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
+#
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+import json
+
+from contextlib import contextmanager
+from io import BytesIO, StringIO
+from ansible_collections.community.general.tests.unit.compat import unittest
+from ansible.module_utils.six import PY3
+from ansible.module_utils.common.text.converters import to_bytes
+
+
+@contextmanager
+def swap_stdin_and_argv(stdin_data='', argv_data=tuple()):
+ """
+ context manager that temporarily masks the test runner's values for stdin and argv
+ """
+ real_stdin = sys.stdin
+ real_argv = sys.argv
+
+ if PY3:
+ fake_stream = StringIO(stdin_data)
+ fake_stream.buffer = BytesIO(to_bytes(stdin_data))
+ else:
+ fake_stream = BytesIO(to_bytes(stdin_data))
+
+ try:
+ sys.stdin = fake_stream
+ sys.argv = argv_data
+
+ yield
+ finally:
+ sys.stdin = real_stdin
+ sys.argv = real_argv
+
+
+@contextmanager
+def swap_stdout():
+ """
+ context manager that temporarily replaces stdout for tests that need to verify output
+ """
+ old_stdout = sys.stdout
+
+ if PY3:
+ fake_stream = StringIO()
+ else:
+ fake_stream = BytesIO()
+
+ try:
+ sys.stdout = fake_stream
+
+ yield fake_stream
+ finally:
+ sys.stdout = old_stdout
+
+
+class ModuleTestCase(unittest.TestCase):
+ def setUp(self, module_args=None):
+ if module_args is None:
+ module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False}
+
+ args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args))
+
+ # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
+ self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
+ self.stdin_swap.__enter__()
+
+ def tearDown(self):
+ # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
+ self.stdin_swap.__exit__(None, None, None)
diff --git a/ansible_collections/community/sap_libs/tests/unit/mock/vault_helper.py b/ansible_collections/community/sap_libs/tests/unit/mock/vault_helper.py
new file mode 100644
index 000000000..6bd2db9c3
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/mock/vault_helper.py
@@ -0,0 +1,27 @@
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible.module_utils.common.text.converters import to_bytes
+
+from ansible.parsing.vault import VaultSecret
+
+
+class TextVaultSecret(VaultSecret):
+ '''A secret piece of text. ie, a password. Tracks text encoding.
+
+ The text encoding of the text may not be the default text encoding so
+ we keep track of the encoding so we encode it to the same bytes.'''
+
+ def __init__(self, text, encoding=None, errors=None, _bytes=None):
+ super(TextVaultSecret, self).__init__()
+ self.text = text
+ self.encoding = encoding or 'utf-8'
+ self._bytes = _bytes
+ self.errors = errors or 'strict'
+
+ @property
+ def bytes(self):
+ '''The text encoded with encoding, unless we specifically set _bytes.'''
+ return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors)
diff --git a/ansible_collections/community/sap_libs/tests/unit/mock/yaml_helper.py b/ansible_collections/community/sap_libs/tests/unit/mock/yaml_helper.py
new file mode 100644
index 000000000..a646c0241
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/mock/yaml_helper.py
@@ -0,0 +1,126 @@
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import io
+import yaml
+
+from ansible.module_utils.six import PY3
+from ansible.parsing.yaml.loader import AnsibleLoader
+from ansible.parsing.yaml.dumper import AnsibleDumper
+
+
+class YamlTestUtils(object):
+ """Mixin class to combine with a unittest.TestCase subclass."""
+ def _loader(self, stream):
+ """Vault related tests will want to override this.
+
+ Vault cases should setup a AnsibleLoader that has the vault password."""
+ return AnsibleLoader(stream)
+
+ def _dump_stream(self, obj, stream, dumper=None):
+ """Dump to a py2-unicode or py3-string stream."""
+ if PY3:
+ return yaml.dump(obj, stream, Dumper=dumper)
+ else:
+ return yaml.dump(obj, stream, Dumper=dumper, encoding=None)
+
+ def _dump_string(self, obj, dumper=None):
+ """Dump to a py2-unicode or py3-string"""
+ if PY3:
+ return yaml.dump(obj, Dumper=dumper)
+ else:
+ return yaml.dump(obj, Dumper=dumper, encoding=None)
+
+ def _dump_load_cycle(self, obj):
+ # Each pass though a dump or load revs the 'generation'
+ # obj to yaml string
+ string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper)
+
+ # wrap a stream/file like StringIO around that yaml
+ stream_from_object_dump = io.StringIO(string_from_object_dump)
+ loader = self._loader(stream_from_object_dump)
+ # load the yaml stream to create a new instance of the object (gen 2)
+ obj_2 = loader.get_data()
+
+ # dump the gen 2 objects directory to strings
+ string_from_object_dump_2 = self._dump_string(obj_2,
+ dumper=AnsibleDumper)
+
+ # The gen 1 and gen 2 yaml strings
+ self.assertEqual(string_from_object_dump, string_from_object_dump_2)
+ # the gen 1 (orig) and gen 2 py object
+ self.assertEqual(obj, obj_2)
+
+ # again! gen 3... load strings into py objects
+ stream_3 = io.StringIO(string_from_object_dump_2)
+ loader_3 = self._loader(stream_3)
+ obj_3 = loader_3.get_data()
+
+ string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper)
+
+ self.assertEqual(obj, obj_3)
+ # should be transitive, but...
+ self.assertEqual(obj_2, obj_3)
+ self.assertEqual(string_from_object_dump, string_from_object_dump_3)
+
+ def _old_dump_load_cycle(self, obj):
+ '''Dump the passed in object to yaml, load it back up, dump again, compare.'''
+ stream = io.StringIO()
+
+ yaml_string = self._dump_string(obj, dumper=AnsibleDumper)
+ self._dump_stream(obj, stream, dumper=AnsibleDumper)
+
+ yaml_string_from_stream = stream.getvalue()
+
+ # reset stream
+ stream.seek(0)
+
+ loader = self._loader(stream)
+ # loader = AnsibleLoader(stream, vault_password=self.vault_password)
+ obj_from_stream = loader.get_data()
+
+ stream_from_string = io.StringIO(yaml_string)
+ loader2 = self._loader(stream_from_string)
+ # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password)
+ obj_from_string = loader2.get_data()
+
+ stream_obj_from_stream = io.StringIO()
+ stream_obj_from_string = io.StringIO()
+
+ if PY3:
+ yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper)
+ yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper)
+ else:
+ yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None)
+ yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None)
+
+ yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue()
+ yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue()
+
+ stream_obj_from_stream.seek(0)
+ stream_obj_from_string.seek(0)
+
+ if PY3:
+ yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper)
+ yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper)
+ else:
+ yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None)
+ yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None)
+
+ assert yaml_string == yaml_string_obj_from_stream
+ assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
+ assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream ==
+ yaml_string_stream_obj_from_string)
+ assert obj == obj_from_stream
+ assert obj == obj_from_string
+ assert obj == yaml_string_obj_from_stream
+ assert obj == yaml_string_obj_from_string
+ assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
+ return {'obj': obj,
+ 'yaml_string': yaml_string,
+ 'yaml_string_from_stream': yaml_string_from_stream,
+ 'obj_from_stream': obj_from_stream,
+ 'obj_from_string': obj_from_string,
+ 'yaml_string_obj_from_string': yaml_string_obj_from_string}
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/__init__.py b/ansible_collections/community/sap_libs/tests/unit/plugins/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/__init__.py
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/__init__.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/__init__.py
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_company.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_company.py
new file mode 100644
index 000000000..b6b7dd11e
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_company.py
@@ -0,0 +1,136 @@
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
+
+sys.modules['pyrfc'] = MagicMock()
+sys.modules['pyrfc.Connection'] = MagicMock()
+
+from ansible_collections.community.sap_libs.plugins.modules import sap_company
+
+
+class TestSAPRfcModule(ModuleTestCase):
+
+ def setUp(self):
+ super(TestSAPRfcModule, self).setUp()
+ self.module = sap_company
+
+ def tearDown(self):
+ super(TestSAPRfcModule, self).tearDown()
+
+ def define_rfc_connect(self, mocker):
+ return mocker.patch(self.module.call_rfc_method)
+
+ def test_without_required_parameters(self):
+ """Failure must occurs when all parameters are missing"""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ self.module.main()
+
+ def test_error_user_create(self):
+ """test fail to create company"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "company_id": "Comp_ID",
+ "name": "Test_comp",
+ "name_2": "LTD",
+ "country": "DE",
+ "time_zone": "UTC",
+ "city": "City",
+ "post_code": "12345",
+ "street": "test_street",
+ "street_no": "1",
+ "e_mail": "test@test.de",
+ })
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': '', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'Something went wrong', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '199',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'E'}]}
+
+ with self.assertRaises(AnsibleFailJson) as result:
+ sap_company.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'Something went wrong')
+
+ def test_success(self):
+ """test execute company create success"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "company_id": "Comp_ID",
+ "name": "Test_comp",
+ "name_2": "LTD",
+ "country": "DE",
+ "time_zone": "UTC",
+ "city": "City",
+ "post_code": "12345",
+ "street": "test_street",
+ "street_no": "1",
+ "e_mail": "test@test.de",
+ })
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': '', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'Company address COMP_ID created', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '102',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]}
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_company.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'Company address COMP_ID created')
+
+ def test_no_changes(self):
+ """test execute company no changes"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "company_id": "Comp_ID",
+ "name": "Test_comp",
+ "name_2": "LTD",
+ "country": "DE",
+ "time_zone": "UTC",
+ "city": "City",
+ "post_code": "12345",
+ "street": "test_street",
+ "street_no": "1",
+ "e_mail": "test@test.de",
+ })
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': '', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'Company address COMP_ID changed', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '079',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]}
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_company.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'No changes where made.')
+
+ def test_absent(self):
+ """test execute company delete success"""
+
+ set_module_args({
+ "state": "absent",
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "company_id": "Comp_ID",
+ })
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': '', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'Company address COMP_ID deleted', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '080',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]}
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_company.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'Company address COMP_ID deleted')
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_control_exec.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_control_exec.py
new file mode 100644
index 000000000..272beaf41
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_control_exec.py
@@ -0,0 +1,129 @@
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock, Mock
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
+
+sys.modules['suds.client'] = MagicMock()
+sys.modules['suds.sudsobject'] = MagicMock()
+sys.modules['suds'] = MagicMock()
+
+from ansible_collections.community.sap_libs.plugins.modules import sap_control_exec
+
+
+class TestSapcontrolModule(ModuleTestCase):
+
+ def setUp(self):
+ super(TestSapcontrolModule, self).setUp()
+ self.module = sap_control_exec
+
+ def tearDown(self):
+ super(TestSapcontrolModule, self).tearDown()
+
+ def define_rfc_connect(self, mocker):
+ return mocker.patch(self.module.call_rfc_method)
+
+ def test_without_required_parameters(self):
+ """Failure must occurs when all parameters are missing"""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ self.module.main()
+
+ def test_error_module_not_found(self):
+ """tests fail module error"""
+
+ set_module_args({
+ "hostname": "192.168.8.15",
+ "sysnr": "01",
+ "function": "GetProcessList"
+ })
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.HAS_SUDS_LIBRARY = False
+ self.module.SUDS_LIBRARY_IMPORT_ERROR = 'Module not found'
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['exception'], 'Module not found')
+
+ def test_error_connection(self):
+ """tests fail module exception"""
+
+ set_module_args({
+ "hostname": "192.168.8.15",
+ "sysnr": "01",
+ "function": "GetProcessList"
+ })
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.Client.side_effect = Mock(side_effect=Exception('Test'))
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'Something went wrong connecting to the SAPCONTROL SOAP API.')
+
+ def test_error_port_sysnr(self):
+ """tests fail multi provide parameters"""
+
+ set_module_args({
+ "hostname": "192.168.8.15",
+ "sysnr": "01",
+ "port": "50113",
+ "function": "GetProcessList"
+ })
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'parameters are mutually exclusive: sysnr|port')
+
+ def test_error_missing_force(self):
+ """tests fail missing force"""
+
+ set_module_args({
+ "hostname": "192.168.8.15",
+ "sysnr": "01",
+ "function": "Stop"
+ })
+
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'Stop function requires force: True')
+
+ def test_success_sysnr(self):
+ """test success with sysnr"""
+
+ set_module_args({
+ "hostname": "192.168.8.15",
+ "sysnr": "01",
+ "function": "GetProcessList"
+ })
+ with patch.object(self.module, 'recursive_dict') as ret:
+ ret.return_value = {'item': [{'name': 'hdbdaemon', 'value': '1'}]}
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['out'], [{'item': [{'name': 'hdbdaemon', 'value': '1'}]}])
+
+ def test_success_port(self):
+ """test success with port"""
+
+ set_module_args({
+ "hostname": "192.168.8.15",
+ "port": "50113",
+ "function": "GetProcessList"
+ })
+ with patch.object(self.module, 'recursive_dict') as ret:
+ ret.return_value = {'item': [{'name': 'hdbdaemon', 'value': '1'}]}
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['out'], [{'item': [{'name': 'hdbdaemon', 'value': '1'}]}])
+
+ def test_success_string(self):
+ """test success with sysnr"""
+
+ set_module_args({
+ "hostname": "192.168.8.15",
+ "sysnr": "01",
+ "function": "ParameterValue",
+ "parameter": "ztta/short_area"
+ })
+ with patch.object(self.module, 'connection') as ret:
+ ret.return_value = '1600000'
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['out'], ['1600000'])
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_hdbsql.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_hdbsql.py
new file mode 100644
index 000000000..2232273f1
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_hdbsql.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber (@rainerleber) <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.sap_libs.plugins.modules import sap_hdbsql
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import (
+ AnsibleExitJson,
+ AnsibleFailJson,
+ ModuleTestCase,
+ set_module_args,
+)
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch
+from ansible.module_utils import basic
+
+
+def get_bin_path(*args, **kwargs):
+ """Function to return path of hdbsql"""
+ return "/usr/sap/HDB/HDB01/exe/hdbsql"
+
+
+class Testsap_hdbsql(ModuleTestCase):
+ """Main class for testing sap_hdbsql module."""
+
+ def setUp(self):
+ """Setup."""
+ super(Testsap_hdbsql, self).setUp()
+ self.module = sap_hdbsql
+ self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path)
+ self.mock_get_bin_path.start()
+ self.addCleanup(self.mock_get_bin_path.stop) # ensure that the patching is 'undone'
+
+ def tearDown(self):
+ """Teardown."""
+ super(Testsap_hdbsql, self).tearDown()
+
+ def test_without_required_parameters(self):
+ """Failure must occurs when all parameters are missing."""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ self.module.main()
+
+ def test_sap_hdbsql(self):
+ """Check that result is processed."""
+ set_module_args({
+ 'sid': "HDB",
+ 'instance': "01",
+ 'encrypted': False,
+ 'host': "localhost",
+ 'user': "SYSTEM",
+ 'password': "1234Qwer",
+ 'database': "HDB",
+ 'query': "SELECT * FROM users;"
+ })
+ with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+ run_command.return_value = 0, 'username,name\n testuser,test user \n myuser, my user \n', ''
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_hdbsql.main()
+ self.assertEqual(result.exception.args[0]['query_result'], [[
+ {'username': 'testuser', 'name': 'test user'},
+ {'username': 'myuser', 'name': 'my user'},
+ ]])
+ self.assertEqual(run_command.call_count, 1)
+
+ def test_hana_userstore_query(self):
+ """Check that result is processed with userstore."""
+ set_module_args({
+ 'sid': "HDB",
+ 'instance': "01",
+ 'encrypted': False,
+ 'host': "localhost",
+ 'user': "SYSTEM",
+ 'userstore': True,
+ 'database': "HDB",
+ 'query': "SELECT * FROM users;"
+ })
+ with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+ run_command.return_value = 0, 'username,name\n testuser,test user \n myuser, my user \n', ''
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_hdbsql.main()
+ self.assertEqual(result.exception.args[0]['query_result'], [[
+ {'username': 'testuser', 'name': 'test user'},
+ {'username': 'myuser', 'name': 'my user'},
+ ]])
+ self.assertEqual(run_command.call_count, 1)
+
+ def test_hana_failed_no_passwd(self):
+ """Check that result is failed with no password."""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({
+ 'sid': "HDB",
+ 'instance': "01",
+ 'encrypted': False,
+ 'host': "localhost",
+ 'user': "SYSTEM",
+ 'database': "HDB",
+ 'query': "SELECT * FROM users;"
+ })
+ self.module.main()
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_pyrfc.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_pyrfc.py
new file mode 100644
index 000000000..2e206b36b
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_pyrfc.py
@@ -0,0 +1,76 @@
+# 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.
+
+from __future__ import (absolute_import, division, print_function)
+
+__metaclass__ = type
+
+import sys
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
+
+sys.modules['pyrfc'] = MagicMock()
+sys.modules['pyrfc.Connection'] = MagicMock()
+from ansible_collections.community.sap_libs.plugins.modules import sap_pyrfc
+
+
+class TestSAPRfcModule(ModuleTestCase):
+
+ def setUp(self):
+ super(TestSAPRfcModule, self).setUp()
+ self.module = sap_pyrfc
+
+ def tearDown(self):
+ super(TestSAPRfcModule, self).tearDown()
+
+ def test_without_required_parameters(self):
+ """Failure must occurs when all parameters are missing"""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ self.module.main()
+
+ def test_error_module_not_found(self):
+ """tests fail module error"""
+
+ set_module_args({
+ "function": "STFC_CONNECTION",
+ "parameters": {"REQUTEXT": "Hello SAP!"},
+ "connection": {"ashost": "s4hana.poc.cloud",
+ "sysnr": "01",
+ "client": "400",
+ "user": "DDIC",
+ "passwd": "Password1",
+ "lang": "EN"}
+ })
+
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.HAS_PYRFC_LIBRARY = False
+ self.module.PYRFC_LIBRARY_IMPORT_ERROR = 'Module not found'
+ self.module.main()
+ self.assertEqual(
+ result.exception.args[0]['exception'], 'Module not found')
+
+ def test_success_communication(self):
+ """tests success"""
+ set_module_args({
+ "function": "STFC_CONNECTION",
+ "parameters": {"REQUTEXT": "Hello SAP!"},
+ "connection": {"ashost": "s4hana.poc.cloud",
+ "sysnr": "01",
+ "client": "400",
+ "user": "DDIC",
+ "passwd": "Password1",
+ "lang": "EN"}
+ })
+ with patch.object(self.module, 'get_connection') as patch_call:
+ patch_call.call.return_value = 'Patched'
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['changed'], True)
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_snote.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_snote.py
new file mode 100644
index 000000000..95ce6df36
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_snote.py
@@ -0,0 +1,181 @@
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock, Mock
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
+
+sys.modules['pyrfc'] = MagicMock()
+sys.modules['pyrfc.Connection'] = MagicMock()
+
+from ansible_collections.community.sap_libs.plugins.modules import sap_snote
+
+
+class TestSAPRfcModule(ModuleTestCase):
+
+ def setUp(self):
+ super(TestSAPRfcModule, self).setUp()
+ self.module = sap_snote
+
+ def tearDown(self):
+ super(TestSAPRfcModule, self).tearDown()
+
+ def define_rfc_connect(self, mocker):
+ return mocker.patch(self.module.call_rfc_method)
+
+ def test_without_required_parameters(self):
+ """Failure must occurs when all parameters are missing"""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ self.module.main()
+
+ def test_error_module_not_found(self):
+ """tests fail module error"""
+
+ set_module_args({
+ "conn_username": "ADMIN",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "snote_path": "/user/sap/trans/temp/000123456.txt"
+ })
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.HAS_PYRFC_LIBRARY = False
+ self.module.ANOTHER_LIBRARY_IMPORT_ERROR = 'Module not found'
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['exception'], 'Module not found')
+
+ def test_error_connection(self):
+ """tests fail module error"""
+
+ set_module_args({
+ "conn_username": "ADMIN",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "snote_path": "/user/sap/trans/temp/000123456.txt"
+ })
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.Connection.side_effect = Mock(side_effect=Exception('Test'))
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'Something went wrong connecting to the SAP system.')
+
+ def test_error_wrong_path(self):
+ """tests fail wrong path extension"""
+
+ set_module_args({
+ "conn_username": "ADMIN",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "snote_path": "/user/sap/trans/temp/000123456_00.tx"
+ })
+
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'The path must include the extracted snote file and ends with txt.')
+
+ def test_error_wrong_user(self):
+ """tests fail wrong path extension"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "snote_path": "/user/sap/trans/temp/000123456_00.tx"
+ })
+
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'User C(DDIC) or C(SAP*) not allowed for this operation.')
+
+ def test_success_absent(self):
+ """test absent execute snote"""
+
+ set_module_args({
+ "conn_username": "ADMIN",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "state": "absent",
+ "snote_path": "/user/sap/trans/temp/000123456.txt"
+ })
+ with patch.object(self.module, 'call_rfc_method') as call:
+ call.return_value = {'EV_RC': 0}
+ with self.assertRaises(AnsibleExitJson) as result:
+ with patch.object(self.module, 'check_implementation') as check:
+ check.side_effect = [True, False]
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'SNOTE "000123456" deimplemented.')
+
+ def test_success_absent_snot_only(self):
+ """test absent execute snote"""
+
+ set_module_args({
+ "conn_username": "ADMIN",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "state": "absent",
+ "snote": "000123456"
+ })
+ with patch.object(self.module, 'call_rfc_method') as call:
+ call.return_value = {'EV_RC': 0}
+ with self.assertRaises(AnsibleExitJson) as result:
+ with patch.object(self.module, 'check_implementation') as check:
+ check.side_effect = [True, False]
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'SNOTE "000123456" deimplemented.')
+
+ def test_nothing_to_do(self):
+ """test nothing to do"""
+
+ set_module_args({
+ "conn_username": "ADMIN",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "state": "present",
+ "snote_path": "/user/sap/trans/temp/000123456.txt"
+ })
+ with patch.object(self.module, 'check_implementation') as check:
+ check.return_value = True
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'Nothing to do.')
+
+ def test_success_present_with_copy(self):
+ """test present execute snote"""
+
+ set_module_args({
+ "conn_username": "ADMIN",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "state": "present",
+ "snote_path": "/user/sap/trans/temp/000123456.txt"
+ })
+ with patch.object(self.module, 'call_rfc_method') as call:
+ call.return_value = {'EV_RC': 0}
+ with self.assertRaises(AnsibleExitJson) as result:
+ with patch.object(self.module, 'check_implementation') as check:
+ check.side_effect = [False, True]
+ with patch.object(self.module, 'call_rfc_method') as callrfc:
+ callrfc.side_effect = [{'EV_RC': 0}, {'EV_RC': 0}, {'ET_MANUAL_ACTIVITIES': ''}]
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'SNOTE "000123456" implemented.')
+
+ def test_success_present_implement_only(self):
+ """test present implement snote"""
+
+ set_module_args({
+ "conn_username": "ADMIN",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "state": "present",
+ "snote": "000123456"
+ })
+ with patch.object(self.module, 'call_rfc_method') as call:
+ call.return_value = {'EV_RC': 0}
+ with self.assertRaises(AnsibleExitJson) as result:
+ with patch.object(self.module, 'check_implementation') as check:
+ check.side_effect = [False, True]
+ with patch.object(self.module, 'call_rfc_method') as callrfc:
+ callrfc.side_effect = [{'EV_RC': 0}, {'ET_MANUAL_ACTIVITIES': ''}]
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'SNOTE "000123456" implemented.')
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_system_facts.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_system_facts.py
new file mode 100644
index 000000000..4c882344f
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_system_facts.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber (@rainerleber) <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from ansible_collections.community.sap_libs.plugins.modules import sap_system_facts
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, ModuleTestCase
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch
+from ansible.module_utils import basic
+
+
+def get_bin_path(*args, **kwargs):
+ """Function to return path of sapcontrol"""
+ return "/usr/sap/hostctrl/exe/sapcontrol"
+
+
+class Testsap_system_facts(ModuleTestCase):
+ """Main class for testing sap_system_facts module."""
+
+ def setUp(self):
+ """Setup."""
+ super(Testsap_system_facts, self).setUp()
+ self.module = sap_system_facts
+ self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path)
+ self.mock_get_bin_path.start()
+ self.addCleanup(self.mock_get_bin_path.stop)
+
+ def tearDown(self):
+ """Teardown."""
+ super(Testsap_system_facts, self).tearDown()
+
+ def test_no_systems_available(self):
+ """No SAP Systems"""
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['ansible_facts'], {})
+
+ def test_sap_system_facts_all(self):
+ """Check that result is changed when all is one system."""
+ with patch.object(self.module, 'get_all_hana_sid') as get_all_hana_sid:
+ get_all_hana_sid.return_value = ['HDB']
+ with patch.object(self.module, 'get_hana_nr') as get_hana_nr:
+ get_hana_nr.return_value = [{"InstanceType": "HANA", "NR": "01", "SID": "HDB", "TYPE": "HDB"}]
+ with patch.object(self.module, 'get_all_nw_sid') as get_all_nw_sid:
+ get_all_nw_sid.return_value = ['ABC']
+ with patch.object(self.module, 'get_nw_nr') as get_nw_nr:
+ get_nw_nr.return_value = [{"InstanceType": "NW", "NR": "00", "SID": "ABC", "TYPE": "ASCS"},
+ {"InstanceType": "NW", "NR": "01", "SID": "ABC", "TYPE": "PAS"}]
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{"InstanceType": "HANA", "NR": "01", "SID": "HDB", "TYPE": "HDB"},
+ {"InstanceType": "NW", "NR": "00", "SID": "ABC", "TYPE": "ASCS"},
+ {"InstanceType": "NW", "NR": "01", "SID": "ABC", "TYPE": "PAS"}]})
+
+ def test_sap_system_facts_command_hana(self):
+ """Check that result for HANA is correct."""
+ with patch.object(self.module, 'get_all_hana_sid') as mock_all_hana_sid:
+ mock_all_hana_sid.return_value = ['HDB']
+ with patch.object(self.module.os, 'listdir') as mock_listdir:
+ mock_listdir.return_value = ['HDB01']
+ with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+ run_command.return_value = [0, '', '']
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{"InstanceType": "HANA", "NR": "01", "SID": "HDB", "TYPE": "HDB"}]})
+
+ def test_sap_system_facts_pas_nw(self):
+ """Check that result for NW is correct."""
+ with patch.object(self.module, 'get_all_nw_sid') as mock_all_nw_sid:
+ mock_all_nw_sid.return_value = ['ABC']
+ with patch.object(self.module.os, 'listdir') as mock_listdir:
+ mock_listdir.return_value = ['D00']
+ with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+ run_command.return_value = [0, 'SAP\nINSTANCE_NAME, Attribute, D00\nSAP', '']
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{'InstanceType': 'NW', 'NR': '00', 'SID': 'ABC', 'TYPE': 'PAS'}]})
+
+ def test_sap_system_facts_future_nw(self):
+ """Check that future apps for NW are correct handled."""
+ with patch.object(self.module, 'get_all_nw_sid') as mock_all_nw_sid:
+ mock_all_nw_sid.return_value = ['ABC']
+ with patch.object(self.module.os, 'listdir') as mock_listdir:
+ mock_listdir.return_value = ['XY00']
+ with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+ run_command.return_value = [0, 'SAP\nINSTANCE_NAME, Attribute, XY00\nSAP', '']
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{'InstanceType': 'NW', 'NR': '00', 'SID': 'ABC', 'TYPE': 'XXX'}]})
+
+ def test_sap_system_facts_wd_nw(self):
+ """Check that WD for NW is correct handled."""
+ with patch.object(self.module, 'get_all_nw_sid') as mock_all_nw_sid:
+ mock_all_nw_sid.return_value = ['ABC']
+ with patch.object(self.module.os, 'listdir') as mock_listdir:
+ mock_listdir.return_value = ['WD80']
+ with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+ run_command.return_value = [0, 'SAP\nINSTANCE_NAME, Attribute, WD80\nSAP', '']
+ with self.assertRaises(AnsibleExitJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{'InstanceType': 'NW', 'NR': '80', 'SID': 'ABC', 'TYPE': 'WebDisp'}]})
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_task_list_execute.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_task_list_execute.py
new file mode 100644
index 000000000..a7ac42e57
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_task_list_execute.py
@@ -0,0 +1,89 @@
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
+
+sys.modules['pyrfc'] = MagicMock()
+sys.modules['pyrfc.Connection'] = MagicMock()
+sys.modules['xmltodict'] = MagicMock()
+sys.modules['xmltodict.parse'] = MagicMock()
+
+from ansible_collections.community.sap_libs.plugins.modules import sap_task_list_execute
+
+
+class TestSAPRfcModule(ModuleTestCase):
+
+ def setUp(self):
+ super(TestSAPRfcModule, self).setUp()
+ self.module = sap_task_list_execute
+
+ def tearDown(self):
+ super(TestSAPRfcModule, self).tearDown()
+
+ def define_rfc_connect(self, mocker):
+ return mocker.patch(self.module.call_rfc_method)
+
+ def test_without_required_parameters(self):
+ """Failure must occurs when all parameters are missing"""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ self.module.main()
+
+ def test_error_no_task_list(self):
+ """tests fail to exec task list"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "task_to_execute": "SAP_BASIS_SSL_CHECK"
+ })
+
+ with patch.object(self.module, 'Connection') as conn:
+ conn.return_value = ''
+ with self.assertRaises(AnsibleFailJson) as result:
+ self.module.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'The task list does not exist.')
+
+ def test_success(self):
+ """test execute task list success"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "task_to_execute": "SAP_BASIS_SSL_CHECK"
+ })
+ with patch.object(self.module, 'xml_to_dict') as XML:
+ XML.return_value = {'item': [{'TASK': {'CHECK_STATUS_DESCR': 'Check successfully',
+ 'STATUS_DESCR': 'Executed successfully', 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO',
+ 'LNR': '1', 'DESCRIPTION': 'Check SAP Cryptographic Library', 'DOCU_EXIST': 'X',
+ 'LOG_EXIST': 'X', 'ACTION_SKIP': None, 'ACTION_UNSKIP': None, 'ACTION_CONFIRM': None,
+ 'ACTION_MAINTAIN': None}}]}
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_task_list_execute.main()
+ self.assertEqual(result.exception.args[0]['out'], {'item': [{'TASK': {'CHECK_STATUS_DESCR': 'Check successfully',
+ 'STATUS_DESCR': 'Executed successfully', 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO',
+ 'LNR': '1', 'DESCRIPTION': 'Check SAP Cryptographic Library', 'DOCU_EXIST': 'X',
+ 'LOG_EXIST': 'X', 'ACTION_SKIP': None, 'ACTION_UNSKIP': None,
+ 'ACTION_CONFIRM': None, 'ACTION_MAINTAIN': None}}]})
+
+ def test_success_no_log(self):
+ """test execute task list success without logs"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "task_to_execute": "SAP_BASIS_SSL_CHECK"
+ })
+ with patch.object(self.module, 'xml_to_dict') as XML:
+ XML.return_value = "No logs available."
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_task_list_execute.main()
+ self.assertEqual(result.exception.args[0]['out'], 'No logs available.')
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_user.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_user.py
new file mode 100644
index 000000000..51f57d945
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sap_user.py
@@ -0,0 +1,189 @@
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
+
+sys.modules['pyrfc'] = MagicMock()
+sys.modules['pyrfc.Connection'] = MagicMock()
+
+from ansible_collections.community.sap_libs.plugins.modules import sap_user
+
+
+class TestSAPRfcModule(ModuleTestCase):
+
+ def setUp(self):
+ super(TestSAPRfcModule, self).setUp()
+ self.module = sap_user
+
+ def tearDown(self):
+ super(TestSAPRfcModule, self).tearDown()
+
+ def define_rfc_connect(self, mocker):
+ return mocker.patch(self.module.call_rfc_method)
+
+ def test_without_required_parameters(self):
+ """Failure must occurs when all parameters are missing"""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ self.module.main()
+
+ def test_error_user_create(self):
+ """test fail to create user"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "username": "ADMIN",
+ "firstname": "first_admin",
+ "lastname": "last_admin",
+ "email": "admin@test.de",
+ "password": "Test123456",
+ "useralias": "ADMIN",
+ "company": "DEFAULT_COMPANY"
+ })
+
+ with patch.object(self.module, 'check_user') as check:
+ check.return_value = False
+
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'Something went wrong', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '199',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'E'}]}
+
+ with self.assertRaises(AnsibleFailJson) as result:
+ sap_user.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'Something went wrong')
+
+ def test_success(self):
+ """test execute user create success"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "username": "ADMIN",
+ "firstname": "first_admin",
+ "lastname": "last_admin",
+ "email": "admin@test.de",
+ "password": "Test123456",
+ "useralias": "ADMIN",
+ "company": "DEFAULT_COMPANY"
+ })
+ with patch.object(self.module, 'check_user') as check:
+ check.return_value = False
+
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'User ADMIN created', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '102',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]}
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_user.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'User ADMIN created')
+
+ def test_no_changes(self):
+ """test execute user no changes"""
+
+ set_module_args({
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "username": "ADMIN",
+ "firstname": "first_admin",
+ "lastname": "last_admin",
+ "email": "admin@test.de",
+ "password": "Test123456",
+ "useralias": "ADMIN",
+ "company": "DEFAULT_COMPANY"
+ })
+ with patch.object(self.module, 'check_user') as check:
+ check.return_value = True
+
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': '', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '029',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]}
+
+ with patch.object(self.module, 'all') as DETAIL:
+ DETAIL.return_value = True
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_user.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'No changes where made.')
+
+ def test_absent(self):
+ """test execute user delete success"""
+
+ set_module_args({
+ "state": "absent",
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "username": "ADMIN",
+ })
+ with patch.object(self.module, 'check_user') as check:
+ check.return_value = True
+
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'User ADMIN deleted', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '102',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]}
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_user.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'User ADMIN deleted')
+
+ def test_lock(self):
+ """test execute user lock success"""
+
+ set_module_args({
+ "state": "lock",
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "username": "ADMIN",
+ })
+ with patch.object(self.module, 'check_user') as check:
+ check.return_value = True
+
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'User ADMIN locked', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '206',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]}
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_user.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'User ADMIN locked')
+
+ def test_unlock(self):
+ """test execute user lock success"""
+
+ set_module_args({
+ "state": "lock",
+ "conn_username": "DDIC",
+ "conn_password": "Test1234",
+ "host": "10.1.8.9",
+ "username": "ADMIN",
+ })
+ with patch.object(self.module, 'check_user') as check:
+ check.return_value = True
+
+ with patch.object(self.module, 'call_rfc_method') as RAW:
+ RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000',
+ 'LOG_NO': '', 'MESSAGE': 'User ADMIN unlocked', 'MESSAGE_V1': 'ADMIN',
+ 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '210',
+ 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]}
+
+ with self.assertRaises(AnsibleExitJson) as result:
+ sap_user.main()
+ self.assertEqual(result.exception.args[0]['msg'], 'User ADMIN unlocked')
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sapcar_extract.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sapcar_extract.py
new file mode 100644
index 000000000..77695e407
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/test_sapcar_extract.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Rainer Leber (@rainerleber) <rainerleber@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.sap_libs.plugins.modules import sapcar_extract
+from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch
+from ansible.module_utils import basic
+
+
+def get_bin_path(*args, **kwargs):
+ """Function to return path of SAPCAR"""
+ return "/tmp/sapcar"
+
+
+class Testsapcar_extract(ModuleTestCase):
+ """Main class for testing sapcar_extract module."""
+
+ def setUp(self):
+ """Setup."""
+ super(Testsapcar_extract, self).setUp()
+ self.module = sapcar_extract
+ self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path)
+ self.mock_get_bin_path.start()
+ self.addCleanup(self.mock_get_bin_path.stop) # ensure that the patching is 'undone'
+
+ def tearDown(self):
+ """Teardown."""
+ super(Testsapcar_extract, self).tearDown()
+
+ def test_without_required_parameters(self):
+ """Failure must occurs when all parameters are missing."""
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ self.module.main()
+
+ def test_sapcar_extract(self):
+ """Check that result is changed."""
+ set_module_args({
+ 'path': "/tmp/HANA_CLIENT_REV2_00_053_00_LINUX_X86_64.SAR",
+ 'dest': "/tmp/test2",
+ 'binary_path': "/tmp/sapcar"
+ })
+ with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+ run_command.return_value = 0, '', '' # successful execution, no output
+ with self.assertRaises(AnsibleExitJson) as result:
+ sapcar_extract.main()
+ self.assertTrue(result.exception.args[0]['changed'])
+ self.assertEqual(run_command.call_count, 1)
diff --git a/ansible_collections/community/sap_libs/tests/unit/plugins/modules/utils.py b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/utils.py
new file mode 100644
index 000000000..5a8ec563b
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/plugins/modules/utils.py
@@ -0,0 +1,52 @@
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import json
+
+from ansible_collections.community.sap_libs.tests.unit.compat import unittest
+from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch
+from ansible.module_utils import basic
+from ansible.module_utils.common.text.converters import to_bytes
+
+
+def set_module_args(args):
+ if '_ansible_remote_tmp' not in args:
+ args['_ansible_remote_tmp'] = '/tmp'
+ if '_ansible_keep_remote_files' not in args:
+ args['_ansible_keep_remote_files'] = False
+
+ args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
+ basic._ANSIBLE_ARGS = to_bytes(args)
+
+
+class AnsibleExitJson(Exception):
+ pass
+
+
+class AnsibleFailJson(Exception):
+ pass
+
+
+def exit_json(*args, **kwargs):
+ if 'changed' not in kwargs:
+ kwargs['changed'] = False
+ raise AnsibleExitJson(kwargs)
+
+
+def fail_json(*args, **kwargs):
+ kwargs['failed'] = True
+ raise AnsibleFailJson(kwargs)
+
+
+class ModuleTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json)
+ self.mock_module.start()
+ self.mock_sleep = patch('time.sleep')
+ self.mock_sleep.start()
+ set_module_args({})
+ self.addCleanup(self.mock_module.stop)
+ self.addCleanup(self.mock_sleep.stop)
diff --git a/ansible_collections/community/sap_libs/tests/unit/requirements.txt b/ansible_collections/community/sap_libs/tests/unit/requirements.txt
new file mode 100644
index 000000000..49654b8b5
--- /dev/null
+++ b/ansible_collections/community/sap_libs/tests/unit/requirements.txt
@@ -0,0 +1,7 @@
+unittest2 ; python_version < '2.7'
+importlib ; python_version < '2.7'
+
+# requirement sap_task_list_execute
+lxml < 4.3.0 ; python_version < '2.7' # lxml 4.3.0 and later require python 2.7 or later
+lxml ; python_version >= '2.7'
+argparse ; python_version >= '2.6' \ No newline at end of file