├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation_report.yml │ └── feature_request.yml └── workflows │ └── ansible-test.yml ├── .gitignore ├── .vscode └── extensions.json ├── CHANGELOG.rst ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS ├── MAINTAINING.md ├── README.md ├── REVIEW_CHECKLIST.md ├── changelogs ├── changelog.yaml ├── config.yaml └── fragments │ ├── .keep │ ├── 0043-Ansible_eol_support_drop.yaml │ └── 0044-hdbsql_sqlfile_fail_on_first_error.yaml ├── codecov.yml ├── galaxy.yml ├── meta └── runtime.yml ├── plugins ├── doc_fragments │ └── __init__.py ├── module_utils │ ├── __init__.py │ ├── pyrfc_handler.py │ └── swpm2_parameters_inifile_generate.py └── modules │ ├── sap_company.py │ ├── sap_control_exec.py │ ├── sap_hdbsql.py │ ├── sap_pyrfc.py │ ├── sap_snote.py │ ├── sap_system_facts.py │ ├── sap_task_list_execute.py │ ├── sap_user.py │ └── sapcar_extract.py └── tests ├── integration ├── __init__.py └── targets │ └── __init__.py ├── sanity ├── __init__.py ├── ignore-2.10.txt ├── ignore-2.11.txt ├── ignore-2.12.txt ├── ignore-2.13.txt ├── ignore-2.14.txt ├── ignore-2.15.txt ├── ignore-2.16.txt ├── ignore-2.17.txt ├── ignore-2.18.txt └── ignore-2.9.txt └── unit ├── __init__.py ├── compat ├── __init__.py ├── builtins.py ├── mock.py └── unittest.py ├── mock ├── __init__.py ├── loader.py ├── path.py ├── procenv.py ├── vault_helper.py └── yaml_helper.py ├── plugins ├── __init__.py └── modules │ ├── __init__.py │ ├── test_sap_company.py │ ├── test_sap_control_exec.py │ ├── test_sap_hdbsql.py │ ├── test_sap_pyrfc.py │ ├── test_sap_snote.py │ ├── test_sap_system_facts.py │ ├── test_sap_task_list_execute.py │ ├── test_sap_user.py │ ├── test_sapcar_extract.py │ └── utils.py └── requirements.txt /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | description: Create a report to help us improve 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ⚠ 10 | Verify first that your issue is not [already reported on GitHub][issue search]. 11 | Also test if the latest release and devel branch are affected too. 12 | *Complete **all** sections as described, this form is processed automatically.* 13 | 14 | [issue search]: https://github.com/sap-linuxlab/community.sap_libs/search?q=is%3Aissue&type=issues 15 | 16 | 17 | - type: textarea 18 | attributes: 19 | label: Summary 20 | description: Explain the problem briefly below. 21 | placeholder: >- 22 | When I try to do X with the collection from the main branch on GitHub, Y 23 | breaks in a way Z under the env E. Here are all the details I know 24 | about this problem... 25 | validations: 26 | required: true 27 | 28 | - type: dropdown 29 | attributes: 30 | label: Issue Type 31 | # FIXME: Once GitHub allows defining the default choice, update this 32 | options: 33 | - Bug Report 34 | validations: 35 | required: true 36 | 37 | - type: textarea 38 | attributes: 39 | # For smaller collections we could use a multi-select and hardcode the list 40 | # May generate this list via GitHub action and walking files under https://github.com/sap-linuxlab/community.sap_libs/tree/main/plugins 41 | # Select from list, filter as you type (`mysql` would only show the 3 mysql components) 42 | # OR freeform - doesn't seem to be supported in adaptivecards 43 | label: Component Name 44 | description: >- 45 | Write the short name of the module, plugin, task or feature below, 46 | *use your best guess if unsure*. 47 | placeholder: dnf, apt, yum, pip, user etc. 48 | validations: 49 | required: true 50 | 51 | - type: textarea 52 | attributes: 53 | label: Ansible Version 54 | description: >- 55 | Paste verbatim output from `ansible --version` between 56 | tripple backticks. 57 | value: | 58 | ```console (paste below) 59 | $ ansible --version 60 | 61 | ``` 62 | validations: 63 | required: true 64 | 65 | - type: textarea 66 | attributes: 67 | label: community.sap_libs Version 68 | description: >- 69 | Paste verbatim output from "ansible-galaxy collection list community.sap_libs" 70 | between tripple backticks. 71 | value: | 72 | ```console (paste below) 73 | $ ansible-galaxy collection list community.sap_libs 74 | 75 | ``` 76 | validations: 77 | required: true 78 | 79 | - type: textarea 80 | attributes: 81 | label: Configuration 82 | description: >- 83 | If this issue has an example piece of YAML that can help to reproduce this problem, please provide it. 84 | This can be a piece of YAML from, e.g., an automation, script, scene or configuration. 85 | Paste verbatim output from `ansible-config dump --only-changed` between quotes 86 | value: | 87 | ```console (paste below) 88 | $ ansible-config dump --only-changed 89 | 90 | ``` 91 | 92 | 93 | - type: textarea 94 | attributes: 95 | label: OS / Environment 96 | description: >- 97 | Provide all relevant information below, e.g. target OS versions, 98 | network device firmware, etc. 99 | placeholder: RHEL 8, SLES 100 | validations: 101 | required: false 102 | 103 | 104 | - type: textarea 105 | attributes: 106 | label: Steps to Reproduce 107 | description: | 108 | 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. 109 | 110 | **HINT:** You can paste https://gist.github.com links for larger files. 111 | value: | 112 | 113 | ```yaml (paste below) 114 | 115 | ``` 116 | validations: 117 | required: true 118 | 119 | - type: textarea 120 | attributes: 121 | label: Expected Results 122 | description: >- 123 | Describe what you expected to happen when running the steps above. 124 | placeholder: >- 125 | I expected X to happen because I assumed Y. 126 | that it did not. 127 | validations: 128 | required: true 129 | 130 | - type: textarea 131 | attributes: 132 | label: Actual Results 133 | description: | 134 | Describe what actually happened. If possible run with extra verbosity (`-vvvv`). 135 | 136 | Paste verbatim command output between quotes. 137 | value: | 138 | ```console (paste below) 139 | 140 | ``` 141 | - type: checkboxes 142 | attributes: 143 | label: Code of Conduct 144 | description: | 145 | 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. 146 | options: 147 | - label: I agree to follow the Ansible Code of Conduct 148 | required: true 149 | ... 150 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 3 | blank_issues_enabled: false # default: true 4 | contact_links: 5 | - name: Security bug report 6 | url: https://docs.ansible.com/ansible-core/devel/community/reporting_bugs_and_features.html?utm_medium=github&utm_source=issue_template_chooser_ansible_collections 7 | about: | 8 | Please learn how to report security vulnerabilities here. 9 | 10 | For all security related bugs, email security@ansible.com 11 | instead of using this issue tracker and you will receive 12 | a prompt response. 13 | 14 | For more information, see 15 | https://docs.ansible.com/ansible/latest/community/reporting_bugs_and_features.html 16 | - name: Ansible Code of Conduct 17 | url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser_ansible_collections 18 | about: Be nice to other members of the community. 19 | - name: Talks to the community 20 | url: https://docs.ansible.com/ansible/latest/community/communication.html?utm_medium=github&utm_source=issue_template_chooser#mailing-list-information 21 | about: Please ask and answer usage questions here 22 | - name: Working groups 23 | url: https://github.com/ansible/community/wiki 24 | about: Interested in improving a specific area? Become a part of a working group! 25 | - name: For Enterprise 26 | url: https://www.ansible.com/products/engine?utm_medium=github&utm_source=issue_template_chooser_ansible_collections 27 | about: Red Hat offers support for the Ansible Automation Platform 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation Report 3 | description: Ask us about docs 4 | # NOTE: issue body is enabled to allow screenshots 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | ⚠ 11 | Verify first that your issue is not [already reported on GitHub][issue search]. 12 | Also test if the latest release and devel branch are affected too. 13 | *Complete **all** sections as described, this form is processed automatically.* 14 | 15 | [issue search]: https://github.com/sap-linuxlab/community.sap_libs/search?q=is%3Aissue&type=issues 16 | 17 | 18 | - type: textarea 19 | attributes: 20 | label: Summary 21 | description: | 22 | Explain the problem briefly below, add suggestions to wording or structure. 23 | 24 | **HINT:** Did you know the documentation has an `Edit on GitHub` link on every page? 25 | placeholder: >- 26 | I was reading the Collection documentation of version X and I'm having 27 | problems understanding Y. It would be very helpful if that got 28 | rephrased as Z. 29 | validations: 30 | required: true 31 | 32 | - type: dropdown 33 | attributes: 34 | label: Issue Type 35 | # FIXME: Once GitHub allows defining the default choice, update this 36 | options: 37 | - Documentation Report 38 | validations: 39 | required: true 40 | 41 | - type: input 42 | attributes: 43 | label: Component Name 44 | description: >- 45 | Write the short name of the rst file, module, plugin, task or 46 | feature below, *use your best guess if unsure*. 47 | placeholder: mysql_user 48 | validations: 49 | required: true 50 | 51 | - type: textarea 52 | attributes: 53 | label: Ansible Version 54 | description: >- 55 | Paste verbatim output from `ansible --version` between 56 | tripple backticks. 57 | value: | 58 | ```console (paste below) 59 | $ ansible --version 60 | 61 | ``` 62 | validations: 63 | required: false 64 | 65 | - type: textarea 66 | attributes: 67 | label: Community.sap_libs Version 68 | description: >- 69 | Paste verbatim output from "ansible-galaxy collection list community.sap_libs" 70 | between tripple backticks. 71 | value: | 72 | ```console (paste below) 73 | $ ansible-galaxy collection list community.sap_libs 74 | 75 | ``` 76 | validations: 77 | required: true 78 | 79 | - type: textarea 80 | attributes: 81 | label: Configuration 82 | description: >- 83 | Paste verbatim output from `ansible-config dump --only-changed` between quotes. 84 | value: | 85 | ```console (paste below) 86 | $ ansible-config dump --only-changed 87 | 88 | ``` 89 | validations: 90 | required: false 91 | 92 | - type: textarea 93 | attributes: 94 | label: OS / Environment 95 | description: >- 96 | Provide all relevant information below, e.g. OS version, 97 | browser, etc. 98 | placeholder: SLES, RHEL 99 | validations: 100 | required: false 101 | 102 | - type: textarea 103 | attributes: 104 | label: Additional Information 105 | description: | 106 | Describe how this improves the documentation, e.g. before/after situation or screenshots. 107 | 108 | **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. 109 | 110 | **HINT:** You can paste https://gist.github.com links for larger files. 111 | placeholder: >- 112 | When the improvement is applied, it makes it more straightforward 113 | to understand X. 114 | validations: 115 | required: false 116 | 117 | - type: checkboxes 118 | attributes: 119 | label: Code of Conduct 120 | description: | 121 | 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. 122 | options: 123 | - label: I agree to follow the Ansible Code of Conduct 124 | required: true 125 | ... 126 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | description: Suggest an idea for this project 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ⚠ 10 | Verify first that your issue is not [already reported on GitHub][issue search]. 11 | Also test if the latest release and devel branch are affected too. 12 | *Complete **all** sections as described, this form is processed automatically.* 13 | 14 | [issue search]: https://github.com/sap-linuxlab/community.sap_libs/search?q=is%3Aissue&type=issues 15 | 16 | 17 | - type: textarea 18 | attributes: 19 | label: Summary 20 | description: Describe the new feature/improvement briefly below. 21 | placeholder: >- 22 | I am trying to do X with the collection from the main branch on GitHub and 23 | I think that implementing a feature Y would be very helpful for me and 24 | every other user of community.sap_libs because of Z. 25 | validations: 26 | required: true 27 | 28 | - type: dropdown 29 | attributes: 30 | label: Issue Type 31 | # FIXME: Once GitHub allows defining the default choice, update this 32 | options: 33 | - Feature Idea 34 | validations: 35 | required: true 36 | 37 | - type: input 38 | attributes: 39 | label: Component Name 40 | description: >- 41 | Write the short name of the module, plugin, task or feature below, 42 | *use your best guess if unsure*. 43 | placeholder: dnf, apt, yum, pip, user etc. 44 | validations: 45 | required: true 46 | 47 | - type: textarea 48 | attributes: 49 | label: Additional Information 50 | description: | 51 | Describe how the feature would be used, why it is needed and what it would solve. 52 | 53 | **HINT:** You can paste https://gist.github.com links for larger files. 54 | value: | 55 | 56 | ```yaml (paste below) 57 | 58 | ``` 59 | validations: 60 | required: false 61 | - type: checkboxes 62 | attributes: 63 | label: Code of Conduct 64 | description: | 65 | 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. 66 | options: 67 | - label: I agree to follow the Ansible Code of Conduct 68 | required: true 69 | ... 70 | -------------------------------------------------------------------------------- /.github/workflows/ansible-test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | # Run CI against all pushes (direct commits, also merged PRs), Pull Requests 4 | push: 5 | pull_request: 6 | # Run CI once per day (at 06:00 UTC) 7 | # 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 8 | schedule: 9 | - cron: '0 6 * * *' 10 | env: 11 | NAMESPACE: community 12 | COLLECTION_NAME: sap_libs 13 | 14 | jobs: 15 | sanity: 16 | name: Sanity (Ⓐ${{ matrix.ansible }}) 17 | strategy: 18 | matrix: 19 | ansible: 20 | - stable-2.13 21 | - stable-2.14 22 | - stable-2.15 23 | - stable-2.16 24 | - stable-2.17 25 | - devel 26 | 27 | runs-on: >- 28 | ${{ contains(fromJson( 29 | '["stable-2.13", "stable-2.14"]' 30 | ), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }} 31 | steps: 32 | 33 | - name: Check out code 34 | uses: actions/checkout@v4 35 | 36 | - name: Perform sanity testing with ansible-test 37 | uses: ansible-community/ansible-test-gh-action@release/v1 38 | with: 39 | ansible-core-version: ${{ matrix.ansible }} 40 | testing-type: sanity 41 | 42 | 43 | units: 44 | runs-on: >- 45 | ${{ contains(fromJson( 46 | '["stable-2.13", "stable-2.14"]' 47 | ), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }} 48 | name: Units (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }}) 49 | strategy: 50 | # As soon as the first unit test fails, cancel the others to free up the CI queue 51 | fail-fast: true 52 | matrix: 53 | ansible: 54 | - stable-2.13 55 | - stable-2.14 56 | - stable-2.15 57 | - stable-2.16 58 | - stable-2.17 59 | - devel 60 | 61 | steps: 62 | - name: Check out code 63 | uses: actions/checkout@v4 64 | 65 | - name: Perform unit testing with ansible-test 66 | uses: ansible-community/ansible-test-gh-action@release/v1 67 | with: 68 | ansible-core-version: ${{ matrix.ansible }} 69 | testing-type: units 70 | # test-deps: >- 71 | # ansible.netcommon 72 | # ansible.utils 73 | 74 | # Please consult the Readme for information on why we disabled integration tests temporarily. 75 | 76 | # integration: 77 | # runs-on: ubuntu-latest 78 | # name: I (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }}) 79 | # strategy: 80 | # fail-fast: false 81 | # matrix: 82 | # ansible: 83 | # - stable-2.9 # Only if your collection supports Ansible 2.9 84 | # - stable-2.10 85 | # - stable-2.11 86 | # - stable-2.12 87 | # - stable-2.13 88 | # - devel 89 | # python: 90 | # - 2.6 91 | # - 2.7 92 | # - 3.5 93 | # - 3.6 94 | # - 3.7 95 | # - 3.8 96 | # - 3.9 97 | # exclude: 98 | # # Because ansible-test doesn't support python3.9 for Ansible 2.9 99 | # - ansible: stable-2.9 100 | # python: 3.9 101 | # - ansible: devel 102 | # python: 2.6 103 | 104 | # steps: 105 | # - name: Check out code 106 | # uses: actions/checkout@v3 107 | 108 | # - name: Perform integration testing with ansible-test 109 | # uses: ansible-community/ansible-test-gh-action@release/v1 110 | # with: 111 | # ansible-core-version: ${{ matrix.ansible }} 112 | # python-version: 3.8 113 | # pre-test-cmd: >- 114 | # mkdir -p tests/output/ 115 | # touch tests/output/coverage 116 | # target-python-version: ${{ matrix.python }} 117 | # testing-type: integration 118 | # test-deps: >- 119 | # ansible.netcommon 120 | # ansible.utils -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /tests/output/ 2 | /changelogs/.plugin-cache.yaml 3 | 4 | # .gitignore defines files to ignore and remain untracked in Git 5 | # Each line in a gitignore file specifies a pattern, eg. directory or file extension 6 | 7 | # Git should not track binary artifacts such as images, libraries, executables, archive files etc. 8 | # Until the team has mature processes, a Binary Artifacts Repository Manager is not in use. 9 | 10 | # Therefore some binary artifacts are tracked in this Git repository 11 | 12 | 13 | # Further .gitignore templates available at: 14 | # https://github.com/github/gitignore 15 | 16 | 17 | # macOS OS generated files 18 | .DS_Store 19 | ._* 20 | .Spotlight-V100 21 | .Trashes 22 | 23 | # Windows OS generated files # 24 | ehthumbs.db 25 | Thumbs.db 26 | 27 | # Compressed Archives 28 | # git has built-in compression 29 | # *.7z 30 | # *.dmg 31 | # *.gz 32 | # *.iso 33 | # *.jar 34 | # *.rar 35 | # *.tar 36 | # *.zip 37 | 38 | # Binaries / Compiled source 39 | # *.com 40 | # *.class 41 | # *.dll 42 | # *.exe 43 | # *.o 44 | # *.so 45 | 46 | # Logs and databases 47 | # *.log 48 | # *.sqlite 49 | 50 | # VSCode 51 | .vscode 52 | 53 | # Byte-compiled / optimized / DLL files 54 | __pycache__/ 55 | *.py[cod] 56 | *$py.class 57 | 58 | # C extensions 59 | *.so 60 | 61 | # Distribution / packaging 62 | .Python 63 | build/ 64 | develop-eggs/ 65 | dist/ 66 | downloads/ 67 | eggs/ 68 | .eggs/ 69 | lib/ 70 | lib64/ 71 | parts/ 72 | sdist/ 73 | var/ 74 | wheels/ 75 | pip-wheel-metadata/ 76 | share/python-wheels/ 77 | *.egg-info/ 78 | .installed.cfg 79 | *.egg 80 | MANIFEST 81 | 82 | # PyInstaller 83 | # Usually these files are written by a python script from a template 84 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 85 | *.manifest 86 | *.spec 87 | 88 | # Installer logs 89 | pip-log.txt 90 | pip-delete-this-directory.txt 91 | 92 | # Unit test / coverage reports 93 | htmlcov/ 94 | .tox/ 95 | .nox/ 96 | .coverage 97 | .coverage.* 98 | .cache 99 | nosetests.xml 100 | coverage.xml 101 | *.cover 102 | *.py,cover 103 | .hypothesis/ 104 | .pytest_cache/ 105 | 106 | # Translations 107 | *.mo 108 | *.pot 109 | 110 | # Django stuff: 111 | *.log 112 | local_settings.py 113 | db.sqlite3 114 | db.sqlite3-journal 115 | 116 | # Flask stuff: 117 | instance/ 118 | .webassets-cache 119 | 120 | # Scrapy stuff: 121 | .scrapy 122 | 123 | # Sphinx documentation 124 | docs/_build/ 125 | 126 | # PyBuilder 127 | target/ 128 | 129 | # Jupyter Notebook 130 | .ipynb_checkpoints 131 | 132 | # IPython 133 | profile_default/ 134 | ipython_config.py 135 | 136 | # pyenv 137 | .python-version 138 | 139 | # pipenv 140 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 141 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 142 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 143 | # install all needed dependencies. 144 | #Pipfile.lock 145 | 146 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 147 | __pypackages__/ 148 | 149 | # Celery stuff 150 | celerybeat-schedule 151 | celerybeat.pid 152 | 153 | # SageMath parsed files 154 | *.sage.py 155 | 156 | # Environments 157 | .env 158 | .venv 159 | env/ 160 | venv/ 161 | ENV/ 162 | env.bak/ 163 | venv.bak/ 164 | 165 | # Spyder project settings 166 | .spyderproject 167 | .spyproject 168 | 169 | # Rope project settings 170 | .ropeproject 171 | 172 | # mkdocs documentation 173 | /site 174 | 175 | # mypy 176 | .mypy_cache/ 177 | .dmypy.json 178 | dmypy.json 179 | 180 | # Pyre type checker 181 | .pyre/ 182 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "redhat.ansible" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Community SAP Release Notes 3 | =========================== 4 | 5 | .. contents:: Topics 6 | 7 | 8 | v1.4.1 9 | ====== 10 | 11 | Release Summary 12 | --------------- 13 | 14 | This is the 1.4.1 patch release of the ``community.sap_libs`` collection. 15 | This changelog contains all changes to the modules and plugins in this collection 16 | that have been made after the previous release. 17 | 18 | Bugfixes 19 | -------- 20 | 21 | - fixes failures in sanity test for plugins/modules/sap_pyrfc.py 22 | - fixes failures in sanity test for tests/unit/compat/builtins.py 23 | - fixes failures in sanity test for tests/unit/plugins/modules/test_sap_system_facts.py 24 | - fixes failures in sanity test for tests/unit/plugins/modules/test_sap_system_facts.py 25 | - fixes pipeline warnings 26 | - 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. 27 | 28 | v1.4.0 29 | ====== 30 | 31 | Release Summary 32 | --------------- 33 | 34 | This is the 1.3.0 minor release of the ``community.sap_libs`` collection. 35 | This changelog contains all changes to the modules and plugins in this collection 36 | that have been made after the previous release. 37 | 38 | Bugfixes 39 | -------- 40 | 41 | - fix a bug where some commands produces no output which cause to crash the module. 42 | - modules - fix a "variable used before assignment" that cannot be reached but causes sanity test failures. 43 | 44 | v1.3.0 45 | ====== 46 | 47 | Release Summary 48 | --------------- 49 | 50 | 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. 51 | 52 | Minor Changes 53 | ------------- 54 | 55 | - License requirements are updated. 56 | - The modules purposes are described clearer. 57 | - The namespaces of the modules are removed to provide a flatter design. 58 | - hana_query - module is moved to sap_hdbsql. 59 | - sapcontrol - module is moved to sap_control_exec to have a clearer separation to other roles and references. 60 | 61 | v1.2.0 62 | ====== 63 | 64 | Release Summary 65 | --------------- 66 | 67 | This is the minor release of the ``community.sap_libs`` collection. 68 | This changelog contains all changes to the modules and plugins in this collection 69 | that have been made after the previous release. 70 | 71 | Bugfixes 72 | -------- 73 | 74 | - syp_system_facts - fix a typo in the usage example which lead to an error if it used as supposed. 75 | 76 | New Modules 77 | ----------- 78 | 79 | - sap_pyrfc - Ansible Module for use of SAP PyRFC to execute SAP RFCs (Remote Function Calls) to SAP remote-enabled function modules 80 | 81 | v1.1.0 82 | ====== 83 | 84 | Release Summary 85 | --------------- 86 | 87 | This is the minor release of the ``community.sap_libs`` collection. 88 | This changelog contains all changes to the modules and plugins in this collection 89 | that have been made after the previous release. 90 | 91 | New Modules 92 | ----------- 93 | 94 | System 95 | ~~~~~~ 96 | 97 | - sapcontrol - Ansible Module to execute SAPCONTROL 98 | 99 | v1.0.0 100 | ====== 101 | 102 | Release Summary 103 | --------------- 104 | 105 | This is the minor release of the ``community.sap`` collection. It is the initial relase for the ``community.sap`` collection 106 | 107 | New Modules 108 | ----------- 109 | 110 | Database 111 | ~~~~~~~~ 112 | 113 | saphana 114 | ^^^^^^^ 115 | 116 | - hana_query - Ansible Module to execute SQL on SAP HANA 117 | 118 | Files 119 | ~~~~~ 120 | 121 | - sapcar_extract - Manages SAP SAPCAR archives 122 | 123 | Identity 124 | ~~~~~~~~ 125 | 126 | - sap_company - This module will manage a company entities in a SAP S4HANA environment 127 | - sap_user - This module will manage a user entities in a SAP S4/HANA environment 128 | 129 | System 130 | ~~~~~~ 131 | 132 | - sap_snote - This module will upload and (de)implements C(SNOTES) in a SAP S4HANA environment. 133 | - sap_system_facts - Gathers SAP facts in a host 134 | - sap_task_list_execute - Perform SAP Task list execution 135 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Code of Conduct 2 | 3 | Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Refer to the [Contributing guidelines](https://github.com/ansible/community-docs/blob/main/contributing.rst). 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | rainerleber 2 | rkpobe -------------------------------------------------------------------------------- /MAINTAINING.md: -------------------------------------------------------------------------------- 1 | # Maintaining this collection 2 | 3 | Refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Community SAP_LIBS Collection 2 | 3 | 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. 4 | 5 | **This collection is migrated from ansbile-collections/community.sap to sap-linuxlab/community.sap_libs.** 6 | 7 | # SAP Module Collection for Ansible 8 | 9 | [![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) 10 | 11 | 12 | 13 | ## Code of Conduct 14 | 15 | 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. 16 | 17 | 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. 18 | 19 | ## Communication 20 | 21 | 22 | 23 | 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). 24 | 25 | 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). 26 | 27 | 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. 28 | 29 | For more information about communication, refer to the [Ansible Communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). 30 | 31 | ## Contributing to this collection 32 | 33 | 34 | 35 | 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. 36 | 37 | We are actively accepting new contributors. 38 | 39 | Any kind of contribution is very welcome. 40 | 41 | You don't know how to start? Refer to our [contribution guide](CONTRIBUTING.md)! 42 | 43 | We use the following guidelines: 44 | 45 | * [CONTRIBUTING.md](CONTRIBUTING.md) 46 | * [REVIEW_CHECKLIST.md](REVIEW_CHECKLIST.md) 47 | * [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html) 48 | * [Ansible Development Guide](https://docs.ansible.com/ansible/devel/dev_guide/index.html) 49 | * [Ansible Collection Development Guide](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections) 50 | 51 | ## Collection maintenance 52 | 53 | 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. 54 | 55 | To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](MAINTAINING.md). 56 | 57 | ## Governance 58 | 59 | 60 | 61 | The process of decision making in this collection is based on discussing and finding consensus among participants. 62 | 63 | Every voice is important. If you have something on your mind, create an issue or dedicated discussion and let's discuss it! 64 | 65 | ## Tested with Ansible and the following Python versions 66 | 67 | Tested Ansible versions: 68 | - 2.13 69 | - 2.14 70 | - 2.15 71 | - 2.16 72 | - devel 73 | 74 | Tested Python versions: 75 | - 3.6 76 | - 3.7 77 | - 3.8 78 | - 3.9 79 | - 3.10 80 | - 3.11 81 | 82 | Due to SAP licensing and hardware requirements, integration tests are momentarily not feasible. 83 | The modules are tested manually against SAP systems until we found a solution or have some 84 | modules where we are able to execute integration test we decided to disable these tests. 85 | 86 | The test support for Ansible versions 2.9 - 2.12 is disabled due to eol of these versions. 87 | The modules may work with these versions but are not tested. 88 | 89 | ## External requirements 90 | 91 | For some modules the below requirements are needed on the host that executes a module. 92 | 93 | - pyrfc >= 2.4.0 94 | - SAPCAR 95 | - SAPCONTROL 96 | 97 | ### Supported connections 98 | 99 | 100 | ## Included content 101 | 102 | - **Modules**: 103 | - [sap_hdbsql](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_hdbsql_module.html) 104 | - [sap_task_list_execute](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_task_list_execute_module.html) 105 | - [sapcar_extract](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sapcar_extract_module.html) 106 | - [sap_company](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_company_module.html) 107 | - [sap_snote](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_snote_module.html) 108 | - [sap_user](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_user_module.html) 109 | - [sap_system_facts](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_system_facts_module.html) 110 | - [sap_control_exec](https://docs.ansible.com/ansible/latest/collections/community/sap_libs/sap_control_exec_module.html) 111 | 112 | ## Using this collection 113 | 114 | 115 | 116 | ### Installing the Collection from Ansible Galaxy 117 | 118 | Before using this collection, you need to install it with the Ansible Galaxy command-line tool: 119 | ```bash 120 | ansible-galaxy collection install community.sap_libs 121 | ``` 122 | 123 | You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format: 124 | ```yaml 125 | --- 126 | collections: 127 | - name: community.sap_libs 128 | ``` 129 | 130 | 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: 131 | ```bash 132 | ansible-galaxy collection install community.sap_libs --upgrade 133 | ``` 134 | 135 | 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`: 136 | 137 | ```bash 138 | ansible-galaxy collection install community.sap_libs:==1.0.0 139 | ``` 140 | 141 | See [Ansible Using collections](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html) for more details. 142 | 143 | ## Release notes 144 | 145 | See the [changelog](https://github.com/sap-linuxlab/community.sap_libs/tree/main/CHANGELOG.rst). 146 | 147 | ## Releasing, Versioning and Deprecation 148 | This collection follows Semantic Versioning. More details on versioning can be found in the Ansible docs. 149 | 150 | We plan to regularly release new minor or bugfix versions once new features or bugfixes have been implemented. 151 | 152 | 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. 153 | 154 | For reference have a look at the issue [Releasing, Versioning and Deprecation](https://github.com/sap-linuxlab/community.sap_libs/issues/1). 155 | 156 | 157 | 158 | ## Roadmap 159 | 160 | Please have a look at the project board. 161 | 162 | ## More information 163 | 164 | 165 | 166 | - [Ansible Collection overview](https://github.com/ansible-collections/overview) 167 | - [Ansible User guide](https://docs.ansible.com/ansible/devel/user_guide/index.html) 168 | - [Ansible Developer guide](https://docs.ansible.com/ansible/devel/dev_guide/index.html) 169 | - [Ansible Collections Checklist](https://github.com/ansible-collections/overview/blob/master/collection_requirements.rst) 170 | - [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html) 171 | - [The Bullhorn (the Ansible Contributor newsletter)](https://us19.campaign-archive.com/home/?u=56d874e027110e35dea0e03c1&id=d6635f5420) 172 | - [News for Maintainers](https://github.com/ansible-collections/news-for-maintainers) 173 | 174 | ## Licensing 175 | 176 | 177 | 178 | Apache License, Version 2.0 179 | 180 | See [LICENSE](http://www.apache.org/licenses/LICENSE-2.0) to see the full text. -------------------------------------------------------------------------------- /REVIEW_CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # Review Checklist 2 | 3 | Refer to the [Collection review checklist](https://github.com/ansible/community-docs/blob/main/review_checklist.rst). 4 | -------------------------------------------------------------------------------- /changelogs/changelog.yaml: -------------------------------------------------------------------------------- 1 | ancestor: null 2 | releases: 3 | 1.0.0: 4 | changes: 5 | release_summary: This is the minor release of the ``community.sap`` collection. 6 | It is the initial relase for the ``community.sap`` collection 7 | fragments: 8 | - 1.0.0.yml 9 | modules: 10 | - description: Ansible Module to execute SQL on SAP HANA 11 | name: hana_query 12 | namespace: database.saphana 13 | - description: This module will manage a company entities in a SAP S4HANA environment 14 | name: sap_company 15 | namespace: identity 16 | - description: This module will upload and (de)implements C(SNOTES) in a SAP S4HANA 17 | environment. 18 | name: sap_snote 19 | namespace: system 20 | - description: Gathers SAP facts in a host 21 | name: sap_system_facts 22 | namespace: system 23 | - description: Perform SAP Task list execution 24 | name: sap_task_list_execute 25 | namespace: system 26 | - description: This module will manage a user entities in a SAP S4/HANA environment 27 | name: sap_user 28 | namespace: identity 29 | - description: Manages SAP SAPCAR archives 30 | name: sapcar_extract 31 | namespace: files 32 | 1.1.0: 33 | changes: 34 | release_summary: 'This is the minor release of the ``community.sap_libs`` collection. 35 | 36 | This changelog contains all changes to the modules and plugins in this collection 37 | 38 | that have been made after the previous release.' 39 | fragments: 40 | - 1.1.0.yml 41 | modules: 42 | - description: Ansible Module to execute SAPCONTROL 43 | name: sapcontrol 44 | namespace: system 45 | release_date: '2022-05-17' 46 | 1.2.0: 47 | changes: 48 | bugfixes: 49 | - syp_system_facts - fix a typo in the usage example which lead to an error 50 | if it used as supposed. 51 | release_summary: 'This is the minor release of the ``community.sap_libs`` collection. 52 | 53 | This changelog contains all changes to the modules and plugins in this collection 54 | 55 | that have been made after the previous release.' 56 | fragments: 57 | - 1.2.0.yml 58 | - 11-sap_system_facts_fix_typo.yml 59 | modules: 60 | - description: Ansible Module for use of SAP PyRFC to execute SAP RFCs (Remote 61 | Function Calls) to SAP remote-enabled function modules 62 | name: sap_pyrfc 63 | namespace: '' 64 | release_date: '2022-07-18' 65 | 1.3.0: 66 | changes: 67 | minor_changes: 68 | - License requirements are updated. 69 | - The modules purposes are described clearer. 70 | - The namespaces of the modules are removed to provide a flatter design. 71 | - hana_query - module is moved to sap_hdbsql. 72 | - sapcontrol - module is moved to sap_control_exec to have a clearer separation 73 | to other roles and references. 74 | release_summary: This is the 1.3.0 minor release of the ``community.sap_libs`` 75 | collection. This changelog contains all changes to the modules and plugins 76 | in this collection that have been made after the previous release. 77 | fragments: 78 | - 1.3.0.yml 79 | release_date: '2022-09-09' 80 | 1.4.0: 81 | changes: 82 | bugfixes: 83 | - fix a bug where some commands produces no output which cause to crash the 84 | module. 85 | - modules - fix a "variable used before assignment" that cannot be reached but 86 | causes sanity test failures. 87 | release_summary: 'This is the 1.3.0 minor release of the ``community.sap_libs`` 88 | collection. 89 | 90 | This changelog contains all changes to the modules and plugins in this collection 91 | 92 | that have been made after the previous release.' 93 | fragments: 94 | - 0020-fix_sap_control_exec.yml 95 | - 1.4.0.yml 96 | - 22-use-before-assignment.yml 97 | release_date: '2022-12-05' 98 | 1.4.1: 99 | changes: 100 | bugfixes: 101 | - fixes failures in sanity test for plugins/modules/sap_pyrfc.py 102 | - fixes failures in sanity test for tests/unit/compat/builtins.py 103 | - fixes failures in sanity test for tests/unit/plugins/modules/test_sap_system_facts.py 104 | - fixes failures in sanity test for tests/unit/plugins/modules/test_sap_system_facts.py 105 | - fixes pipeline warnings 106 | - sapcontrol_exec - This pr fixes problems on c(StartSystem), c(StopSystem), 107 | c(RestartSystem) which needs parameters they ca not provided by the parameters 108 | argument because of special format like c(waittimeout=1) without string quotes. 109 | This is caused by the suds module itself. 110 | release_summary: This is the 1.4.1 patch release of the ``community.sap_libs`` 111 | collection. This changelog contains all changes to the modules and plugins 112 | in this collection that have been made after the previous release. 113 | fragments: 114 | - 28-fix-sapcontrol-issue.yml 115 | - 29-fix-lint-issues.yml 116 | release_date: '2023-03-09' 117 | 1.4.2: 118 | changes: 119 | bugfixes: 120 | - fixes failures in sanity test for all modules 121 | release_summary: This is the 1.4.2 patch release of the ``community.sap_libs`` 122 | collection. This changelog contains all changes to the modules and plugins 123 | in this collection that have been made after the previous release. 124 | fragments: 125 | - 30-fix-lint-issues.yml 126 | release_date: '2024-23-01' 127 | -------------------------------------------------------------------------------- /changelogs/config.yaml: -------------------------------------------------------------------------------- 1 | changelog_filename_template: ../CHANGELOG.rst 2 | changelog_filename_version_depth: 0 3 | changes_file: changelog.yaml 4 | changes_format: combined 5 | keep_fragments: false 6 | mention_ancestor: true 7 | flatmap: true 8 | new_plugins_after_name: removed_features 9 | notesdir: fragments 10 | prelude_section_name: release_summary 11 | prelude_section_title: Release Summary 12 | sections: 13 | - - major_changes 14 | - Major Changes 15 | - - minor_changes 16 | - Minor Changes 17 | - - breaking_changes 18 | - Breaking Changes / Porting Guide 19 | - - deprecated_features 20 | - Deprecated Features 21 | - - removed_features 22 | - Removed Features (previously deprecated) 23 | - - security_fixes 24 | - Security Fixes 25 | - - bugfixes 26 | - Bugfixes 27 | - - known_issues 28 | - Known Issues 29 | title: Community SAP 30 | trivial_section_name: trivial 31 | -------------------------------------------------------------------------------- /changelogs/fragments/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/changelogs/fragments/.keep -------------------------------------------------------------------------------- /changelogs/fragments/0043-Ansible_eol_support_drop.yaml: -------------------------------------------------------------------------------- 1 | minor_changes: 2 | - Drop support for ansible <= 2.12 3 | - Add test for ansible 2.18 aka devel 4 | bugfixes: 5 | - Fix pipelines -------------------------------------------------------------------------------- /changelogs/fragments/0044-hdbsql_sqlfile_fail_on_first_error.yaml: -------------------------------------------------------------------------------- 1 | minor_changes: 2 | - Add -E 3 Option to hdbsql run, if filepaht is selected, to fail on first sql error 3 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | fixes: 2 | - "/sap-linuxlab/community/sap_libs/::" 3 | -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | # See https://docs.ansible.com/ansible/latest/dev_guide/collections_galaxy_meta.html 2 | 3 | namespace: community 4 | name: sap_libs 5 | version: 1.4.2 6 | readme: README.md 7 | authors: 8 | - Rainer Leber (github.com/rainerleber) 9 | - Robert Kraemer (github.com/rkpobe) 10 | - Sean Freeman (github.com/sean-freeman) 11 | description: SAP Module community collection for Ansible 12 | license_file: LICENSE 13 | tags: 14 | - sap 15 | repository: https://github.com/sap-linuxlab/community.sap_libs 16 | documentation: https://github.com/sap-linuxlab/community.sap_libs 17 | homepage: https://github.com/sap-linuxlab/community.sap_libs 18 | issues: https://github.com/sap-linuxlab/community.sap_libs 19 | build_ignore: 20 | # https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#ignoring-files-and-folders 21 | - .gitignore 22 | - changelogs/.plugin-cache.yaml -------------------------------------------------------------------------------- /meta/runtime.yml: -------------------------------------------------------------------------------- 1 | --- 2 | requires_ansible: ">=2.9.10" 3 | 4 | plugin_routing: 5 | modules: 6 | hana_query: 7 | redirect: community.sap_libs.sap_hdbsql 8 | deprecation: 9 | removal_version: 2.0.0 10 | warning_text: Use community.sap_libs.sap_hdbsql instead. 11 | sapcontrol: 12 | redirect: community.sap_libs.sap_control_exec 13 | deprecation: 14 | removal_version: 2.0.0 15 | warning_text: Use community.sap_libs.sap_control_exec instead. 16 | -------------------------------------------------------------------------------- /plugins/doc_fragments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/plugins/doc_fragments/__init__.py -------------------------------------------------------------------------------- /plugins/module_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/plugins/module_utils/__init__.py -------------------------------------------------------------------------------- /plugins/module_utils/pyrfc_handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright: (c) 2022, Sean Freeman , 4 | # Rainer Leber 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from __future__ import (absolute_import, division, print_function) 16 | __metaclass__ = type 17 | 18 | from ansible.module_utils.basic import missing_required_lib 19 | 20 | import traceback 21 | 22 | PYRFC_LIBRARY_IMPORT_ERROR = None 23 | try: 24 | import pyrfc 25 | except ImportError: 26 | PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc() 27 | HAS_PYRFC_LIBRARY = False 28 | else: 29 | HAS_PYRFC_LIBRARY = True 30 | 31 | 32 | def get_connection(module, conn_params): 33 | if not HAS_PYRFC_LIBRARY: 34 | module.fail_json(msg=missing_required_lib( 35 | "pyrfc"), exception=PYRFC_LIBRARY_IMPORT_ERROR) 36 | 37 | module.warn('Connecting ... %s' % conn_params['ashost']) 38 | if "saprouter" in conn_params: 39 | module.warn("...via SAPRouter to SAP System") 40 | elif "gwhost" in conn_params: 41 | module.warn("...via Gateway to SAP System") 42 | else: 43 | module.warn("...direct to SAP System") 44 | 45 | conn = pyrfc.Connection(**conn_params) 46 | 47 | module.warn("Verifying connection is open/alive: %s" % conn.alive) 48 | return conn 49 | -------------------------------------------------------------------------------- /plugins/module_utils/swpm2_parameters_inifile_generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Sean Freeman , 5 | # Rainer Leber 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from __future__ import (absolute_import, division, print_function) 17 | __metaclass__ = type 18 | 19 | from ansible.module_utils.basic import missing_required_lib 20 | import traceback 21 | import sys 22 | import os 23 | 24 | 25 | BS4_LIBRARY_IMPORT_ERROR = None 26 | try: 27 | from bs4 import BeautifulSoup 28 | except ImportError: 29 | BS4_LIBRARY_IMPORT_ERROR = traceback.format_exc() 30 | HAS_BS4_LIBRARY = False 31 | else: 32 | HAS_BS4_LIBRARY = True 33 | 34 | LXML_LIBRARY_IMPORT_ERROR = None 35 | try: 36 | from lxml import etree 37 | except ImportError: 38 | LXML_LIBRARY_IMPORT_ERROR = traceback.format_exc() 39 | HAS_LXML_LIBRARY = False 40 | else: 41 | HAS_LXML_LIBRARY = True 42 | 43 | 44 | def debug_bs4(module): 45 | # Diagnose XML file parsing errors in Beautiful Soup 46 | # https://stackoverflow.com/questions/56942892/cannot-parse-iso-8859-15-encoded-xml-with-bs4/56947172#56947172 47 | if not HAS_BS4_LIBRARY: 48 | module.fail_json(msg=missing_required_lib( 49 | "bs4"), exception=BS4_LIBRARY_IMPORT_ERROR) 50 | from bs4.diagnose import diagnose 51 | with open('control.xml', 'rb') as f: 52 | diagnose(f) 53 | 54 | 55 | # SWPM2 control.xml conversion to utf8 56 | def control_xml_utf8(filepath, module): 57 | if not HAS_LXML_LIBRARY: 58 | module.fail_json(msg=missing_required_lib( 59 | "lxml"), exception=LXML_LIBRARY_IMPORT_ERROR) 60 | source = filepath + "/control.xml" 61 | 62 | # Convert control.xml from iso-8859-1 to UTF-8, so it can be used with Beautiful Soup lxml-xml parser 63 | # https://stackoverflow.com/questions/64629600/how-can-you-convert-a-xml-iso-8859-1-to-utf-8-using-python-3-7-7/64634454#64634454 64 | with open(source, 'rb') as source: 65 | parser = etree.XMLParser(encoding="iso-8859-1", strip_cdata=False) 66 | root = etree.parse(source, parser) 67 | 68 | string = etree.tostring(root, xml_declaration=True, encoding="UTF-8", 69 | pretty_print=True).decode('utf8').encode('iso-8859-1') 70 | 71 | # string1 = etree.tostring(root, xml_declaration=True, encoding="UTF-8", 72 | # pretty_print=True).decode('utf8').encode('utf-8').strip() 73 | 74 | with open('control_utf8.xml', 'wb') as target: 75 | target.write(string) 76 | 77 | 78 | # SWPM2 Component and Parameters extract all as CSV 79 | def control_xml_to_csv(filepath, module): 80 | if not HAS_BS4_LIBRARY: 81 | module.fail_json(msg=missing_required_lib( 82 | "bs4"), exception=BS4_LIBRARY_IMPORT_ERROR) 83 | 84 | infile = open(filepath + "/control_utf8.xml", "r") 85 | contents = infile.read() 86 | 87 | soup = BeautifulSoup(markup=contents, features='lxml-xml') 88 | space = soup.find('components') 89 | 90 | component_list = space.findChildren("component", recursive=False) 91 | 92 | csv_output = open('control_output.csv', 'w') 93 | csv_header = '"' + 'Component Name' + '","' + 'Component Display Name' + '","' + 'Parameter Name' + '","' + 'Parameter Inifile Key' + \ 94 | '","' + 'Parameter Access' + '","' + 'Parameter Encode' + '","' + \ 95 | 'Parameter Default Value' + '","' + 'Parameter Inifile description' + '"' 96 | csv_output.write("%s\n" % csv_header) 97 | 98 | for component in component_list: 99 | for parameter in component.findChildren("parameter"): 100 | component_key = parameter.findParent("component") 101 | component_key_name_text = component_key["name"] 102 | for child in component_key.findChildren("display-name"): 103 | component_key_display_name_text = child.get_text().replace('\n', '') 104 | component_parameter_key_name = parameter["name"] 105 | component_parameter_key_inifile_name = parameter.get( 106 | "defval-for-inifile-generation", "") 107 | component_parameter_key_access = parameter.get("access", "") 108 | component_parameter_key_encode = parameter.get("encode", "") 109 | component_parameter_key_defval = parameter.get("defval", "") 110 | component_parameter_contents_doclong_text = parameter.get_text().replace('\n', '') 111 | component_parameter_contents_doclong_text_quote_replacement = component_parameter_contents_doclong_text.replace( 112 | '"', '\'') 113 | csv_string = '"' + component_key_name_text + '","' + component_key_display_name_text + '","' + \ 114 | component_parameter_key_name + '","' + component_parameter_key_inifile_name + '","' + \ 115 | component_parameter_key_access + '","' + component_parameter_key_encode + '","' + \ 116 | component_parameter_key_defval + '","' + \ 117 | component_parameter_contents_doclong_text_quote_replacement + '"' 118 | csv_output.write("%s\n" % csv_string) 119 | 120 | csv_output.close() 121 | 122 | 123 | # SWPM2 Component and Parameters extract all and generate template inifile.params 124 | def control_xml_to_inifile_params(filepath, module): 125 | if not HAS_BS4_LIBRARY: 126 | module.fail_json(msg=missing_required_lib( 127 | "bs4"), exception=BS4_LIBRARY_IMPORT_ERROR) 128 | 129 | infile = open(filepath + "/control_utf8.xml", "r") 130 | contents = infile.read() 131 | 132 | soup = BeautifulSoup(markup=contents, features='lxml-xml') 133 | space = soup.find('components') 134 | 135 | component_list = space.findChildren("component", recursive=False) 136 | 137 | inifile_output = open('generated_inifile_params', 'w') 138 | 139 | inifile_params_header = """############ 140 | # SWPM Unattended Parameters inifile.params generated export 141 | # 142 | # 143 | # Export of all SWPM Component and the SWPM Unattended Parameters. Not all components have SWPM Unattended Parameters. 144 | # 145 | # All parameters are commented-out, each hash # before the parameter is removed to activate the parameter. 146 | # When running SWPM in Unattended Mode, the activated parameters will create a new SWPM file in the sapinst directory. 147 | # If any parameter is marked as 'encode', the plaintext value will be coverted to DES hash 148 | # for this parameter in the new SWPM file (in the sapinst directory). 149 | # 150 | # An inifile.params is otherwise obtained after running SWPM as GUI or Unattended install, 151 | # and will be generated for a specific Product ID (such as 'NW_ABAP_OneHost:S4HANA1809.CORE.HDB.CP'). 152 | ############ 153 | 154 | 155 | 156 | ############ 157 | # MANUAL 158 | ############ 159 | 160 | # The folder containing all archives that have been downloaded from http://support.sap.com/swdc and are supposed to be used in this procedure 161 | # archives.downloadBasket = 162 | """ 163 | 164 | inifile_output.write(inifile_params_header) 165 | 166 | for component in component_list: 167 | component_key_name_text = component["name"] 168 | component_key_display_name = component.find("display-name") 169 | if component_key_display_name is not None: 170 | component_key_display_name_text = component_key_display_name.get_text() 171 | inifile_output.write("\n\n\n\n############\n# Component: %s\n# Component Display Name: %s\n############\n" % ( 172 | component_key_name_text, component_key_display_name_text)) 173 | for parameter in component.findChildren("parameter"): 174 | # component_key=parameter.findParent("component") 175 | component_parameter_key_encode = parameter.get("encode", None) 176 | component_parameter_key_inifile_name = parameter.get( 177 | "defval-for-inifile-generation", None) 178 | component_parameter_key_defval = parameter.get("defval", "") 179 | component_parameter_contents_doclong_text = parameter.get_text().replace('\n', '') 180 | # component_parameter_contents_doclong_text_quote_replacement=component_parameter_contents_doclong_text.replace('"','\'') 181 | if component_parameter_key_inifile_name is not None: 182 | inifile_output.write("\n# %s" % ( 183 | component_parameter_contents_doclong_text)) 184 | if component_parameter_key_encode == "true": 185 | inifile_output.write( 186 | "\n# Encoded parameter. Plaintext values will be coverted to DES hash") 187 | inifile_output.write("\n# %s = %s\n" % ( 188 | component_parameter_key_inifile_name, component_parameter_key_defval)) 189 | 190 | inifile_output.close() 191 | 192 | # SWPM2 product.catalog conversion to utf8 193 | 194 | 195 | def product_catalog_xml_utf8(filepath, module): 196 | if not HAS_LXML_LIBRARY: 197 | module.fail_json(msg=missing_required_lib( 198 | "lxml"), exception=LXML_LIBRARY_IMPORT_ERROR) 199 | 200 | source = filepath + "/product.catalog" 201 | 202 | # Convert control.xml from iso-8859-1 to UTF-8, so it can be used with Beautiful Soup lxml-xml parser 203 | # https://stackoverflow.com/questions/64629600/how-can-you-convert-a-xml-iso-8859-1-to-utf-8-using-python-3-7-7/64634454#64634454 204 | with open(source, 'rb') as source: 205 | parser = etree.XMLParser(encoding="iso-8859-1", strip_cdata=False) 206 | root = etree.parse(source, parser) 207 | 208 | string = etree.tostring(root, xml_declaration=True, encoding="UTF-8", 209 | pretty_print=True).decode('utf8').encode('iso-8859-1') 210 | 211 | with open('product_catalog_utf8.xml', 'wb') as target: 212 | target.write(string) 213 | 214 | # SWPM2 Product Catalog entries to CSV 215 | # Each Product Catalog entry is part of a components group, which may have attributes: 216 | # output-dir, control-file, product-dir (link to SWPM directory of param file etc) 217 | # Attributes possible for each entry = control-file, db, id, name, os, os-type, output-dir, 218 | # ppms-component, ppms-component-release, product, product-dir, release, table 219 | 220 | 221 | def product_catalog_xml_to_csv(filepath, module): 222 | if not HAS_BS4_LIBRARY: 223 | module.fail_json(msg=missing_required_lib( 224 | "bs4"), exception=BS4_LIBRARY_IMPORT_ERROR) 225 | 226 | infile = open(filepath + "/product_catalog_utf8.xml", "r") 227 | contents = infile.read() 228 | 229 | soup = BeautifulSoup(markup=contents, features='lxml-xml') 230 | space = soup.find_all('component') 231 | 232 | csv_output = open('product_catalog_output.csv', 'w') 233 | csv_header = '"' + 'Product Catalog Component Name' + '","' + 'Product Catalog Component ID' + '","' + 'Product Catalog Component Table' + '","' + \ 234 | 'Product Catalog Component Output Dir' + '","' + 'Product Catalog Component Display Name' + \ 235 | '","' + 'Product Catalog Component UserInfo' + '"' 236 | csv_output.write("%s\n" % csv_header) 237 | 238 | for component in space: 239 | component_name = component.get("name", "") 240 | component_id = component.get("id", "") 241 | component_table = component.get("table", "") 242 | component_output_dir = component.get("output-dir", "") 243 | for displayname in component.findChildren("display-name"): 244 | component_displayname = displayname.get_text().strip() 245 | for userinfo in component.findChildren("user-info"): 246 | html_raw = userinfo.get_text().strip() 247 | html_parsed = BeautifulSoup(html_raw, 'html.parser') 248 | component_userinfo = html_parsed.get_text().replace('"', '\'') 249 | csv_string = '"' + component_name + '","' + component_id + '","' + component_table + '","' + \ 250 | component_output_dir + '","' + component_displayname + \ 251 | '","' + component_userinfo + '"' 252 | csv_output.write("%s\n" % csv_string) 253 | 254 | csv_output.close() 255 | 256 | 257 | # Get arguments passed to Python script session 258 | # Define path to control.xml, else assume in /tmp directory 259 | 260 | if len(sys.argv) > 1: 261 | control_xml_path = sys.argv[1] 262 | else: 263 | control_xml_path = "/tmp" 264 | 265 | if control_xml_path == "": 266 | control_xml_path = os.getcwd() 267 | 268 | if os.path.exists(control_xml_path + '/control.xml'): 269 | control_xml_utf8(control_xml_path, '') 270 | control_xml_to_csv(control_xml_path, '') 271 | control_xml_to_inifile_params(control_xml_path, '') 272 | -------------------------------------------------------------------------------- /plugins/modules/sap_company.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2021, Rainer Leber 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import (absolute_import, division, print_function) 15 | __metaclass__ = type 16 | 17 | DOCUMENTATION = r''' 18 | --- 19 | module: sap_company 20 | 21 | short_description: This module will manage a company entities in a SAP S4HANA environment 22 | 23 | version_added: "1.0.0" 24 | 25 | description: 26 | - The M(community.sap_libs.sap_user) module depends on C(pyrfc) Python library (version 2.4.0 and upwards). 27 | Depending on distribution you are using, you may need to install additional packages to 28 | have these available. 29 | - This module will use the company BAPIs C(BAPI_COMPANY_CLONE) and C(BAPI_COMPANY_DELETE) to manage company entities. 30 | 31 | options: 32 | state: 33 | description: 34 | - The decision what to do with the company. 35 | default: 'present' 36 | choices: 37 | - 'present' 38 | - 'absent' 39 | required: false 40 | type: str 41 | conn_username: 42 | description: The required username for the SAP system. 43 | required: true 44 | type: str 45 | conn_password: 46 | description: The required password for the SAP system. 47 | required: true 48 | type: str 49 | host: 50 | description: The required host for the SAP system. Can be either an FQDN or IP Address. 51 | required: true 52 | type: str 53 | sysnr: 54 | description: 55 | - The system number of the SAP system. 56 | - You must quote the value to ensure retaining the leading zeros. 57 | required: false 58 | default: '01' 59 | type: str 60 | client: 61 | description: 62 | - The client number to connect to. 63 | - You must quote the value to ensure retaining the leading zeros. 64 | required: false 65 | default: '000' 66 | type: str 67 | company_id: 68 | description: The company id. 69 | required: true 70 | type: str 71 | name: 72 | description: The company name. 73 | required: false 74 | type: str 75 | name_2: 76 | description: Additional company name. 77 | required: false 78 | type: str 79 | country: 80 | description: The country code for the company. For example, C('DE'). 81 | required: false 82 | type: str 83 | time_zone: 84 | description: The timezone. 85 | required: false 86 | type: str 87 | city: 88 | description: The city where the company is located. 89 | required: false 90 | type: str 91 | post_code: 92 | description: The post code from the city. 93 | required: false 94 | type: str 95 | street: 96 | description: Street where the company is located. 97 | required: false 98 | type: str 99 | street_no: 100 | description: Street number. 101 | required: false 102 | type: str 103 | e_mail: 104 | description: General E-Mail address. 105 | required: false 106 | type: str 107 | 108 | requirements: 109 | - pyrfc >= 2.4.0 110 | 111 | author: 112 | - Rainer Leber (@rainerleber) 113 | 114 | notes: 115 | - Does not support C(check_mode). 116 | ''' 117 | 118 | EXAMPLES = r''' 119 | - name: Create SAP Company 120 | community.sap_libs.sap_company: 121 | conn_username: 'DDIC' 122 | conn_password: 'HECtna2021#' 123 | host: 100.0.201.20 124 | sysnr: '01' 125 | client: '000' 126 | state: present 127 | company_id: "Comp_ID" 128 | name: "Test_comp" 129 | name_2: "LTD" 130 | country: "DE" 131 | time_zone: "UTC" 132 | city: "City" 133 | post_code: "12345" 134 | street: "test_street" 135 | street_no: "1" 136 | e_mail: "test@test.de" 137 | 138 | # pass in a message and have changed true 139 | - name: Delete SAP Company 140 | community.sap_libs.sap_company: 141 | conn_username: 'DDIC' 142 | conn_password: 'HECtna2021#' 143 | host: 100.0.201.20 144 | sysnr: '01' 145 | client: '000' 146 | state: absent 147 | company_id: "Comp_ID" 148 | name: "Test_comp" 149 | name_2: "LTD" 150 | country: "DE" 151 | time_zone: "UTC" 152 | city: "City" 153 | post_code: "12345" 154 | street: "test_street" 155 | street_no: "1" 156 | e_mail: "test@test.de" 157 | ''' 158 | 159 | RETURN = r''' 160 | # These are examples of possible return values, and in general should use other names for return values. 161 | msg: 162 | description: A small execution description. 163 | type: str 164 | returned: always 165 | sample: 'Company address COMP_ID created' 166 | out: 167 | description: A complete description of the executed tasks. If this is available. 168 | type: list 169 | elements: dict 170 | returned: always 171 | sample: '{ 172 | "RETURN": [ 173 | { 174 | "FIELD": "", 175 | "ID": "01", 176 | "LOG_MSG_NO": "000000", 177 | "LOG_NO": "", 178 | "MESSAGE": "Company address COMP_ID created", 179 | "MESSAGE_V1": "COMP_ID", 180 | "MESSAGE_V2": "", 181 | "MESSAGE_V3": "", 182 | "MESSAGE_V4": "", 183 | "NUMBER": "078", 184 | "PARAMETER": "", 185 | "ROW": 0, 186 | "SYSTEM": "", 187 | "TYPE": "S" 188 | } 189 | ] 190 | } 191 | }' 192 | ''' 193 | 194 | from ansible.module_utils.basic import AnsibleModule, missing_required_lib 195 | import traceback 196 | try: 197 | from pyrfc import Connection 198 | except ImportError: 199 | HAS_PYRFC_LIBRARY = False 200 | ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc() 201 | else: 202 | ANOTHER_LIBRARY_IMPORT_ERROR = None 203 | HAS_PYRFC_LIBRARY = True 204 | 205 | 206 | def call_rfc_method(connection, method_name, kwargs): 207 | # PyRFC call function 208 | return connection.call(method_name, **kwargs) 209 | 210 | 211 | def build_company_params(name, name_2, country, time_zone, city, post_code, street, street_no, e_mail): 212 | # Creates RFC parameters for creating organizations 213 | # define dicts in batch 214 | params = dict() 215 | # define company name 216 | params['NAME'] = name 217 | params['NAME_2'] = name_2 218 | # define location 219 | params['COUNTRY'] = country 220 | params['TIME_ZONE'] = time_zone 221 | params['CITY'] = city 222 | params['POSTL_COD1'] = post_code 223 | params['STREET'] = street 224 | params['STREET_NO'] = street_no 225 | # define communication 226 | params['E_MAIL'] = e_mail 227 | # return dict 228 | return params 229 | 230 | 231 | def return_analysis(raw): 232 | change = False 233 | failed = False 234 | msg = raw['RETURN'][0]['MESSAGE'] 235 | for state in raw['RETURN']: 236 | if state['TYPE'] == "E": 237 | if state['NUMBER'] == '081': 238 | change = False 239 | else: 240 | failed = True 241 | if state['TYPE'] == "S": 242 | if state['NUMBER'] != '079': 243 | change = True 244 | else: 245 | msg = "No changes where made." 246 | return [{"change": change}, {"failed": failed}, {"msg": msg}] 247 | 248 | 249 | def run_module(): 250 | module = AnsibleModule( 251 | argument_spec=dict( 252 | state=dict(default='present', choices=['absent', 'present']), 253 | conn_username=dict(type='str', required=True), 254 | conn_password=dict(type='str', required=True, no_log=True), 255 | host=dict(type='str', required=True), 256 | sysnr=dict(type='str', default="01"), 257 | client=dict(type='str', default="000"), 258 | company_id=dict(type='str', required=True), 259 | name=dict(type='str', required=False), 260 | name_2=dict(type='str', required=False), 261 | country=dict(type='str', required=False), 262 | time_zone=dict(type='str', required=False), 263 | city=dict(type='str', required=False), 264 | post_code=dict(type='str', required=False), 265 | street=dict(type='str', required=False), 266 | street_no=dict(type='str', required=False), 267 | e_mail=dict(type='str', required=False), 268 | ), 269 | supports_check_mode=False, 270 | ) 271 | result = dict(changed=False, msg='', out={}) 272 | raw = "" 273 | 274 | params = module.params 275 | 276 | state = params['state'] 277 | conn_username = (params['conn_username']).upper() 278 | conn_password = params['conn_password'] 279 | host = params['host'] 280 | sysnr = params['sysnr'] 281 | client = params['client'] 282 | 283 | company_id = (params['company_id']).upper() 284 | name = params['name'] 285 | name_2 = params['name_2'] 286 | country = params['country'] 287 | time_zone = params['time_zone'] 288 | city = params['city'] 289 | post_code = params['post_code'] 290 | street = params['street'] 291 | street_no = params['street_no'] 292 | e_mail = params['e_mail'] 293 | 294 | if not HAS_PYRFC_LIBRARY: 295 | module.fail_json( 296 | msg=missing_required_lib('pyrfc'), 297 | exception=ANOTHER_LIBRARY_IMPORT_ERROR) 298 | 299 | # basic RFC connection with pyrfc 300 | try: 301 | conn = Connection(user=conn_username, passwd=conn_password, ashost=host, sysnr=sysnr, client=client) 302 | except Exception as err: 303 | result['error'] = str(err) 304 | result['msg'] = 'Something went wrong connecting to the SAP system.' 305 | module.fail_json(**result) 306 | 307 | # build parameter dict of dict 308 | company_params = build_company_params(name, name_2, country, time_zone, city, post_code, street, street_no, e_mail) 309 | 310 | if state == "absent": 311 | raw = call_rfc_method(conn, 'BAPI_COMPANY_DELETE', {'COMPANY': company_id}) 312 | 313 | if state == "present": 314 | raw = call_rfc_method(conn, 'BAPI_COMPANY_CLONE', 315 | {'METHOD': {'USMETHOD': 'COMPANY_CLONE'}, 'COMPANY': company_id, 'COMP_DATA': company_params}) 316 | 317 | analysed = return_analysis(raw) 318 | 319 | result['out'] = raw 320 | 321 | result['changed'] = analysed[0]['change'] 322 | result['msg'] = analysed[2]['msg'] 323 | 324 | if analysed[1]['failed']: 325 | module.fail_json(**result) 326 | 327 | module.exit_json(**result) 328 | 329 | 330 | def main(): 331 | run_module() 332 | 333 | 334 | if __name__ == '__main__': 335 | main() 336 | -------------------------------------------------------------------------------- /plugins/modules/sap_hdbsql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2021, Rainer Leber 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import (absolute_import, division, print_function) 15 | __metaclass__ = type 16 | 17 | DOCUMENTATION = r''' 18 | --- 19 | module: sap_hdbsql 20 | short_description: Ansible Module to execute SQL on SAP HANA 21 | version_added: "1.0.0" 22 | description: This module executes SQL statements on HANA with hdbsql. 23 | options: 24 | sid: 25 | description: The system ID. 26 | type: str 27 | required: false 28 | bin_path: 29 | description: The path to the hdbsql binary. 30 | type: str 31 | required: false 32 | instance: 33 | description: The instance number. 34 | type: str 35 | required: true 36 | user: 37 | description: A dedicated username. The user could be also in hdbuserstore. 38 | type: str 39 | default: SYSTEM 40 | userstore: 41 | description: If C(true), the user must be in hdbuserstore. 42 | type: bool 43 | default: false 44 | password: 45 | description: 46 | - The password to connect to the database. 47 | - "B(Note:) Since the passwords have to be passed as command line arguments, I(userstore=true) should 48 | be used whenever possible, as command line arguments can be seen by other users 49 | on the same machine." 50 | type: str 51 | autocommit: 52 | description: Autocommit the statement. 53 | type: bool 54 | default: true 55 | host: 56 | description: The Host IP address. The port can be defined as well. 57 | type: str 58 | database: 59 | description: Define the database on which to connect. 60 | type: str 61 | encrypted: 62 | description: Use encrypted connection. 63 | type: bool 64 | default: false 65 | filepath: 66 | description: 67 | - One or more files each containing one SQL query to run. 68 | - Must be a string or list containing strings. 69 | type: list 70 | elements: path 71 | query: 72 | description: 73 | - SQL query to run. 74 | - 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. 75 | It is better to supply a one-element list instead to avoid mangled input. 76 | type: list 77 | elements: str 78 | notes: 79 | - Does not support C(check_mode). Always reports that the state has changed even if no changes have been made. 80 | author: 81 | - Rainer Leber (@rainerleber) 82 | ''' 83 | 84 | EXAMPLES = r''' 85 | - name: Simple select query 86 | community.sap_libs.sap_hdbsql: 87 | sid: "hdb" 88 | instance: "01" 89 | password: "Test123" 90 | query: select user_name from users 91 | 92 | - name: RUN select query with host port 93 | community.sap_libs.sap_hdbsql: 94 | sid: "hdb" 95 | instance: "01" 96 | password: "Test123" 97 | host: "10.10.2.4:30001" 98 | query: select user_name from users 99 | 100 | - name: Run several queries 101 | community.sap_libs.sap_hdbsql: 102 | sid: "hdb" 103 | instance: "01" 104 | password: "Test123" 105 | query: 106 | - select user_name from users 107 | - select * from SYSTEM 108 | host: "localhost" 109 | autocommit: False 110 | 111 | - name: Run several queries with path 112 | community.sap_libs.sap_hdbsql: 113 | bin_path: "/usr/sap/HDB/HDB01/exe/hdbsql" 114 | instance: "01" 115 | password: "Test123" 116 | query: 117 | - select user_name from users 118 | - select * from users 119 | host: "localhost" 120 | autocommit: False 121 | 122 | - name: Run several queries from file 123 | community.sap_libs.sap_hdbsql: 124 | sid: "hdb" 125 | instance: "01" 126 | password: "Test123" 127 | filepath: 128 | - /tmp/HANA_CPU_UtilizationPerCore_2.00.020+.txt 129 | - /tmp/HANA.txt 130 | host: "localhost" 131 | 132 | - name: Run several queries from user store 133 | community.sap_libs.sap_hdbsql: 134 | sid: "hdb" 135 | instance: "01" 136 | user: hdbstoreuser 137 | userstore: true 138 | query: 139 | - select user_name from users 140 | - select * from users 141 | autocommit: False 142 | ''' 143 | 144 | RETURN = r''' 145 | query_result: 146 | description: List containing results of all queries executed (one sublist for every query). 147 | returned: on success 148 | type: list 149 | elements: list 150 | sample: [[{"Column": "Value1"}, {"Column": "Value2"}], [{"Column": "Value1"}, {"Column": "Value2"}]] 151 | ''' 152 | 153 | import csv 154 | from ansible.module_utils.basic import AnsibleModule 155 | from ansible.module_utils.six import StringIO 156 | from ansible.module_utils.common.text.converters import to_native 157 | 158 | 159 | def csv_to_list(rawcsv): 160 | reader_raw = csv.DictReader(StringIO(rawcsv)) 161 | reader = [dict((k, v.strip()) for k, v in row.items()) for row in reader_raw] 162 | return list(reader) 163 | 164 | 165 | def main(): 166 | module = AnsibleModule( 167 | argument_spec=dict( 168 | sid=dict(type='str', required=False), 169 | bin_path=dict(type='str', required=False), 170 | instance=dict(type='str', required=True), 171 | encrypted=dict(type='bool', default=False), 172 | host=dict(type='str', required=False), 173 | user=dict(type='str', default="SYSTEM"), 174 | userstore=dict(type='bool', default=False), 175 | password=dict(type='str', no_log=True), 176 | database=dict(type='str', required=False), 177 | query=dict(type='list', elements='str', required=False), 178 | filepath=dict(type='list', elements='path', required=False), 179 | autocommit=dict(type='bool', default=True), 180 | ), 181 | required_one_of=[('query', 'filepath'), ('sid', 'instance')], 182 | required_if=[('userstore', False, ['password'])], 183 | supports_check_mode=False, 184 | ) 185 | rc, out, err, out_raw = [0, [], "", ""] 186 | 187 | params = module.params 188 | 189 | sid = params['sid'] 190 | bin_path = params['bin_path'] 191 | instance = params['instance'] 192 | user = params['user'] 193 | userstore = params['userstore'] 194 | password = params['password'] 195 | autocommit = params['autocommit'] 196 | host = params['host'] 197 | database = params['database'] 198 | encrypted = params['encrypted'] 199 | 200 | filepath = params['filepath'] 201 | query = params['query'] 202 | 203 | if bin_path is None: 204 | bin_path = "/usr/sap/{sid}/HDB{instance}/exe/hdbsql".format(sid=sid.upper(), instance=instance) 205 | 206 | try: 207 | command = [module.get_bin_path(bin_path, required=True)] 208 | except Exception as e: 209 | 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))) 210 | 211 | if encrypted is True: 212 | command.extend(['-attemptencrypt']) 213 | if autocommit is False: 214 | command.extend(['-z']) 215 | if host is not None: 216 | command.extend(['-n', host]) 217 | if database is not None: 218 | command.extend(['-d', database]) 219 | # -x Suppresses additional output, such as the number of selected rows in a result set. 220 | if userstore: 221 | command.extend(['-x', '-U', user]) 222 | else: 223 | command.extend(['-x', '-i', instance, '-u', user, '-p', password]) 224 | 225 | if filepath is not None: 226 | command.extend(['-E 3', '-I']) 227 | for p in filepath: 228 | # makes a command like hdbsql -i 01 -u SYSTEM -p secret123# -I /tmp/HANA_CPU_UtilizationPerCore_2.00.020+.txt, 229 | # iterates through files and append the output to var out. 230 | query_command = command + [p] 231 | (rc, out_raw, err) = module.run_command(query_command) 232 | out.append(csv_to_list(out_raw)) 233 | if query is not None: 234 | for q in query: 235 | # makes a command like hdbsql -i 01 -u SYSTEM -p secret123# "select user_name from users", 236 | # iterates through multiple commands and append the output to var out. 237 | query_command = command + [q] 238 | (rc, out_raw, err) = module.run_command(query_command) 239 | out.append(csv_to_list(out_raw)) 240 | changed = True 241 | 242 | module.exit_json(changed=changed, rc=rc, query_result=out, stderr=err) 243 | 244 | 245 | if __name__ == '__main__': 246 | main() 247 | -------------------------------------------------------------------------------- /plugins/modules/sap_pyrfc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Sean Freeman , 5 | # Rainer Leber 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | from __future__ import absolute_import, division, print_function 16 | __metaclass__ = type 17 | 18 | DOCUMENTATION = r''' 19 | --- 20 | module: sap_pyrfc 21 | 22 | short_description: Ansible Module for use of SAP PyRFC to execute SAP RFCs (Remote Function Calls) to SAP remote-enabled function modules 23 | 24 | version_added: "1.2.0" 25 | 26 | description: 27 | - This module will executes rfc calls on a sap system. 28 | - It is a generic approach to call rfc functions on a SAP System. 29 | - This module should be used where no module or role is provided. 30 | 31 | options: 32 | function: 33 | description: The SAP RFC function to call. 34 | required: true 35 | type: str 36 | parameters: 37 | description: The parameters which are needed by the function. 38 | required: true 39 | type: dict 40 | connection: 41 | description: The required connection details. 42 | required: true 43 | type: dict 44 | suboptions: 45 | ashost: 46 | description: The required host for the SAP system. Can be either an FQDN or IP Address. 47 | type: str 48 | required: true 49 | sysid: 50 | description: The systemid of the SAP system. 51 | type: str 52 | required: false 53 | sysnr: 54 | description: 55 | - The system number of the SAP system. 56 | - You must quote the value to ensure retaining the leading zeros. 57 | type: str 58 | required: true 59 | client: 60 | description: 61 | - The client number to connect to. 62 | - You must quote the value to ensure retaining the leading zeros. 63 | type: str 64 | required: true 65 | user: 66 | description: The required username for the SAP system. 67 | type: str 68 | required: true 69 | passwd: 70 | description: The required password for the SAP system. 71 | type: str 72 | required: true 73 | lang: 74 | description: The used language to execute. 75 | type: str 76 | required: false 77 | 78 | requirements: 79 | - pyrfc >= 2.4.0 80 | 81 | author: 82 | - Sean Freeman (@seanfreeman) 83 | - Rainer Leber (@rainerleber) 84 | ''' 85 | 86 | EXAMPLES = ''' 87 | - name: test the pyrfc module 88 | community.sap_libs.sap_pyrfc: 89 | function: STFC_CONNECTION 90 | parameters: 91 | REQUTEXT: "Hello SAP!" 92 | connection: 93 | ashost: s4hana.poc.cloud 94 | sysid: TDT 95 | sysnr: "01" 96 | client: "400" 97 | user: DDIC 98 | passwd: Password1 99 | lang: EN 100 | ''' 101 | 102 | RETURN = r''' 103 | result: 104 | description: The execution description. 105 | type: dict 106 | returned: always 107 | sample: {"ECHOTEXT": "Hello SAP!", 108 | "RESPTEXT": "SAP R/3 Rel. 756 Sysid: TST Date: 20220710 Time: 140717 Logon_Data: 000/DDIC/E"} 109 | ''' 110 | 111 | import traceback 112 | from ansible.module_utils.basic import AnsibleModule, missing_required_lib 113 | from ..module_utils.pyrfc_handler import get_connection 114 | 115 | try: 116 | from pyrfc import ABAPApplicationError, ABAPRuntimeError, CommunicationError, LogonError 117 | except ImportError: 118 | HAS_PYRFC_LIBRARY = False 119 | PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc() 120 | else: 121 | PYRFC_LIBRARY_IMPORT_ERROR = None 122 | HAS_PYRFC_LIBRARY = True 123 | 124 | 125 | def main(): 126 | msg = None 127 | params_spec = dict( 128 | ashost=dict(type='str', required=True), 129 | sysid=dict(type='str', required=False), 130 | sysnr=dict(type='str', required=True), 131 | client=dict(type='str', required=True), 132 | user=dict(type='str', required=True), 133 | passwd=dict(type='str', required=True, no_log=True), 134 | lang=dict(type='str', required=False), 135 | ) 136 | 137 | argument_spec = dict(function=dict(required=True, type='str'), 138 | parameters=dict(required=True, type='dict'), 139 | connection=dict( 140 | required=True, type='dict', options=params_spec), 141 | ) 142 | 143 | module = AnsibleModule( 144 | argument_spec=argument_spec, 145 | supports_check_mode=True, 146 | ) 147 | 148 | function = module.params.get('function') 149 | func_params = module.params.get('parameters') 150 | conn_params = module.params.get('connection') 151 | 152 | if not HAS_PYRFC_LIBRARY: 153 | module.fail_json( 154 | msg=missing_required_lib('pyrfc'), 155 | exception=PYRFC_LIBRARY_IMPORT_ERROR) 156 | 157 | # Check mode 158 | if module.check_mode: 159 | msg = "function: %s; params: %s; login: %s" % ( 160 | function, func_params, conn_params) 161 | module.exit_json(msg=msg, changed=True) 162 | 163 | try: 164 | conn = get_connection(module, conn_params) 165 | result = conn.call(function, **func_params) 166 | error_msg = None 167 | except CommunicationError as err: 168 | msg = "Could not connect to server" 169 | error_msg = err.message 170 | except LogonError as err: 171 | msg = "Could not log in" 172 | error_msg = err.message 173 | except (ABAPApplicationError, ABAPRuntimeError) as err: 174 | msg = "ABAP error occurred" 175 | error_msg = err.message 176 | except Exception as err: 177 | msg = "Something went wrong." 178 | error_msg = err 179 | else: 180 | module.exit_json(changed=True, result=result) 181 | 182 | if msg: 183 | module.fail_json(msg=msg, exception=error_msg) 184 | 185 | 186 | if __name__ == '__main__': 187 | main() 188 | -------------------------------------------------------------------------------- /plugins/modules/sap_snote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2021, Rainer Leber 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import (absolute_import, division, print_function) 15 | __metaclass__ = type 16 | 17 | DOCUMENTATION = r''' 18 | --- 19 | module: sap_snote 20 | 21 | short_description: This module will upload and (de)implements C(SNOTES) in a SAP S4HANA environment. 22 | 23 | version_added: "1.0.0" 24 | 25 | description: 26 | - The C(sap_snote) module depends on C(pyrfc) Python library (version 2.4.0 and upwards). 27 | Depending on distribution you are using, you may need to install additional packages to 28 | have these available. 29 | - This module will use the Function Group C(SCWB_API). 30 | - The C(TMS) must be configured at first. 31 | - Integrating SNOTES cannot be done via C(DDIC)- or C(SAP*)-User. 32 | options: 33 | state: 34 | description: 35 | - The decision what to do with the SNOTE. 36 | - Could be C('present'), C('absent') 37 | default: 'present' 38 | choices: 39 | - 'present' 40 | - 'absent' 41 | required: false 42 | type: str 43 | conn_username: 44 | description: The required username for the SAP system. 45 | required: true 46 | type: str 47 | conn_password: 48 | description: The required password for the SAP system. 49 | required: true 50 | type: str 51 | host: 52 | description: The required host for the SAP system. Can be either an FQDN or IP Address. 53 | required: true 54 | type: str 55 | sysnr: 56 | description: 57 | - The system number of the SAP system. 58 | - You must quote the value to ensure retaining the leading zeros. 59 | required: false 60 | default: '01' 61 | type: str 62 | client: 63 | description: 64 | - The client number to connect to. 65 | - You must quote the value to ensure retaining the leading zeros. 66 | required: false 67 | default: '000' 68 | type: str 69 | snote_path: 70 | description: 71 | - The path to the extracted SNOTE txt file. 72 | - The File could be extracted from SAR package. 73 | - If C(snote_path) is not provided, the C(snote) parameter must be defined. 74 | - The SNOTE txt file must be at a place where the SAP System is authorized for. For example C(/usr/sap/trans/files). 75 | required: false 76 | type: str 77 | snote: 78 | description: 79 | - With the C(snote) paramter only implementation and deimplementation will work. 80 | - Upload SNOTES to the System is only available if C(snote_path) is provided. 81 | required: false 82 | type: str 83 | 84 | requirements: 85 | - pyrfc >= 2.4.0 86 | 87 | author: 88 | - Rainer Leber (@rainerleber) 89 | ''' 90 | 91 | EXAMPLES = r''' 92 | - name: test snote module 93 | hosts: localhost 94 | tasks: 95 | - name: implement SNOTE 96 | community.sap_libs.sap_snote: 97 | conn_username: 'DDIC' 98 | conn_password: 'Passwd1234' 99 | host: 192.168.1.100 100 | sysnr: '01' 101 | client: '000' 102 | state: present 103 | snote_path: /usr/sap/trans/tmp/0002949148.txt 104 | 105 | - name: test snote module without path 106 | hosts: localhost 107 | tasks: 108 | - name: deimplement SNOTE 109 | community.sap_libs.sap_snote: 110 | conn_username: 'DDIC' 111 | conn_password: 'Passwd1234' 112 | host: 192.168.1.100 113 | sysnr: '01' 114 | client: '000' 115 | state: absent 116 | snote: 0002949148 117 | 118 | ''' 119 | 120 | RETURN = r''' 121 | msg: 122 | description: A small execution description. 123 | type: str 124 | returned: always 125 | sample: 'SNOTE 000298026 implemented.' 126 | out: 127 | description: A complete description of the SNOTE implementation. If this is available. 128 | type: list 129 | elements: dict 130 | returned: always 131 | sample: '{ 132 | "RETURN": [{"ES_MSG": { "MSGNO": "000", "MSGTY": "", "MSGTXT": "", "MSGV1": "" }, 133 | "ET_MSG": [], 134 | "EV_RC": 0, 135 | "ET_MISSING_NOTES": [], 136 | "IT_FILENAME": [{"FILENAME": "/usr/sap/trans/tmp/0002980265.txt"}], 137 | "IT_NOTES": [{"NUMM": "0002980265", "VERSNO": "0000"}] 138 | }]}' 139 | ''' 140 | 141 | from ansible.module_utils.basic import AnsibleModule, missing_required_lib 142 | from os import path as os_path 143 | import traceback 144 | try: 145 | from pyrfc import Connection 146 | except ImportError: 147 | HAS_PYRFC_LIBRARY = False 148 | ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc() 149 | else: 150 | ANOTHER_LIBRARY_IMPORT_ERROR = None 151 | HAS_PYRFC_LIBRARY = True 152 | 153 | 154 | def call_rfc_method(connection, method_name, kwargs): 155 | # PyRFC call function 156 | return connection.call(method_name, **kwargs) 157 | 158 | 159 | def check_implementation(conn, snote): 160 | check_implemented = call_rfc_method(conn, 'SCWB_API_GET_NOTES_IMPLEMENTED', {}) 161 | for snote_list in check_implemented['ET_NOTES_IMPL']: 162 | if snote in snote_list['NUMM']: 163 | return True 164 | return False 165 | 166 | 167 | def run_module(): 168 | module = AnsibleModule( 169 | argument_spec=dict( 170 | state=dict(default='present', choices=['absent', 'present']), 171 | conn_username=dict(type='str', required=True), 172 | conn_password=dict(type='str', required=True, no_log=True), 173 | host=dict(type='str', required=True), 174 | sysnr=dict(type='str', default="01"), 175 | client=dict(type='str', default="000"), 176 | snote_path=dict(type='str', required=False), 177 | snote=dict(type='str', required=False), 178 | ), 179 | required_one_of=[('snote_path', 'snote')], 180 | supports_check_mode=False, 181 | ) 182 | result = dict(changed=False, msg='', out={}, error='') 183 | raw = "" 184 | post_check = False 185 | 186 | params = module.params 187 | 188 | state = params['state'] 189 | conn_username = (params['conn_username']).upper() 190 | conn_password = params['conn_password'] 191 | host = params['host'] 192 | sysnr = (params['sysnr']).zfill(2) 193 | client = params['client'] 194 | 195 | path = params['snote_path'] 196 | snote = params['snote'] 197 | 198 | if not HAS_PYRFC_LIBRARY: 199 | module.fail_json( 200 | msg=missing_required_lib('pyrfc'), 201 | exception=ANOTHER_LIBRARY_IMPORT_ERROR) 202 | 203 | if conn_username == "DDIC" or conn_username == "SAP*": 204 | result['msg'] = 'User C(DDIC) or C(SAP*) not allowed for this operation.' 205 | module.fail_json(**result) 206 | 207 | # basic RFC connection with pyrfc 208 | try: 209 | conn = Connection(user=conn_username, passwd=conn_password, ashost=host, sysnr=sysnr, client=client) 210 | except Exception as err: 211 | result['error'] = str(err) 212 | result['msg'] = 'Something went wrong connecting to the SAP system.' 213 | module.fail_json(**result) 214 | 215 | # pre evaluation of parameters 216 | if path is not None: 217 | if path.endswith('.txt'): 218 | # splits snote number from path and txt extension 219 | snote = os_path.basename(os_path.normpath(path)).split('.')[0] 220 | else: 221 | result['msg'] = 'The path must include the extracted snote file and ends with txt.' 222 | module.fail_json(**result) 223 | 224 | pre_check = check_implementation(conn, snote) 225 | 226 | if state == "absent" and pre_check: 227 | raw = call_rfc_method(conn, 'SCWB_API_NOTES_DEIMPLEMENT', {'IT_NOTES': [snote]}) 228 | 229 | if state == "present" and not pre_check: 230 | if path: 231 | raw_upload = call_rfc_method(conn, 'SCWB_API_UPLOAD_NOTES', {'IT_FILENAME': [path], 'IT_NOTES': [snote]}) 232 | if raw_upload['EV_RC'] != 0: 233 | result['out'] = raw_upload 234 | result['msg'] = raw_upload['ES_MSG']['MSGTXT'] 235 | module.fail_json(**result) 236 | 237 | raw = call_rfc_method(conn, 'SCWB_API_NOTES_IMPLEMENT', {'IT_NOTES': [snote]}) 238 | queued = call_rfc_method(conn, 'SCWB_API_CINST_QUEUE_GET', {}) 239 | 240 | if queued['ET_MANUAL_ACTIVITIES']: 241 | raw = call_rfc_method(conn, 'SCWB_API_CONFIRM_MAN_ACTIVITY', {}) 242 | 243 | if raw: 244 | if raw['EV_RC'] == 0: 245 | post_check = check_implementation(conn, snote) 246 | if post_check and state == "present": 247 | result['changed'] = True 248 | result['msg'] = 'SNOTE "{0}" implemented.'.format(snote) 249 | if not post_check and state == "absent": 250 | result['changed'] = True 251 | result['msg'] = 'SNOTE "{0}" deimplemented.'.format(snote) 252 | else: 253 | result['msg'] = "Something went wrong." 254 | module.fail_json(**result) 255 | result['out'] = raw 256 | else: 257 | result['msg'] = "Nothing to do." 258 | 259 | module.exit_json(**result) 260 | 261 | 262 | def main(): 263 | run_module() 264 | 265 | 266 | if __name__ == '__main__': 267 | main() 268 | -------------------------------------------------------------------------------- /plugins/modules/sap_system_facts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright: (c) 2022, Rainer Leber rainerleber@gmail.com> 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | from __future__ import (absolute_import, division, print_function) 14 | __metaclass__ = type 15 | 16 | DOCUMENTATION = r''' 17 | --- 18 | module: sap_system_facts 19 | 20 | short_description: Gathers SAP facts in a host 21 | 22 | version_added: "1.0.0" 23 | 24 | description: 25 | - This facts module gathers SAP system facts about the running instance. 26 | 27 | author: 28 | - Rainer Leber (@rainerleber) 29 | 30 | notes: 31 | - Supports C(check_mode). 32 | ''' 33 | 34 | EXAMPLES = r''' 35 | - name: Return SAP system ansible_facts 36 | community.sap_libs.sap_system_facts: 37 | ''' 38 | 39 | RETURN = r''' 40 | # These are examples of possible return values, 41 | # and in general should use other names for return values. 42 | ansible_facts: 43 | description: Facts about the running SAP systems. 44 | returned: always 45 | type: dict 46 | contains: 47 | sap: 48 | description: Facts about the running SAP systems. 49 | type: list 50 | elements: dict 51 | returned: When SAP system fact is present 52 | sample: [ 53 | { 54 | "InstanceType": "NW", 55 | "NR": "00", 56 | "SID": "ABC", 57 | "TYPE": "ASCS" 58 | }, 59 | { 60 | "InstanceType": "NW", 61 | "NR": "01", 62 | "SID": "ABC", 63 | "TYPE": "PAS" 64 | }, 65 | { 66 | "InstanceType": "HANA", 67 | "NR": "02", 68 | "SID": "HDB", 69 | "TYPE": "HDB" 70 | }, 71 | { 72 | "InstanceType": "NW", 73 | "NR": "80", 74 | "SID": "WEB", 75 | "TYPE": "WebDisp" 76 | } 77 | ] 78 | ''' 79 | 80 | from ansible.module_utils.basic import AnsibleModule 81 | import os 82 | import re 83 | 84 | 85 | def get_all_hana_sid(): 86 | hana_sid = list() 87 | if os.path.isdir("/hana/shared"): 88 | # /hana/shared directory exists 89 | for sid in os.listdir('/hana/shared'): 90 | if os.path.isdir("/usr/sap/" + sid): 91 | hana_sid = hana_sid + [sid] 92 | if hana_sid: 93 | return hana_sid 94 | 95 | 96 | def get_all_nw_sid(): 97 | nw_sid = list() 98 | if os.path.isdir("/sapmnt"): 99 | # /sapmnt directory exists 100 | for sid in os.listdir('/sapmnt'): 101 | if os.path.isdir("/usr/sap/" + sid): 102 | nw_sid = nw_sid + [sid] 103 | else: 104 | # Check to see if /sapmnt/SID/sap_bobj exists 105 | if os.path.isdir("/sapmnt/" + sid + "/sap_bobj"): 106 | # is a bobj system 107 | nw_sid = nw_sid + [sid] 108 | if nw_sid: 109 | return nw_sid 110 | 111 | 112 | def get_hana_nr(sids, module): 113 | hana_list = list() 114 | for sid in sids: 115 | for instance in os.listdir('/usr/sap/' + sid): 116 | if 'HDB' in instance: 117 | instance_nr = instance[-2:] 118 | # check if instance number exists 119 | command = [module.get_bin_path('/usr/sap/hostctrl/exe/sapcontrol', required=True)] 120 | command.extend(['-nr', instance_nr, '-function', 'GetProcessList']) 121 | check_instance = module.run_command(command, check_rc=False) 122 | # sapcontrol returns c(0 - 5) exit codes only c(1) is unavailable 123 | if check_instance[0] != 1: 124 | hana_list.append({'NR': instance_nr, 'SID': sid, 'TYPE': 'HDB', 'InstanceType': 'HANA'}) 125 | return hana_list 126 | 127 | 128 | def get_nw_nr(sids, module): 129 | nw_list = list() 130 | type = "" 131 | for sid in sids: 132 | for instance in os.listdir('/usr/sap/' + sid): 133 | instance_nr = instance[-2:] 134 | command = [module.get_bin_path('/usr/sap/hostctrl/exe/sapcontrol', required=True)] 135 | # check if returned instance_nr is a number because sapcontrol returns all if a random string is provided 136 | if instance_nr.isdigit(): 137 | command.extend(['-nr', instance_nr, '-function', 'GetInstanceProperties']) 138 | check_instance = module.run_command(command, check_rc=False) 139 | if check_instance[0] != 1: 140 | for line in check_instance[1].splitlines(): 141 | if re.search('INSTANCE_NAME', line): 142 | # convert to list and extract last 143 | type_raw = (line.strip('][').split(', '))[-1] 144 | # split instance number 145 | type = type_raw[:-2] 146 | nw_list.append({'NR': instance_nr, 'SID': sid, 'TYPE': get_instance_type(type), 'InstanceType': 'NW'}) 147 | return nw_list 148 | 149 | 150 | def get_instance_type(raw_type): 151 | if raw_type[0] == "D": 152 | # It's a PAS 153 | type = "PAS" 154 | elif raw_type[0] == "A": 155 | # It's an ASCS 156 | type = "ASCS" 157 | elif raw_type[0] == "W": 158 | # It's a Webdisp 159 | type = "WebDisp" 160 | elif raw_type[0] == "J": 161 | # It's a Java 162 | type = "Java" 163 | elif raw_type[0] == "S": 164 | # It's an SCS 165 | type = "SCS" 166 | elif raw_type[0] == "E": 167 | # It's an ERS 168 | type = "ERS" 169 | else: 170 | # Unknown instance type 171 | type = "XXX" 172 | return type 173 | 174 | 175 | def run_module(): 176 | module_args = dict() 177 | system_result = list() 178 | 179 | result = dict( 180 | changed=False, 181 | ansible_facts=dict(), 182 | ) 183 | 184 | module = AnsibleModule( 185 | argument_spec=module_args, 186 | supports_check_mode=True, 187 | ) 188 | 189 | hana_sid = get_all_hana_sid() 190 | if hana_sid: 191 | system_result = system_result + get_hana_nr(hana_sid, module) 192 | 193 | nw_sid = get_all_nw_sid() 194 | if nw_sid: 195 | system_result = system_result + get_nw_nr(nw_sid, module) 196 | 197 | if system_result: 198 | result['ansible_facts'] = {'sap': system_result} 199 | else: 200 | result['ansible_facts'] 201 | 202 | if module.check_mode: 203 | module.exit_json(**result) 204 | 205 | module.exit_json(**result) 206 | 207 | 208 | def main(): 209 | run_module() 210 | 211 | 212 | if __name__ == '__main__': 213 | main() 214 | -------------------------------------------------------------------------------- /plugins/modules/sap_task_list_execute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2021, Rainer Leber 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import (absolute_import, division, print_function) 15 | __metaclass__ = type 16 | 17 | DOCUMENTATION = r''' 18 | --- 19 | module: sap_task_list_execute 20 | short_description: Perform SAP Task list execution 21 | version_added: "0.1.0" 22 | description: 23 | - The M(community.sap_libs.sap_task_list_execute) module depends on C(pyrfc) Python library (version 2.4.0 and upwards). 24 | Depending on distribution you are using, you may need to install additional packages to 25 | have these available. 26 | - Tasks in the task list which requires manual activities will be confirmed automatically. 27 | - This module will use the RFC package C(STC_TM_API). 28 | 29 | requirements: 30 | - pyrfc >= 2.4.0 31 | - xmltodict 32 | 33 | options: 34 | conn_username: 35 | description: The required username for the SAP system. 36 | required: true 37 | type: str 38 | conn_password: 39 | description: The required password for the SAP system. 40 | required: true 41 | type: str 42 | host: 43 | description: The required host for the SAP system. Can be either an FQDN or IP Address. 44 | required: true 45 | type: str 46 | sysnr: 47 | description: 48 | - The system number of the SAP system. 49 | - You must quote the value to ensure retaining the leading zeros. 50 | default: '00' 51 | type: str 52 | client: 53 | description: 54 | - The client number to connect to. 55 | - You must quote the value to ensure retaining the leading zeros. 56 | default: '000' 57 | type: str 58 | task_to_execute: 59 | description: The task list which will be executed. 60 | required: true 61 | type: str 62 | task_parameters: 63 | description: 64 | - The tasks and the parameters for execution. 65 | - If the task list does not need any parameters, this could be empty. 66 | - If only specific tasks from the task list should be executed, 67 | the tasks even when no parameter is needed must be provided 68 | alongside with the module parameter I(task_skip=true). 69 | type: list 70 | elements: dict 71 | suboptions: 72 | TASKNAME: 73 | description: The name of the task in the task list. 74 | type: str 75 | required: true 76 | FIELDNAME: 77 | description: The name of the field of the task. 78 | type: str 79 | VALUE: 80 | description: The value which have to be set. 81 | type: raw 82 | task_settings: 83 | description: 84 | - Setting for the execution of the task list. This can be the following as in TCODE SE80 described. 85 | Check Mode C(CHECKRUN), Background Processing Active C(BATCH) (this is the default value), 86 | Asynchronous Execution C(ASYNC), Trace Mode C(TRACE), Server Name C(BATCH_TARGET). 87 | default: ['BATCH'] 88 | type: list 89 | elements: str 90 | task_skip: 91 | description: 92 | - If this parameter is C(true), not defined tasks in I(task_parameters) are skipped. 93 | - This could be the case when only certain tasks should run from the task list. 94 | default: false 95 | type: bool 96 | 97 | notes: 98 | - Does not support C(check_mode). Always returns that the state has changed. 99 | author: 100 | - Rainer Leber (@rainerleber) 101 | ''' 102 | 103 | EXAMPLES = r''' 104 | # Pass in a message 105 | - name: Test task execution 106 | community.sap_libs.sap_task_list_execute: 107 | conn_username: DDIC 108 | conn_password: Passwd1234 109 | host: 10.1.8.10 110 | sysnr: '01' 111 | client: '000' 112 | task_to_execute: SAP_BASIS_SSL_CHECK 113 | task_settings: batch 114 | 115 | - name: Pass in input parameters 116 | community.sap_libs.sap_task_list_execute: 117 | conn_username: DDIC 118 | conn_password: Passwd1234 119 | host: 10.1.8.10 120 | sysnr: '00' 121 | client: '000' 122 | task_to_execute: SAP_BASIS_SSL_CHECK 123 | task_parameters : 124 | - { 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO', 'FIELDNAME': 'P_OPT2', 'VALUE': 'X' } 125 | - TASKNAME: CL_STCT_CHECK_SEC_CRYPTO 126 | FIELDNAME: P_OPT3 127 | VALUE: X 128 | task_settings: batch 129 | 130 | # Exported environment variables 131 | - name: Hint if module will fail with error message like ImportError libsapnwrfc.so... 132 | community.sap_libs.sap_task_list_execute: 133 | conn_username: DDIC 134 | conn_password: Passwd1234 135 | host: 10.1.8.10 136 | sysnr: '00' 137 | client: '000' 138 | task_to_execute: SAP_BASIS_SSL_CHECK 139 | task_settings: batch 140 | environment: 141 | SAPNWRFC_HOME: /usr/local/sap/nwrfcsdk 142 | LD_LIBRARY_PATH: /usr/local/sap/nwrfcsdk/lib 143 | ''' 144 | 145 | RETURN = r''' 146 | msg: 147 | description: A small execution description. 148 | type: str 149 | returned: always 150 | sample: 'Successful' 151 | out: 152 | description: A complete description of the executed tasks. If this is available. 153 | type: list 154 | elements: dict 155 | returned: on success 156 | sample: [...,{ 157 | "LOG": { 158 | "STCTM_S_LOG": [ 159 | { 160 | "ACTIVITY": "U_CONFIG", 161 | "ACTIVITY_DESCR": "Configuration changed", 162 | "DETAILS": null, 163 | "EXEC_ID": "20210728184903.815739", 164 | "FIELD": null, 165 | "ID": "STC_TASK", 166 | "LOG_MSG_NO": "000000", 167 | "LOG_NO": null, 168 | "MESSAGE": "For radiobutton group ICM too many options are set; choose only one option", 169 | "MESSAGE_V1": "ICM", 170 | "MESSAGE_V2": null, 171 | "MESSAGE_V3": null, 172 | "MESSAGE_V4": null, 173 | "NUMBER": "048", 174 | "PARAMETER": null, 175 | "PERIOD": "M", 176 | "PERIOD_DESCR": "Maintenance", 177 | "ROW": "0", 178 | "SRC_LINE": "170", 179 | "SRC_OBJECT": "CL_STCTM_REPORT_UI IF_STCTM_UI_TASK~SET_PARAMETERS", 180 | "SYSTEM": null, 181 | "TIMESTMP": "20210728184903", 182 | "TSTPNM": "DDIC", 183 | "TYPE": "E" 184 | },... 185 | ]}}] 186 | ''' 187 | 188 | from ansible.module_utils.basic import AnsibleModule, missing_required_lib 189 | import traceback 190 | try: 191 | from pyrfc import Connection 192 | except ImportError: 193 | HAS_PYRFC_LIBRARY = False 194 | PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc() 195 | else: 196 | PYRFC_LIBRARY_IMPORT_ERROR = None 197 | HAS_PYRFC_LIBRARY = True 198 | try: 199 | import xmltodict 200 | except ImportError: 201 | HAS_XMLTODICT_LIBRARY = False 202 | XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc() 203 | else: 204 | XMLTODICT_LIBRARY_IMPORT_ERROR = None 205 | HAS_XMLTODICT_LIBRARY = True 206 | 207 | 208 | def call_rfc_method(connection, method_name, kwargs): 209 | # PyRFC call function 210 | return connection.call(method_name, **kwargs) 211 | 212 | 213 | def process_exec_settings(task_settings): 214 | # processes task settings to objects 215 | exec_settings = {} 216 | for settings in task_settings: 217 | temp_dict = {settings.upper(): 'X'} 218 | for key, value in temp_dict.items(): 219 | exec_settings[key] = value 220 | return exec_settings 221 | 222 | 223 | def xml_to_dict(xml_raw): 224 | try: 225 | xml_parsed = xmltodict.parse(xml_raw, dict_constructor=dict) 226 | xml_dict = xml_parsed['asx:abap']['asx:values']['SESSION']['TASKLIST'] 227 | except KeyError: 228 | xml_dict = "No logs available." 229 | return xml_dict 230 | 231 | 232 | def run_module(): 233 | 234 | params_spec = dict( 235 | TASKNAME=dict(type='str', required=True), 236 | FIELDNAME=dict(type='str'), 237 | VALUE=dict(type='raw'), 238 | ) 239 | 240 | # define available arguments/parameters a user can pass to the module 241 | module = AnsibleModule( 242 | argument_spec=dict( 243 | # values for connection 244 | conn_username=dict(type='str', required=True), 245 | conn_password=dict(type='str', required=True, no_log=True), 246 | host=dict(type='str', required=True), 247 | sysnr=dict(type='str', default="00"), 248 | client=dict(type='str', default="000"), 249 | # values for execution tasks 250 | task_to_execute=dict(type='str', required=True), 251 | task_parameters=dict(type='list', elements='dict', options=params_spec), 252 | task_settings=dict(type='list', elements='str', default=['BATCH']), 253 | task_skip=dict(type='bool', default=False), 254 | ), 255 | supports_check_mode=False, 256 | ) 257 | result = dict(changed=False, msg='', out={}) 258 | 259 | params = module.params 260 | 261 | username = params['conn_username'].upper() 262 | password = params['conn_password'] 263 | host = params['host'] 264 | sysnr = params['sysnr'] 265 | client = params['client'] 266 | 267 | task_parameters = params['task_parameters'] 268 | task_to_execute = params['task_to_execute'] 269 | task_settings = params['task_settings'] 270 | task_skip = params['task_skip'] 271 | 272 | if not HAS_PYRFC_LIBRARY: 273 | module.fail_json( 274 | msg=missing_required_lib('pyrfc'), 275 | exception=PYRFC_LIBRARY_IMPORT_ERROR) 276 | 277 | if not HAS_XMLTODICT_LIBRARY: 278 | module.fail_json( 279 | msg=missing_required_lib('xmltodict'), 280 | exception=XMLTODICT_LIBRARY_IMPORT_ERROR) 281 | 282 | # basic RFC connection with pyrfc 283 | try: 284 | conn = Connection(user=username, passwd=password, ashost=host, sysnr=sysnr, client=client) 285 | except Exception as err: 286 | result['error'] = str(err) 287 | result['msg'] = 'Something went wrong connecting to the SAP system.' 288 | module.fail_json(**result) 289 | 290 | try: 291 | raw_params = call_rfc_method(conn, 'STC_TM_SCENARIO_GET_PARAMETERS', 292 | {'I_SCENARIO_ID': task_to_execute}) 293 | except Exception as err: 294 | result['error'] = str(err) 295 | result['msg'] = 'The task list does not exist.' 296 | module.fail_json(**result) 297 | exec_settings = process_exec_settings(task_settings) 298 | # initialize session task 299 | session_init = call_rfc_method(conn, 'STC_TM_SESSION_BEGIN', 300 | {'I_SCENARIO_ID': task_to_execute, 301 | 'I_INIT_ONLY': 'X'}) 302 | # Confirm Tasks which requires manual activities from Task List Run 303 | for task in raw_params['ET_PARAMETER']: 304 | call_rfc_method(conn, 'STC_TM_TASK_CONFIRM', 305 | {'I_SESSION_ID': session_init['E_SESSION_ID'], 306 | 'I_TASKNAME': task['TASKNAME']}) 307 | if task_skip: 308 | for task in raw_params['ET_PARAMETER']: 309 | call_rfc_method(conn, 'STC_TM_TASK_SKIP', 310 | {'I_SESSION_ID': session_init['E_SESSION_ID'], 311 | 'I_TASKNAME': task['TASKNAME'], 'I_SKIP_DEP_TASKS': 'X'}) 312 | # unskip defined tasks and set parameters 313 | if task_parameters is not None: 314 | for task in task_parameters: 315 | call_rfc_method(conn, 'STC_TM_TASK_UNSKIP', 316 | {'I_SESSION_ID': session_init['E_SESSION_ID'], 317 | 'I_TASKNAME': task['TASKNAME'], 'I_UNSKIP_DEP_TASKS': 'X'}) 318 | 319 | call_rfc_method(conn, 'STC_TM_SESSION_SET_PARAMETERS', 320 | {'I_SESSION_ID': session_init['E_SESSION_ID'], 321 | 'IT_PARAMETER': task_parameters}) 322 | # start the task 323 | try: 324 | session_start = call_rfc_method(conn, 'STC_TM_SESSION_RESUME', 325 | {'I_SESSION_ID': session_init['E_SESSION_ID'], 326 | 'IS_EXEC_SETTINGS': exec_settings}) 327 | except Exception as err: 328 | result['error'] = str(err) 329 | result['msg'] = 'Something went wrong. See error.' 330 | module.fail_json(**result) 331 | # get task logs because the execution may successfully but the tasks shows errors or warnings 332 | # returned value is ABAPXML https://help.sap.com/doc/abapdocu_755_index_htm/7.55/en-US/abenabap_xslt_asxml_general.htm 333 | session_log = call_rfc_method(conn, 'STC_TM_SESSION_GET_LOG', 334 | {'I_SESSION_ID': session_init['E_SESSION_ID']}) 335 | 336 | task_list = xml_to_dict(session_log['E_LOG']) 337 | 338 | result['changed'] = True 339 | result['msg'] = session_start['E_STATUS_DESCR'] 340 | result['out'] = task_list 341 | 342 | module.exit_json(**result) 343 | 344 | 345 | def main(): 346 | run_module() 347 | 348 | 349 | if __name__ == '__main__': 350 | main() 351 | -------------------------------------------------------------------------------- /plugins/modules/sapcar_extract.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2021, Rainer Leber 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import (absolute_import, division, print_function) 15 | __metaclass__ = type 16 | 17 | DOCUMENTATION = r''' 18 | --- 19 | module: sapcar_extract 20 | short_description: Manages SAP SAPCAR archives 21 | version_added: "1.0.0" 22 | description: 23 | - Provides support for unpacking C(sar)/C(car) files with the SAPCAR binary from SAP and pulling 24 | information back into Ansible. 25 | options: 26 | path: 27 | description: The path to the SAR/CAR file. 28 | type: path 29 | required: true 30 | dest: 31 | description: 32 | - The destination where SAPCAR extracts the SAR file. Missing folders will be created. 33 | If this parameter is not provided, it will unpack in the same folder as the SAR file. 34 | type: path 35 | binary_path: 36 | description: 37 | - The path to the SAPCAR binary, for example, C(/home/dummy/sapcar) or C(https://myserver/SAPCAR). 38 | If this parameter is not provided, the module will look in C(PATH). 39 | type: path 40 | signature: 41 | description: 42 | - If C(true), the signature will be extracted. 43 | default: false 44 | type: bool 45 | security_library: 46 | description: 47 | - The path to the security library, for example, C(/usr/sap/hostctrl/exe/libsapcrytp.so), for signature operations. 48 | type: path 49 | manifest: 50 | description: 51 | - The name of the manifest. 52 | default: "SIGNATURE.SMF" 53 | type: str 54 | remove: 55 | description: 56 | - If C(true), the SAR/CAR file will be removed. B(This should be used with caution!) 57 | default: false 58 | type: bool 59 | author: 60 | - Rainer Leber (@RainerLeber) 61 | notes: 62 | - Always returns C(changed=true) in C(check_mode). 63 | ''' 64 | 65 | EXAMPLES = r""" 66 | - name: Extract SAR file 67 | community.sap_libs.sapcar_extract: 68 | path: "~/source/hana.sar" 69 | 70 | - name: Extract SAR file with destination 71 | community.sap_libs.sapcar_extract: 72 | path: "~/source/hana.sar" 73 | dest: "~/test/" 74 | 75 | - name: Extract SAR file with destination and download from webserver can be a fileshare as well 76 | community.sap_libs.sapcar_extract: 77 | path: "~/source/hana.sar" 78 | dest: "~/dest/" 79 | binary_path: "https://myserver/SAPCAR" 80 | 81 | - name: Extract SAR file and delete SAR after extract 82 | community.sap_libs.sapcar_extract: 83 | path: "~/source/hana.sar" 84 | remove: true 85 | 86 | - name: Extract SAR file with manifest 87 | community.sap_libs.sapcar_extract: 88 | path: "~/source/hana.sar" 89 | signature: true 90 | 91 | - name: Extract SAR file with manifest and rename it 92 | community.sap_libs.sapcar_extract: 93 | path: "~/source/hana.sar" 94 | manifest: "MyNewSignature.SMF" 95 | signature: true 96 | """ 97 | 98 | import os 99 | from tempfile import NamedTemporaryFile 100 | from ansible.module_utils.basic import AnsibleModule 101 | from ansible.module_utils.urls import open_url 102 | from ansible.module_utils.common.text.converters import to_native 103 | 104 | 105 | def get_list_of_files(dir_name): 106 | # create a list of file and directories 107 | # names in the given directory 108 | list_of_file = os.listdir(dir_name) 109 | allFiles = list() 110 | # Iterate over all the entries 111 | for entry in list_of_file: 112 | # Create full path 113 | fullPath = os.path.join(dir_name, entry) 114 | # If entry is a directory then get the list of files in this directory 115 | if os.path.isdir(fullPath): 116 | allFiles = allFiles + [fullPath] 117 | allFiles = allFiles + get_list_of_files(fullPath) 118 | else: 119 | allFiles.append(fullPath) 120 | return allFiles 121 | 122 | 123 | def download_SAPCAR(binary_path, module): 124 | bin_path = None 125 | # download sapcar binary if url is provided otherwise path is returned 126 | if binary_path is not None: 127 | if binary_path.startswith('https://') or binary_path.startswith('http://'): 128 | random_file = NamedTemporaryFile(delete=False) 129 | with open_url(binary_path) as response: 130 | with random_file as out_file: 131 | data = response.read() 132 | out_file.write(data) 133 | os.chmod(out_file.name, 0o700) 134 | bin_path = out_file.name 135 | module.add_cleanup_file(bin_path) 136 | else: 137 | bin_path = binary_path 138 | return bin_path 139 | 140 | 141 | def check_if_present(command, path, dest, signature, manifest, module): 142 | # manipulating output from SAR file for compare with already extracted files 143 | iter_command = [command, '-tvf', path] 144 | sar_out = module.run_command(iter_command)[1] 145 | sar_raw = sar_out.split("\n")[1:] 146 | if dest[-1] != "/": 147 | dest = dest + "/" 148 | sar_files = [dest + x.split(" ")[-1] for x in sar_raw if x] 149 | # remove any SIGNATURE.SMF from list because it will not unpacked if signature is false 150 | if not signature: 151 | sar_files = [item for item in sar_files if not item.endswith('.SMF')] 152 | # if signature is renamed manipulate files in list of sar file for compare. 153 | if manifest != "SIGNATURE.SMF": 154 | sar_files = [item for item in sar_files if not item.endswith('.SMF')] 155 | sar_files = sar_files + [manifest] 156 | # get extracted files if present 157 | files_extracted = get_list_of_files(dest) 158 | # compare extracted files with files in sar file 159 | present = all(elem in files_extracted for elem in sar_files) 160 | return present 161 | 162 | 163 | def main(): 164 | module = AnsibleModule( 165 | argument_spec=dict( 166 | path=dict(type='path', required=True), 167 | dest=dict(type='path'), 168 | binary_path=dict(type='path'), 169 | signature=dict(type='bool', default=False), 170 | security_library=dict(type='path'), 171 | manifest=dict(type='str', default="SIGNATURE.SMF"), 172 | remove=dict(type='bool', default=False), 173 | ), 174 | supports_check_mode=True, 175 | ) 176 | rc, out, err = [0, "", ""] 177 | params = module.params 178 | check_mode = module.check_mode 179 | 180 | path = params['path'] 181 | dest = params['dest'] 182 | signature = params['signature'] 183 | security_library = params['security_library'] 184 | manifest = params['manifest'] 185 | remove = params['remove'] 186 | 187 | bin_path = download_SAPCAR(params['binary_path'], module) 188 | 189 | if dest is None: 190 | dest_head_tail = os.path.split(path) 191 | dest = dest_head_tail[0] + '/' 192 | else: 193 | if not os.path.exists(dest): 194 | os.makedirs(dest, 0o755) 195 | 196 | if bin_path is not None: 197 | command = [module.get_bin_path(bin_path, required=True)] 198 | else: 199 | try: 200 | command = [module.get_bin_path('sapcar', required=True)] 201 | except Exception as e: 202 | module.fail_json(msg='Failed to find SAPCAR at the expected path or URL "{0}". Please check whether it is available: {1}' 203 | .format(bin_path, to_native(e))) 204 | 205 | present = check_if_present(command[0], path, dest, signature, manifest, module) 206 | 207 | if not present: 208 | command.extend(['-xvf', path, '-R', dest]) 209 | if security_library: 210 | command.extend(['-L', security_library]) 211 | if signature: 212 | command.extend(['-manifest', manifest]) 213 | if not check_mode: 214 | (rc, out, err) = module.run_command(command, check_rc=True) 215 | changed = True 216 | else: 217 | changed = False 218 | out = "already unpacked" 219 | 220 | if remove: 221 | os.remove(path) 222 | 223 | module.exit_json(changed=changed, message=rc, stdout=out, 224 | stderr=err, command=' '.join(command)) 225 | 226 | 227 | if __name__ == '__main__': 228 | main() 229 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/targets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/tests/integration/targets/__init__.py -------------------------------------------------------------------------------- /tests/sanity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/tests/sanity/__init__.py -------------------------------------------------------------------------------- /tests/sanity/ignore-2.10.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 10 | -------------------------------------------------------------------------------- /tests/sanity/ignore-2.11.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 10 | -------------------------------------------------------------------------------- /tests/sanity/ignore-2.12.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 -------------------------------------------------------------------------------- /tests/sanity/ignore-2.13.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 -------------------------------------------------------------------------------- /tests/sanity/ignore-2.14.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 -------------------------------------------------------------------------------- /tests/sanity/ignore-2.15.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 -------------------------------------------------------------------------------- /tests/sanity/ignore-2.16.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 -------------------------------------------------------------------------------- /tests/sanity/ignore-2.17.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 10 | tests/unit/compat/mock.py pylint:use-yield-from # suggested construct does not work with Python 2 -------------------------------------------------------------------------------- /tests/sanity/ignore-2.18.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 10 | tests/unit/compat/mock.py pylint:use-yield-from # suggested construct does not work with Python 2 -------------------------------------------------------------------------------- /tests/sanity/ignore-2.9.txt: -------------------------------------------------------------------------------- 1 | plugins/modules/sap_pyrfc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 2 | plugins/modules/sap_company.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 3 | plugins/modules/sap_control_exec.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 4 | plugins/modules/sap_hdbsql.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 5 | plugins/modules/sap_snote.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 6 | plugins/modules/sap_system_facts.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 7 | plugins/modules/sap_task_list_execute.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 8 | plugins/modules/sap_user.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 9 | plugins/modules/sapcar_extract.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 10 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/compat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/tests/unit/compat/__init__.py -------------------------------------------------------------------------------- /tests/unit/compat/builtins.py: -------------------------------------------------------------------------------- 1 | # (c) 2014, Toshio Kuratomi 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | # Make coding more python3-ish 19 | from __future__ import (absolute_import, division, print_function) 20 | __metaclass__ = type 21 | 22 | # 23 | # Compat for python2.7 24 | # 25 | -------------------------------------------------------------------------------- /tests/unit/compat/mock.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, Toshio Kuratomi 2 | # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | # Make coding more python3-ish 6 | from __future__ import (absolute_import, division, print_function) 7 | __metaclass__ = type 8 | 9 | ''' 10 | Compat module for Python3.x's unittest.mock module 11 | ''' 12 | import sys 13 | 14 | # Python 2.7 15 | 16 | # Note: Could use the pypi mock library on python3.x as well as python2.x. It 17 | # is the same as the python3 stdlib mock library 18 | 19 | try: 20 | # Allow wildcard import because we really do want to import all of mock's 21 | # symbols into this compat shim 22 | # pylint: disable=wildcard-import,unused-wildcard-import 23 | from unittest.mock import * # noqa: F401, pylint: disable=unused-import 24 | except ImportError: 25 | # Python 2 26 | # pylint: disable=wildcard-import,unused-wildcard-import 27 | try: 28 | from mock import * # noqa: F401, pylint: disable=unused-import 29 | except ImportError: 30 | print('You need the mock library installed on python2.x to run tests') 31 | 32 | 33 | # Prior to 3.4.4, mock_open cannot handle binary read_data 34 | if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): 35 | file_spec = None 36 | 37 | def _iterate_read_data(read_data): 38 | # Helper for mock_open: 39 | # Retrieve lines from read_data via a generator so that separate calls to 40 | # readline, read, and readlines are properly interleaved 41 | sep = b'\n' if isinstance(read_data, bytes) else '\n' 42 | data_as_list = [l + sep for l in read_data.split(sep)] 43 | 44 | if data_as_list[-1] == sep: 45 | # If the last line ended in a newline, the list comprehension will have an 46 | # extra entry that's just a newline. Remove this. 47 | data_as_list = data_as_list[:-1] 48 | else: 49 | # If there wasn't an extra newline by itself, then the file being 50 | # emulated doesn't have a newline to end the last line remove the 51 | # newline that our naive format() added 52 | data_as_list[-1] = data_as_list[-1][:-1] 53 | 54 | for line in data_as_list: 55 | yield line 56 | 57 | def mock_open(mock=None, read_data=''): 58 | """ 59 | A helper function to create a mock to replace the use of `open`. It works 60 | for `open` called directly or used as a context manager. 61 | 62 | The `mock` argument is the mock object to configure. If `None` (the 63 | default) then a `MagicMock` will be created for you, with the API limited 64 | to methods or attributes available on standard file handles. 65 | 66 | `read_data` is a string for the `read` methoddline`, and `readlines` of the 67 | file handle to return. This is an empty string by default. 68 | """ 69 | def _readlines_side_effect(*args, **kwargs): 70 | if handle.readlines.return_value is not None: 71 | return handle.readlines.return_value 72 | return list(_data) 73 | 74 | def _read_side_effect(*args, **kwargs): 75 | if handle.read.return_value is not None: 76 | return handle.read.return_value 77 | return type(read_data)().join(_data) 78 | 79 | def _readline_side_effect(): 80 | if handle.readline.return_value is not None: 81 | while True: 82 | yield handle.readline.return_value 83 | for line in _data: 84 | yield line 85 | 86 | global file_spec 87 | if file_spec is None: 88 | import _io 89 | file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) 90 | 91 | if mock is None: 92 | mock = MagicMock(name='open', spec=open) 93 | 94 | handle = MagicMock(spec=file_spec) 95 | handle.__enter__.return_value = handle 96 | 97 | _data = _iterate_read_data(read_data) 98 | 99 | handle.write.return_value = None 100 | handle.read.return_value = None 101 | handle.readline.return_value = None 102 | handle.readlines.return_value = None 103 | 104 | handle.read.side_effect = _read_side_effect 105 | handle.readline.side_effect = _readline_side_effect() 106 | handle.readlines.side_effect = _readlines_side_effect 107 | 108 | mock.return_value = handle 109 | return mock 110 | -------------------------------------------------------------------------------- /tests/unit/compat/unittest.py: -------------------------------------------------------------------------------- 1 | # (c) 2014, Toshio Kuratomi 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | # Make coding more python3-ish 19 | from __future__ import (absolute_import, division, print_function) 20 | __metaclass__ = type 21 | 22 | ''' 23 | Compat module for Python2.7's unittest module 24 | ''' 25 | 26 | import sys 27 | 28 | # Allow wildcard import because we really do want to import all of 29 | # unittests's symbols into this compat shim 30 | # pylint: disable=wildcard-import,unused-wildcard-import 31 | if sys.version_info < (2, 7): 32 | try: 33 | # Need unittest2 on python2.6 34 | from unittest2 import * 35 | except ImportError: 36 | print('You need unittest2 installed on python2.6.x to run tests') 37 | else: 38 | from unittest import * 39 | -------------------------------------------------------------------------------- /tests/unit/mock/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/tests/unit/mock/__init__.py -------------------------------------------------------------------------------- /tests/unit/mock/loader.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2014, Michael DeHaan 2 | # 3 | # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | from __future__ import (absolute_import, division, print_function) 7 | __metaclass__ = type 8 | 9 | import os 10 | 11 | from ansible.errors import AnsibleParserError 12 | from ansible.parsing.dataloader import DataLoader 13 | from ansible.module_utils.common.text.converters import to_bytes, to_text 14 | 15 | 16 | class DictDataLoader(DataLoader): 17 | 18 | def __init__(self, file_mapping=None): 19 | file_mapping = {} if file_mapping is None else file_mapping 20 | assert isinstance(file_mapping, dict) 21 | 22 | super(DictDataLoader, self).__init__() 23 | 24 | self._file_mapping = file_mapping 25 | self._build_known_directories() 26 | self._vault_secrets = None 27 | 28 | def load_from_file(self, path, cache=True, unsafe=False): 29 | path = to_text(path) 30 | if path in self._file_mapping: 31 | return self.load(self._file_mapping[path], path) 32 | return None 33 | 34 | # TODO: the real _get_file_contents returns a bytestring, so we actually convert the 35 | # unicode/text it's created with to utf-8 36 | def _get_file_contents(self, file_name): 37 | path = to_text(file_name) 38 | if path in self._file_mapping: 39 | return (to_bytes(self._file_mapping[path]), False) 40 | else: 41 | raise AnsibleParserError("file not found: %s" % path) 42 | 43 | def path_exists(self, path): 44 | path = to_text(path) 45 | return path in self._file_mapping or path in self._known_directories 46 | 47 | def is_file(self, path): 48 | path = to_text(path) 49 | return path in self._file_mapping 50 | 51 | def is_directory(self, path): 52 | path = to_text(path) 53 | return path in self._known_directories 54 | 55 | def list_directory(self, path): 56 | ret = [] 57 | path = to_text(path) 58 | for x in (list(self._file_mapping.keys()) + self._known_directories): 59 | if x.startswith(path): 60 | if os.path.dirname(x) == path: 61 | ret.append(os.path.basename(x)) 62 | return ret 63 | 64 | def is_executable(self, path): 65 | # FIXME: figure out a way to make paths return true for this 66 | return False 67 | 68 | def _add_known_directory(self, directory): 69 | if directory not in self._known_directories: 70 | self._known_directories.append(directory) 71 | 72 | def _build_known_directories(self): 73 | self._known_directories = [] 74 | for path in self._file_mapping: 75 | dirname = os.path.dirname(path) 76 | while dirname not in ('/', ''): 77 | self._add_known_directory(dirname) 78 | dirname = os.path.dirname(dirname) 79 | 80 | def push(self, path, content): 81 | rebuild_dirs = False 82 | if path not in self._file_mapping: 83 | rebuild_dirs = True 84 | 85 | self._file_mapping[path] = content 86 | 87 | if rebuild_dirs: 88 | self._build_known_directories() 89 | 90 | def pop(self, path): 91 | if path in self._file_mapping: 92 | del self._file_mapping[path] 93 | self._build_known_directories() 94 | 95 | def clear(self): 96 | self._file_mapping = dict() 97 | self._known_directories = [] 98 | 99 | def get_basedir(self): 100 | return os.getcwd() 101 | 102 | def set_vault_secrets(self, vault_secrets): 103 | self._vault_secrets = vault_secrets 104 | -------------------------------------------------------------------------------- /tests/unit/mock/path.py: -------------------------------------------------------------------------------- 1 | from __future__ import (absolute_import, division, print_function) 2 | __metaclass__ = type 3 | 4 | from ansible_collections.community.general.tests.unit.compat.mock import MagicMock 5 | from ansible.utils.path import unfrackpath 6 | 7 | 8 | mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) 9 | -------------------------------------------------------------------------------- /tests/unit/mock/procenv.py: -------------------------------------------------------------------------------- 1 | # (c) 2016, Matt Davis 2 | # (c) 2016, Toshio Kuratomi 3 | # 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import (absolute_import, division, print_function) 7 | __metaclass__ = type 8 | 9 | import sys 10 | import json 11 | 12 | from contextlib import contextmanager 13 | from io import BytesIO, StringIO 14 | from ansible_collections.community.general.tests.unit.compat import unittest 15 | from ansible.module_utils.six import PY3 16 | from ansible.module_utils.common.text.converters import to_bytes 17 | 18 | 19 | @contextmanager 20 | def swap_stdin_and_argv(stdin_data='', argv_data=tuple()): 21 | """ 22 | context manager that temporarily masks the test runner's values for stdin and argv 23 | """ 24 | real_stdin = sys.stdin 25 | real_argv = sys.argv 26 | 27 | if PY3: 28 | fake_stream = StringIO(stdin_data) 29 | fake_stream.buffer = BytesIO(to_bytes(stdin_data)) 30 | else: 31 | fake_stream = BytesIO(to_bytes(stdin_data)) 32 | 33 | try: 34 | sys.stdin = fake_stream 35 | sys.argv = argv_data 36 | 37 | yield 38 | finally: 39 | sys.stdin = real_stdin 40 | sys.argv = real_argv 41 | 42 | 43 | @contextmanager 44 | def swap_stdout(): 45 | """ 46 | context manager that temporarily replaces stdout for tests that need to verify output 47 | """ 48 | old_stdout = sys.stdout 49 | 50 | if PY3: 51 | fake_stream = StringIO() 52 | else: 53 | fake_stream = BytesIO() 54 | 55 | try: 56 | sys.stdout = fake_stream 57 | 58 | yield fake_stream 59 | finally: 60 | sys.stdout = old_stdout 61 | 62 | 63 | class ModuleTestCase(unittest.TestCase): 64 | def setUp(self, module_args=None): 65 | if module_args is None: 66 | module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False} 67 | 68 | args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) 69 | 70 | # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually 71 | self.stdin_swap = swap_stdin_and_argv(stdin_data=args) 72 | self.stdin_swap.__enter__() 73 | 74 | def tearDown(self): 75 | # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually 76 | self.stdin_swap.__exit__(None, None, None) 77 | -------------------------------------------------------------------------------- /tests/unit/mock/vault_helper.py: -------------------------------------------------------------------------------- 1 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 2 | 3 | from __future__ import (absolute_import, division, print_function) 4 | __metaclass__ = type 5 | 6 | from ansible.module_utils.common.text.converters import to_bytes 7 | 8 | from ansible.parsing.vault import VaultSecret 9 | 10 | 11 | class TextVaultSecret(VaultSecret): 12 | '''A secret piece of text. ie, a password. Tracks text encoding. 13 | 14 | The text encoding of the text may not be the default text encoding so 15 | we keep track of the encoding so we encode it to the same bytes.''' 16 | 17 | def __init__(self, text, encoding=None, errors=None, _bytes=None): 18 | super(TextVaultSecret, self).__init__() 19 | self.text = text 20 | self.encoding = encoding or 'utf-8' 21 | self._bytes = _bytes 22 | self.errors = errors or 'strict' 23 | 24 | @property 25 | def bytes(self): 26 | '''The text encoded with encoding, unless we specifically set _bytes.''' 27 | return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) 28 | -------------------------------------------------------------------------------- /tests/unit/mock/yaml_helper.py: -------------------------------------------------------------------------------- 1 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 2 | 3 | from __future__ import (absolute_import, division, print_function) 4 | __metaclass__ = type 5 | 6 | import io 7 | import yaml 8 | 9 | from ansible.module_utils.six import PY3 10 | from ansible.parsing.yaml.loader import AnsibleLoader 11 | from ansible.parsing.yaml.dumper import AnsibleDumper 12 | 13 | 14 | class YamlTestUtils(object): 15 | """Mixin class to combine with a unittest.TestCase subclass.""" 16 | def _loader(self, stream): 17 | """Vault related tests will want to override this. 18 | 19 | Vault cases should setup a AnsibleLoader that has the vault password.""" 20 | return AnsibleLoader(stream) 21 | 22 | def _dump_stream(self, obj, stream, dumper=None): 23 | """Dump to a py2-unicode or py3-string stream.""" 24 | if PY3: 25 | return yaml.dump(obj, stream, Dumper=dumper) 26 | else: 27 | return yaml.dump(obj, stream, Dumper=dumper, encoding=None) 28 | 29 | def _dump_string(self, obj, dumper=None): 30 | """Dump to a py2-unicode or py3-string""" 31 | if PY3: 32 | return yaml.dump(obj, Dumper=dumper) 33 | else: 34 | return yaml.dump(obj, Dumper=dumper, encoding=None) 35 | 36 | def _dump_load_cycle(self, obj): 37 | # Each pass though a dump or load revs the 'generation' 38 | # obj to yaml string 39 | string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper) 40 | 41 | # wrap a stream/file like StringIO around that yaml 42 | stream_from_object_dump = io.StringIO(string_from_object_dump) 43 | loader = self._loader(stream_from_object_dump) 44 | # load the yaml stream to create a new instance of the object (gen 2) 45 | obj_2 = loader.get_data() 46 | 47 | # dump the gen 2 objects directory to strings 48 | string_from_object_dump_2 = self._dump_string(obj_2, 49 | dumper=AnsibleDumper) 50 | 51 | # The gen 1 and gen 2 yaml strings 52 | self.assertEqual(string_from_object_dump, string_from_object_dump_2) 53 | # the gen 1 (orig) and gen 2 py object 54 | self.assertEqual(obj, obj_2) 55 | 56 | # again! gen 3... load strings into py objects 57 | stream_3 = io.StringIO(string_from_object_dump_2) 58 | loader_3 = self._loader(stream_3) 59 | obj_3 = loader_3.get_data() 60 | 61 | string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper) 62 | 63 | self.assertEqual(obj, obj_3) 64 | # should be transitive, but... 65 | self.assertEqual(obj_2, obj_3) 66 | self.assertEqual(string_from_object_dump, string_from_object_dump_3) 67 | 68 | def _old_dump_load_cycle(self, obj): 69 | '''Dump the passed in object to yaml, load it back up, dump again, compare.''' 70 | stream = io.StringIO() 71 | 72 | yaml_string = self._dump_string(obj, dumper=AnsibleDumper) 73 | self._dump_stream(obj, stream, dumper=AnsibleDumper) 74 | 75 | yaml_string_from_stream = stream.getvalue() 76 | 77 | # reset stream 78 | stream.seek(0) 79 | 80 | loader = self._loader(stream) 81 | # loader = AnsibleLoader(stream, vault_password=self.vault_password) 82 | obj_from_stream = loader.get_data() 83 | 84 | stream_from_string = io.StringIO(yaml_string) 85 | loader2 = self._loader(stream_from_string) 86 | # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password) 87 | obj_from_string = loader2.get_data() 88 | 89 | stream_obj_from_stream = io.StringIO() 90 | stream_obj_from_string = io.StringIO() 91 | 92 | if PY3: 93 | yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) 94 | yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) 95 | else: 96 | yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None) 97 | yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None) 98 | 99 | yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue() 100 | yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue() 101 | 102 | stream_obj_from_stream.seek(0) 103 | stream_obj_from_string.seek(0) 104 | 105 | if PY3: 106 | yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) 107 | yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) 108 | else: 109 | yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None) 110 | yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None) 111 | 112 | assert yaml_string == yaml_string_obj_from_stream 113 | assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string 114 | assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream == 115 | yaml_string_stream_obj_from_string) 116 | assert obj == obj_from_stream 117 | assert obj == obj_from_string 118 | assert obj == yaml_string_obj_from_stream 119 | assert obj == yaml_string_obj_from_string 120 | assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string 121 | return {'obj': obj, 122 | 'yaml_string': yaml_string, 123 | 'yaml_string_from_stream': yaml_string_from_stream, 124 | 'obj_from_stream': obj_from_stream, 125 | 'obj_from_string': obj_from_string, 126 | 'yaml_string_obj_from_string': yaml_string_obj_from_string} 127 | -------------------------------------------------------------------------------- /tests/unit/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/tests/unit/plugins/__init__.py -------------------------------------------------------------------------------- /tests/unit/plugins/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sap-linuxlab/community.sap_libs/7d8bc0f11c2e1f58da73710e7925f456d011ec0c/tests/unit/plugins/modules/__init__.py -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sap_company.py: -------------------------------------------------------------------------------- 1 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 2 | 3 | from __future__ import (absolute_import, division, print_function) 4 | __metaclass__ = type 5 | 6 | import sys 7 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock 8 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args 9 | 10 | sys.modules['pyrfc'] = MagicMock() 11 | sys.modules['pyrfc.Connection'] = MagicMock() 12 | 13 | from ansible_collections.community.sap_libs.plugins.modules import sap_company 14 | 15 | 16 | class TestSAPRfcModule(ModuleTestCase): 17 | 18 | def setUp(self): 19 | super(TestSAPRfcModule, self).setUp() 20 | self.module = sap_company 21 | 22 | def tearDown(self): 23 | super(TestSAPRfcModule, self).tearDown() 24 | 25 | def define_rfc_connect(self, mocker): 26 | return mocker.patch(self.module.call_rfc_method) 27 | 28 | def test_without_required_parameters(self): 29 | """Failure must occurs when all parameters are missing""" 30 | with self.assertRaises(AnsibleFailJson): 31 | set_module_args({}) 32 | self.module.main() 33 | 34 | def test_error_user_create(self): 35 | """test fail to create company""" 36 | 37 | set_module_args({ 38 | "conn_username": "DDIC", 39 | "conn_password": "Test1234", 40 | "host": "10.1.8.9", 41 | "company_id": "Comp_ID", 42 | "name": "Test_comp", 43 | "name_2": "LTD", 44 | "country": "DE", 45 | "time_zone": "UTC", 46 | "city": "City", 47 | "post_code": "12345", 48 | "street": "test_street", 49 | "street_no": "1", 50 | "e_mail": "test@test.de", 51 | }) 52 | with patch.object(self.module, 'call_rfc_method') as RAW: 53 | RAW.return_value = {'RETURN': [{'FIELD': '', 'ID': '01', 'LOG_MSG_NO': '000000', 54 | 'LOG_NO': '', 'MESSAGE': 'Something went wrong', 'MESSAGE_V1': 'ADMIN', 55 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '199', 56 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'E'}]} 57 | 58 | with self.assertRaises(AnsibleFailJson) as result: 59 | sap_company.main() 60 | self.assertEqual(result.exception.args[0]['msg'], 'Something went wrong') 61 | 62 | def test_success(self): 63 | """test execute company create success""" 64 | 65 | set_module_args({ 66 | "conn_username": "DDIC", 67 | "conn_password": "Test1234", 68 | "host": "10.1.8.9", 69 | "company_id": "Comp_ID", 70 | "name": "Test_comp", 71 | "name_2": "LTD", 72 | "country": "DE", 73 | "time_zone": "UTC", 74 | "city": "City", 75 | "post_code": "12345", 76 | "street": "test_street", 77 | "street_no": "1", 78 | "e_mail": "test@test.de", 79 | }) 80 | with patch.object(self.module, 'call_rfc_method') as RAW: 81 | RAW.return_value = {'RETURN': [{'FIELD': '', 'ID': '01', 'LOG_MSG_NO': '000000', 82 | 'LOG_NO': '', 'MESSAGE': 'Company address COMP_ID created', 'MESSAGE_V1': 'ADMIN', 83 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '102', 84 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]} 85 | 86 | with self.assertRaises(AnsibleExitJson) as result: 87 | sap_company.main() 88 | self.assertEqual(result.exception.args[0]['msg'], 'Company address COMP_ID created') 89 | 90 | def test_no_changes(self): 91 | """test execute company no changes""" 92 | 93 | set_module_args({ 94 | "conn_username": "DDIC", 95 | "conn_password": "Test1234", 96 | "host": "10.1.8.9", 97 | "company_id": "Comp_ID", 98 | "name": "Test_comp", 99 | "name_2": "LTD", 100 | "country": "DE", 101 | "time_zone": "UTC", 102 | "city": "City", 103 | "post_code": "12345", 104 | "street": "test_street", 105 | "street_no": "1", 106 | "e_mail": "test@test.de", 107 | }) 108 | with patch.object(self.module, 'call_rfc_method') as RAW: 109 | RAW.return_value = {'RETURN': [{'FIELD': '', 'ID': '01', 'LOG_MSG_NO': '000000', 110 | 'LOG_NO': '', 'MESSAGE': 'Company address COMP_ID changed', 'MESSAGE_V1': 'ADMIN', 111 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '079', 112 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]} 113 | 114 | with self.assertRaises(AnsibleExitJson) as result: 115 | sap_company.main() 116 | self.assertEqual(result.exception.args[0]['msg'], 'No changes where made.') 117 | 118 | def test_absent(self): 119 | """test execute company delete success""" 120 | 121 | set_module_args({ 122 | "state": "absent", 123 | "conn_username": "DDIC", 124 | "conn_password": "Test1234", 125 | "host": "10.1.8.9", 126 | "company_id": "Comp_ID", 127 | }) 128 | with patch.object(self.module, 'call_rfc_method') as RAW: 129 | RAW.return_value = {'RETURN': [{'FIELD': '', 'ID': '01', 'LOG_MSG_NO': '000000', 130 | 'LOG_NO': '', 'MESSAGE': 'Company address COMP_ID deleted', 'MESSAGE_V1': 'ADMIN', 131 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '080', 132 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]} 133 | 134 | with self.assertRaises(AnsibleExitJson) as result: 135 | sap_company.main() 136 | self.assertEqual(result.exception.args[0]['msg'], 'Company address COMP_ID deleted') 137 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sap_control_exec.py: -------------------------------------------------------------------------------- 1 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 2 | 3 | from __future__ import (absolute_import, division, print_function) 4 | __metaclass__ = type 5 | 6 | import sys 7 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock, Mock 8 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args 9 | 10 | sys.modules['suds.client'] = MagicMock() 11 | sys.modules['suds.sudsobject'] = MagicMock() 12 | sys.modules['suds'] = MagicMock() 13 | 14 | from ansible_collections.community.sap_libs.plugins.modules import sap_control_exec 15 | 16 | 17 | class TestSapcontrolModule(ModuleTestCase): 18 | 19 | def setUp(self): 20 | super(TestSapcontrolModule, self).setUp() 21 | self.module = sap_control_exec 22 | 23 | def tearDown(self): 24 | super(TestSapcontrolModule, self).tearDown() 25 | 26 | def define_rfc_connect(self, mocker): 27 | return mocker.patch(self.module.call_rfc_method) 28 | 29 | def test_without_required_parameters(self): 30 | """Failure must occurs when all parameters are missing""" 31 | with self.assertRaises(AnsibleFailJson): 32 | set_module_args({}) 33 | self.module.main() 34 | 35 | def test_error_module_not_found(self): 36 | """tests fail module error""" 37 | 38 | set_module_args({ 39 | "hostname": "192.168.8.15", 40 | "sysnr": "01", 41 | "function": "GetProcessList" 42 | }) 43 | with self.assertRaises(AnsibleFailJson) as result: 44 | self.module.HAS_SUDS_LIBRARY = False 45 | self.module.SUDS_LIBRARY_IMPORT_ERROR = 'Module not found' 46 | self.module.main() 47 | self.assertEqual(result.exception.args[0]['exception'], 'Module not found') 48 | 49 | def test_error_connection(self): 50 | """tests fail module exception""" 51 | 52 | set_module_args({ 53 | "hostname": "192.168.8.15", 54 | "sysnr": "01", 55 | "function": "GetProcessList" 56 | }) 57 | with self.assertRaises(AnsibleFailJson) as result: 58 | self.module.Client.side_effect = Mock(side_effect=Exception('Test')) 59 | self.module.main() 60 | self.assertEqual(result.exception.args[0]['msg'], 'Something went wrong connecting to the SAPCONTROL SOAP API.') 61 | 62 | def test_error_port_sysnr(self): 63 | """tests fail multi provide parameters""" 64 | 65 | set_module_args({ 66 | "hostname": "192.168.8.15", 67 | "sysnr": "01", 68 | "port": "50113", 69 | "function": "GetProcessList" 70 | }) 71 | with self.assertRaises(AnsibleFailJson) as result: 72 | self.module.main() 73 | self.assertEqual(result.exception.args[0]['msg'], 'parameters are mutually exclusive: sysnr|port') 74 | 75 | def test_error_missing_force(self): 76 | """tests fail missing force""" 77 | 78 | set_module_args({ 79 | "hostname": "192.168.8.15", 80 | "sysnr": "01", 81 | "function": "Stop" 82 | }) 83 | 84 | with self.assertRaises(AnsibleFailJson) as result: 85 | self.module.main() 86 | self.assertEqual(result.exception.args[0]['msg'], 'Stop function requires force: True') 87 | 88 | def test_success_sysnr(self): 89 | """test success with sysnr""" 90 | 91 | set_module_args({ 92 | "hostname": "192.168.8.15", 93 | "sysnr": "01", 94 | "function": "GetProcessList" 95 | }) 96 | with patch.object(self.module, 'recursive_dict') as ret: 97 | ret.return_value = {'item': [{'name': 'hdbdaemon', 'value': '1'}]} 98 | with self.assertRaises(AnsibleExitJson) as result: 99 | self.module.main() 100 | self.assertEqual(result.exception.args[0]['out'], [{'item': [{'name': 'hdbdaemon', 'value': '1'}]}]) 101 | 102 | def test_success_port(self): 103 | """test success with port""" 104 | 105 | set_module_args({ 106 | "hostname": "192.168.8.15", 107 | "port": "50113", 108 | "function": "GetProcessList" 109 | }) 110 | with patch.object(self.module, 'recursive_dict') as ret: 111 | ret.return_value = {'item': [{'name': 'hdbdaemon', 'value': '1'}]} 112 | with self.assertRaises(AnsibleExitJson) as result: 113 | self.module.main() 114 | self.assertEqual(result.exception.args[0]['out'], [{'item': [{'name': 'hdbdaemon', 'value': '1'}]}]) 115 | 116 | def test_success_string(self): 117 | """test success with sysnr""" 118 | 119 | set_module_args({ 120 | "hostname": "192.168.8.15", 121 | "sysnr": "01", 122 | "function": "ParameterValue", 123 | "parameter": "ztta/short_area" 124 | }) 125 | with patch.object(self.module, 'connection') as ret: 126 | ret.return_value = '1600000' 127 | with self.assertRaises(AnsibleExitJson) as result: 128 | self.module.main() 129 | self.assertEqual(result.exception.args[0]['out'], ['1600000']) 130 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sap_hdbsql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright: (c) 2021, Rainer Leber (@rainerleber) 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import absolute_import, division, print_function 7 | __metaclass__ = type 8 | 9 | from ansible_collections.community.sap_libs.plugins.modules import sap_hdbsql 10 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import ( 11 | AnsibleExitJson, 12 | AnsibleFailJson, 13 | ModuleTestCase, 14 | set_module_args, 15 | ) 16 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch 17 | from ansible.module_utils import basic 18 | 19 | 20 | def get_bin_path(*args, **kwargs): 21 | """Function to return path of hdbsql""" 22 | return "/usr/sap/HDB/HDB01/exe/hdbsql" 23 | 24 | 25 | class Testsap_hdbsql(ModuleTestCase): 26 | """Main class for testing sap_hdbsql module.""" 27 | 28 | def setUp(self): 29 | """Setup.""" 30 | super(Testsap_hdbsql, self).setUp() 31 | self.module = sap_hdbsql 32 | self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path) 33 | self.mock_get_bin_path.start() 34 | self.addCleanup(self.mock_get_bin_path.stop) # ensure that the patching is 'undone' 35 | 36 | def tearDown(self): 37 | """Teardown.""" 38 | super(Testsap_hdbsql, self).tearDown() 39 | 40 | def test_without_required_parameters(self): 41 | """Failure must occurs when all parameters are missing.""" 42 | with self.assertRaises(AnsibleFailJson): 43 | set_module_args({}) 44 | self.module.main() 45 | 46 | def test_sap_hdbsql(self): 47 | """Check that result is processed.""" 48 | set_module_args({ 49 | 'sid': "HDB", 50 | 'instance': "01", 51 | 'encrypted': False, 52 | 'host': "localhost", 53 | 'user': "SYSTEM", 54 | 'password': "1234Qwer", 55 | 'database': "HDB", 56 | 'query': "SELECT * FROM users;" 57 | }) 58 | with patch.object(basic.AnsibleModule, 'run_command') as run_command: 59 | run_command.return_value = 0, 'username,name\n testuser,test user \n myuser, my user \n', '' 60 | with self.assertRaises(AnsibleExitJson) as result: 61 | sap_hdbsql.main() 62 | self.assertEqual(result.exception.args[0]['query_result'], [[ 63 | {'username': 'testuser', 'name': 'test user'}, 64 | {'username': 'myuser', 'name': 'my user'}, 65 | ]]) 66 | self.assertEqual(run_command.call_count, 1) 67 | 68 | def test_hana_userstore_query(self): 69 | """Check that result is processed with userstore.""" 70 | set_module_args({ 71 | 'sid': "HDB", 72 | 'instance': "01", 73 | 'encrypted': False, 74 | 'host': "localhost", 75 | 'user': "SYSTEM", 76 | 'userstore': True, 77 | 'database': "HDB", 78 | 'query': "SELECT * FROM users;" 79 | }) 80 | with patch.object(basic.AnsibleModule, 'run_command') as run_command: 81 | run_command.return_value = 0, 'username,name\n testuser,test user \n myuser, my user \n', '' 82 | with self.assertRaises(AnsibleExitJson) as result: 83 | sap_hdbsql.main() 84 | self.assertEqual(result.exception.args[0]['query_result'], [[ 85 | {'username': 'testuser', 'name': 'test user'}, 86 | {'username': 'myuser', 'name': 'my user'}, 87 | ]]) 88 | self.assertEqual(run_command.call_count, 1) 89 | 90 | def test_hana_failed_no_passwd(self): 91 | """Check that result is failed with no password.""" 92 | with self.assertRaises(AnsibleFailJson): 93 | set_module_args({ 94 | 'sid': "HDB", 95 | 'instance': "01", 96 | 'encrypted': False, 97 | 'host': "localhost", 98 | 'user': "SYSTEM", 99 | 'database': "HDB", 100 | 'query': "SELECT * FROM users;" 101 | }) 102 | self.module.main() 103 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sap_pyrfc.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # http://www.apache.org/licenses/LICENSE-2.0 5 | # Unless required by applicable law or agreed to in writing, software 6 | # distributed under the License is distributed on an "AS IS" BASIS, 7 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | # See the License for the specific language governing permissions and 9 | # limitations under the License. 10 | 11 | from __future__ import (absolute_import, division, print_function) 12 | 13 | __metaclass__ = type 14 | 15 | import sys 16 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock 17 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args 18 | 19 | sys.modules['pyrfc'] = MagicMock() 20 | sys.modules['pyrfc.Connection'] = MagicMock() 21 | from ansible_collections.community.sap_libs.plugins.modules import sap_pyrfc 22 | 23 | 24 | class TestSAPRfcModule(ModuleTestCase): 25 | 26 | def setUp(self): 27 | super(TestSAPRfcModule, self).setUp() 28 | self.module = sap_pyrfc 29 | 30 | def tearDown(self): 31 | super(TestSAPRfcModule, self).tearDown() 32 | 33 | def test_without_required_parameters(self): 34 | """Failure must occurs when all parameters are missing""" 35 | with self.assertRaises(AnsibleFailJson): 36 | set_module_args({}) 37 | self.module.main() 38 | 39 | def test_error_module_not_found(self): 40 | """tests fail module error""" 41 | 42 | set_module_args({ 43 | "function": "STFC_CONNECTION", 44 | "parameters": {"REQUTEXT": "Hello SAP!"}, 45 | "connection": {"ashost": "s4hana.poc.cloud", 46 | "sysnr": "01", 47 | "client": "400", 48 | "user": "DDIC", 49 | "passwd": "Password1", 50 | "lang": "EN"} 51 | }) 52 | 53 | with self.assertRaises(AnsibleFailJson) as result: 54 | self.module.HAS_PYRFC_LIBRARY = False 55 | self.module.PYRFC_LIBRARY_IMPORT_ERROR = 'Module not found' 56 | self.module.main() 57 | self.assertEqual( 58 | result.exception.args[0]['exception'], 'Module not found') 59 | 60 | def test_success_communication(self): 61 | """tests success""" 62 | set_module_args({ 63 | "function": "STFC_CONNECTION", 64 | "parameters": {"REQUTEXT": "Hello SAP!"}, 65 | "connection": {"ashost": "s4hana.poc.cloud", 66 | "sysnr": "01", 67 | "client": "400", 68 | "user": "DDIC", 69 | "passwd": "Password1", 70 | "lang": "EN"} 71 | }) 72 | with patch.object(self.module, 'get_connection') as patch_call: 73 | patch_call.call.return_value = 'Patched' 74 | with self.assertRaises(AnsibleExitJson) as result: 75 | self.module.main() 76 | self.assertEqual(result.exception.args[0]['changed'], True) 77 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sap_snote.py: -------------------------------------------------------------------------------- 1 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 2 | 3 | from __future__ import (absolute_import, division, print_function) 4 | __metaclass__ = type 5 | 6 | import sys 7 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock, Mock 8 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args 9 | 10 | sys.modules['pyrfc'] = MagicMock() 11 | sys.modules['pyrfc.Connection'] = MagicMock() 12 | 13 | from ansible_collections.community.sap_libs.plugins.modules import sap_snote 14 | 15 | 16 | class TestSAPRfcModule(ModuleTestCase): 17 | 18 | def setUp(self): 19 | super(TestSAPRfcModule, self).setUp() 20 | self.module = sap_snote 21 | 22 | def tearDown(self): 23 | super(TestSAPRfcModule, self).tearDown() 24 | 25 | def define_rfc_connect(self, mocker): 26 | return mocker.patch(self.module.call_rfc_method) 27 | 28 | def test_without_required_parameters(self): 29 | """Failure must occurs when all parameters are missing""" 30 | with self.assertRaises(AnsibleFailJson): 31 | set_module_args({}) 32 | self.module.main() 33 | 34 | def test_error_module_not_found(self): 35 | """tests fail module error""" 36 | 37 | set_module_args({ 38 | "conn_username": "ADMIN", 39 | "conn_password": "Test1234", 40 | "host": "10.1.8.9", 41 | "snote_path": "/user/sap/trans/temp/000123456.txt" 42 | }) 43 | with self.assertRaises(AnsibleFailJson) as result: 44 | self.module.HAS_PYRFC_LIBRARY = False 45 | self.module.ANOTHER_LIBRARY_IMPORT_ERROR = 'Module not found' 46 | self.module.main() 47 | self.assertEqual(result.exception.args[0]['exception'], 'Module not found') 48 | 49 | def test_error_connection(self): 50 | """tests fail module error""" 51 | 52 | set_module_args({ 53 | "conn_username": "ADMIN", 54 | "conn_password": "Test1234", 55 | "host": "10.1.8.9", 56 | "snote_path": "/user/sap/trans/temp/000123456.txt" 57 | }) 58 | with self.assertRaises(AnsibleFailJson) as result: 59 | self.module.Connection.side_effect = Mock(side_effect=Exception('Test')) 60 | self.module.main() 61 | self.assertEqual(result.exception.args[0]['msg'], 'Something went wrong connecting to the SAP system.') 62 | 63 | def test_error_wrong_path(self): 64 | """tests fail wrong path extension""" 65 | 66 | set_module_args({ 67 | "conn_username": "ADMIN", 68 | "conn_password": "Test1234", 69 | "host": "10.1.8.9", 70 | "snote_path": "/user/sap/trans/temp/000123456_00.tx" 71 | }) 72 | 73 | with self.assertRaises(AnsibleFailJson) as result: 74 | self.module.main() 75 | self.assertEqual(result.exception.args[0]['msg'], 'The path must include the extracted snote file and ends with txt.') 76 | 77 | def test_error_wrong_user(self): 78 | """tests fail wrong path extension""" 79 | 80 | set_module_args({ 81 | "conn_username": "DDIC", 82 | "conn_password": "Test1234", 83 | "host": "10.1.8.9", 84 | "snote_path": "/user/sap/trans/temp/000123456_00.tx" 85 | }) 86 | 87 | with self.assertRaises(AnsibleFailJson) as result: 88 | self.module.main() 89 | self.assertEqual(result.exception.args[0]['msg'], 'User C(DDIC) or C(SAP*) not allowed for this operation.') 90 | 91 | def test_success_absent(self): 92 | """test absent execute snote""" 93 | 94 | set_module_args({ 95 | "conn_username": "ADMIN", 96 | "conn_password": "Test1234", 97 | "host": "10.1.8.9", 98 | "state": "absent", 99 | "snote_path": "/user/sap/trans/temp/000123456.txt" 100 | }) 101 | with patch.object(self.module, 'call_rfc_method') as call: 102 | call.return_value = {'EV_RC': 0} 103 | with self.assertRaises(AnsibleExitJson) as result: 104 | with patch.object(self.module, 'check_implementation') as check: 105 | check.side_effect = [True, False] 106 | self.module.main() 107 | self.assertEqual(result.exception.args[0]['msg'], 'SNOTE "000123456" deimplemented.') 108 | 109 | def test_success_absent_snot_only(self): 110 | """test absent execute snote""" 111 | 112 | set_module_args({ 113 | "conn_username": "ADMIN", 114 | "conn_password": "Test1234", 115 | "host": "10.1.8.9", 116 | "state": "absent", 117 | "snote": "000123456" 118 | }) 119 | with patch.object(self.module, 'call_rfc_method') as call: 120 | call.return_value = {'EV_RC': 0} 121 | with self.assertRaises(AnsibleExitJson) as result: 122 | with patch.object(self.module, 'check_implementation') as check: 123 | check.side_effect = [True, False] 124 | self.module.main() 125 | self.assertEqual(result.exception.args[0]['msg'], 'SNOTE "000123456" deimplemented.') 126 | 127 | def test_nothing_to_do(self): 128 | """test nothing to do""" 129 | 130 | set_module_args({ 131 | "conn_username": "ADMIN", 132 | "conn_password": "Test1234", 133 | "host": "10.1.8.9", 134 | "state": "present", 135 | "snote_path": "/user/sap/trans/temp/000123456.txt" 136 | }) 137 | with patch.object(self.module, 'check_implementation') as check: 138 | check.return_value = True 139 | with self.assertRaises(AnsibleExitJson) as result: 140 | self.module.main() 141 | self.assertEqual(result.exception.args[0]['msg'], 'Nothing to do.') 142 | 143 | def test_success_present_with_copy(self): 144 | """test present execute snote""" 145 | 146 | set_module_args({ 147 | "conn_username": "ADMIN", 148 | "conn_password": "Test1234", 149 | "host": "10.1.8.9", 150 | "state": "present", 151 | "snote_path": "/user/sap/trans/temp/000123456.txt" 152 | }) 153 | with patch.object(self.module, 'call_rfc_method') as call: 154 | call.return_value = {'EV_RC': 0} 155 | with self.assertRaises(AnsibleExitJson) as result: 156 | with patch.object(self.module, 'check_implementation') as check: 157 | check.side_effect = [False, True] 158 | with patch.object(self.module, 'call_rfc_method') as callrfc: 159 | callrfc.side_effect = [{'EV_RC': 0}, {'EV_RC': 0}, {'ET_MANUAL_ACTIVITIES': ''}] 160 | self.module.main() 161 | self.assertEqual(result.exception.args[0]['msg'], 'SNOTE "000123456" implemented.') 162 | 163 | def test_success_present_implement_only(self): 164 | """test present implement snote""" 165 | 166 | set_module_args({ 167 | "conn_username": "ADMIN", 168 | "conn_password": "Test1234", 169 | "host": "10.1.8.9", 170 | "state": "present", 171 | "snote": "000123456" 172 | }) 173 | with patch.object(self.module, 'call_rfc_method') as call: 174 | call.return_value = {'EV_RC': 0} 175 | with self.assertRaises(AnsibleExitJson) as result: 176 | with patch.object(self.module, 'check_implementation') as check: 177 | check.side_effect = [False, True] 178 | with patch.object(self.module, 'call_rfc_method') as callrfc: 179 | callrfc.side_effect = [{'EV_RC': 0}, {'ET_MANUAL_ACTIVITIES': ''}] 180 | self.module.main() 181 | self.assertEqual(result.exception.args[0]['msg'], 'SNOTE "000123456" implemented.') 182 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sap_system_facts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright: (c) 2021, Rainer Leber (@rainerleber) 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import absolute_import, division, print_function 7 | 8 | __metaclass__ = type 9 | 10 | from ansible_collections.community.sap_libs.plugins.modules import sap_system_facts 11 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, ModuleTestCase 12 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch 13 | from ansible.module_utils import basic 14 | 15 | 16 | def get_bin_path(*args, **kwargs): 17 | """Function to return path of sapcontrol""" 18 | return "/usr/sap/hostctrl/exe/sapcontrol" 19 | 20 | 21 | class Testsap_system_facts(ModuleTestCase): 22 | """Main class for testing sap_system_facts module.""" 23 | 24 | def setUp(self): 25 | """Setup.""" 26 | super(Testsap_system_facts, self).setUp() 27 | self.module = sap_system_facts 28 | self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path) 29 | self.mock_get_bin_path.start() 30 | self.addCleanup(self.mock_get_bin_path.stop) 31 | 32 | def tearDown(self): 33 | """Teardown.""" 34 | super(Testsap_system_facts, self).tearDown() 35 | 36 | def test_no_systems_available(self): 37 | """No SAP Systems""" 38 | with self.assertRaises(AnsibleExitJson) as result: 39 | self.module.main() 40 | self.assertEqual(result.exception.args[0]['ansible_facts'], {}) 41 | 42 | def test_sap_system_facts_all(self): 43 | """Check that result is changed when all is one system.""" 44 | with patch.object(self.module, 'get_all_hana_sid') as get_all_hana_sid: 45 | get_all_hana_sid.return_value = ['HDB'] 46 | with patch.object(self.module, 'get_hana_nr') as get_hana_nr: 47 | get_hana_nr.return_value = [{"InstanceType": "HANA", "NR": "01", "SID": "HDB", "TYPE": "HDB"}] 48 | with patch.object(self.module, 'get_all_nw_sid') as get_all_nw_sid: 49 | get_all_nw_sid.return_value = ['ABC'] 50 | with patch.object(self.module, 'get_nw_nr') as get_nw_nr: 51 | get_nw_nr.return_value = [{"InstanceType": "NW", "NR": "00", "SID": "ABC", "TYPE": "ASCS"}, 52 | {"InstanceType": "NW", "NR": "01", "SID": "ABC", "TYPE": "PAS"}] 53 | with self.assertRaises(AnsibleExitJson) as result: 54 | self.module.main() 55 | self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{"InstanceType": "HANA", "NR": "01", "SID": "HDB", "TYPE": "HDB"}, 56 | {"InstanceType": "NW", "NR": "00", "SID": "ABC", "TYPE": "ASCS"}, 57 | {"InstanceType": "NW", "NR": "01", "SID": "ABC", "TYPE": "PAS"}]}) 58 | 59 | def test_sap_system_facts_command_hana(self): 60 | """Check that result for HANA is correct.""" 61 | with patch.object(self.module, 'get_all_hana_sid') as mock_all_hana_sid: 62 | mock_all_hana_sid.return_value = ['HDB'] 63 | with patch.object(self.module.os, 'listdir') as mock_listdir: 64 | mock_listdir.return_value = ['HDB01'] 65 | with patch.object(basic.AnsibleModule, 'run_command') as run_command: 66 | run_command.return_value = [0, '', ''] 67 | with self.assertRaises(AnsibleExitJson) as result: 68 | self.module.main() 69 | self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{"InstanceType": "HANA", "NR": "01", "SID": "HDB", "TYPE": "HDB"}]}) 70 | 71 | def test_sap_system_facts_pas_nw(self): 72 | """Check that result for NW is correct.""" 73 | with patch.object(self.module, 'get_all_nw_sid') as mock_all_nw_sid: 74 | mock_all_nw_sid.return_value = ['ABC'] 75 | with patch.object(self.module.os, 'listdir') as mock_listdir: 76 | mock_listdir.return_value = ['D00'] 77 | with patch.object(basic.AnsibleModule, 'run_command') as run_command: 78 | run_command.return_value = [0, 'SAP\nINSTANCE_NAME, Attribute, D00\nSAP', ''] 79 | with self.assertRaises(AnsibleExitJson) as result: 80 | self.module.main() 81 | self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{'InstanceType': 'NW', 'NR': '00', 'SID': 'ABC', 'TYPE': 'PAS'}]}) 82 | 83 | def test_sap_system_facts_future_nw(self): 84 | """Check that future apps for NW are correct handled.""" 85 | with patch.object(self.module, 'get_all_nw_sid') as mock_all_nw_sid: 86 | mock_all_nw_sid.return_value = ['ABC'] 87 | with patch.object(self.module.os, 'listdir') as mock_listdir: 88 | mock_listdir.return_value = ['XY00'] 89 | with patch.object(basic.AnsibleModule, 'run_command') as run_command: 90 | run_command.return_value = [0, 'SAP\nINSTANCE_NAME, Attribute, XY00\nSAP', ''] 91 | with self.assertRaises(AnsibleExitJson) as result: 92 | self.module.main() 93 | self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{'InstanceType': 'NW', 'NR': '00', 'SID': 'ABC', 'TYPE': 'XXX'}]}) 94 | 95 | def test_sap_system_facts_wd_nw(self): 96 | """Check that WD for NW is correct handled.""" 97 | with patch.object(self.module, 'get_all_nw_sid') as mock_all_nw_sid: 98 | mock_all_nw_sid.return_value = ['ABC'] 99 | with patch.object(self.module.os, 'listdir') as mock_listdir: 100 | mock_listdir.return_value = ['WD80'] 101 | with patch.object(basic.AnsibleModule, 'run_command') as run_command: 102 | run_command.return_value = [0, 'SAP\nINSTANCE_NAME, Attribute, WD80\nSAP', ''] 103 | with self.assertRaises(AnsibleExitJson) as result: 104 | self.module.main() 105 | self.assertEqual(result.exception.args[0]['ansible_facts'], {'sap': [{'InstanceType': 'NW', 'NR': '80', 'SID': 'ABC', 'TYPE': 'WebDisp'}]}) 106 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sap_task_list_execute.py: -------------------------------------------------------------------------------- 1 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 2 | 3 | from __future__ import (absolute_import, division, print_function) 4 | __metaclass__ = type 5 | 6 | import sys 7 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock 8 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args 9 | 10 | sys.modules['pyrfc'] = MagicMock() 11 | sys.modules['pyrfc.Connection'] = MagicMock() 12 | sys.modules['xmltodict'] = MagicMock() 13 | sys.modules['xmltodict.parse'] = MagicMock() 14 | 15 | from ansible_collections.community.sap_libs.plugins.modules import sap_task_list_execute 16 | 17 | 18 | class TestSAPRfcModule(ModuleTestCase): 19 | 20 | def setUp(self): 21 | super(TestSAPRfcModule, self).setUp() 22 | self.module = sap_task_list_execute 23 | 24 | def tearDown(self): 25 | super(TestSAPRfcModule, self).tearDown() 26 | 27 | def define_rfc_connect(self, mocker): 28 | return mocker.patch(self.module.call_rfc_method) 29 | 30 | def test_without_required_parameters(self): 31 | """Failure must occurs when all parameters are missing""" 32 | with self.assertRaises(AnsibleFailJson): 33 | set_module_args({}) 34 | self.module.main() 35 | 36 | def test_error_no_task_list(self): 37 | """tests fail to exec task list""" 38 | 39 | set_module_args({ 40 | "conn_username": "DDIC", 41 | "conn_password": "Test1234", 42 | "host": "10.1.8.9", 43 | "task_to_execute": "SAP_BASIS_SSL_CHECK" 44 | }) 45 | 46 | with patch.object(self.module, 'Connection') as conn: 47 | conn.return_value = '' 48 | with self.assertRaises(AnsibleFailJson) as result: 49 | self.module.main() 50 | self.assertEqual(result.exception.args[0]['msg'], 'The task list does not exist.') 51 | 52 | def test_success(self): 53 | """test execute task list success""" 54 | 55 | set_module_args({ 56 | "conn_username": "DDIC", 57 | "conn_password": "Test1234", 58 | "host": "10.1.8.9", 59 | "task_to_execute": "SAP_BASIS_SSL_CHECK" 60 | }) 61 | with patch.object(self.module, 'xml_to_dict') as XML: 62 | XML.return_value = {'item': [{'TASK': {'CHECK_STATUS_DESCR': 'Check successfully', 63 | 'STATUS_DESCR': 'Executed successfully', 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO', 64 | 'LNR': '1', 'DESCRIPTION': 'Check SAP Cryptographic Library', 'DOCU_EXIST': 'X', 65 | 'LOG_EXIST': 'X', 'ACTION_SKIP': None, 'ACTION_UNSKIP': None, 'ACTION_CONFIRM': None, 66 | 'ACTION_MAINTAIN': None}}]} 67 | 68 | with self.assertRaises(AnsibleExitJson) as result: 69 | sap_task_list_execute.main() 70 | self.assertEqual(result.exception.args[0]['out'], {'item': [{'TASK': {'CHECK_STATUS_DESCR': 'Check successfully', 71 | 'STATUS_DESCR': 'Executed successfully', 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO', 72 | 'LNR': '1', 'DESCRIPTION': 'Check SAP Cryptographic Library', 'DOCU_EXIST': 'X', 73 | 'LOG_EXIST': 'X', 'ACTION_SKIP': None, 'ACTION_UNSKIP': None, 74 | 'ACTION_CONFIRM': None, 'ACTION_MAINTAIN': None}}]}) 75 | 76 | def test_success_no_log(self): 77 | """test execute task list success without logs""" 78 | 79 | set_module_args({ 80 | "conn_username": "DDIC", 81 | "conn_password": "Test1234", 82 | "host": "10.1.8.9", 83 | "task_to_execute": "SAP_BASIS_SSL_CHECK" 84 | }) 85 | with patch.object(self.module, 'xml_to_dict') as XML: 86 | XML.return_value = "No logs available." 87 | with self.assertRaises(AnsibleExitJson) as result: 88 | sap_task_list_execute.main() 89 | self.assertEqual(result.exception.args[0]['out'], 'No logs available.') 90 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sap_user.py: -------------------------------------------------------------------------------- 1 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 2 | 3 | from __future__ import (absolute_import, division, print_function) 4 | __metaclass__ = type 5 | 6 | import sys 7 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch, MagicMock 8 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args 9 | 10 | sys.modules['pyrfc'] = MagicMock() 11 | sys.modules['pyrfc.Connection'] = MagicMock() 12 | 13 | from ansible_collections.community.sap_libs.plugins.modules import sap_user 14 | 15 | 16 | class TestSAPRfcModule(ModuleTestCase): 17 | 18 | def setUp(self): 19 | super(TestSAPRfcModule, self).setUp() 20 | self.module = sap_user 21 | 22 | def tearDown(self): 23 | super(TestSAPRfcModule, self).tearDown() 24 | 25 | def define_rfc_connect(self, mocker): 26 | return mocker.patch(self.module.call_rfc_method) 27 | 28 | def test_without_required_parameters(self): 29 | """Failure must occurs when all parameters are missing""" 30 | with self.assertRaises(AnsibleFailJson): 31 | set_module_args({}) 32 | self.module.main() 33 | 34 | def test_error_user_create(self): 35 | """test fail to create user""" 36 | 37 | set_module_args({ 38 | "conn_username": "DDIC", 39 | "conn_password": "Test1234", 40 | "host": "10.1.8.9", 41 | "username": "ADMIN", 42 | "firstname": "first_admin", 43 | "lastname": "last_admin", 44 | "email": "admin@test.de", 45 | "password": "Test123456", 46 | "useralias": "ADMIN", 47 | "company": "DEFAULT_COMPANY" 48 | }) 49 | 50 | with patch.object(self.module, 'check_user') as check: 51 | check.return_value = False 52 | 53 | with patch.object(self.module, 'call_rfc_method') as RAW: 54 | RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000', 55 | 'LOG_NO': '', 'MESSAGE': 'Something went wrong', 'MESSAGE_V1': 'ADMIN', 56 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '199', 57 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'E'}]} 58 | 59 | with self.assertRaises(AnsibleFailJson) as result: 60 | sap_user.main() 61 | self.assertEqual(result.exception.args[0]['msg'], 'Something went wrong') 62 | 63 | def test_success(self): 64 | """test execute user create success""" 65 | 66 | set_module_args({ 67 | "conn_username": "DDIC", 68 | "conn_password": "Test1234", 69 | "host": "10.1.8.9", 70 | "username": "ADMIN", 71 | "firstname": "first_admin", 72 | "lastname": "last_admin", 73 | "email": "admin@test.de", 74 | "password": "Test123456", 75 | "useralias": "ADMIN", 76 | "company": "DEFAULT_COMPANY" 77 | }) 78 | with patch.object(self.module, 'check_user') as check: 79 | check.return_value = False 80 | 81 | with patch.object(self.module, 'call_rfc_method') as RAW: 82 | RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000', 83 | 'LOG_NO': '', 'MESSAGE': 'User ADMIN created', 'MESSAGE_V1': 'ADMIN', 84 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '102', 85 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]} 86 | 87 | with self.assertRaises(AnsibleExitJson) as result: 88 | sap_user.main() 89 | self.assertEqual(result.exception.args[0]['msg'], 'User ADMIN created') 90 | 91 | def test_no_changes(self): 92 | """test execute user no changes""" 93 | 94 | set_module_args({ 95 | "conn_username": "DDIC", 96 | "conn_password": "Test1234", 97 | "host": "10.1.8.9", 98 | "username": "ADMIN", 99 | "firstname": "first_admin", 100 | "lastname": "last_admin", 101 | "email": "admin@test.de", 102 | "password": "Test123456", 103 | "useralias": "ADMIN", 104 | "company": "DEFAULT_COMPANY" 105 | }) 106 | with patch.object(self.module, 'check_user') as check: 107 | check.return_value = True 108 | 109 | with patch.object(self.module, 'call_rfc_method') as RAW: 110 | RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000', 111 | 'LOG_NO': '', 'MESSAGE': '', 'MESSAGE_V1': 'ADMIN', 112 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '029', 113 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]} 114 | 115 | with patch.object(self.module, 'all') as DETAIL: 116 | DETAIL.return_value = True 117 | 118 | with self.assertRaises(AnsibleExitJson) as result: 119 | sap_user.main() 120 | self.assertEqual(result.exception.args[0]['msg'], 'No changes where made.') 121 | 122 | def test_absent(self): 123 | """test execute user delete success""" 124 | 125 | set_module_args({ 126 | "state": "absent", 127 | "conn_username": "DDIC", 128 | "conn_password": "Test1234", 129 | "host": "10.1.8.9", 130 | "username": "ADMIN", 131 | }) 132 | with patch.object(self.module, 'check_user') as check: 133 | check.return_value = True 134 | 135 | with patch.object(self.module, 'call_rfc_method') as RAW: 136 | RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000', 137 | 'LOG_NO': '', 'MESSAGE': 'User ADMIN deleted', 'MESSAGE_V1': 'ADMIN', 138 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '102', 139 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]} 140 | 141 | with self.assertRaises(AnsibleExitJson) as result: 142 | sap_user.main() 143 | self.assertEqual(result.exception.args[0]['msg'], 'User ADMIN deleted') 144 | 145 | def test_lock(self): 146 | """test execute user lock success""" 147 | 148 | set_module_args({ 149 | "state": "lock", 150 | "conn_username": "DDIC", 151 | "conn_password": "Test1234", 152 | "host": "10.1.8.9", 153 | "username": "ADMIN", 154 | }) 155 | with patch.object(self.module, 'check_user') as check: 156 | check.return_value = True 157 | 158 | with patch.object(self.module, 'call_rfc_method') as RAW: 159 | RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000', 160 | 'LOG_NO': '', 'MESSAGE': 'User ADMIN locked', 'MESSAGE_V1': 'ADMIN', 161 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '206', 162 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]} 163 | 164 | with self.assertRaises(AnsibleExitJson) as result: 165 | sap_user.main() 166 | self.assertEqual(result.exception.args[0]['msg'], 'User ADMIN locked') 167 | 168 | def test_unlock(self): 169 | """test execute user lock success""" 170 | 171 | set_module_args({ 172 | "state": "lock", 173 | "conn_username": "DDIC", 174 | "conn_password": "Test1234", 175 | "host": "10.1.8.9", 176 | "username": "ADMIN", 177 | }) 178 | with patch.object(self.module, 'check_user') as check: 179 | check.return_value = True 180 | 181 | with patch.object(self.module, 'call_rfc_method') as RAW: 182 | RAW.return_value = {'RETURN': [{'FIELD': 'BNAME', 'ID': '01', 'LOG_MSG_NO': '000000', 183 | 'LOG_NO': '', 'MESSAGE': 'User ADMIN unlocked', 'MESSAGE_V1': 'ADMIN', 184 | 'MESSAGE_V2': '', 'MESSAGE_V3': '', 'MESSAGE_V4': '', 'NUMBER': '210', 185 | 'PARAMETER': '', 'ROW': 0, 'SYSTEM': '', 'TYPE': 'S'}]} 186 | 187 | with self.assertRaises(AnsibleExitJson) as result: 188 | sap_user.main() 189 | self.assertEqual(result.exception.args[0]['msg'], 'User ADMIN unlocked') 190 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/test_sapcar_extract.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright: (c) 2021, Rainer Leber (@rainerleber) 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import absolute_import, division, print_function 7 | __metaclass__ = type 8 | 9 | from ansible_collections.community.sap_libs.plugins.modules import sapcar_extract 10 | from ansible_collections.community.sap_libs.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args 11 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch 12 | from ansible.module_utils import basic 13 | 14 | 15 | def get_bin_path(*args, **kwargs): 16 | """Function to return path of SAPCAR""" 17 | return "/tmp/sapcar" 18 | 19 | 20 | class Testsapcar_extract(ModuleTestCase): 21 | """Main class for testing sapcar_extract module.""" 22 | 23 | def setUp(self): 24 | """Setup.""" 25 | super(Testsapcar_extract, self).setUp() 26 | self.module = sapcar_extract 27 | self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path) 28 | self.mock_get_bin_path.start() 29 | self.addCleanup(self.mock_get_bin_path.stop) # ensure that the patching is 'undone' 30 | 31 | def tearDown(self): 32 | """Teardown.""" 33 | super(Testsapcar_extract, self).tearDown() 34 | 35 | def test_without_required_parameters(self): 36 | """Failure must occurs when all parameters are missing.""" 37 | with self.assertRaises(AnsibleFailJson): 38 | set_module_args({}) 39 | self.module.main() 40 | 41 | def test_sapcar_extract(self): 42 | """Check that result is changed.""" 43 | set_module_args({ 44 | 'path': "/tmp/HANA_CLIENT_REV2_00_053_00_LINUX_X86_64.SAR", 45 | 'dest': "/tmp/test2", 46 | 'binary_path': "/tmp/sapcar" 47 | }) 48 | with patch.object(basic.AnsibleModule, 'run_command') as run_command: 49 | run_command.return_value = 0, '', '' # successful execution, no output 50 | with self.assertRaises(AnsibleExitJson) as result: 51 | sapcar_extract.main() 52 | self.assertTrue(result.exception.args[0]['changed']) 53 | self.assertEqual(run_command.call_count, 1) 54 | -------------------------------------------------------------------------------- /tests/unit/plugins/modules/utils.py: -------------------------------------------------------------------------------- 1 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 2 | 3 | from __future__ import (absolute_import, division, print_function) 4 | __metaclass__ = type 5 | 6 | import json 7 | 8 | from ansible_collections.community.sap_libs.tests.unit.compat import unittest 9 | from ansible_collections.community.sap_libs.tests.unit.compat.mock import patch 10 | from ansible.module_utils import basic 11 | from ansible.module_utils.common.text.converters import to_bytes 12 | 13 | 14 | def set_module_args(args): 15 | if '_ansible_remote_tmp' not in args: 16 | args['_ansible_remote_tmp'] = '/tmp' 17 | if '_ansible_keep_remote_files' not in args: 18 | args['_ansible_keep_remote_files'] = False 19 | 20 | args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) 21 | basic._ANSIBLE_ARGS = to_bytes(args) 22 | 23 | 24 | class AnsibleExitJson(Exception): 25 | pass 26 | 27 | 28 | class AnsibleFailJson(Exception): 29 | pass 30 | 31 | 32 | def exit_json(*args, **kwargs): 33 | if 'changed' not in kwargs: 34 | kwargs['changed'] = False 35 | raise AnsibleExitJson(kwargs) 36 | 37 | 38 | def fail_json(*args, **kwargs): 39 | kwargs['failed'] = True 40 | raise AnsibleFailJson(kwargs) 41 | 42 | 43 | class ModuleTestCase(unittest.TestCase): 44 | 45 | def setUp(self): 46 | self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) 47 | self.mock_module.start() 48 | self.mock_sleep = patch('time.sleep') 49 | self.mock_sleep.start() 50 | set_module_args({}) 51 | self.addCleanup(self.mock_module.stop) 52 | self.addCleanup(self.mock_sleep.stop) 53 | -------------------------------------------------------------------------------- /tests/unit/requirements.txt: -------------------------------------------------------------------------------- 1 | unittest2 ; python_version < '2.7' 2 | importlib ; python_version < '2.7' 3 | 4 | # requirement sap_task_list_execute 5 | lxml < 4.3.0 ; python_version < '2.7' # lxml 4.3.0 and later require python 2.7 or later 6 | lxml ; python_version >= '2.7' 7 | argparse ; python_version >= '2.6' --------------------------------------------------------------------------------