├── .gitignore ├── .readthedocs.yml ├── AUTHORS.md ├── CHANGELOG.rst ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── README.rst ├── SUPPORTED_METHODS.md ├── USERGUIDE.md ├── docs ├── Makefile ├── conf.py ├── dev │ └── package.rst ├── index.rst ├── make.bat └── user │ ├── changelog.rst │ ├── error_handling.rst │ ├── examples.rst │ ├── general_tips.rst │ ├── getting_started.rst │ ├── installation.rst │ ├── responses.rst │ └── supported_api_methods.rst ├── pyproject.toml ├── requirements.txt ├── rtpy ├── __init__.py ├── artifacts_and_storage.py ├── builds.py ├── import_and_export.py ├── plugins.py ├── repositories.py ├── rtpy.py ├── searches.py ├── security.py ├── system_and_configuration.py ├── tools.py └── xray.py ├── setup.py └── tests ├── __init__.py ├── assets ├── bundle_creation.json └── python_logo.png ├── helpers.py ├── import_license.py ├── mixins.py ├── test_artifacts_and_storage.py ├── test_builds.py ├── test_repositories.py ├── test_searches.py ├── test_security.py ├── test_system_and_configuration.py └── test_tools.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .pytest_cache/ 3 | perso/ 4 | trash/ 5 | tests/license.json 6 | curl_output.txt 7 | launch_local_af.sh 8 | *.sh 9 | *.lock 10 | 11 | # Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | MANIFEST 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # celery beat schedule file 89 | celerybeat-schedule 90 | 91 | # SageMath parsed files 92 | *.sage.py 93 | 94 | # Environments 95 | .env 96 | .venv 97 | env/ 98 | venv/ 99 | ENV/ 100 | env.bak/ 101 | venv.bak/ 102 | 103 | # Spyder project settings 104 | .spyderproject 105 | .spyproject 106 | 107 | # Rope project settings 108 | .ropeproject 109 | 110 | # mkdocs documentation 111 | /site 112 | 113 | # mypy 114 | .mypy_cache/ -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | requirements_file: requirements.txt -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | rtpy was written by Guillaume Renault [@guillaumerenault](https://github.com/guillaumerenault) with the help and feedback of [@Richard Dumais](mailto:richard@nerux.org). 4 | 5 | # Core maintainers 6 | 7 | - Guillaume Renault [@guillaumerenault](https://github.com/guillaumerenault) 8 | 9 | # Contributors -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 1.4.9 (2020.07.06) 5 | ------------------ 6 | * Fixed settings override not working properly 7 | * Dropped support for all the "SUPPORT" API methods (not working anymore) 8 | 9 | 1.4.8 (2019.02.10) 10 | ------------------ 11 | * Initial Open-source release -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via an issue with the owners of this repository before making a change. 4 | 5 | ## Pull Request Process 6 | 7 | The repository is following [git-flow](https://danielkummer.github.io/git-flow-cheatsheet/index.html) 8 | 9 | - Clone the repository in your namespace 10 | - Create a branch, commit on this branch 11 | - Open a pull request with from your branch to develop in this repository 12 | 13 | There are currently no CI/CD integration for the tests, to validate a pull request, the tests are run locally by the owner of the repository against a test Artifactory instance (contributors should do the same). -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 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 2018 Orange 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rtpy 2 | 3 | [![image](https://img.shields.io/pypi/v/rtpy.svg)](https://pypi.org/project/rtpy/) 4 | [![image](https://img.shields.io/pypi/pyversions/rtpy.svg)](https://pypi.org/project/rtpy/) 5 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) 6 | [![Documentation Status](https://readthedocs.org/projects/rtpy/badge/?version=latest)](https://rtpy.readthedocs.io/en/latest/?badge=latest) 7 | [![image](https://img.shields.io/pypi/l/rtpy.svg)](https://pypi.org/project/rtpy/) 8 | 9 | Python wrapper for the **[JFrog Artifactory REST API](https://www.jfrog.com/confluence/display/RTF/Artifactory+REST+API)** 10 |
11 |
12 | 13 | ## Documentation 14 | 15 | **[https://rtpy.rtfd.io](https://rtpy.rtfd.io)** 16 | 17 |
18 | 19 | ## Installation 20 | 21 | ```shell 22 | $ pip install rtpy 23 | ``` 24 |
25 | 26 | ## Usage 27 | 28 | ```python 29 | import rtpy 30 | 31 | # instantiate a rtpy.Rtpy object 32 | settings = {} 33 | settings["af_url"] = "http://..." 34 | settings["api_key"] = "123QWA..." 35 | # settings["username"] = "my_username" 36 | # settings["password"] = "my_password" 37 | 38 | af = rtpy.Rtpy(settings) 39 | 40 | # use a method 41 | r = af.system_and_configuration.system_health_ping() 42 | print(r) 43 | # OK 44 | ``` 45 |
46 | 47 | ## Running the tests 48 | 49 | ### Requirements : 50 | 51 | - Dependencies : see [tool.poetry.dependencies] and [tool.poetry.dev-dependencies] in [pyproject.toml](./pyproject.toml) 52 | - Artifactory instance (with a valid license) running 53 | 54 | **NEVER run the tests on a production instance!** 55 | 56 | 57 | ### Launch 58 | 59 | - Set the following environment variables: 60 | - AF_TEST_URL 61 | - AF_TEST_USERNAME 62 | - AF_TEST_PASSWORD 63 | 64 | The user must have admin privileges (it's API key will be revoked during the tests) 65 | - Clone the repository and launch the tests using the command : 66 | 67 | ```shell 68 | $ python -m pytest -v 69 | ``` -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. role:: raw-html-m2r(raw) 2 | :format: html 3 | 4 | 5 | rtpy 6 | ==== 7 | 8 | 9 | .. image:: https://img.shields.io/pypi/v/rtpy.svg 10 | :target: https://pypi.org/project/rtpy/ 11 | :alt: image 12 | 13 | 14 | .. image:: https://img.shields.io/pypi/pyversions/rtpy.svg 15 | :target: https://pypi.org/project/rtpy/ 16 | :alt: image 17 | 18 | 19 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 20 | :target: https://github.com/ambv/black 21 | :alt: Code style: black 22 | 23 | 24 | .. image:: https://readthedocs.org/projects/rtpy/badge/?version=latest 25 | :target: https://rtpy.readthedocs.io/en/latest/?badge=latest 26 | :alt: Documentation Status 27 | 28 | 29 | .. image:: https://img.shields.io/pypi/l/rtpy.svg 30 | :target: https://pypi.org/project/rtpy/ 31 | :alt: image 32 | 33 | 34 | Python wrapper for the **\ `JFrog Artifactory REST API `_\ ** 35 | :raw-html-m2r:`
` 36 | :raw-html-m2r:`
` 37 | 38 | Documentation 39 | ------------- 40 | 41 | **\ `https://rtpy.rtfd.io `_\ ** 42 | 43 | :raw-html-m2r:`
` 44 | 45 | Installation 46 | ------------ 47 | 48 | .. code-block:: shell 49 | 50 | $ pip install rtpy 51 | 52 | :raw-html-m2r:`
` 53 | 54 | Usage 55 | ----- 56 | 57 | .. code-block:: python 58 | 59 | import rtpy 60 | 61 | # instantiate a rtpy.Rtpy object 62 | settings = {} 63 | settings["af_url"] = "http://..." 64 | settings["api_key"] = "123QWA..." 65 | # settings["username"] = "my_username" 66 | # settings["password"] = "my_password" 67 | 68 | af = rtpy.Rtpy(settings) 69 | 70 | # use a method 71 | r = af.system_and_configuration.system_health_ping() 72 | print(r) 73 | # OK 74 | 75 | :raw-html-m2r:`
` 76 | 77 | Running the tests 78 | ----------------- 79 | 80 | Requirements : 81 | ^^^^^^^^^^^^^^ 82 | 83 | 84 | * Dependencies : see [tool.poetry.dependencies] and [tool.poetry.dev-dependencies] in `pyproject.toml <./pyproject.toml>`_ 85 | * Artifactory instance (with a valid license) running 86 | 87 | **NEVER run the tests on a production instance!** 88 | 89 | Launch 90 | ^^^^^^ 91 | 92 | 93 | * Set the following environment variables: 94 | 95 | * AF_TEST_URL 96 | * AF_TEST_USERNAME 97 | * AF_TEST_PASSWORD 98 | 99 | The user must have admin privileges (it's API key will be revoked during the tests) 100 | 101 | 102 | * Clone the repository and launch the tests using the command : 103 | 104 | .. code-block:: shell 105 | 106 | $ python -m pytest -v 107 | -------------------------------------------------------------------------------- /SUPPORTED_METHODS.md: -------------------------------------------------------------------------------- 1 | # Curently supported REST API methods 2 | 3 | - [BUILDS](#builds) 4 | - [ARTIFACTS AND STORAGE](#artifacts-and-storage) 5 | - [SEARCHES](#searches) 6 | - [SECURITY](#security) 7 | - [REPOSITORIES](#repositories) 8 | - [SYSTEM AND CONFIGURATION](#system-and-configuration) 9 | - [PLUGINS](#plugins) 10 | - [IMPORT AND EXPORT](#import-and-export) 11 | - [SUPPORT](#support) 12 | 13 | **Original complete JFrog Artifactory REST API method list : https://www.jfrog.com/confluence/display/RTF/Artifactory+REST+API** 14 | 15 | # BUILDS 16 | 17 | * [x] All Builds 18 | * [ ] Build Runs 19 | * [ ] Build Upload 20 | * [ ] Build Info 21 | * [ ] Builds Diff 22 | * [ ] Build Promotion 23 | * [ ] Promote Docker Image 24 | * [ ] Delete Builds 25 | * [ ] Build Rename 26 | * [ ] Push Build to Bintray 27 | * [ ] Distribute Build 28 | * [ ] Control Build Retention 29 | 30 | # ARTIFACTS AND STORAGE 31 | 32 | * [x] Folder Info 33 | * [x] File Info 34 | * [x] Get Storage Summary Info 35 | * [x] Item Last Modified 36 | * [x] File Statistics 37 | * [x] Item Properties 38 | * [x] Set Item Properties 39 | * [x] Delete Item Properties 40 | * [x] Set Item SHA256 Checksum 41 | * [x] Retrieve Artifact 42 | * [ ] Retrieve Latest Artifact 43 | * [ ] Retrieve Build Artifacts Archive 44 | * [x] Retrieve Folder or Repository Archive 45 | * [x] Trace Artifact Retrieval 46 | * [ ] Archive Entry Download 47 | * [x] Create Directory 48 | * [x] Deploy Artifact 49 | * [x] Deploy Artifact by Checksum 50 | * [ ] Deploy Artifacts from Archive 51 | * [ ] Push a Set of Artifacts to Bintray 52 | * [ ] Push Docker Tag to Bintray 53 | * [ ] Distribute Artifact 54 | * [ ] File Compliance Info 55 | * [x] Delete Item 56 | * [x] Copy Item 57 | * [x] Move Item 58 | * [ ] Get Repository Replication Configuration 59 | * [ ] Set Repository Replication Configuration 60 | * [ ] Update Repository Replication Configuration 61 | * [ ] Delete Repository Replication Configuration 62 | * [ ] Scheduled Replication Status 63 | * [ ] Pull/Push Replication 64 | * [ ] Pull/Push Replication (Deprecated) 65 | * [ ] Create or Replace Local Multi-push Replication 66 | * [ ] Update Local Multi-push Replication 67 | * [ ] Delete Local Multi-push Replication 68 | * [ ] Enable or Disable Multiple Replications 69 | * [ ] Get Global System Replication Configuration 70 | * [ ] Block System Replication 71 | * [ ] Unblock System Replication 72 | * [x] Artifact Sync Download 73 | * [ ] Folder Sync (Deprecated) 74 | * [x] File List 75 | * [x] Get Background Tasks 76 | * [x] Empty Trash Can 77 | * [x] Delete Item From Trash Can 78 | * [x] Restore Item from Trash Can 79 | * [x] Optimize System Storage 80 | * [ ] Get Puppet Modules 81 | * [ ] Get Puppet Module 82 | * [ ] Get Puppet Releases 83 | * [ ] Get Puppet Release 84 | 85 | # SEARCHES 86 | 87 | * [x] Artifactory Query Language (AQL) 88 | * [ ] Artifact Search (Quick Search) 89 | * [ ] Archive Entries Search (Class Search) 90 | * [ ] GAVC Search 91 | * [ ] Property Search 92 | * [ ] Checksum Search 93 | * [ ] Bad Checksum Search 94 | * [ ] Artifacts Not Downloaded Since 95 | * [ ] Artifacts With Date in Date Range 96 | * [ ] Artifacts Created in Date Range 97 | * [ ] Pattern Search 98 | * [ ] Builds for Dependency 99 | * [ ] License Search 100 | * [ ] Artifact Version Search 101 | * [ ] Artifact Latest Version Search Based on Layout 102 | * [ ] Artifact Latest Version Search Based on Properties 103 | * [ ] Build Artifacts Search 104 | * [x] List Docker Repositories 105 | * [x] List Docker Tags 106 | 107 | # SECURITY 108 | 109 | * [x] Get Users 110 | * [x] Get User Details 111 | * [ ] Get User Encrypted Password 112 | * [x] Create or Replace User 113 | * [x] Update User 114 | * [x] Delete User 115 | * [ ] Expire Password for a Single User 116 | * [ ] Expire Password for Multiple Users 117 | * [ ] Expire Password for All Users 118 | * [ ] Unexpire Password for a Single User 119 | * [ ] Change Password 120 | * [ ] Get Password Expiration Policy 121 | * [ ] Set Password Expiration Policy 122 | * [ ] Configure User Lock Policy 123 | * [ ] Retrieve User Lock Policy 124 | * [x] Get Locked Out Users 125 | * [x] Unlock Locked Out User 126 | * [x] Unlock Locked Out Users 127 | * [x] Unlock All Locked Out Users 128 | * [x] Create API Key 129 | * [x] Regenerate API Key 130 | * [x] Get API Key 131 | * [x] Revoke API Key 132 | * [x] Revoke User API Key 133 | * [ ] Revoke All API Keys 134 | * [x] Get Groups 135 | * [x] Get Group Details 136 | * [x] Create or Replace Group 137 | * [x] Update Group 138 | * [x] Delete Group 139 | * [x] Get Permission Targets 140 | * [x] Get Permission Target Details 141 | * [x] Create or Replace Permission Target 142 | * [x] Delete Permission Target 143 | * [x] Effective Item Permissions 144 | * [ ] Security Configuration (Deprecated) 145 | * [ ] Save Security Configuration (Deprecated) 146 | * [ ] Activate Artifactory Key Encryption 147 | * [ ] Deactivate Artifactory Key Encryption 148 | * [ ] Set GPG Public Key 149 | * [ ] Get GPG Public Key 150 | * [ ] Set GPG Private Key 151 | * [ ] Set GPG Pass Phrase 152 | * [ ] Create Token 153 | * [ ] Refresh Token 154 | * [ ] Revoke Token 155 | * [ ] Get Service ID 156 | * [ ] Get Certificates 157 | * [ ] Add Certificate 158 | * [ ] Delete Certificate 159 | 160 | # REPOSITORIES 161 | 162 | * [x] Get Repositories 163 | * [x] Repository Configuration 164 | * [x] Create Repository 165 | * [x] Update Repository Configuration 166 | * [x] Delete Repository 167 | * [x] Calculate YUM Repository Metadata 168 | * [x] Calculate NuGet Repository Metadata 169 | * [x] Calculate Npm Repository Metadata 170 | * [x] Calculate Maven Index 171 | * [x] Calculate Maven Metadata 172 | * [x] Calculate Debian Repository Metadata 173 | * [x] Calculate Opkg Repository Metadata 174 | * [x] Calculate Bower Index 175 | * [x] Calculate Helm Chart Index 176 | 177 | # SYSTEM AND CONFIGURATION 178 | 179 | * [x] System Info 180 | * [x] System Health Ping 181 | * [ ] Verify Connection 182 | * [x] General Configuration 183 | * [ ] Save General Configuration 184 | * [ ] Update Custom URL Base 185 | * [x] License Information 186 | * [x] Install License 187 | * [x] Version and Add-ons information 188 | * [x] Get Reverse Proxy Configuration 189 | * [ ] Update Reverse Proxy Configuration 190 | * [x] Get Reverse Proxy Snippet 191 | 192 | # PLUGINS 193 | 194 | * [ ] Execute Plugin Code 195 | * [ ] Retrieve Plugin Info 196 | * [ ] Retrieve Plugin Info Of A Certain Type 197 | * [ ] Retrieve Build Staging Strategy 198 | * [ ] Execute Build Promotion 199 | * [ ] Reload Plugins 200 | 201 | # IMPORT AND EXPORT 202 | 203 | * [ ] Import Repository Content 204 | * [ ] Import System Settings Example 205 | * [ ] Full System Import 206 | * [ ] Export System Settings Example 207 | * [ ] Export System 208 | 209 | # SUPPORT 210 | 211 | * [x] Create Bundle 212 | * [x] List Bundles 213 | * [x] Get Bundle 214 | * [x] Delete Bundle -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = rtpy 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import sys 4 | 5 | sys.path.append("../") 6 | 7 | # 8 | # Configuration file for the Sphinx documentation builder. 9 | # 10 | # This file does only contain a selection of the most common options. For a 11 | # full list see the documentation: 12 | # http://www.sphinx-doc.org/en/master/config 13 | 14 | # -- Path setup -------------------------------------------------------------- 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- Project information ----------------------------------------------------- 25 | 26 | project = "rtpy" 27 | copyright = "2018, Orange" 28 | author = "Guillaume Renault" 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # If your documentation needs a minimal Sphinx version, state it here. 33 | # 34 | # needs_sphinx = '1.0' 35 | 36 | # Add any Sphinx extension module names here, as strings. They can be 37 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 38 | # ones. 39 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinxcontrib.napoleon"] 40 | 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ["_templates"] 44 | 45 | # The suffix(es) of source filenames. 46 | # You can specify multiple suffix as a list of string: 47 | # 48 | # source_suffix = ['.rst', '.md'] 49 | source_suffix = ".rst" 50 | 51 | # The master toctree document. 52 | master_doc = "index" 53 | 54 | # The language for content autogenerated by Sphinx. Refer to documentation 55 | # for a list of supported languages. 56 | # 57 | # This is also used if you do content translation via gettext catalogs. 58 | # Usually you set "language" from the command line for these cases. 59 | language = None 60 | 61 | # List of patterns, relative to source directory, that match files and 62 | # directories to ignore when looking for source files. 63 | # This pattern also affects html_static_path and html_extra_path . 64 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 65 | 66 | # The name of the Pygments (syntax highlighting) style to use. 67 | pygments_style = "sphinx" 68 | 69 | # -- Options for HTML output ------------------------------------------------- 70 | 71 | # The theme to use for HTML and HTML Help pages. See the documentation for 72 | # a list of builtin themes. 73 | # 74 | html_theme = "sphinx_rtd_theme" 75 | 76 | html_theme_options = {"display_version": False} 77 | # Theme options are theme-specific and customize the look and feel of a theme 78 | # further. For a list of options available for each theme, see the 79 | # documentation. 80 | # 81 | # html_theme_options = {} 82 | 83 | # Add any paths that contain custom static files (such as style sheets) here, 84 | # relative to this directory. They are copied after the builtin static files, 85 | # so a file named "default.css" will overwrite the builtin "default.css". 86 | html_static_path = ["_static"] 87 | 88 | # Custom sidebar templates, must be a dictionary that maps document names 89 | # to template names. 90 | # 91 | # The default sidebars (for documents that don't match any pattern) are 92 | # defined by theme itself. Builtin themes are using these templates by 93 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 94 | # 'searchbox.html']``. 95 | # 96 | # html_sidebars = {} 97 | 98 | 99 | # -- Options for HTMLHelp output --------------------------------------------- 100 | 101 | # Output file base name for HTML help builder. 102 | htmlhelp_basename = "rtpydoc" 103 | 104 | 105 | # -- Options for LaTeX output ------------------------------------------------ 106 | 107 | latex_elements = { 108 | # The paper size ('letterpaper' or 'a4paper'). 109 | # 110 | # 'papersize': 'letterpaper', 111 | # The font size ('10pt', '11pt' or '12pt'). 112 | # 113 | # 'pointsize': '10pt', 114 | # Additional stuff for the LaTeX preamble. 115 | # 116 | # 'preamble': '', 117 | # Latex figure (float) alignment 118 | # 119 | # 'figure_align': 'htbp', 120 | } 121 | 122 | # Grouping the document tree into LaTeX files. List of tuples 123 | # (source start file, target name, title, 124 | # author, documentclass [howto, manual, or own class]). 125 | latex_documents = [ 126 | (master_doc, "rtpy.tex", "rtpy Documentation", "Guillaume Renault", "manual") 127 | ] 128 | 129 | 130 | # -- Options for manual page output ------------------------------------------ 131 | 132 | # One entry per manual page. List of tuples 133 | # (source start file, name, description, authors, manual section). 134 | man_pages = [(master_doc, "rtpy", "rtpy Documentation", [author], 1)] 135 | 136 | 137 | # -- Options for Texinfo output ---------------------------------------------- 138 | 139 | # Grouping the document tree into Texinfo files. List of tuples 140 | # (source start file, target name, title, author, 141 | # dir menu entry, description, category) 142 | texinfo_documents = [ 143 | ( 144 | master_doc, 145 | "rtpy", 146 | "rtpy Documentation", 147 | author, 148 | "rtpy", 149 | "One line description of project.", 150 | "Miscellaneous", 151 | ) 152 | ] 153 | -------------------------------------------------------------------------------- /docs/dev/package.rst: -------------------------------------------------------------------------------- 1 | rtpy package 2 | ============ 3 | 4 | rtpy.__init__.py 5 | ^^^^^^^^^^^^^^^^ 6 | .. automodule:: rtpy.__init__ 7 | :members: 8 | 9 | rtpy.artifacts_and_storage.py 10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 11 | .. automodule:: rtpy.artifacts_and_storage 12 | :members: 13 | 14 | rtpy.builds.py 15 | ^^^^^^^^^^^^^^ 16 | .. automodule:: rtpy.builds 17 | :members: 18 | 19 | rtpy.import_and_export.py 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 21 | .. automodule:: rtpy.import_and_export 22 | :members: 23 | 24 | rtpy.plugins.py 25 | ^^^^^^^^^^^^^^^ 26 | .. automodule:: rtpy.plugins 27 | :members: 28 | 29 | rtpy.repositories.py 30 | ^^^^^^^^^^^^^^^^^^^^ 31 | .. automodule:: rtpy.repositories 32 | :members: 33 | 34 | rtpy.rtpy.py 35 | ^^^^^^^^^^^^ 36 | .. automodule:: rtpy.rtpy 37 | :members: 38 | 39 | rtpy.searches.py 40 | ^^^^^^^^^^^^^^^^ 41 | .. automodule:: rtpy.searches 42 | :members: 43 | 44 | rtpy.support.py 45 | ^^^^^^^^^^^^^^^ 46 | .. automodule:: rtpy.support 47 | :members: 48 | 49 | rtpy.system_and_configuration.py 50 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 51 | .. automodule:: rtpy.system_and_configuration 52 | :members: 53 | 54 | rtpy.tools.py 55 | ^^^^^^^^^^^^^ 56 | .. automodule:: rtpy.tools 57 | :members: 58 | 59 | rtpy.xray.py 60 | ^^^^^^^^^^^^ 61 | .. automodule:: rtpy.xray 62 | :members: -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. rtpy documentation master file, created by 2 | sphinx-quickstart on Thu Sep 6 09:27:25 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to rtpy's documentation! 7 | ================================ 8 | 9 | .. toctree:: 10 | 11 | user/installation 12 | user/getting_started 13 | user/examples 14 | user/responses 15 | user/error_handling 16 | user/general_tips 17 | user/supported_api_methods 18 | user/changelog 19 | dev/package 20 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=rtpy 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/user/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../CHANGELOG.rst -------------------------------------------------------------------------------- /docs/user/error_handling.rst: -------------------------------------------------------------------------------- 1 | Error handling 2 | ============== 3 | 4 | UserSettingsError 5 | ----------------- 6 | 7 | Standalone error, when the instantiation of the Rtpy object fails due to incorrect settings 8 | 9 | .. code-block:: python 10 | 11 | try: 12 | af = rtpy.Rtpy(settings) 13 | except rtpy.UserSettingsError: 14 | # Do stuff 15 | 16 | 17 | AfApiError 18 | ---------- 19 | 20 | When the status code is 4xx-5xx and the API sends a well formed JSON 21 | 22 | The error has specific attributes 23 | 24 | .. code-block:: python 25 | 26 | try: 27 | r = af.category.method_xyz() 28 | 29 | except af.AfApiError as error: 30 | 31 | # All the attributes of the error 32 | print(dir(error)) 33 | 34 | # Rtpy attributes for the error 35 | print(error.api_method) 36 | print(error.url) 37 | print(error.verb) 38 | print(error.status_code) 39 | print(error.message) 40 | print(error.print_message) 41 | 42 | if error.status_code == 404: 43 | # Do stuff 44 | 45 | if error.status_code == 403: 46 | # Do stuff 47 | 48 | 49 | MalformedAfApiError 50 | ------------------- 51 | 52 | When the status code is 4xx-5xx and the API sends a malformed JSON 53 | 54 | .. code-block:: python 55 | 56 | try: 57 | # The JSON is currently malformed when the API sends an error when using this method 58 | af.system_and_configuration.install_license(params) 59 | except af.MalformedAfApiError: 60 | # Do stuff 61 | 62 | 63 | RtpyError 64 | --------- 65 | 66 | When a method is called and parameters are missing or incorrect 67 | 68 | .. code-block:: python 69 | 70 | try: 71 | # Providing "" for artifact_path will raise the RtpyError 72 | af.artifacts_and_storage.retrieve_artifact("repo_key", "") 73 | except af.RtpyError: 74 | # Do stuff -------------------------------------------------------------------------------- /docs/user/general_tips.rst: -------------------------------------------------------------------------------- 1 | General tips 2 | ============ 3 | 4 | Verify connectivity with Rtpy self call 5 | --------------------------------------- 6 | Call an instantiated Rtby object to verify connectivity (binding to the system health ping method) 7 | 8 | .. code-block:: python 9 | 10 | import rtpy 11 | 12 | # instantiate a Rtpy object 13 | settings = {} 14 | settings["af_url"] = "http://..." 15 | settings["api_key"] = "123QWA..." 16 | # settings["username"] = "my_username" 17 | # settings["password"] = "my_password" 18 | 19 | af = rtpy.Rtpy(settings) 20 | r = af() 21 | # or r = af.system_and_configuration.system_health_ping() 22 | # print(r) 23 | # OK 24 | 25 | Environment variables as settings 26 | --------------------------------- 27 | 28 | Use environement variables for the api_key and af_url in the settings dictionary 29 | 30 | .. code-block:: python 31 | 32 | settings = {} 33 | settings["af_url"] = os.environ["ARTIFACTORY_URL"] # URL of the AF instance 34 | settings["api_key"] = os.environ["ARTIFACTORY_API_KEY"] # User/Admin API key in the given AF instance 35 | 36 | af = rtpy.Rtpy(settings) 37 | 38 | Overriding settings 39 | ------------------- 40 | 41 | All the settings can be overridden for a **single function call** (original settings are restored when the call is over) 42 | This is useful for debugging (verbose level) or not raising errors (raw_response). It can also be used to provide different credentials 43 | 44 | .. code-block:: python 45 | 46 | r = af.category.method_xyz(settings={"raw_response" : True, "verbose_level" : 1}) 47 | 48 | r = af.category.method_xyz(settings={"verbose_level" : 1}) 49 | 50 | r = af.category.method_xyz(settings={"api_key" : "123ABC..."}) 51 | 52 | session = requests.Session() 53 | session.verify = "path/to/ca_bundle.crt" 54 | r = af.category.method_xyz(settings={"session" : session}) 55 | 56 | 57 | pretty-print 58 | ------------ 59 | 60 | Use the pprint package to print the json responses in a more readable way 61 | 62 | .. code-block:: python 63 | 64 | from pprint import pprint 65 | ... 66 | r = af.category.method_xyz() 67 | pprint(r) 68 | 69 | 70 | Json file to Python dictionary 71 | ------------------------------ 72 | 73 | Convert a json file to a python dictionnary using the json_to_dict method 74 | 75 | .. code-block:: python 76 | 77 | my_dict = rtpy.json_to_dict(json_file_path) -------------------------------------------------------------------------------- /docs/user/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | Instantiate a rtpy.Rtpy object 5 | ------------------------------ 6 | 7 | A rtpy.Rtpy object is used to make all the API calls. 8 | To be instantiated the rtpy.Rtpy class only takes a **Python dictionary as first positional argument**. 9 | This dictionary contains the user's settings such as API key and Artifactory instance URL. 10 | 11 | Mandatory keys 12 | ^^^^^^^^^^^^^^ 13 | 14 | 15 | * **"af_url"** : URL of the AF instance (starting with http(s)://) 16 | * **"api_key"** or **"username"** and **"password"** : API key or username and password for the user in the Artifactory instance 17 | 18 | .. code-block:: python 19 | 20 | import rtpy 21 | 22 | # instantiate a rtpy.Rtpy object 23 | settings = {} 24 | settings["af_url"] = "http://..." 25 | settings["api_key"] = "123QWA..." 26 | # settings["username"] = "my_username" 27 | # settings["password"] = "my_password" 28 | 29 | af = rtpy.Rtpy(settings) 30 | 31 | # use a method 32 | r = af.system_and_configuration.system_health_ping() 33 | print(r) 34 | # OK 35 | 36 | 37 | Optional keys 38 | ^^^^^^^^^^^^^ 39 | 40 | 41 | * **"verbose_level"** : 0/1 42 | 43 | * The desired verbose level, 0 for nothing, 1 to print performed operations 44 | * 0 if not not provided 45 | 46 | * **"raw_response"** : False/True 47 | 48 | * True will return a `requests.Response object `_ and the errors will not be automatically raised 49 | * False will return a python object 50 | * False if not provided 51 | 52 | * **"session"**\ : `requests.Session `_ object 53 | 54 | * rtpy uses a `requests.Session `_ object to make calls to the Artifactory API endpoint. A custom can be provided session object when creating a rtpy.Rtpy object for advanced HTTP configurations, proxies, SSL... 55 | * request.Session() if not provided 56 | 57 | .. code-block:: python 58 | 59 | import requests 60 | import rtpy 61 | 62 | settings["verbose_level"] = 0/1 63 | settings["raw_response"] = False/True 64 | 65 | # SSL : custom CA bundle example 66 | session = requests.Session() 67 | session.verify = "path/to/ca_bundle.crt" 68 | settings['session'] = session 69 | 70 | af = rtpy.Rtpy(settings) 71 | 72 | r = af.system_and_configuration.system_health_ping() 73 | print(r) 74 | # OK -------------------------------------------------------------------------------- /docs/user/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | ``rtpy`` is compatible with Python 2.7 and 3.4+. 5 | 6 | Use :command:`pip` to install the latest stable version of ``rtpy``: 7 | 8 | .. code-block:: console 9 | 10 | $ pip install rtpy -------------------------------------------------------------------------------- /docs/user/responses.rst: -------------------------------------------------------------------------------- 1 | Responses 2 | ========= 3 | 4 | The response when using methods can be a : 5 | 6 | * 7 | When "raw_response" is **False** (default): 8 | 9 | * python dictionary or list (a converted json output) (most cases) 10 | * unicode string (\ `(requests.Response) `_.text) (if the json content can't be decoded/is missing/isn't expected) 11 | 12 | * 13 | When "raw_response" is **True** : 14 | 15 | * `requests.Response Object `_ -------------------------------------------------------------------------------- /docs/user/supported_api_methods.rst: -------------------------------------------------------------------------------- 1 | Supported API methods 2 | =================================== 3 | 4 | Original complete JFrog Artifactory REST API method list : ``_ 5 | 6 | BUILDS 7 | ------ 8 | 9 | 10 | * [x] All Builds 11 | * [ ] Build Runs 12 | * [ ] Build Upload 13 | * [ ] Build Info 14 | * [ ] Builds Diff 15 | * [ ] Build Promotion 16 | * [ ] Promote Docker Image 17 | * [ ] Delete Builds 18 | * [ ] Build Rename 19 | * [ ] Push Build to Bintray 20 | * [ ] Distribute Build 21 | * [ ] Control Build Retention 22 | 23 | ARTIFACTS AND STORAGE 24 | --------------------- 25 | 26 | 27 | * [x] Folder Info 28 | * [x] File Info 29 | * [x] Get Storage Summary Info 30 | * [x] Item Last Modified 31 | * [x] File Statistics 32 | * [x] Item Properties 33 | * [x] Set Item Properties 34 | * [x] Delete Item Properties 35 | * [x] Set Item SHA256 Checksum 36 | * [x] Retrieve Artifact 37 | * [ ] Retrieve Latest Artifact 38 | * [ ] Retrieve Build Artifacts Archive 39 | * [x] Retrieve Folder or Repository Archive 40 | * [x] Trace Artifact Retrieval 41 | * [ ] Archive Entry Download 42 | * [x] Create Directory 43 | * [x] Deploy Artifact 44 | * [x] Deploy Artifact by Checksum 45 | * [ ] Deploy Artifacts from Archive 46 | * [ ] Push a Set of Artifacts to Bintray 47 | * [ ] Push Docker Tag to Bintray 48 | * [ ] Distribute Artifact 49 | * [ ] File Compliance Info 50 | * [x] Delete Item 51 | * [x] Copy Item 52 | * [x] Move Item 53 | * [ ] Get Repository Replication Configuration 54 | * [ ] Set Repository Replication Configuration 55 | * [ ] Update Repository Replication Configuration 56 | * [ ] Delete Repository Replication Configuration 57 | * [ ] Scheduled Replication Status 58 | * [ ] Pull/Push Replication 59 | * [ ] Pull/Push Replication (Deprecated) 60 | * [ ] Create or Replace Local Multi-push Replication 61 | * [ ] Update Local Multi-push Replication 62 | * [ ] Delete Local Multi-push Replication 63 | * [ ] Enable or Disable Multiple Replications 64 | * [ ] Get Global System Replication Configuration 65 | * [ ] Block System Replication 66 | * [ ] Unblock System Replication 67 | * [x] Artifact Sync Download 68 | * [ ] Folder Sync (Deprecated) 69 | * [x] File List 70 | * [x] Get Background Tasks 71 | * [x] Empty Trash Can 72 | * [x] Delete Item From Trash Can 73 | * [x] Restore Item from Trash Can 74 | * [x] Optimize System Storage 75 | * [ ] Get Puppet Modules 76 | * [ ] Get Puppet Module 77 | * [ ] Get Puppet Releases 78 | * [ ] Get Puppet Release 79 | 80 | SEARCHES 81 | -------- 82 | 83 | 84 | * [x] Artifactory Query Language (AQL) 85 | * [ ] Artifact Search (Quick Search) 86 | * [ ] Archive Entries Search (Class Search) 87 | * [ ] GAVC Search 88 | * [ ] Property Search 89 | * [ ] Checksum Search 90 | * [ ] Bad Checksum Search 91 | * [ ] Artifacts Not Downloaded Since 92 | * [ ] Artifacts With Date in Date Range 93 | * [ ] Artifacts Created in Date Range 94 | * [ ] Pattern Search 95 | * [ ] Builds for Dependency 96 | * [ ] License Search 97 | * [ ] Artifact Version Search 98 | * [ ] Artifact Latest Version Search Based on Layout 99 | * [ ] Artifact Latest Version Search Based on Properties 100 | * [ ] Build Artifacts Search 101 | * [x] List Docker Repositories 102 | * [x] List Docker Tags 103 | 104 | SECURITY 105 | -------- 106 | 107 | 108 | * [x] Get Users 109 | * [x] Get User Details 110 | * [ ] Get User Encrypted Password 111 | * [x] Create or Replace User 112 | * [x] Update User 113 | * [x] Delete User 114 | * [ ] Expire Password for a Single User 115 | * [ ] Expire Password for Multiple Users 116 | * [ ] Expire Password for All Users 117 | * [ ] Unexpire Password for a Single User 118 | * [ ] Change Password 119 | * [ ] Get Password Expiration Policy 120 | * [ ] Set Password Expiration Policy 121 | * [ ] Configure User Lock Policy 122 | * [ ] Retrieve User Lock Policy 123 | * [x] Get Locked Out Users 124 | * [x] Unlock Locked Out User 125 | * [x] Unlock Locked Out Users 126 | * [x] Unlock All Locked Out Users 127 | * [x] Create API Key 128 | * [x] Regenerate API Key 129 | * [x] Get API Key 130 | * [x] Revoke API Key 131 | * [x] Revoke User API Key 132 | * [ ] Revoke All API Keys 133 | * [x] Get Groups 134 | * [x] Get Group Details 135 | * [x] Create or Replace Group 136 | * [x] Update Group 137 | * [x] Delete Group 138 | * [x] Get Permission Targets 139 | * [x] Get Permission Target Details 140 | * [x] Create or Replace Permission Target 141 | * [x] Delete Permission Target 142 | * [x] Effective Item Permissions 143 | * [ ] Security Configuration (Deprecated) 144 | * [ ] Save Security Configuration (Deprecated) 145 | * [ ] Activate Artifactory Key Encryption 146 | * [ ] Deactivate Artifactory Key Encryption 147 | * [ ] Set GPG Public Key 148 | * [ ] Get GPG Public Key 149 | * [ ] Set GPG Private Key 150 | * [ ] Set GPG Pass Phrase 151 | * [ ] Create Token 152 | * [ ] Refresh Token 153 | * [ ] Revoke Token 154 | * [ ] Get Service ID 155 | * [ ] Get Certificates 156 | * [ ] Add Certificate 157 | * [ ] Delete Certificate 158 | 159 | REPOSITORIES 160 | ------------ 161 | 162 | 163 | * [x] Get Repositories 164 | * [x] Repository Configuration 165 | * [x] Create Repository 166 | * [x] Update Repository Configuration 167 | * [x] Delete Repository 168 | * [x] Calculate YUM Repository Metadata 169 | * [x] Calculate NuGet Repository Metadata 170 | * [x] Calculate Npm Repository Metadata 171 | * [x] Calculate Maven Index 172 | * [x] Calculate Maven Metadata 173 | * [x] Calculate Debian Repository Metadata 174 | * [x] Calculate Opkg Repository Metadata 175 | * [x] Calculate Bower Index 176 | * [x] Calculate Helm Chart Index 177 | 178 | SYSTEM AND CONFIGURATION 179 | ------------------------ 180 | 181 | 182 | * [x] System Info 183 | * [x] System Health Ping 184 | * [ ] Verify Connection 185 | * [x] General Configuration 186 | * [ ] Save General Configuration 187 | * [ ] Update Custom URL Base 188 | * [x] License Information 189 | * [x] Install License 190 | * [x] Version and Add-ons information 191 | * [x] Get Reverse Proxy Configuration 192 | * [ ] Update Reverse Proxy Configuration 193 | * [x] Get Reverse Proxy Snippet 194 | 195 | PLUGINS 196 | ------- 197 | 198 | 199 | * [ ] Execute Plugin Code 200 | * [ ] Retrieve Plugin Info 201 | * [ ] Retrieve Plugin Info Of A Certain Type 202 | * [ ] Retrieve Build Staging Strategy 203 | * [ ] Execute Build Promotion 204 | * [ ] Reload Plugins 205 | 206 | IMPORT AND EXPORT 207 | ----------------- 208 | 209 | 210 | * [ ] Import Repository Content 211 | * [ ] Import System Settings Example 212 | * [ ] Full System Import 213 | * [ ] Export System Settings Example 214 | * [ ] Export System 215 | 216 | SUPPORT 217 | ------- 218 | 219 | 220 | * [ ] Create Bundle 221 | * [ ] List Bundles 222 | * [ ] Get Bundle 223 | * [ ] Delete Bundle 224 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "rtpy" 3 | version = "1.4.9" 4 | description = "Python wrapper for the JFrog Artifactory REST API." 5 | authors = ["Guillaume Renault "] 6 | license = "Apache-2.0" 7 | readme = 'README.md' 8 | homepage = "https://github.com/Orange-OpenSource/rtpy" 9 | keywords = ['artifactory', 'rest', 'api', 'wrapper'] 10 | 11 | [tool.poetry.dependencies] 12 | python = "~2.7 || ^3.4" 13 | requests = "^2.18.4" 14 | 15 | [tool.poetry.dev-dependencies] 16 | poetry-setup = "^0.3" 17 | pytest = "^3.6" 18 | coverage = "^4.5" 19 | tox = "^3.2" 20 | tox-pyenv = "^1.1" 21 | setuptools = "^40.0" 22 | Sphinx="^1.8.2" 23 | sphinxcontrib-napoleon = "^0.7" 24 | sphinx_rtd_theme = "^0.4" 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # IMPORTANT: this file is autogenerated. Do not edit it manually. 2 | Sphinx (>=1.8.2,<2.0.0) 3 | coverage (>=4.5,<5.0) 4 | poetry-setup (>=0.3,<0.4) 5 | pytest (>=3.6,<4.0) 6 | requests (>=2.18.4,<3.0.0) 7 | setuptools (>=40.0,<41.0) 8 | sphinx_rtd_theme (>=0.4,<0.5) 9 | sphinxcontrib-napoleon (>=0.7,<0.8) 10 | tox (>=3.2,<4.0) 11 | tox-pyenv (>=1.1,<2.0) 12 | -------------------------------------------------------------------------------- /rtpy/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Exposed objects/functions to end user.""" 10 | 11 | from .rtpy import Rtpy 12 | from .tools import json_to_dict, UserSettingsError 13 | 14 | __all__ = ["Rtpy", "json_to_dict", "UserSettingsError"] 15 | -------------------------------------------------------------------------------- /rtpy/artifacts_and_storage.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the ARTIFACTS AND STORAGE REST API Methods category.""" 10 | 11 | from .tools import RtpyBase 12 | 13 | 14 | class RtpyArtifactsAndStorage(RtpyBase): 15 | """ARTIFACTS AND STORAGE methods category.""" 16 | 17 | def folder_info(self, repo_key, folder_path, **kwargs): 18 | """ 19 | Folder Info. 20 | 21 | For virtual use, the virtual repository returns the unified children. 22 | Supported by local, local-cached and virtual repositories. 23 | 24 | Parameters 25 | ---------- 26 | repo_key: str 27 | Key of the repository 28 | folder_path: str 29 | The path of the folder in the repository 30 | **kwargs 31 | Keyword arguments 32 | 33 | """ 34 | api_method = self._category + "Folder Info" 35 | target = self._prefix + repo_key 36 | target = self._add_forward_slash_if_not_empty(target, folder_path) 37 | return self._request("GET", target, api_method, kwargs) 38 | 39 | def file_info(self, repo_key, file_path, **kwargs): 40 | """ 41 | File Info. 42 | 43 | For virtual use the virtual repository returns the resolved file. 44 | Supported by local, local-cached and virtual repositories. 45 | 46 | Parameters 47 | ---------- 48 | repo_key: str 49 | Key of the repository 50 | file_path: str 51 | The path of the file in the repository 52 | **kwargs 53 | Keyword arguments 54 | 55 | """ 56 | api_method = self._category + "File Info" 57 | target = self._prefix + repo_key 58 | target = self._add_forward_slash_if_not_empty(target, file_path) 59 | return self._request("GET", target, api_method, kwargs) 60 | 61 | def get_storage_summary_info(self, **kwargs): 62 | """ 63 | Return storage summary information. 64 | 65 | regarding binaries file store and repositories. 66 | 67 | Parameters 68 | ---------- 69 | **kwargs 70 | Keyword arguments 71 | 72 | """ 73 | api_method = self._category + "Get Storage Summary Info" 74 | target = "storageinfo" 75 | return self._request("GET", target, api_method, kwargs) 76 | 77 | def item_last_modified(self, repo_key, item_path, **kwargs): 78 | """ 79 | Retrieve the last modified item at the given path. 80 | 81 | If the given path is a folder, 82 | the latest last modified item is searched for recursively. 83 | Supported by local and local-cached repositories. 84 | 85 | Parameters 86 | ---------- 87 | repo_key: str 88 | Key of the repository 89 | item_path: str 90 | The path of the item in the repository 91 | **kwargs 92 | Keyword arguments 93 | 94 | """ 95 | api_method = self._category + "Item Last Modified" 96 | target = self._prefix + repo_key 97 | target = self._add_forward_slash_if_not_empty(target, item_path) 98 | target = target + "?lastModified" 99 | return self._request("GET", target, api_method, kwargs) 100 | 101 | def file_statistics(self, repo_key, item_path, **kwargs): 102 | """ 103 | Item statistics. 104 | 105 | Record the number of times an item was downloaded, 106 | last download date and last downloader. 107 | Supported by local and local-cached repositories. 108 | 109 | Parameters 110 | ---------- 111 | repo_key: str 112 | Key of the repository 113 | item_path: str 114 | The path of the item in the repository 115 | **kwargs 116 | Keyword arguments 117 | 118 | """ 119 | api_method = self._category + "File Statistics" 120 | target = self._prefix + repo_key 121 | target = self._add_forward_slash_if_not_empty(target, item_path) 122 | target = target + "?stats" 123 | return self._request("GET", target, api_method, kwargs) 124 | 125 | def item_properties(self, repo_key, item_path, properties=None, **kwargs): 126 | """ 127 | Item Properties. Optionally return only the properties requested. 128 | 129 | Parameters 130 | ---------- 131 | repo_key: str 132 | Key of the repository 133 | item_path: str 134 | The path of the item in the repository 135 | properties: str 136 | String of properties 137 | **kwargs 138 | Keyword arguments 139 | 140 | """ 141 | api_method = self._category + "Item Properties" 142 | target = self._prefix + repo_key 143 | target = self._add_forward_slash_if_not_empty(target, item_path) 144 | target = target + "?properties" 145 | if properties: 146 | target = target + "=" + properties 147 | return self._request("GET", target, api_method, kwargs) 148 | 149 | def set_item_properties( 150 | self, repo_key, item_path, properties, options=None, **kwargs 151 | ): 152 | """ 153 | Attach properties to an item (file or folder). 154 | 155 | When a folder is used property attachment is recursive by default. 156 | Supported by local and local-cached repositories 157 | 158 | Parameters 159 | ---------- 160 | repo_key: str 161 | Key of the repository 162 | item_path: str 163 | The path of the item in the repository 164 | properties: str 165 | String of properties 166 | options: str, optional 167 | String of options 168 | **kwargs 169 | Keyword arguments 170 | 171 | """ 172 | api_method = self._category + "Set Item Properties" 173 | target = self._prefix + repo_key 174 | target = self._add_forward_slash_if_not_empty(target, item_path) 175 | target = target + "?properties=" + properties 176 | target = self._append_to_string(target, options) 177 | return self._request("PUT", target, api_method, kwargs) 178 | 179 | def delete_item_properties( 180 | self, repo_key, item_path, properties, options=None, **kwargs 181 | ): 182 | """ 183 | Delete the specified properties from an item (file or folder). 184 | 185 | When a folder is used property removal is recursive by default. 186 | Supported by local and local-cached repositories. 187 | 188 | Parameters 189 | ---------- 190 | repo_key: str 191 | Key of the repository 192 | item_path: str 193 | The path of the item in the repository 194 | properties: str 195 | String of properties 196 | options: str, optional 197 | String of options 198 | **kwargs 199 | Keyword arguments 200 | 201 | """ 202 | api_method = self._category + "Delete Item Properties" 203 | target = self._prefix + repo_key 204 | target = self._add_forward_slash_if_not_empty(target, item_path) 205 | target = target + "?properties=" + properties 206 | target = self._append_to_string(target, options) 207 | return self._request("DELETE", target, api_method, kwargs) 208 | 209 | def set_item_sha256_checksum(self, params, **kwargs): 210 | """ 211 | Calculate an artifact's SHA256 checksum and attaches it as a property. 212 | 213 | (with key "sha256"). If the artifact is a folder, 214 | then recursively calculates the SHA256 of each item in 215 | the folder and attaches the property to each item. 216 | 217 | Parameters 218 | ---------- 219 | params: dict 220 | Dictionary comprised of {"repo_key": str, "path": str} 221 | **kwargs 222 | Keyword arguments 223 | 224 | """ 225 | api_method = self._category + "Set Item SHA256 Checksum" 226 | target = "checksum/sha256" 227 | params["Content-Type"] = "application/json" 228 | return self._request("POST", target, api_method, kwargs, params=params) 229 | 230 | def retrieve_artifact(self, repo_key, artifact_path, **kwargs): 231 | """ 232 | Retrieve an artifact from the specified destination. 233 | 234 | Parameters 235 | ---------- 236 | repo_key: str 237 | Key of the repository 238 | artifact_path: str 239 | Path of the artifact in the repository 240 | **kwargs 241 | Keyword arguments 242 | 243 | """ 244 | api_method = self._category + "Retrieve Artifact" 245 | if artifact_path == "": 246 | message = "artifact path can't be empty !" 247 | raise self.RtpyError(message) 248 | target = "/" + repo_key + "/" + artifact_path 249 | return self._request( 250 | "GET", target, api_method, kwargs, byte_output=True, no_api=True 251 | ) 252 | 253 | # Unsupported methods 254 | # def retrieve_latest_artifact(): 255 | # def retrieve_build_artifacts_archive(): 256 | 257 | def retrieve_folder_or_repository_archive( 258 | self, repo_key, path, archive_type, include_checksums=False, **kwargs 259 | ): 260 | """ 261 | Retrieve an archive file (supports zip/tar/tar.gz/tgz). 262 | 263 | containing all the artifacts that reside under the specified path 264 | (folder or repository root). Requires Enable Folder Download to be set. 265 | 266 | Parameters 267 | ---------- 268 | repo_key: str 269 | Key of the repository 270 | path: str 271 | Path of the folder in the repository 272 | archive_type: str 273 | Type of archive 274 | include_checksums: bool, optional 275 | True to include checksums, False by default 276 | **kwargs 277 | Keyword arguments 278 | 279 | """ 280 | api_method = self._category + "Retrieve Folder or Repository Archive" 281 | if archive_type not in ["zip", "tar", "tar.gz", "tgz"]: 282 | message = "archive_type must be zip, tar, tar.gz or tgz !" 283 | raise self.RtpyError(message) 284 | target = "archive/download/" + repo_key 285 | target = self._add_forward_slash_if_not_empty(target, path) 286 | target = target + "?archiveType=" + archive_type 287 | 288 | if include_checksums: 289 | target = target + "&includeChecksumFiles=true" 290 | if not include_checksums: 291 | target = target + "&includeChecksumFiles=false" 292 | return self._request("GET", target, api_method, kwargs, byte_output=True) 293 | 294 | def trace_artifact_retrieval(self, repo_key, item_path, **kwargs): 295 | """ 296 | Simulate an artifact retrieval request from the specified. 297 | 298 | location and returns verbose output about the resolution process. 299 | 300 | Parameters 301 | ---------- 302 | repo_key: str 303 | Key of the repository 304 | item_path: str 305 | The path of the item in the repository 306 | **kwargs 307 | Keyword arguments 308 | 309 | """ 310 | api_method = self._category + "Trace Artifact Retrieval" 311 | target = "/" + repo_key + "/" + item_path + "?trace" 312 | return self._request("GET", target, api_method, kwargs, no_api=True) 313 | 314 | # def archive_entry_download(): 315 | 316 | def create_directory(self, repo_key, directory_path, **kwargs): 317 | """ 318 | Create new directory at the specified destination. 319 | 320 | Parameters 321 | ---------- 322 | repo_key: str 323 | Key of the repository 324 | directory_path: str 325 | Path of the directory in the repository 326 | **kwargs 327 | Keyword arguments 328 | 329 | """ 330 | api_method = self._category + "Create Directory" 331 | target = "/" + repo_key + "/" + directory_path + "/" 332 | return self._request("PUT", target, api_method, kwargs, no_api=True) 333 | 334 | def deploy_artifact( 335 | self, repo_key, local_artifact_path, target_artifact_path, **kwargs 336 | ): 337 | """ 338 | Deploy an artifact to the specified destination. 339 | 340 | Parameters 341 | ---------- 342 | repo_key: str 343 | Key of the repository 344 | local_artifact_path: str 345 | Local path of the artifact to upload 346 | target_artifact_path: str 347 | Target path of the artifact in the repository 348 | **kwargs 349 | Keyword arguments 350 | 351 | """ 352 | api_method = self._category + "Deploy Artifact" 353 | target = "/" + repo_key + "/" + target_artifact_path 354 | with open(local_artifact_path, "rb") as files: 355 | return self._request( 356 | "PUT", target, api_method, kwargs, data=files, no_api=True 357 | ) 358 | 359 | def deploy_artifact_by_checksum( 360 | self, repo_key, target_artifact_path, sha_type, sha_value, **kwargs 361 | ): 362 | """ 363 | Deploy an artifact to the specified destination. 364 | 365 | By checking if the artifact content already exists in Artifactory. 366 | If Artifactory already contains a user readable artifact with 367 | the same checksum the artifact content is copied over to 368 | the new location and return a response 369 | without requiring content transfer. 370 | Otherwise, a 404 error is returned to indicate 371 | that content upload is expected in order to deploy the artifact. 372 | 373 | Parameters 374 | ---------- 375 | repo_key: str 376 | Key of the repository 377 | target_artifact_path: str 378 | Target path of the artifact in the repository 379 | sha_type: str 380 | Type of secure hash 381 | sha_value: str 382 | Value of the secure hash 383 | **kwargs 384 | Keyword arguments 385 | 386 | """ 387 | api_method = self._category + "Deploy Artifact By Checksum" 388 | if sha_type not in ["sha1", "sha256"]: 389 | message = ( 390 | 'sha_type must be "sha1" or "sha256", ' 391 | + 'type given was "' 392 | + sha_type 393 | + '"' 394 | ) 395 | raise self.RtpyError(message) 396 | 397 | params = {} 398 | params["X-Checksum-Deploy"] = True 399 | if sha_type == "sha1": 400 | params["X-Checksum-Sha1"] = sha_value 401 | if sha_type == "sha256": 402 | params["X-Checksum-Sha256"] = sha_value 403 | target = "/" + repo_key + "/" + target_artifact_path 404 | return self._request( 405 | "PUT", target, api_method, kwargs, no_api=True, params=params 406 | ) 407 | 408 | # Unsupported methods 409 | # def deploy_artifacts_from_archive() 410 | # def push_a_set_of_artifacts_to_bintray() 411 | # def push_docker_tag_to_bintray() 412 | # def distribute_artifact() 413 | # def file_compliance_info() 414 | 415 | def delete_item(self, repo_key, path_to_item, **kwargs): 416 | """ 417 | Delete a file or a folder from the specified destination. 418 | 419 | Parameters 420 | ---------- 421 | repo_key: str 422 | Key of the repository 423 | path_to_item: str 424 | Path of the item in the repository 425 | **kwargs 426 | Keyword arguments 427 | 428 | """ 429 | api_method = self._category + "Delete Item" 430 | target = "/" + repo_key + "/" + path_to_item 431 | return self._request("DELETE", target, api_method, kwargs, no_api=True) 432 | 433 | def copy_item( 434 | self, 435 | src_repo_key, 436 | src_item_path, 437 | target_repo_key, 438 | target_item_path, 439 | options=None, 440 | **kwargs 441 | ): 442 | """ 443 | Copy an artifact or a folder to the specified destination. 444 | 445 | Supported by local repositories only. 446 | 447 | Parameters 448 | ---------- 449 | src_repo_key: str 450 | Key of the source repository 451 | src_item_path: str 452 | Path of the item in the source repository 453 | target_repo_key: str 454 | Key of the target repository 455 | target_item_path: str 456 | Path of the item in the target repository 457 | options: str, optional 458 | String of options 459 | **kwargs 460 | Keyword arguments 461 | 462 | """ 463 | api_method = self._category + "Copy Item" 464 | target = "copy/" + src_repo_key 465 | target = self._add_forward_slash_if_not_empty(target, src_item_path) 466 | target = target + "?to=/" + target_repo_key 467 | if target_repo_key != "": 468 | target = target + "/" + target_item_path 469 | target = self._append_to_string(target, options) 470 | return self._request("POST", target, api_method, kwargs) 471 | 472 | def move_item( 473 | self, 474 | src_repo_key, 475 | src_item_path, 476 | target_repo_key, 477 | target_item_path, 478 | options=None, 479 | **kwargs 480 | ): 481 | """ 482 | Move an artifact or a folder to the specified destination. 483 | 484 | Supported by local repositories only. 485 | 486 | Parameters 487 | ---------- 488 | src_repo_key: str 489 | Key of the source repository 490 | src_item_path: str 491 | Path of the item in the source repository 492 | target_repo_key: str 493 | Key of the target repository 494 | target_item_path: str 495 | Path of the item in the target repository 496 | options: str, optional 497 | String of options 498 | **kwargs 499 | Keyword arguments 500 | 501 | """ 502 | api_method = self._category + "Move Item" 503 | target = "move/" + src_repo_key 504 | target = self._add_forward_slash_if_not_empty(target, src_item_path) 505 | target = target + "?to=/" + target_repo_key 506 | if target_repo_key != "": 507 | target = target + "/" + target_item_path 508 | target = self._append_to_string(target, options) 509 | return self._request("POST", target, api_method, kwargs) 510 | 511 | # Unsupported methods 512 | # def get_repository_replication_configuration(): 513 | # def set_repository_replication_configuration(): 514 | # def update_repository_replication_configuration(): 515 | # def delete_repository_replication_configuration(): 516 | # def scheduled_replication_status(): 517 | # def pull_or_push_replication(): 518 | # def update_local_multi_push_replication(): 519 | # def delete_local_multi_push_replication(): 520 | # def enable_or_disable_multiple_replications(): 521 | # def global_system_replication_configuration(): 522 | # def block_system_replication(): 523 | # def unblock_system_replication(): 524 | 525 | def artifact_sync_download(self, repo_key, artifact_path, options=None, **kwargs): 526 | """ 527 | Download an artifact. 528 | 529 | With or without returning 530 | he actual content to the client. 531 | 532 | When tracking the progress marks are printed 533 | (by default every 1024 bytes). 534 | This is extremely useful 535 | if you want to trigger downloads on a remote Artifactory server, 536 | for example to force eager cache population of large artifacts, 537 | but want to avoid the bandwidth consumption involved in transferring 538 | the artifacts to the triggering client. 539 | If no content parameter is specified 540 | the file content is downloaded to the client. 541 | 542 | Parameters 543 | ---------- 544 | repo_key: str 545 | Key of the repository 546 | artifact_path: str 547 | Path of the artifact in the repository 548 | options: str, optional 549 | String of options 550 | **kwargs 551 | Keyword arguments 552 | 553 | """ 554 | api_method = self._category + "Artifact Sync Download" 555 | if artifact_path == "": 556 | message = "artifact path can't be empty !" 557 | raise self.RtpyError(message) 558 | target = "download/" + repo_key + "/" + artifact_path 559 | target = self._append_to_string(target, options) 560 | return self._request("GET", target, api_method, kwargs) 561 | 562 | def file_list(self, repo_key, folder_path, options=None, **kwargs): 563 | """ 564 | Get a flat (the default) or deep listing of the files and folders. 565 | 566 | (not included by default) within a folder 567 | For deep listing you can specify an optional 568 | depth to limit the results. 569 | Optionally include a map of metadata timestamp values 570 | as part of the result 571 | 572 | Parameters 573 | ---------- 574 | repo_key: str 575 | Key of the repository 576 | folder_path: str 577 | Path of the folder in the repository 578 | options: str, optional 579 | String of options 580 | **kwargs 581 | Keyword arguments 582 | 583 | """ 584 | api_method = self._category + "File List" 585 | target = "storage/" + repo_key 586 | target = self._add_forward_slash_if_not_empty(target, folder_path) 587 | target = target + "?list" 588 | target = self._append_to_string(target, options) 589 | return self._request("GET", target, api_method, kwargs) 590 | 591 | def get_background_tasks(self, **kwargs): 592 | """ 593 | Retrieve list of background tasks currently scheduled. 594 | 595 | Or running in Artifactory 596 | In HA, the nodeId is added to each task. 597 | Task can be in one of few states: scheduled, 598 | running, stopped, canceled. 599 | Running task also shows the task start time. 600 | 601 | Parameters 602 | ---------- 603 | **kwargs 604 | Keyword arguments 605 | 606 | """ 607 | api_method = self._category + "Get Background Tasks" 608 | target = "tasks" 609 | return self._request("GET", target, api_method, kwargs) 610 | 611 | def empty_trash_can(self, **kwargs): 612 | """ 613 | Empty the trash can permanently deleting all its current contents. 614 | 615 | Parameters 616 | ---------- 617 | **kwargs 618 | Keyword arguments 619 | 620 | """ 621 | api_method = self._category + "Empty Trash Can" 622 | target = "trash/empty" 623 | return self._request("POST", target, api_method, kwargs) 624 | 625 | def delete_item_from_trash_can(self, path_in_trashcan, **kwargs): 626 | """ 627 | Permanently delete an item from the trash can. 628 | 629 | Parameters 630 | ---------- 631 | **kwargs 632 | Keyword arguments 633 | 634 | """ 635 | api_method = self._category + "Delete Item From Trash Can" 636 | target = "trash/clean/" + path_in_trashcan 637 | return self._request("DELETE", target, api_method, kwargs) 638 | 639 | def restore_item_from_trash_can(self, path_in_trashcan, target_path, **kwargs): 640 | """ 641 | Restore an item from the trash can. 642 | 643 | Parameters 644 | ---------- 645 | path_in_trashcan: str 646 | Path of the item in the trashcan (repo_name/folder/file) 647 | target_path: str 648 | Where to restore the item (repo_name/folder/file) 649 | 650 | """ 651 | api_method = self._category + "Restore Item From Trash Can" 652 | target = "trash/restore/" + path_in_trashcan + "?to=" + target_path 653 | return self._request("POST", target, api_method, kwargs) 654 | 655 | def optimize_system_storage(self, **kwargs): 656 | """ 657 | Raise a flag to invoke balancing between redundant storage units. 658 | 659 | Of a sharded filestore following the next garbage collection. 660 | 661 | """ 662 | api_method = self._category + "Optimize System Storage" 663 | target = "system/storage/optimize" 664 | return self._request("POST", target, api_method, kwargs) 665 | 666 | # Unsupported methods 667 | # def get_puppet_modules() 668 | # def get_puppet_module() 669 | # def get_puppet_releases() 670 | # def get_puppet_release() 671 | -------------------------------------------------------------------------------- /rtpy/builds.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the BUILDS REST API Methods category.""" 10 | 11 | 12 | from .tools import RtpyBase 13 | 14 | 15 | class RtpyBuilds(RtpyBase): 16 | """BUILDS methods category.""" 17 | 18 | def all_builds(self, **kwargs): 19 | """Provide information on all builds. 20 | 21 | Parameters 22 | ---------- 23 | **kwargs 24 | Keyword arguments 25 | 26 | """ 27 | api_method = self._category + "All Builds" 28 | return self._request("GET", "build", api_method, kwargs) 29 | 30 | # Unsupported methods 31 | # def build_runs() 32 | # def build_upload() 33 | # def build_info() 34 | # def builds_diff() 35 | # def build_promotion() 36 | # def promote_docker_image() 37 | # def delete_builds() 38 | # def push_build_to_bintray() 39 | # def distribute_build() 40 | # def control_build_retention() 41 | -------------------------------------------------------------------------------- /rtpy/import_and_export.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the IMPORT AND EXPORT REST API Methods category.""" 10 | -------------------------------------------------------------------------------- /rtpy/plugins.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the PLUGINS REST API Methods category.""" 10 | -------------------------------------------------------------------------------- /rtpy/repositories.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the REPOSITORIES REST API Methods category.""" 10 | 11 | from .tools import RtpyBase 12 | 13 | 14 | class RtpyRepositories(RtpyBase): 15 | """REPOSITORIES methods category.""" 16 | 17 | def get_repositories(self, options=None, **kwargs): 18 | """ 19 | Return a list of minimal repository details. 20 | 21 | For all repositories of the specified type. 22 | 23 | Parameters 24 | ---------- 25 | options: str 26 | String of options 27 | **kwargs 28 | Keyword arguments 29 | 30 | """ 31 | api_method = self._category + "Get Repositories" 32 | target = self._append_to_string("repositories", options) 33 | return self._request("GET", target, api_method, kwargs) 34 | 35 | def repository_configuration(self, repo_key, **kwargs): 36 | """ 37 | Retrieve the current configuration of a repository. 38 | 39 | Supported by local, remote and virtual repositories. 40 | 41 | Parameters 42 | ---------- 43 | repo_key: str 44 | Key of the repository 45 | **kwargs 46 | Keyword arguments 47 | 48 | """ 49 | api_method = self._category + "Repository Configuration" 50 | target = self._prefix + repo_key 51 | return self._request("GET", target, api_method, kwargs) 52 | 53 | def create_repository(self, params, **kwargs): 54 | """ 55 | Create a new repository in Artifactory with the provided configuration. 56 | 57 | Supported by local, remote and virtual repositories. 58 | A position may be specified using the pos parameter. 59 | If the map size is shorter than pos the repository is the last one 60 | (the default behavior). 61 | 62 | Parameters 63 | ---------- 64 | params: dict 65 | Parameters of the repository 66 | **kwargs 67 | Keyword arguments 68 | 69 | """ 70 | api_method = self._category + "Create Repository" 71 | repo_key = params["key"] 72 | target = self._prefix + repo_key 73 | return self._request("PUT", target, api_method, kwargs, params=params) 74 | 75 | def update_repository_configuration(self, params, **kwargs): 76 | """ 77 | Update an exiting repository configuration in Artifactory. 78 | 79 | With the provided configuration elements. 80 | Supported by local, remote and virtual repositories. 81 | 82 | Parameters 83 | ---------- 84 | params: dict 85 | Parameters of the repository 86 | **kwargs 87 | Keyword arguments 88 | 89 | """ 90 | api_method = self._category + "Update Repository Configuration" 91 | repo_key = params["key"] 92 | target = self._prefix + repo_key 93 | params["Content-Type"] = "application/json" 94 | return self._request("POST", target, api_method, kwargs, params=params) 95 | 96 | def delete_repository(self, repo_key, **kwargs): 97 | """ 98 | Remove a repository. 99 | 100 | Configuration together with the whole repository content. 101 | Supported by local, remote and virtual repositories 102 | 103 | Parameters 104 | ---------- 105 | repo_key: str 106 | Key of the repository 107 | **kwargs 108 | Keyword arguments 109 | 110 | """ 111 | api_method = self._category + "Delete Repository" 112 | target = self._prefix + repo_key 113 | return self._request("DELETE", target, api_method, kwargs) 114 | 115 | def calculate_yum_repository_metadata( 116 | self, repo_key, x_gpg_passphrase=None, options=None, **kwargs 117 | ): 118 | """ 119 | Calculate/recalculate the YUM metdata for a repository. 120 | 121 | Based on the RPM package currently hosted in the repository. 122 | Supported by local and virtual repositories only. 123 | Calculation can be synchronous (the default) or asynchronous. 124 | For Virtual repositories, calculates the merged metadata 125 | from all aggregated repositories on the specified path. 126 | The path parameter must be passed for virtual calculation. 127 | 128 | Parameters 129 | ---------- 130 | repo_key: str 131 | Key of the repository 132 | x_gpg_passphrase: str 133 | Passphrase 134 | options: str 135 | String of options 136 | **kwargs 137 | Keyword arguments 138 | 139 | """ 140 | api_method = self._category + "Calculate YUM Repository Metadata" 141 | target = "yum/" + repo_key 142 | target = self._append_to_string(target, options) 143 | params = {} 144 | if x_gpg_passphrase: 145 | params["X-GPG-PASSPHRASE"] = x_gpg_passphrase 146 | return self._request("POST", target, api_method, kwargs, params=params) 147 | 148 | def calculate_nuget_repository_metadata(self, repo_key, **kwargs): 149 | """ 150 | Recalculate all the NuGet packages for a repository. 151 | 152 | (local/cache/virtual), and re-annotate the NuGet properties 153 | for each NuGet package according to it's internal nuspec file. 154 | Please see the NuGet integration documentation for more details. 155 | Supported by local, local-cache, remote and virtual repositories. 156 | 157 | Parameters 158 | ---------- 159 | repo_key: str 160 | Key of the repository 161 | **kwargs 162 | Keyword arguments 163 | 164 | """ 165 | api_method = self._category + "Calculate NuGet Repository Metadata" 166 | target = "nuget/" + repo_key + "/reindex" 167 | return self._request("POST", target, api_method, kwargs) 168 | 169 | def calculate_npm_repository_metadata(self, repo_key, **kwargs): 170 | """ 171 | Recalculate the npm search index for this repository (local/virtual). 172 | 173 | Please see the Npm integration documentation for more details. 174 | Supported by local and virtual repositories. 175 | 176 | Parameters 177 | ---------- 178 | repo_key: str 179 | Key of the repository 180 | **kwargs 181 | Keyword arguments 182 | 183 | """ 184 | api_method = self._category + "Calculate Npm Repository Metadata" 185 | target = "npm/" + repo_key + "/reindex" 186 | return self._request("POST", target, api_method, kwargs) 187 | 188 | def calculate_maven_index(self, options, **kwargs): 189 | """ 190 | Calculates/caches a Maven index for the specified repositories. 191 | 192 | For a virtual repository specify all underlying repositories 193 | that you want the aggregated index to include. 194 | Calculation can be forced, which for remote repositories 195 | will cause downloading of a remote index even if a locally 196 | ached index has not yet expired; 197 | and index recalculation based on the cache 198 | on any failure to download the remote index, 199 | including communication errors 200 | (the default behavior is to only use the cache when a remote index 201 | cannot be found and returns a 404). Forcing has no effect 202 | on local repositories index calculation. 203 | 204 | Parameters 205 | ---------- 206 | options: str 207 | String of options 208 | **kwargs 209 | Keyword arguments 210 | 211 | """ 212 | api_method = self._category + "Calculate Maven Index" 213 | target = "maven?" + options 214 | return self._request("POST", target, api_method, kwargs) 215 | 216 | def calculate_maven_metadata(self, repo_key, folder_path, options=None, **kwargs): 217 | """ 218 | Calculate Maven metadata on the specified path. 219 | 220 | (local repositories only). 221 | 222 | Parameters 223 | ---------- 224 | repo_key: str 225 | Key of the repository 226 | folder_path: str 227 | Path of the folder in the repository 228 | options: str 229 | String of options 230 | **kwargs 231 | Keyword arguments 232 | 233 | """ 234 | api_method = self._category + "Calculate Maven Metadata" 235 | target = "maven/calculateMetadata/" + repo_key + "/" + folder_path 236 | target = self._append_to_string(target, options) 237 | 238 | return self._request("POST", target, api_method, kwargs) 239 | 240 | # Unsupported ressource intensive methods 241 | 242 | def calculate_debian_repository_metadata( 243 | self, repo_key, x_gpg_passphrase=None, options=None, **kwargs 244 | ): 245 | """ 246 | Calculate/recalculate the Packages and Release metadata. 247 | 248 | for this repository, based on the Debian packages in it. 249 | Calculation can be synchronous (the default) or asynchronous. 250 | Please refer to Debian Repositories for more details. 251 | Supported by local repositories only. 252 | From version 4.4, by default, the recalculation process 253 | also writes several entries from the Debian package's 254 | metadata as properties on all of the artifacts (based on the control 255 | file's content). 256 | This operation may not always be required 257 | (for example, if the Debian files are intact and were not modified, 258 | only the index needs to be recalculated. 259 | The operation is resource intensive and can be disabled by 260 | passing the ?writeProps=0 query param. 261 | From version 5.7, the target repository can be a virtual repository. 262 | 263 | Parameters 264 | ---------- 265 | repo_key: str 266 | Key of the repository 267 | x_gpg_passphrase: str 268 | Passphrase 269 | options: str 270 | String of options 271 | **kwargs 272 | Keyword arguments 273 | 274 | """ 275 | api_method = self._category + "Calculate Debian Repository Metadata" 276 | target = "deb/reindex/" + repo_key 277 | target = self._append_to_string(target, options) 278 | params = {} 279 | if x_gpg_passphrase: 280 | params["X-GPG-PASSPHRASE"] = x_gpg_passphrase 281 | return self._request("POST", target, api_method, kwargs, params=params) 282 | 283 | def calculate_opkg_repository_metadata( 284 | self, repo_key, x_gpg_passphrase=None, options=None, **kwargs 285 | ): 286 | """ 287 | Calculate/recalculate the Packages and Release metadata fora repository. 288 | 289 | Based on the ipk packages in it (in each feed location). 290 | Calculation can be synchronous (the default) or asynchronous. 291 | Please refer to Opkg Repositories for more details. 292 | Supported by local repositories only. 293 | By default, the recalculation process also writes several entries 294 | from the ipk package's metadata as properties on all of the artifacts 295 | (based on the control file's content). 296 | This operation may not always be required 297 | (for example, if the ipk files are intact and were not modified, 298 | only the index needs to be recalculated. 299 | The operation is resource intensive and can be disabled by passing 300 | the ?writeProps=0 query param. 301 | 302 | Parameters 303 | ---------- 304 | repo_key: str 305 | Key of the repository 306 | x_gpg_passphrase: str 307 | Passphrase 308 | options: str 309 | String of options 310 | **kwargs 311 | Keyword arguments 312 | 313 | """ 314 | api_method = self._category + "Calculate Opkg Repository Metadata" 315 | target = "opkg/reindex/" + repo_key 316 | target = self._append_to_string(target, options) 317 | params = {} 318 | if x_gpg_passphrase: 319 | params["X-GPG-PASSPHRASE"] = x_gpg_passphrase 320 | return self._request("POST", target, api_method, kwargs, params=params) 321 | 322 | def calculate_bower_index(self, repo_key, **kwargs): 323 | """ 324 | Recalculate the index for a Bower repository. 325 | 326 | Parameters 327 | ---------- 328 | repo_key: str 329 | Key of the repository 330 | **kwargs 331 | Keyword arguments 332 | 333 | """ 334 | api_method = self._category + "Calculate Bower Index" 335 | target = "bower/" + repo_key + "/" + "reindex" 336 | return self._request("POST", target, api_method, kwargs) 337 | 338 | def calculate_helm_chart_index(self, repo_key, **kwargs): 339 | """ 340 | Calculate Helm chart index on the specified path. 341 | 342 | (local repositories only). 343 | 344 | Parameters 345 | ---------- 346 | repo_key: str 347 | Key of the repository 348 | **kwargs 349 | Keyword arguments 350 | 351 | """ 352 | api_method = self._category + "Calculate Helm Chart Index" 353 | target = "helm/" + repo_key + "/" + "reindex" 354 | return self._request("POST", target, api_method, kwargs) 355 | 356 | def calculate_cran_repository_metadata(self, repo_key, options=None, **kwargs): 357 | """ 358 | Calculates/recalculates the Packages and Release metadata for a repository. 359 | 360 | Based on the CRAN packages in it. 361 | The calculation can be synchronous (the default) or asynchronous. 362 | Please refer to CRAN Repositories for more details. 363 | Supported by local repositories only. 364 | From version 6.1, by default, the recalculation process 365 | also writes several entries from the CRAN package's metadata 366 | as properties on all of the artifacts (based on the control file's content). 367 | 368 | Parameters 369 | ---------- 370 | repo_key: str 371 | Key of the repository 372 | options: str 373 | String of options 374 | **kwargs 375 | Keyword arguments 376 | 377 | """ 378 | api_method = self._category + "Calculate CRAN Repository Metadata" 379 | target = "cran/reindex/" + repo_key 380 | target = self._append_to_string(target, options) 381 | return self._request("POST", target, api_method, kwargs) 382 | 383 | def calculate_conda_repository_metadata(self, repo_key, options=None, **kwargs): 384 | """ 385 | Calculate/recalculate the Conda packages and release metadata for a repository. 386 | 387 | The calculation can be synchronous (the default) or asynchronous. 388 | Please refer to Conda Repositories for more details. 389 | Supported for local repositories only 390 | 391 | Parameters 392 | ---------- 393 | repo_key: str 394 | Key of the repository 395 | options: str 396 | String of options 397 | **kwargs 398 | Keyword arguments 399 | 400 | """ 401 | api_method = self._category + "Calculate Conda Repository Metadata" 402 | target = "conda/reindex/" + repo_key 403 | target = self._append_to_string(target, options) 404 | return self._request("POST", target, api_method, kwargs) 405 | -------------------------------------------------------------------------------- /rtpy/rtpy.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Rtpy class definition with it's attributes which is exposed to the end user.""" 10 | 11 | from copy import deepcopy 12 | 13 | from .tools import RtpyBase 14 | from .artifacts_and_storage import RtpyArtifactsAndStorage 15 | from .builds import RtpyBuilds 16 | from .repositories import RtpyRepositories 17 | from .searches import RtpySearches 18 | from .security import RtpySecurity 19 | from .system_and_configuration import RtpySystemAndConfiguration 20 | 21 | 22 | class Rtpy(RtpyBase): 23 | """ 24 | Main parent class. 25 | 26 | Attributes are the classes are the methods categories. 27 | 28 | Parameters 29 | --------- 30 | settings: dict 31 | The user settings, mandaroty keys are "af_url" and "api_key" 32 | or "username" and "password". 33 | 34 | Attributes 35 | ---------- 36 | artifacts_and_storage: rtpy.artifacts_and_storage.RtpyArtifactsAndStorage 37 | Category for multiple API methods 38 | buils: rtpy.builds.RtpyBuilds 39 | Category for multiple API methods 40 | repositories: rtpy.repositories.RtpyRepositories 41 | Category for multiple API methods 42 | searches: rtpy.searches.RtpySearches 43 | Category for multiple API methods 44 | security: rtpy.security.RtpySecurity 45 | Category for multiple API methods 46 | support: rtpy.support.RtpySupport 47 | Category for multiple API methods 48 | system_and_configuration: rtpy.system_and_configuration.RtpySystemAndConfiguration 49 | Category for multiple API methods 50 | settings: dict 51 | Previously supplied settings at class instantiation 52 | 53 | """ 54 | 55 | def __init__(self, settings): 56 | """Object Instantiation.""" 57 | settings = deepcopy(settings) 58 | self.artifacts_and_storage = RtpyArtifactsAndStorage( 59 | settings, "storage/", "[ARTIFACTS & STORAGE] : " 60 | ) 61 | self.builds = RtpyBuilds(settings, "build/", "[BUILDS] : ") 62 | self.repositories = RtpyRepositories( 63 | settings, "repositories/", "[REPOSITORIES] : " 64 | ) 65 | self.searches = RtpySearches(settings, "search/", "[SEARCHES] : ") 66 | self.security = RtpySecurity(settings, "security/", "[SECURITY] : ") 67 | self.system_and_configuration = RtpySystemAndConfiguration( 68 | settings, "system/", "[SYSTEM & CONFIGURATION] : " 69 | ) 70 | self.settings = settings 71 | 72 | def __call__(self): 73 | """Shortcut to all the system health ping method to verify connectivity.""" 74 | return self.system_and_configuration.system_health_ping() 75 | -------------------------------------------------------------------------------- /rtpy/searches.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the SEARCHES REST API Methods category.""" 10 | 11 | 12 | from .tools import RtpyBase 13 | 14 | 15 | class RtpySearches(RtpyBase): 16 | """SEARCHES methods category.""" 17 | 18 | def artifactory_query_language(self, query, **kwargs): 19 | """ 20 | Search items using the Artifactory Query Language (AQL). 21 | 22 | Parameters 23 | --------- 24 | query: str 25 | The AQL string 26 | **kwargs 27 | Keyword arguments 28 | 29 | """ 30 | api_method = self._category + "Artifactory Query Language" 31 | target = self._prefix + "aql" 32 | params = {"Content-Type": "text/plain"} 33 | return self._request( 34 | "POST", target, api_method, kwargs, data=query, params=params 35 | ) 36 | 37 | """Old previously supported methods (code not updated for current rtpy) 38 | def artifact_search_quick_search( 39 | artifact_name, options=None, 40 | x_result_detail=None, **kwargs): 41 | 42 | # Artifact search by part of file name. 43 | # Searches return file info URIs. 44 | # Can limit search to specific repositories (local or caches). 45 | 46 | api_method = self._category + "Artifact Search Quick Search" 47 | target = self._prefix + 'artifact?name=' + artifact_name 48 | params = {} 49 | if x_result_detail: 50 | if x_result_detail not in ['info', 'properties', 51 | 'info, properties']: 52 | message = "x_result_detail must be 'info', 'properties', " + \ 53 | "'info, properties'!" 54 | raise self.RtpyError(message) 55 | params['X-Result-detail'] = x_result_detail 56 | 57 | if options: 58 | target = target + options 59 | return request('GET', target, api_method, kwargs, params=params) 60 | 61 | # def archive_entries_search_class_search(): 62 | # def gavc_search(): 63 | 64 | 65 | def property_search(properties, options=None, 66 | x_result_detail=None, **kwargs): 67 | api_method = self._category + "Property Search" 68 | target = self._prefix + 'prop?' + properties 69 | params = {} 70 | if x_result_detail: 71 | if x_result_detail not in ['info', 'properties', 72 | 'info, properties']: 73 | message = "x_result_detail must be 'info', 'properties', " + \ 74 | "'info, properties'!" 75 | raise self.RtpyError(message) 76 | params['X-Result-detail'] = x_result_detail 77 | if options: 78 | target = target + options 79 | return request('GET', target, api_method, kwargs, params=params) 80 | 81 | 82 | def checksum_search(checksum_type, checksum_value, options=None, 83 | x_result_detail=None, **kwargs): 84 | api_method = self._category + "Checksum Search" 85 | if checksum_type not in ['md5', 'sha1', 'sha256']: 86 | message = 'sha_type must be "md5", "sha1" or "sha256", ' + \ 87 | 'type given was "'+checksum_type+'"' 88 | raise self.RtpyError(message) 89 | 90 | target = self._prefix + 'checksum?' + checksum_type + \ 91 | '=' + checksum_value 92 | params = {} 93 | if x_result_detail: 94 | if x_result_detail not in ['info', 'properties', 95 | 'info, properties']: 96 | message = "x_result_detail must be 'info', 'properties', " + \ 97 | "'info, properties'!" 98 | raise self.RtpyError(message) 99 | params['X-Result-detail'] = x_result_detail 100 | if options: 101 | target = target + options 102 | return request('GET', target, api_method, kwargs, params=params) 103 | 104 | 105 | def bad_checksum_search(checksum_type, options=None, 106 | **kwargs): 107 | api_method = self._category + "Bad Checksum Search" 108 | target = self._prefix + 'badChecksum?type=' + checksum_type 109 | if checksum_type not in ['md5', 'sha1']: 110 | message = 'sha_type must be "md5"or "sha1", ' + \ 111 | 'type given was "'+checksum_type+'"' 112 | raise self.RtpyError(message) 113 | 114 | if options: 115 | target = target + options 116 | return request('GET', target, api_method, kwargs) 117 | 118 | 119 | def artifacts_not_downloaded_since(not_used_since, options=None, 120 | **kwargs): 121 | api_method = self._category + "Artifacts Not Downloaded Since" 122 | target = self._prefix + 'usage?notUsedSince=' + not_used_since 123 | if options: 124 | target = target + options 125 | 126 | return request('GET', target, api_method, kwargs) 127 | """ 128 | 129 | # Unsupported methods 130 | # def artifacts_with_date_in_date_range() 131 | # def artifacts_created_in_date_range() 132 | # def pattern_search() 133 | # def builds_for_dependcy() 134 | # def license_search() 135 | # def artifact_version_search() 136 | # def artifact_latest_version_search_based_on_layout() 137 | # def artifact_latest_version_search_based_on_properties() 138 | # def build_artifacts_search() 139 | 140 | def list_docker_repositories(self, repo_key, options=None, **kwargs): 141 | """ 142 | List all Docker repositories (the registry's _catalog). 143 | 144 | (Hosted in an Artifactory Docker repository). 145 | 146 | Parameters 147 | ---------- 148 | repo_key: str 149 | Key of the repository 150 | options: str 151 | String of options 152 | **kwargs 153 | Keyword arguments 154 | 155 | """ 156 | api_method = self._category + "List Docker Repositories" 157 | target = "docker/" + repo_key + "/v2/_catalog" 158 | target = self._append_to_string(target, options) 159 | return self._request("GET", target, api_method, kwargs) 160 | 161 | def list_docker_tags(self, repo_key, image_path, options=None, **kwargs): 162 | """ 163 | List all tags of the specified Artifactory Docker repository. 164 | 165 | Parameters 166 | ---------- 167 | repo_key: str 168 | Key of the repository 169 | image_path: str 170 | Path of the image in the repository 171 | options: str 172 | String of options 173 | **kwargs 174 | Keyword arguments 175 | 176 | """ 177 | api_method = self._category + "List Docker Tags" 178 | target = "docker/" + repo_key + "/v2/" + image_path + "/tags/list" 179 | target = self._append_to_string(target, options) 180 | return self._request("GET", target, api_method, kwargs) 181 | -------------------------------------------------------------------------------- /rtpy/security.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the SECURITY REST API Methods category.""" 10 | 11 | from .tools import RtpyBase 12 | 13 | 14 | class RtpySecurity(RtpyBase): 15 | """SECURITY methods category.""" 16 | 17 | def get_users(self, **kwargs): 18 | """ 19 | Get the users list. 20 | 21 | Parameters 22 | --------- 23 | **kwargs 24 | Keyword arguments 25 | 26 | """ 27 | api_method = self._category + "Get Users" 28 | return self._request("GET", "security/users", api_method, kwargs) 29 | 30 | def get_user_details(self, username, **kwargs): 31 | """ 32 | Get the details of an Artifactory user. 33 | 34 | Parameters 35 | --------- 36 | username: str 37 | Name of the user 38 | **kwargs 39 | Keyword arguments 40 | 41 | """ 42 | api_method = self._category + "Get User Details" 43 | target = self._prefix + "users/" + username 44 | return self._request("GET", target, api_method, kwargs) 45 | 46 | def get_user_encrypted_password(self, **kwargs): 47 | """ 48 | Get the encrypted password of the authenticated requestor. 49 | 50 | Parameters 51 | --------- 52 | **kwargs 53 | Keyword arguments 54 | 55 | """ 56 | api_method = self._category + "Get User Encrypted Password" 57 | target = self._prefix + "encryptedPassword" 58 | return self._request("GET", target, api_method, kwargs) 59 | 60 | def create_or_replace_user(self, params, **kwargs): 61 | """ 62 | Create a new user in Artifactory or replaces an existing user. 63 | 64 | Parameters 65 | --------- 66 | params: dict 67 | Settings of the user 68 | **kwargs 69 | Keyword arguments 70 | 71 | """ 72 | api_method = self._category + "Create or Replace User" 73 | username = params["name"] 74 | target = self._prefix + "users/" + username 75 | params["Content-Type"] = "application/json" 76 | return self._request("PUT", target, api_method, kwargs, params=params) 77 | 78 | def update_user(self, params, **kwargs): 79 | """ 80 | Update an exiting user in Artifactory with the provided user details. 81 | 82 | Parameters 83 | --------- 84 | params: dict 85 | Settings of the user 86 | **kwargs 87 | Keyword arguments 88 | 89 | """ 90 | api_method = self._category + "Update User" 91 | username = params["name"] 92 | target = self._prefix + "users/" + username 93 | params["Content-Type"] = "application/json" 94 | return self._request("POST", target, api_method, kwargs, params=params) 95 | 96 | def delete_user(self, username, **kwargs): 97 | """ 98 | Remove an Artifactory user. 99 | 100 | Parameters 101 | --------- 102 | username: str 103 | Name of the user 104 | **kwargs 105 | Keyword arguments 106 | 107 | """ 108 | api_method = self._category + "Delete User" 109 | target = self._prefix + "users/" + username 110 | return self._request("DELETE", target, api_method, kwargs) 111 | 112 | # Unsupported Methods 113 | # def expire_password_for_a_single_user() 114 | # def expire_password_for_multiple_users() 115 | # def expire_password_for_all_users() 116 | # def unexpire_password_for_a_single_user() 117 | # def change_password() 118 | # def get_password_expiration_policy() 119 | # def set_password_expiration_policy() 120 | # def configure_user_lock_policy() 121 | # def retrieve_user_lock_policy() 122 | 123 | def get_locked_out_users(self, **kwargs): 124 | """ 125 | Get a list of the locked out users. 126 | 127 | If locking out users is enabled, lists all users 128 | that were locked out due to recurrent incorrect login attempts. 129 | 130 | Parameters 131 | --------- 132 | **kwargs 133 | Keyword arguments 134 | 135 | """ 136 | api_method = self._category + "Get Locked Out Users" 137 | target = self._prefix + "lockedUsers" 138 | return self._request("GET", target, api_method, kwargs) 139 | 140 | def unlock_locked_out_user(self, username, **kwargs): 141 | """ 142 | Unlock a single user that was locked out. 143 | 144 | Parameters 145 | --------- 146 | username: str 147 | Name of the user 148 | **kwargs 149 | Keyword arguments 150 | 151 | """ 152 | api_method = self._category + "Unlock Locked Out User" 153 | target = self._prefix + "unlockUsers/" + username 154 | return self._request("POST", target, api_method, kwargs) 155 | 156 | def unlock_locked_out_users(self, user_list, **kwargs): 157 | """ 158 | Unlock a list of users that were locked out. 159 | 160 | Parameters 161 | --------- 162 | user_list: list 163 | List of str representing usernames 164 | **kwargs 165 | Keyword arguments 166 | 167 | """ 168 | api_method = self._category + "Unlock Locked Out Users" 169 | target = self._prefix + "unlockUsers" 170 | 171 | counter = 0 172 | data = "[ " 173 | for user in user_list: 174 | if counter == len(user_list) - 1: 175 | data = data + '"' + user + '"' 176 | else: 177 | data = data + '"' + user + '", ' 178 | counter = counter + 1 179 | 180 | data = data + " ]" 181 | params = {} 182 | params["Content-Type"] = "application/json" 183 | return self._request( 184 | "POST", target, api_method, kwargs, data=data, params=params 185 | ) 186 | 187 | def unlock_all_locked_out_users(self, **kwargs): 188 | """ 189 | Unlock all users that were locked out. 190 | 191 | Parameters 192 | --------- 193 | **kwargs 194 | Keyword arguments 195 | 196 | """ 197 | api_method = self._category + "Unlock All Locked Out Users" 198 | target = self._prefix + "unlockAllUsers" 199 | return self._request("POST", target, api_method, kwargs) 200 | 201 | def create_api_key(self, **kwargs): 202 | """ 203 | Create an API key for the current user. 204 | 205 | Returns an error if API key already exists, 206 | use regenerate API key instead. 207 | 208 | Parameters 209 | --------- 210 | **kwargs 211 | Keyword arguments 212 | 213 | """ 214 | api_method = self._category + "Create API key" 215 | target = self._prefix + "apiKey" 216 | return self._request("POST", target, api_method, kwargs) 217 | 218 | def regenerate_api_key(self, **kwargs): 219 | """ 220 | Regenerate an API key for the current user. 221 | 222 | Parameters 223 | --------- 224 | **kwargs 225 | Keyword arguments 226 | 227 | """ 228 | api_method = self._category + "Regenerate API key" 229 | target = self._prefix + "apiKey" 230 | return self._request("PUT", target, api_method, kwargs) 231 | 232 | def get_api_key(self, **kwargs): 233 | """ 234 | Get the current user's own API key. 235 | 236 | Parameters 237 | --------- 238 | **kwargs 239 | Keyword arguments 240 | 241 | """ 242 | api_method = self._category + "Get API key" 243 | target = self._prefix + "apiKey" 244 | return self._request("GET", target, api_method, kwargs) 245 | 246 | def revoke_api_key(self, **kwargs): 247 | """ 248 | Revoke the current user's API key. 249 | 250 | Parameters 251 | --------- 252 | **kwargs 253 | Keyword arguments 254 | 255 | """ 256 | api_method = self._category + "Revoke API key" 257 | target = self._prefix + "apiKey" 258 | return self._request("DELETE", target, api_method, kwargs) 259 | 260 | def revoke_user_api_key(self, username, **kwargs): 261 | """ 262 | Revoke the API key of another user. 263 | 264 | Parameters 265 | --------- 266 | username: str 267 | Name of the user 268 | **kwargs 269 | Keyword arguments 270 | 271 | """ 272 | api_method = self._category + "Revoke User API key" 273 | target = self._prefix + "apiKey/" + username 274 | return self._request("DELETE", target, api_method, kwargs) 275 | 276 | def get_groups(self, **kwargs): 277 | """ 278 | Get the groups list. 279 | 280 | Parameters 281 | --------- 282 | **kwargs 283 | Keyword arguments 284 | 285 | """ 286 | api_method = self._category + "Get Groups" 287 | target = self._prefix + "groups" 288 | return self._request("GET", target, api_method, kwargs) 289 | 290 | def get_group_details(self, group_name, **kwargs): 291 | """ 292 | Get the details of an Artifactory Group. 293 | 294 | Parameters 295 | --------- 296 | group_name: str 297 | Name of the group 298 | **kwargs 299 | Keyword arguments 300 | 301 | """ 302 | api_method = self._category + "Get Group Details" 303 | target = self._prefix + "groups" + "/" + group_name 304 | return self._request("GET", target, api_method, kwargs) 305 | 306 | def create_or_replace_group(self, params, **kwargs): 307 | """ 308 | Create a new group in Artifactory or replace an existing group. 309 | 310 | Parameters 311 | --------- 312 | params: dict 313 | Settings of the group 314 | **kwargs 315 | Keyword arguments 316 | 317 | """ 318 | api_method = self._category + "Create or Replace Group" 319 | group_name = params["group_name"] 320 | target = self._prefix + "groups" + "/" + group_name 321 | return self._request("PUT", target, api_method, kwargs, params=params) 322 | 323 | def update_group(self, params, **kwargs): 324 | """ 325 | Update an exiting group in Artifactory. 326 | 327 | Parameters 328 | --------- 329 | params: dict 330 | Settings of the group 331 | **kwargs 332 | Keyword arguments 333 | 334 | """ 335 | api_method = self._category + "Update Group" 336 | group_name = params["group_name"] 337 | target = self._prefix + "groups" + "/" + group_name 338 | return self._request("POST", target, api_method, kwargs, params=params) 339 | 340 | def delete_group(self, group_name, **kwargs): 341 | """ 342 | Remove an Artifactory group. 343 | 344 | Parameters 345 | --------- 346 | group_name: str 347 | Name of the group 348 | **kwargs 349 | Keyword arguments 350 | 351 | """ 352 | api_method = self._category + "Delete Group" 353 | target = self._prefix + "groups" + "/" + group_name 354 | return self._request("DELETE", target, api_method, kwargs) 355 | 356 | def get_permission_targets(self, **kwargs): 357 | """ 358 | Get the permission targets list. 359 | 360 | Parameters 361 | ---------- 362 | **kwargs 363 | Keyword arguments 364 | 365 | """ 366 | api_method = self._category + "Get Permission Targets" 367 | target = self._prefix + "permissions" 368 | return self._request("GET", target, api_method, kwargs) 369 | 370 | def get_permission_target_details(self, permission_target_name, **kwargs): 371 | """ 372 | Get the details of an Artifactory Permission Target. 373 | 374 | Parameters 375 | ---------- 376 | permission_target_name: str 377 | Name of the permission target 378 | **kwargs 379 | Keyword arguments 380 | 381 | """ 382 | api_method = self._category + "Get Permission Target Details" 383 | target = self._prefix + "permissions/" + permission_target_name 384 | return self._request("GET", target, api_method, kwargs) 385 | 386 | def create_or_replace_permission_target(self, params, **kwargs): 387 | """ 388 | Create a new permission target in Artifactory. 389 | 390 | (or replace an existing permission target). 391 | 392 | Parameters 393 | ---------- 394 | params: dict 395 | Settings of the permission target 396 | **kwargs 397 | Keyword arguments 398 | 399 | """ 400 | api_method = self._category + "Create or Replace Permission Target" 401 | permission_target_name = params["name"] 402 | target = self._prefix + "permissions/" + permission_target_name 403 | return self._request("PUT", target, api_method, kwargs, params=params) 404 | 405 | def delete_permission_target(self, permission_target_name, **kwargs): 406 | """ 407 | Delete an Artifactory permission target. 408 | 409 | Parameters 410 | ---------- 411 | permission_target_name: str 412 | Name of the permission target 413 | **kwargs 414 | Keyword arguments 415 | 416 | """ 417 | api_method = self._category + "Delete Permission Target" 418 | target = self._prefix + "permissions/" + permission_target_name 419 | return self._request("DELETE", target, api_method, kwargs) 420 | 421 | def effective_item_permissions(self, repo_key, item_path, **kwargs): 422 | """ 423 | Return a list of effective permissions. 424 | 425 | (for the specified item(file or folder). 426 | 427 | Parameters 428 | ---------- 429 | repo_key: str 430 | Key of the repository 431 | item_path: str 432 | The path of the item in the repository 433 | **kwargs 434 | Keyword arguments 435 | 436 | """ 437 | api_method = self._category + "Effective item permissions" 438 | target = "storage/" + repo_key + "/" + item_path + "?permissions" 439 | return self._request("GET", target, api_method, kwargs) 440 | 441 | # Unsupported Methods 442 | # def security_configuration() 443 | # def activate_artifactory_key_encryption() 444 | # def deactivate_artifactory_key_encryption() 445 | # def get_gpg_public_key() 446 | # def set_gpg_public_key() 447 | # def set_gpg_private_key() 448 | # def set_gpg_pass_phrase() 449 | # def create_token() 450 | # def refresh_token() 451 | # def revoke_token() 452 | # def get_service_id() 453 | # def get_certificates() 454 | # def get_certificate() 455 | # def add_certificate() 456 | # def delete_certificate() 457 | -------------------------------------------------------------------------------- /rtpy/system_and_configuration.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the SYSTEM AND CONFIGURATION REST API Methods category.""" 10 | 11 | from .tools import RtpyBase 12 | 13 | 14 | class RtpySystemAndConfiguration(RtpyBase): 15 | """SYSTEM AND CONFIGURATION methods category.""" 16 | 17 | def system_info(self, **kwargs): 18 | """ 19 | Get general system information. 20 | 21 | Parameters 22 | ---------- 23 | **kwargs 24 | Keyword arguments 25 | 26 | """ 27 | api_method = self._category + "System Info" 28 | return self._request("GET", self._prefix, api_method, kwargs) 29 | 30 | def system_health_ping(self, **kwargs): 31 | """ 32 | Get a simple status response about the state of Artifactory. 33 | 34 | Parameters 35 | ---------- 36 | **kwargs 37 | Keyword arguments 38 | 39 | """ 40 | api_method = self._category + "System Health Ping" 41 | target = self._prefix + "ping" 42 | return self._request("GET", target, api_method, kwargs) 43 | 44 | # def verify_connection() 45 | 46 | def general_configuration(self, **kwargs): 47 | """ 48 | Get the general configuration (artifactory.config.xml). 49 | 50 | Parameters 51 | ---------- 52 | **kwargs 53 | Keyword arguments 54 | 55 | """ 56 | api_method = self._category + "General Configuration" 57 | target = self._prefix + "configuration" 58 | return self._request("GET", target, api_method, kwargs) 59 | 60 | def save_general_configuration(self, xml_file_path, **kwargs): 61 | """ 62 | Save the general configuration (artifactory.config.xml). 63 | 64 | Parameters 65 | ---------- 66 | xml_file_path: str 67 | Path of the xml file to POST 68 | **kwargs 69 | Keyword arguments 70 | 71 | """ 72 | api_method = self._category + "Save General Configuration" 73 | target = self._prefix + "configuration" 74 | myparams = {"Content-Type": "application/xml"} 75 | with open(xml_file_path, "rb") as files: 76 | return self._request( 77 | "POST", target, api_method, kwargs, params=myparams, data=files 78 | ) 79 | 80 | # Unsupported method 81 | # def update_custom_url_base(new_url) 82 | 83 | def license_information(self, **kwargs): 84 | """ 85 | Retrieve information about the currently installed license. 86 | 87 | Parameters 88 | ---------- 89 | **kwargs 90 | Keyword arguments 91 | 92 | """ 93 | api_method = self._category + "Licence Information" 94 | target = self._prefix + "license" 95 | return self._request("GET", target, api_method, kwargs) 96 | 97 | def install_license(self, params, **kwargs): 98 | """ 99 | Install new license key or change the current one. 100 | 101 | Parameters 102 | ---------- 103 | params: str 104 | Settings of the license 105 | **kwargs 106 | Keyword arguments 107 | 108 | """ 109 | # The JSON output in case of an error is currently incorrect 110 | api_method = self._category + "Install License" 111 | target = self._prefix + "license" 112 | return self._request("POST", target, api_method, kwargs, params=params) 113 | 114 | # Unsupported methods 115 | # def ha_license_information() 116 | # def install_ha_cluster_licenses() 117 | # def delete_ha_cluster_license() 118 | 119 | def version_and_addons_information(self, **kwargs): 120 | """ 121 | Retrieve information about versions and addons. 122 | 123 | (the current Artifactory version, revision, and currently installed Add-ons). 124 | 125 | Parameters 126 | ---------- 127 | **kwargs 128 | Keyword arguments 129 | 130 | """ 131 | api_method = self._category + "Versions and Add-ons Information" 132 | target = self._prefix + "version" 133 | return self._request("GET", target, api_method, kwargs) 134 | 135 | def get_reverse_proxy_configuration(self, **kwargs): 136 | """ 137 | Retrieve the reverse proxy configuration. 138 | 139 | Parameters 140 | ---------- 141 | **kwargs 142 | Keyword arguments 143 | 144 | """ 145 | api_method = self._category + "Get Reverse Proxy Configuration" 146 | target = self._prefix + "configuration/webServer" 147 | return self._request("GET", target, api_method, kwargs) 148 | 149 | # Unsupported method 150 | # def update_reverse_proxy_configuration() 151 | 152 | def get_reverse_proxy_snippet(self, **kwargs): 153 | """ 154 | Get the reverse proxy configuration snippet in text format. 155 | 156 | Parameters 157 | ---------- 158 | **kwargs 159 | Keyword arguments 160 | 161 | """ 162 | api_method = self._category + "Get Reverse Proxy Snippet" 163 | target = self._prefix + "configuration/reverseProxy/nginx" 164 | return self._request("GET", target, api_method, kwargs) 165 | -------------------------------------------------------------------------------- /rtpy/tools.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions and classes used by the main categories of methods.""" 10 | 11 | from __future__ import unicode_literals 12 | import json 13 | import sys 14 | from copy import deepcopy 15 | 16 | import requests 17 | from requests.exceptions import HTTPError 18 | 19 | 20 | class RtpyBase(object): 21 | """ 22 | Rtpy Base class. 23 | 24 | Attributes 25 | ---------- 26 | _prefix: str 27 | API endpoint used for a given API method category ("search/"...) 28 | _category: str 29 | Major API method category (Searches, Repositories...) 30 | _user_settings: dict 31 | Complete user settings, default values are changed at class instantiation 32 | 33 | """ 34 | 35 | def __init__(self, provided_settings, prefix, category): 36 | """ 37 | Object instantiation. 38 | 39 | User settings get set and verified, _category and _prefix attributes are set. 40 | 41 | Parameters 42 | ---------- 43 | provided_settings: str 44 | Original settings dict supplied by the user from the rtpy.Rtpy class 45 | prefix: str 46 | API endpoint used for a given API method category ("search/"...) 47 | category: str 48 | Major API method category (Searches, Repositories...) 49 | 50 | """ 51 | self._category = category 52 | self._prefix = prefix 53 | self._user_settings = { 54 | "af_url": None, 55 | "api_key": None, 56 | "username": None, 57 | "password": None, 58 | "raw_response": False, 59 | "verbose_level": 0, 60 | "auth": (), 61 | "X-JFrog-Art-Api": None, 62 | "api_endpoint": None, 63 | "session": requests.Session(), 64 | } 65 | self._configure_user_settings(provided_settings) 66 | self._validate_user_settings() 67 | 68 | def _check_provided_keys_in_settings(self, provided_settings): 69 | """ 70 | Check if the provided settings only modify the correct fields. 71 | 72 | Parameters 73 | ---------- 74 | provided_settings: str 75 | Original settings dict supplied by the user from the rtpy.Rtpy class 76 | 77 | Raises 78 | ------ 79 | UserSettingsError 80 | If the settings are invalid 81 | 82 | Returns 83 | ------- 84 | None 85 | Nothing 86 | 87 | """ 88 | allowed_settings_keys = [ 89 | "af_url", 90 | "api_key", 91 | "username", 92 | "password", 93 | "raw_response", 94 | "verbose_level", 95 | "session", 96 | ] 97 | 98 | message = "" 99 | for setting in allowed_settings_keys: 100 | message += '"' + str(setting) + '" ' 101 | 102 | message += ( 103 | "are the only settings " 104 | "that can provided!\n" 105 | "Offending key(s) supplied : " 106 | ) 107 | 108 | offending_keys = ( 109 | key for key in provided_settings if key not in allowed_settings_keys 110 | ) 111 | offending_keys = tuple(key for key in offending_keys) 112 | for key in offending_keys: 113 | message += '"{}" ' 114 | 115 | message = message.format(*offending_keys) 116 | if offending_keys: 117 | raise UserSettingsError(message) 118 | 119 | def _configure_user_settings(self, provided_settings): 120 | """ 121 | Configure or update user settings. 122 | 123 | Returns 124 | ------- 125 | None 126 | Nothing 127 | 128 | """ 129 | self._check_provided_keys_in_settings(provided_settings) 130 | self._original_user_settings = deepcopy(self._user_settings) 131 | for setting in provided_settings: 132 | self._user_settings[setting] = provided_settings[setting] 133 | 134 | def _restore_user_settings(self): 135 | """Restore original user settings.""" 136 | self._user_settings = self._original_user_settings 137 | 138 | def _validate_user_settings(self): 139 | """ 140 | Verify if the _user_settings attribute dict is valid. 141 | 142 | Raises 143 | ------ 144 | UserSettingsError 145 | If the _user_settings dict is invalid 146 | 147 | Returns 148 | ------- 149 | None 150 | Nothing 151 | 152 | """ 153 | if ( 154 | not self._user_settings["af_url"] 155 | or not self._user_settings["api_key"] 156 | and ( 157 | not self._user_settings["username"] 158 | or not self._user_settings["password"] 159 | ) 160 | ): 161 | 162 | message = ( 163 | "An af_url, and api_key or username and password " 164 | + "must be provided in a dictionary to set the user settings!" 165 | ) 166 | raise UserSettingsError(message) 167 | 168 | if self._user_settings["api_key"] and ( 169 | self._user_settings["username"] or self._user_settings["password"] 170 | ): 171 | message = ( 172 | "An api_key and user name and password" 173 | + " can't be provided at the same time!" 174 | ) 175 | raise UserSettingsError(message) 176 | 177 | self._user_settings["auth"] = None 178 | if self._user_settings["username"] and self._user_settings["password"]: 179 | self._user_settings["auth"] = ( 180 | self._user_settings["username"], 181 | self._user_settings["password"], 182 | ) 183 | 184 | self._user_settings["X-JFrog-Art-Api"] = self._user_settings["api_key"] 185 | self._user_settings["api_endpoint"] = self._user_settings["af_url"] + "/api/" 186 | 187 | allowed_raw_response = [False, True] 188 | allowed_verbose_level = [0, 1] 189 | 190 | if self._user_settings["raw_response"] not in allowed_raw_response: 191 | raise UserSettingsError( 192 | "raw_response must be " + str(allowed_raw_response) + "!" 193 | ) 194 | 195 | if self._user_settings["verbose_level"] not in allowed_verbose_level: 196 | raise UserSettingsError( 197 | "verbose_level must be " + str(allowed_verbose_level) + "!" 198 | ) 199 | 200 | def _request( 201 | self, 202 | verb, 203 | target, 204 | api_method, 205 | kwargs, 206 | byte_output=False, 207 | no_api=False, 208 | data=None, 209 | params=None, 210 | ): 211 | """ 212 | Call the remote API, process the response and return it. 213 | 214 | Parameters 215 | ---------- 216 | verb: str 217 | HTTP verb ("GET", "POST"...) 218 | target: str 219 | API sub endpoint specific for the method 220 | api_method: str 221 | Name of the specific method (category and name) 222 | kwargs: dict 223 | Dictionary of keyword arguments (supplied by the method) 224 | byte_output: bool 225 | True will return a raw requests.Response() object 226 | True indicates that the result of the method will be used to download 227 | a file/artifact 228 | False by default 229 | no_api: bool 230 | True to remove 'api/' from the target endpoint 231 | False by default 232 | data 233 | Python requests data keyword argument 234 | params 235 | Python requests json keyword argument 236 | 237 | Returns 238 | ------- 239 | response 240 | The result of the processed request.Response() 241 | given by _convert_response method 242 | 243 | """ 244 | # Checking if the settings kwarg was supplied 245 | settings = self._settings_if_settings_in_kwargs(kwargs) 246 | if settings: 247 | self._configure_user_settings(settings) 248 | self._validate_user_settings() 249 | 250 | headers = {} 251 | 252 | if self._user_settings["X-JFrog-Art-Api"]: 253 | headers = {"X-JFrog-Art-Api": self._user_settings["X-JFrog-Art-Api"]} 254 | 255 | auth = self._user_settings["auth"] 256 | raw_response = self._user_settings["raw_response"] 257 | 258 | # Changing the endpoint when necessary 259 | if not no_api: 260 | request_url = self._user_settings["api_endpoint"] + target 261 | if no_api: 262 | request_url = self._user_settings["af_url"] + target 263 | 264 | possible_headers = [ 265 | "Content-Type", 266 | "X-Checksum-Deploy", 267 | "X-Checksum-Sha1", 268 | "X-Checksum-Sha256", 269 | "X-Result-detail", 270 | "X-GPG-PASSPHRASE", 271 | ] 272 | # Extracting the headers from the params dict 273 | if params: 274 | 275 | headers_to_add = [field for field in params if field in possible_headers] 276 | 277 | for header in headers_to_add: 278 | headers[header] = str(params[header]) 279 | del params[header] 280 | else: 281 | params = None 282 | 283 | if self._user_settings["verbose_level"] >= 1: 284 | message = ( 285 | "\n\nPerforming Artifactory REST API operation : " 286 | + api_method 287 | + "\nVerb : " 288 | + verb 289 | + "\nURL : " 290 | + request_url 291 | ) 292 | sys.stdout.write(message) 293 | 294 | response = self._user_settings["session"].request( 295 | verb, request_url, headers=headers, json=params, data=data, auth=auth 296 | ) 297 | 298 | if self._user_settings["verbose_level"] >= 1: 299 | message = "\nStatus Code : " + str(response.status_code) + "\n" 300 | sys.stdout.write(message) 301 | 302 | if settings: 303 | self._restore_user_settings() 304 | 305 | return self._convert_response( 306 | api_method, request_url, verb, response, raw_response, byte_output 307 | ) 308 | 309 | def _convert_response( 310 | self, api_method, target, verb, response, raw_response, byte_output 311 | ): 312 | """ 313 | Convert (if necessary) a requests.Response() object raise an error. 314 | 315 | If response.text is of json format, a Python dict will be returned 316 | or an error if that dict is an error output dict 317 | Else a raw response.text will be returned 318 | 319 | A raw Python Requests response object is returned if raw_response 320 | or byte_output is true 321 | 322 | Parameters 323 | ---------- 324 | api_method: str 325 | Name of the specific method (category and name) 326 | target: str 327 | API sub endpoint specific for the method 328 | verb: str 329 | HTTP verb ("GET", "POST"...) 330 | response: requests.Response 331 | The requests.Response object for the API call 332 | raw_response: bool 333 | True to return the original requests.Response 334 | byte_output: bool 335 | True to return the original requests.Response if no errors are found 336 | 337 | Raises 338 | ------ 339 | error 340 | An error raised by the _process_and_raise_error method 341 | 342 | Returns 343 | ------- 344 | response: requests.Response or str or dict 345 | The returned response to the client 346 | 347 | """ 348 | if raw_response: 349 | return response 350 | 351 | # Trying to see if the response content can be loaded as a json 352 | bad_status_codes = list(range(400, 600)) 353 | if response.status_code in bad_status_codes: 354 | self._process_and_raise_error(response, api_method, target, verb) 355 | 356 | if not raw_response: 357 | try: 358 | if byte_output: 359 | return response 360 | else: 361 | response2 = response.json() 362 | return response2 363 | 364 | except ValueError: 365 | return response.text 366 | 367 | def _process_and_raise_error(self, response, api_method, target, verb): 368 | """ 369 | Look for errors in a requests.Response object from the Artifactory REST API. 370 | 371 | Parameters 372 | ---------- 373 | response: requests.Response() 374 | Original response for the API call 375 | api_method: str 376 | Name of the specific method (category and name) 377 | target: str 378 | API sub endpoint specific for the method 379 | verb: str 380 | HTTP verb ("GET", "POST"...) 381 | 382 | Raises 383 | ------ 384 | MalformedAfApiError 385 | If there is an error but the JSON is malformed 386 | AfApiError 387 | If the is an error 388 | and the JSON is a standard Artifactory REST API error JSON 389 | 390 | """ 391 | malformed_json = False 392 | error_details = None 393 | try: 394 | json_content = response.json() 395 | if "errors" in json_content: 396 | message = json_content["errors"][0]["message"] 397 | if "status" in json_content["errors"][0]: 398 | status_code = json_content["errors"][0]["status"] 399 | else: 400 | status_code = response.status_code 401 | 402 | error_details = { 403 | "api_method": api_method, 404 | "url": target, 405 | "verb": verb, 406 | "status_code": status_code, 407 | "message": message, 408 | } 409 | if not error_details: 410 | malformed_json = True 411 | except ValueError: 412 | malformed_json = True 413 | 414 | if malformed_json: 415 | message = ( 416 | "The json output for the error was malformed " 417 | "(Not a standard Artifactory REST API error json), " 418 | 'set the "raw_response" setting to True in your ' 419 | "settings dictionary to get a raw requests.Response() object " 420 | "for debugging!, requests.Response.text : {}".format(response.text) 421 | ) 422 | raise self.MalformedAfApiError(message) 423 | else: 424 | raise self.AfApiError(error_details) 425 | 426 | def _append_to_string(self, target, options): 427 | """ 428 | Add a string of options to a target string (URL in this case). 429 | 430 | Parameters 431 | ---------- 432 | target: str 433 | API sub endpoint specific for the method 434 | options: str 435 | String of options 436 | 437 | Returns 438 | ------- 439 | target: str 440 | The target with the string of options appended to it 441 | 442 | """ 443 | if options: 444 | target += options 445 | return target 446 | return target 447 | 448 | def _add_forward_slash_if_not_empty(self, target, path): 449 | """ 450 | Add a forward slash to a string if the other string != "". 451 | 452 | Parameters 453 | ---------- 454 | target: str 455 | API sub endpoint specific for the method 456 | path: str 457 | Path in the sub endpoint (usually and item) 458 | 459 | """ 460 | if path != "": 461 | target += "/" + path 462 | return target 463 | return target 464 | 465 | def _settings_if_settings_in_kwargs(self, kwargs): 466 | """ 467 | Extract the settings key from the kwargs dict and return it if present. 468 | 469 | Parameters 470 | ---------- 471 | kwargs: dict 472 | Keyword arguments 473 | 474 | Returns 475 | ------- 476 | settings: dict or None 477 | The settings dict if in kwargs, None otherwise 478 | 479 | """ 480 | if "settings" in kwargs: 481 | settings = kwargs["settings"] 482 | else: 483 | settings = None 484 | return settings 485 | 486 | class MalformedAfApiError(HTTPError): 487 | """Raised when encountering a malformed Artifactory REST API error JSON.""" 488 | 489 | pass 490 | 491 | class AfApiError(HTTPError): 492 | """ 493 | Raised when encountering a standard Artifactory REST API error JSON. 494 | 495 | Attributes 496 | ---------- 497 | api_method: str 498 | Name of the specific method (category and name) 499 | url: str 500 | Full URL used for API call 501 | verb: str 502 | HTTP verb used for the API call ("GET", "POST"...) 503 | status_code: int 504 | HTTP status code for the API call 505 | message: str 506 | Error message given by the Artifactory REST API 507 | print_message: str 508 | Well formatted multiline message with all the attributes 509 | 510 | """ 511 | 512 | def __init__(self, error_details): 513 | """Error object instantiation.""" 514 | self.api_method = error_details["api_method"] 515 | self.url = error_details["url"] 516 | self.verb = error_details["verb"] 517 | self.status_code = error_details["status_code"] 518 | self.message = error_details["message"] 519 | self.print_message = ( 520 | "\nArtifactory REST API operation : " 521 | + self.api_method 522 | + "\nURL : " 523 | + self.url 524 | + "\nVerb : " 525 | + self.verb 526 | + "\nStatus Code : " 527 | + str(self.status_code) 528 | + "\nMessage : " 529 | + self.message 530 | ) 531 | HTTPError.__init__(self, self.print_message) 532 | 533 | class RtpyError(ValueError): 534 | """ 535 | Raised if arguments are wrong or incomplete for a method. 536 | 537 | This error is used as a preemptive measure in some methods to check arguments. 538 | 539 | """ 540 | 541 | pass 542 | 543 | 544 | class UserSettingsError(ValueError): 545 | """Raised if some of the provided settings are incorrect or missing.""" 546 | 547 | pass 548 | 549 | 550 | def json_to_dict(json_file_path): 551 | """ 552 | Convert a .json file to a Python dictionary. 553 | 554 | Parameters 555 | ---------- 556 | json_file_path: str 557 | Path of the JSON file 558 | 559 | Returns 560 | ------- 561 | dictionary: dict 562 | The original JSON file as a Python dictionary 563 | 564 | """ 565 | with open(json_file_path, "r") as json_data: 566 | dictionary = json.load(json_data, encoding="utf8") 567 | return dictionary 568 | -------------------------------------------------------------------------------- /rtpy/xray.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Functions for the XRAY REST API Methods category.""" 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # DO NOT EDIT THIS FILE! 4 | # This file has been autogenerated by dephell <3 5 | # https://github.com/dephell/dephell 6 | 7 | try: 8 | from setuptools import setup 9 | except ImportError: 10 | from distutils.core import setup 11 | 12 | import os.path 13 | 14 | readme = '' 15 | here = os.path.abspath(os.path.dirname(__file__)) 16 | readme_path = os.path.join(here, 'README.rst') 17 | if os.path.exists(readme_path): 18 | with open(readme_path, 'rb') as stream: 19 | readme = stream.read().decode('utf8') 20 | 21 | setup( 22 | long_description=readme, 23 | name='rtpy', 24 | version='1.4.9', 25 | description='Python wrapper for the JFrog Artifactory REST API.', 26 | python_requires='!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,<4.0,>=2.7', 27 | project_urls={"homepage": "https://github.com/Orange-OpenSource/rtpy"}, 28 | author='Guillaume Renault', 29 | author_email='me@grenault.org', 30 | license='Apache-2.0', 31 | keywords='artifactory rest api wrapper', 32 | packages=['rtpy'], 33 | package_dir={"": "."}, 34 | package_data={}, 35 | install_requires=['requests==2.*,>=2.18.4'], 36 | extras_require={ 37 | "dev": [ 38 | "coverage==4.*,>=4.5.0", "poetry-setup==0.*,>=0.3.0", 39 | "pytest==3.*,>=3.6.0", "setuptools==40.*,>=40.0.0", 40 | "sphinx==1.*,>=1.8.2", "sphinx-rtd-theme==0.*,>=0.4.0", 41 | "sphinxcontrib-napoleon==0.*,>=0.7.0", "tox==3.*,>=3.2.0", 42 | "tox-pyenv==1.*,>=1.1.0" 43 | ] 44 | }, 45 | ) 46 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Test suite package.""" 10 | -------------------------------------------------------------------------------- /tests/assets/bundle_creation.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"test_bundle", 3 | "description":"test_bundle", 4 | "parameters":{ 5 | "configuration": "false", 6 | "system": "false", 7 | "logs":{ 8 | "include": "false" 9 | }, 10 | "thread_dump":{ 11 | "count": 1, 12 | "interval": 0 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/assets/python_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Orange-OpenSource/rtpy/389d2fd0186176862b1dbf2cf1df77d9e5b5e623/tests/assets/python_logo.png -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Definitions of the functions used during the tests.""" 10 | 11 | from __future__ import unicode_literals 12 | import os 13 | import random 14 | import string 15 | import sys 16 | 17 | import pytest 18 | 19 | import rtpy 20 | 21 | 22 | class RtpyTestHelpers(object): 23 | """ 24 | Main test class with fixtures and tools. 25 | 26 | Used in the tests, Subclassed by pytest classes in tests 27 | """ 28 | 29 | def instantiate_af_object_with_api_key_helpers(self): 30 | """Configure a Rtpy object using an api key.""" 31 | self.settings = {} 32 | self.settings["af_url"] = os.environ["AF_TEST_URL"] 33 | 34 | self.settings["username"] = os.environ["AF_TEST_USERNAME"] 35 | self.settings["password"] = os.environ["AF_TEST_PASSWORD"] 36 | self.af = rtpy.Rtpy(self.settings) 37 | try: 38 | self.af.security.revoke_api_key() 39 | except (self.af.AfApiError, self.af.MalformedAfApiError): 40 | pass 41 | r = self.af.security.create_api_key() 42 | del self.settings["username"] 43 | del self.settings["password"] 44 | self.settings["api_key"] = r["apiKey"] 45 | 46 | self.af = rtpy.Rtpy(self.settings) 47 | self.py_version = sys.version_info 48 | 49 | @pytest.fixture 50 | def instantiate_af_object_with_api_key(self): 51 | """Callable fixture.""" 52 | self.instantiate_af_object_with_api_key_helpers() 53 | 54 | def instantiate_af_object_with_credentials_helpers(self): 55 | """Configure a Rtpy object using a username and password.""" 56 | self.settings = {} 57 | self.settings["af_url"] = os.environ["AF_TEST_URL"] 58 | self.settings["username"] = os.environ["AF_TEST_USERNAME"] 59 | self.settings["password"] = os.environ["AF_TEST_PASSWORD"] 60 | self.af = rtpy.Rtpy(self.settings) 61 | self.py_version = sys.version_info 62 | 63 | @pytest.fixture 64 | def instantiate_af_object_with_credentials(self): 65 | """Callable fixture.""" 66 | self.instantiate_af_object_with_credentials_helpers() 67 | 68 | @pytest.fixture(params=["api_key", "credentials"]) 69 | def instantiate_af_objects_credentials_and_api_key(self, request): 70 | """ 71 | Create a Rtpy object using a api_key or username and password. 72 | 73 | The test will be executed twice if this fixture is called 74 | due to the params argument in the decorator 75 | """ 76 | if request.param == "api_key": 77 | self.instantiate_af_object_with_api_key_helpers() 78 | 79 | if request.param == "credentials": 80 | self.instantiate_af_object_with_credentials_helpers() 81 | 82 | def set_attribute_and_create_repo(self, package_type): 83 | """Set a repo key attribute and create a repository.""" 84 | setattr( 85 | self, package_type + "_repo_key", "rtpy_tests_" + package_type + "_repo" 86 | ) 87 | params = { 88 | "key": "rtpy_tests_" + package_type + "_repo", 89 | "rclass": "local", 90 | "packageType": package_type, 91 | } 92 | self.af.repositories.create_repository(params) 93 | 94 | @pytest.fixture 95 | def create_and_delete_yum_repo(self): 96 | """Pytest fixture to create and delete a yum repository.""" 97 | self.set_attribute_and_create_repo("yum") 98 | yield 99 | self.af.repositories.delete_repository(self.yum_repo_key) 100 | 101 | @pytest.fixture 102 | def create_and_delete_nuget_repo(self): 103 | """Pytest fixture to create and delete a nuget repository.""" 104 | self.set_attribute_and_create_repo("nuget") 105 | yield 106 | self.af.repositories.delete_repository(self.nuget_repo_key) 107 | 108 | @pytest.fixture 109 | def create_and_delete_npm_repo(self): 110 | """Pytest fixture to create and delete a npm repository.""" 111 | self.set_attribute_and_create_repo("npm") 112 | yield 113 | self.af.repositories.delete_repository(self.npm_repo_key) 114 | 115 | @pytest.fixture 116 | def create_and_delete_maven_repo(self): 117 | """Pytest fixture to create and delete a maven repository.""" 118 | self.set_attribute_and_create_repo("maven") 119 | yield 120 | self.af.repositories.delete_repository(self.maven_repo_key) 121 | 122 | @pytest.fixture 123 | def create_and_delete_opkg_repo(self): 124 | """Pytest fixture to create and delete a opkg repository.""" 125 | self.set_attribute_and_create_repo("opkg") 126 | yield 127 | self.af.repositories.delete_repository(self.opkg_repo_key) 128 | 129 | @pytest.fixture 130 | def create_and_delete_bower_repo(self): 131 | """Pytest fixture to create and delete a bower repository.""" 132 | self.set_attribute_and_create_repo("bower") 133 | yield 134 | self.af.repositories.delete_repository(self.bower_repo_key) 135 | 136 | @pytest.fixture 137 | def create_and_delete_helm_repo(self): 138 | """Pytest fixture to create and delete a helm repository.""" 139 | self.set_attribute_and_create_repo("helm") 140 | yield 141 | self.af.repositories.delete_repository(self.helm_repo_key) 142 | 143 | @pytest.fixture 144 | def create_and_delete_cran_repo(self): 145 | """Pytest fixture to create and delete a cran repository.""" 146 | self.set_attribute_and_create_repo("cran") 147 | yield 148 | self.af.repositories.delete_repository(self.cran_repo_key) 149 | 150 | @pytest.fixture 151 | def create_and_delete_conda_repo(self): 152 | """Pytest fixture to create and delete a conda repository.""" 153 | self.set_attribute_and_create_repo("conda") 154 | yield 155 | self.af.repositories.delete_repository(self.conda_repo_key) 156 | 157 | @pytest.fixture 158 | def create_and_delete_docker_repo(self): 159 | """Pytest fixture to create and delete a docker repository.""" 160 | self.set_attribute_and_create_repo("docker") 161 | yield 162 | self.af.repositories.delete_repository(self.docker_repo_key) 163 | 164 | @staticmethod 165 | def assert_isinstance_dict(r): 166 | """Assert that the input is a dict.""" 167 | assert isinstance(r, dict), "Invalid output, not a dict!" 168 | 169 | @staticmethod 170 | def assert_isinstance_str(py_version, r): 171 | """Assert that the input is a str.""" 172 | if py_version < (3, 4): 173 | assert isinstance(r, (str, unicode)), "Invalid output, not a str!" 174 | else: 175 | assert isinstance(r, (str)), "Invalid output, not a str!" 176 | 177 | @staticmethod 178 | def assert_isinstance_list(r): 179 | """Assert that the input is a list.""" 180 | assert isinstance(r, list), "Invalid output, not a list!" 181 | 182 | @staticmethod 183 | def generate_random_string(): 184 | """Generate a random string.""" 185 | # 64 characters total 186 | return "rtpy_tests_" + "".join( 187 | random.choice(string.ascii_uppercase + string.digits) for _ in range(53) 188 | ) 189 | 190 | @staticmethod 191 | def genererate_random_list_of_random_strings(): 192 | """Generate a random list of random strings.""" 193 | random_list = [] 194 | for i in range(0, 10): 195 | random_list.append(generate_random_string()) 196 | return random_list 197 | 198 | class RtpyTestError(AssertionError): 199 | """Raised in tests in case of failure or missing expected result.""" 200 | 201 | pass 202 | -------------------------------------------------------------------------------- /tests/import_license.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Use an environment variable to create a JSON file for the Artifactory license.""" 10 | 11 | import os 12 | import json 13 | 14 | key = os.environ["AF_TEST_LICENSE"] 15 | 16 | with open("tests/license.json", "w") as jsonfile: 17 | licensedata = {"licenseKey": key} 18 | jsonfile.write(json.dumps(licensedata)) 19 | -------------------------------------------------------------------------------- /tests/mixins.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Definitions of the functions used during the tests.""" 10 | 11 | from __future__ import unicode_literals 12 | import os 13 | import random 14 | import string 15 | import sys 16 | 17 | import pytest 18 | 19 | import rtpy 20 | 21 | 22 | class RtpyTestMixin(object): 23 | """ 24 | Main test class with fixtures and tools. 25 | 26 | Used in the tests, Subclassed by pytest classes in tests 27 | """ 28 | 29 | def instantiate_af_object_with_api_key_helpers(self): 30 | """Configure a Rtpy object using an api key.""" 31 | self.settings = {} 32 | self.settings["af_url"] = os.environ["AF_TEST_URL"] 33 | 34 | self.settings["username"] = os.environ["AF_TEST_USERNAME"] 35 | self.settings["password"] = os.environ["AF_TEST_PASSWORD"] 36 | self.af = rtpy.Rtpy(self.settings) 37 | try: 38 | self.af.security.revoke_api_key() 39 | except (self.af.AfApiError, self.af.MalformedAfApiError): 40 | pass 41 | r = self.af.security.create_api_key() 42 | del self.settings["username"] 43 | del self.settings["password"] 44 | self.settings["api_key"] = r["apiKey"] 45 | 46 | self.af = rtpy.Rtpy(self.settings) 47 | self.py_version = sys.version_info 48 | 49 | @pytest.fixture 50 | def instantiate_af_object_with_api_key(self): 51 | """Callable fixture.""" 52 | self.instantiate_af_object_with_api_key_helpers() 53 | 54 | def instantiate_af_object_with_credentials_helpers(self): 55 | """Configure a Rtpy object using a username and password.""" 56 | self.settings = {} 57 | self.settings["af_url"] = os.environ["AF_TEST_URL"] 58 | self.settings["username"] = os.environ["AF_TEST_USERNAME"] 59 | self.settings["password"] = os.environ["AF_TEST_PASSWORD"] 60 | self.af = rtpy.Rtpy(self.settings) 61 | self.py_version = sys.version_info 62 | 63 | @pytest.fixture 64 | def instantiate_af_object_with_credentials(self): 65 | """Callable fixture.""" 66 | self.instantiate_af_object_with_credentials_helpers() 67 | 68 | @pytest.fixture(params=["api_key", "credentials"]) 69 | def instantiate_af_objects_credentials_and_api_key(self, request): 70 | """ 71 | Create a Rtpy object using a api_key or username and password. 72 | 73 | The test will be executed twice if this fixture is called 74 | due to the params argument in the decorator 75 | """ 76 | if request.param == "api_key": 77 | self.instantiate_af_object_with_api_key_helpers() 78 | 79 | if request.param == "credentials": 80 | self.instantiate_af_object_with_credentials_helpers() 81 | 82 | def set_attribute_and_create_repo(self, package_type): 83 | """Set a repo key attribute and create a repository.""" 84 | setattr( 85 | self, package_type + "_repo_key", "rtpy_tests_" + package_type + "_repo" 86 | ) 87 | params = { 88 | "key": "rtpy_tests_" + package_type + "_repo", 89 | "rclass": "local", 90 | "packageType": package_type, 91 | } 92 | self.af.repositories.create_repository(params) 93 | 94 | @pytest.fixture 95 | def create_and_delete_yum_repo(self): 96 | """Pytest fixture to create and delete a yum repository.""" 97 | self.set_attribute_and_create_repo("yum") 98 | yield 99 | self.af.repositories.delete_repository(self.yum_repo_key) 100 | 101 | @pytest.fixture 102 | def create_and_delete_nuget_repo(self): 103 | """Pytest fixture to create and delete a nuget repository.""" 104 | self.set_attribute_and_create_repo("nuget") 105 | yield 106 | self.af.repositories.delete_repository(self.nuget_repo_key) 107 | 108 | @pytest.fixture 109 | def create_and_delete_npm_repo(self): 110 | """Pytest fixture to create and delete a npm repository.""" 111 | self.set_attribute_and_create_repo("npm") 112 | yield 113 | self.af.repositories.delete_repository(self.npm_repo_key) 114 | 115 | @pytest.fixture 116 | def create_and_delete_maven_repo(self): 117 | """Pytest fixture to create and delete a maven repository.""" 118 | self.set_attribute_and_create_repo("maven") 119 | yield 120 | self.af.repositories.delete_repository(self.maven_repo_key) 121 | 122 | @pytest.fixture 123 | def create_and_delete_opkg_repo(self): 124 | """Pytest fixture to create and delete a opkg repository.""" 125 | self.set_attribute_and_create_repo("opkg") 126 | yield 127 | self.af.repositories.delete_repository(self.opkg_repo_key) 128 | 129 | @pytest.fixture 130 | def create_and_delete_bower_repo(self): 131 | """Pytest fixture to create and delete a bower repository.""" 132 | self.set_attribute_and_create_repo("bower") 133 | yield 134 | self.af.repositories.delete_repository(self.bower_repo_key) 135 | 136 | @pytest.fixture 137 | def create_and_delete_helm_repo(self): 138 | """Pytest fixture to create and delete a helm repository.""" 139 | self.set_attribute_and_create_repo("helm") 140 | yield 141 | self.af.repositories.delete_repository(self.helm_repo_key) 142 | 143 | @pytest.fixture 144 | def create_and_delete_cran_repo(self): 145 | """Pytest fixture to create and delete a cran repository.""" 146 | self.set_attribute_and_create_repo("cran") 147 | yield 148 | self.af.repositories.delete_repository(self.cran_repo_key) 149 | 150 | @pytest.fixture 151 | def create_and_delete_conda_repo(self): 152 | """Pytest fixture to create and delete a conda repository.""" 153 | self.set_attribute_and_create_repo("conda") 154 | yield 155 | self.af.repositories.delete_repository(self.conda_repo_key) 156 | 157 | @pytest.fixture 158 | def create_and_delete_docker_repo(self): 159 | """Pytest fixture to create and delete a docker repository.""" 160 | self.set_attribute_and_create_repo("docker") 161 | yield 162 | self.af.repositories.delete_repository(self.docker_repo_key) 163 | 164 | @staticmethod 165 | def assert_isinstance_dict(r): 166 | """Assert that the input is a dict.""" 167 | assert isinstance(r, dict), "Invalid output, not a dict!" 168 | 169 | @staticmethod 170 | def assert_isinstance_str(py_version, r): 171 | """Assert that the input is a str.""" 172 | if py_version < (3, 4): 173 | assert isinstance(r, (str, unicode)), "Invalid output, not a str!" 174 | else: 175 | assert isinstance(r, (str)), "Invalid output, not a str!" 176 | 177 | @staticmethod 178 | def assert_isinstance_list(r): 179 | """Assert that the input is a list.""" 180 | assert isinstance(r, list), "Invalid output, not a list!" 181 | 182 | @staticmethod 183 | def generate_random_string(): 184 | """Generate a random string.""" 185 | # 64 characters total 186 | return "rtpy_tests_" + "".join( 187 | random.choice(string.ascii_uppercase + string.digits) for _ in range(53) 188 | ) 189 | 190 | @staticmethod 191 | def genererate_random_list_of_random_strings(): 192 | """Generate a random list of random strings.""" 193 | random_list = [] 194 | for i in range(0, 10): 195 | random_list.append(generate_random_string()) 196 | return random_list 197 | 198 | class RtpyTestError(AssertionError): 199 | """Raised in tests in case of failure or missing expected result.""" 200 | 201 | pass 202 | -------------------------------------------------------------------------------- /tests/test_artifacts_and_storage.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Definitions of the tests for the ARTIFACTS AND STORAGE REST API Methods category.""" 10 | 11 | from __future__ import unicode_literals 12 | import os 13 | 14 | import pytest 15 | 16 | from .mixins import RtpyTestMixin 17 | 18 | 19 | class TestsArtifactsAndStorage(RtpyTestMixin): 20 | """ARTIFACTS AND STORAGE methods category tests.""" 21 | 22 | @pytest.fixture 23 | def setup_then_destroy_test_env(self): 24 | """Create the test environement for each specific test.""" 25 | # setup 26 | self.repo_name = RtpyTestMixin.generate_random_string() 27 | self.artifact_path = "python_logo.png" 28 | self.params = {} 29 | self.params["key"] = self.repo_name 30 | self.params["rclass"] = "local" 31 | self.params["packageType"] = "debian" 32 | self.af.repositories.create_repository(self.params) 33 | 34 | self.folder_name = RtpyTestMixin.generate_random_string() 35 | self.af.artifacts_and_storage.create_directory(self.repo_name, self.folder_name) 36 | 37 | self.af.artifacts_and_storage.deploy_artifact( 38 | self.repo_name, "tests/assets/python_logo.png", self.artifact_path 39 | ) 40 | self.af.artifacts_and_storage.set_item_properties( 41 | self.repo_name, "python_logo.png", "test=true" 42 | ) 43 | 44 | yield 45 | 46 | # teardown 47 | self.af.repositories.delete_repository(self.repo_name) 48 | 49 | def test_folder_info( 50 | self, 51 | instantiate_af_objects_credentials_and_api_key, 52 | setup_then_destroy_test_env, 53 | ): 54 | """Folder Info method tests.""" 55 | r = self.af.artifacts_and_storage.folder_info(self.repo_name, self.folder_name) 56 | RtpyTestMixin.assert_isinstance_dict(r) 57 | 58 | def test_file_info( 59 | self, 60 | instantiate_af_objects_credentials_and_api_key, 61 | setup_then_destroy_test_env, 62 | ): 63 | """File Info method tests.""" 64 | r = self.af.artifacts_and_storage.file_info(self.repo_name, self.artifact_path) 65 | RtpyTestMixin.assert_isinstance_dict(r) 66 | 67 | def test_get_storage_summary_info( 68 | self, 69 | instantiate_af_objects_credentials_and_api_key, 70 | setup_then_destroy_test_env, 71 | ): 72 | """Get Storage Summary Info tests.""" 73 | r = self.af.artifacts_and_storage.get_storage_summary_info() 74 | RtpyTestMixin.assert_isinstance_dict(r) 75 | 76 | def test_item_last_modified( 77 | self, 78 | instantiate_af_objects_credentials_and_api_key, 79 | setup_then_destroy_test_env, 80 | ): 81 | """Item Last Modified tests.""" 82 | r = self.af.artifacts_and_storage.item_last_modified( 83 | self.repo_name, self.artifact_path 84 | ) 85 | RtpyTestMixin.assert_isinstance_dict(r) 86 | 87 | def test_file_statistics( 88 | self, 89 | instantiate_af_objects_credentials_and_api_key, 90 | setup_then_destroy_test_env, 91 | ): 92 | """File Statistics tests.""" 93 | r = self.af.artifacts_and_storage.file_statistics( 94 | self.repo_name, self.artifact_path 95 | ) 96 | RtpyTestMixin.assert_isinstance_dict(r) 97 | 98 | def test_item_properties( 99 | self, 100 | instantiate_af_objects_credentials_and_api_key, 101 | setup_then_destroy_test_env, 102 | ): 103 | """Item Properties tests.""" 104 | r = self.af.artifacts_and_storage.set_item_properties( 105 | self.repo_name, self.artifact_path, "rtpy=true" 106 | ) 107 | r = self.af.artifacts_and_storage.item_properties( 108 | self.repo_name, self.artifact_path 109 | ) 110 | RtpyTestMixin.assert_isinstance_dict(r) 111 | r = self.af.artifacts_and_storage.item_properties( 112 | self.repo_name, self.artifact_path, properties="rtpy" 113 | ) 114 | RtpyTestMixin.assert_isinstance_dict(r) 115 | 116 | def test_set_item_properties( 117 | self, 118 | instantiate_af_objects_credentials_and_api_key, 119 | setup_then_destroy_test_env, 120 | ): 121 | """Set Item Properties tests.""" 122 | self.af.artifacts_and_storage.set_item_properties( 123 | self.repo_name, self.artifact_path, "rtpy=true" 124 | ) 125 | r = self.af.artifacts_and_storage.item_properties( 126 | self.repo_name, self.artifact_path 127 | ) 128 | if r["properties"]["rtpy"][0] != "true": 129 | message = "Property missing or has incorrect value" 130 | raise self.RtpyTestError(message) 131 | self.af.artifacts_and_storage.delete_item_properties( 132 | self.repo_name, self.artifact_path, "rtpy" 133 | ) 134 | RtpyTestMixin.assert_isinstance_dict(r) 135 | self.af.artifacts_and_storage.set_item_properties( 136 | self.repo_name, self.artifact_path, "rtpy=true", options="&recursive=0" 137 | ) 138 | r = self.af.artifacts_and_storage.item_properties( 139 | self.repo_name, self.artifact_path 140 | ) 141 | if r["properties"]["rtpy"][0] != "true": 142 | message = "Property missing or has incorrect value" 143 | raise self.RtpyTestError(message) 144 | self.af.artifacts_and_storage.delete_item_properties( 145 | self.repo_name, self.artifact_path, "rtpy" 146 | ) 147 | 148 | def test_delete_item_properties( 149 | self, 150 | instantiate_af_objects_credentials_and_api_key, 151 | setup_then_destroy_test_env, 152 | ): 153 | """Delete Item Properties tests.""" 154 | self.af.artifacts_and_storage.set_item_properties( 155 | self.repo_name, self.artifact_path, "rtpy=true;p2=false" 156 | ) 157 | 158 | r = self.af.artifacts_and_storage.item_properties( 159 | self.repo_name, self.artifact_path 160 | ) 161 | if r["properties"]["rtpy"][0] != "true": 162 | message = "Property missing or has incorrect value" 163 | raise self.RtpyTestError(message) 164 | self.af.artifacts_and_storage.delete_item_properties( 165 | self.repo_name, self.artifact_path, "rtpy" 166 | ) 167 | self.af.artifacts_and_storage.delete_item_properties( 168 | self.repo_name, self.artifact_path, "rtpy", options="&recursive=0" 169 | ) 170 | r = self.af.artifacts_and_storage.item_properties( 171 | self.repo_name, self.artifact_path 172 | ) 173 | if "rtpy" in r["properties"]: 174 | message = "Property wasn't deleted!" 175 | raise self.RtpyTestError(message) 176 | RtpyTestMixin.assert_isinstance_dict(r) 177 | 178 | def test_set_item_sha256_checksum( 179 | self, 180 | instantiate_af_objects_credentials_and_api_key, 181 | setup_then_destroy_test_env, 182 | ): 183 | """Set Item 256 checksum tests.""" 184 | params = {"repoKey": self.repo_name, "path": self.artifact_path} 185 | self.af.artifacts_and_storage.set_item_sha256_checksum(params) 186 | r = self.af.artifacts_and_storage.item_properties( 187 | self.repo_name, self.artifact_path 188 | ) 189 | if "sha256" not in r["properties"]: 190 | message = "sha256 property wasn't added!" 191 | raise self.RtpyTestError(message) 192 | RtpyTestMixin.assert_isinstance_dict(r) 193 | 194 | def test_retrieve_artifact( 195 | self, 196 | instantiate_af_objects_credentials_and_api_key, 197 | setup_then_destroy_test_env, 198 | ): 199 | """Retrieve Artifact tests.""" 200 | r = self.af.artifacts_and_storage.retrieve_artifact( 201 | self.repo_name, self.artifact_path 202 | ) 203 | with open("myartifact.png", "wb") as artifact: 204 | artifact.write(r.content) 205 | 206 | os.remove("myartifact.png") 207 | 208 | # Unsupported methods 209 | # def test_retrieve_latest_artifact(): 210 | # def test_retrieve_build_artifacts_archive():""" 211 | 212 | def test_retrieve_folder_or_repository_archive( 213 | self, 214 | instantiate_af_objects_credentials_and_api_key, 215 | setup_then_destroy_test_env, 216 | ): 217 | """ 218 | Retrieve Folder or Repository Archive tests. 219 | 220 | Folder download is not allowed by default 221 | catching the HTTP 403 so the test can pass 222 | 223 | """ 224 | archive_types = ["zip", "tar", "tar.gz", "tgz"] 225 | 226 | self.af.artifacts_and_storage.create_directory(self.repo_name, "mydir") 227 | 228 | for archive_type in archive_types: 229 | try: 230 | r = self.af.artifacts_and_storage.retrieve_folder_or_repository_archive( 231 | self.repo_name, "", archive_type 232 | ) 233 | with open("myarchive." + archive_type, "wb") as archive: 234 | archive.write(r.content) 235 | os.remove("myarchive." + archive_type) 236 | except self.af.AfApiError as error: 237 | if error.status_code != 403: 238 | raise error 239 | 240 | try: 241 | r = self.af.artifacts_and_storage.retrieve_folder_or_repository_archive( 242 | self.repo_name, "mydir", archive_type, include_checksums=True 243 | ) 244 | with open("myarchive." + archive_type, "wb") as archive: 245 | archive.write(r.content) 246 | os.remove("myarchive." + archive_type) 247 | except self.af.AfApiError as error: 248 | if error.status_code != 403: 249 | raise error 250 | 251 | def test_trace_artifact_retrieval( 252 | self, 253 | instantiate_af_objects_credentials_and_api_key, 254 | setup_then_destroy_test_env, 255 | ): 256 | """Trace Artifact Retrieval tests.""" 257 | r = self.af.artifacts_and_storage.trace_artifact_retrieval( 258 | self.repo_name, self.artifact_path 259 | ) 260 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 261 | 262 | # Unsupported method 263 | # def test_archive_entry_download(): 264 | 265 | def test_create_directory( 266 | self, 267 | instantiate_af_objects_credentials_and_api_key, 268 | setup_then_destroy_test_env, 269 | ): 270 | """Create Directory tests.""" 271 | r = self.af.artifacts_and_storage.create_directory(self.repo_name, "mydir") 272 | RtpyTestMixin.assert_isinstance_dict(r) 273 | r = self.af.artifacts_and_storage.folder_info(self.repo_name, "mydir") 274 | 275 | def test_deploy_artifact( 276 | self, 277 | instantiate_af_objects_credentials_and_api_key, 278 | setup_then_destroy_test_env, 279 | ): 280 | """Deploy Artifact tests.""" 281 | r = self.af.artifacts_and_storage.deploy_artifact( 282 | self.repo_name, "tests/assets/python_logo.png", "python_logo.png" 283 | ) 284 | RtpyTestMixin.assert_isinstance_dict(r) 285 | self.af.artifacts_and_storage.file_info(self.repo_name, "python_logo.png") 286 | 287 | def test_deploy_artifact_by_checksum( 288 | self, 289 | instantiate_af_objects_credentials_and_api_key, 290 | setup_then_destroy_test_env, 291 | ): 292 | """Deploy Artifact by checksum tests.""" 293 | r = self.af.artifacts_and_storage.deploy_artifact_by_checksum( 294 | self.repo_name, 295 | "python_logo_copy1.png", 296 | "sha1", 297 | "9e0a0e7dbc3d5f05c71812e0a38aebefe525506c", 298 | ) 299 | 300 | RtpyTestMixin.assert_isinstance_dict(r) 301 | self.af.artifacts_and_storage.file_info(self.repo_name, "python_logo_copy1.png") 302 | 303 | r = self.af.artifacts_and_storage.deploy_artifact_by_checksum( 304 | self.repo_name, 305 | "python_logo_copy2.png", 306 | "sha256", 307 | "fd04e86f3b8992bd599bab7ec407223aebd14541b7b055f5725d6d55398708c5", 308 | ) 309 | RtpyTestMixin.assert_isinstance_dict(r) 310 | self.af.artifacts_and_storage.file_info(self.repo_name, "python_logo_copy2.png") 311 | 312 | # Unsupported methods 313 | # def test_deploy_artifacts_from_archive(): 314 | # def test_push_a_set_of_artifacts_to_bintray(): 315 | # def test_push_docker_tag_to_bintray(): 316 | # def test_distribute_artifact(): 317 | # def test_file_compliance_info(): 318 | 319 | def test_delete_item( 320 | self, 321 | instantiate_af_objects_credentials_and_api_key, 322 | setup_then_destroy_test_env, 323 | ): 324 | """Delete Item tests.""" 325 | r = self.af.artifacts_and_storage.delete_item( 326 | self.repo_name, self.artifact_path 327 | ) 328 | try: 329 | r = self.af.artifacts_and_storage.file_info( 330 | self.repo_name, self.artifact_path 331 | ) 332 | except self.af.AfApiError as error: 333 | if error.status_code != 404: 334 | raise self.RtpyTestError("Artifact wasn't deleted!") 335 | 336 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 337 | 338 | def test_copy_item( 339 | self, 340 | instantiate_af_objects_credentials_and_api_key, 341 | setup_then_destroy_test_env, 342 | ): 343 | """Copy Item tests.""" 344 | r = self.af.artifacts_and_storage.copy_item( 345 | self.repo_name, self.artifact_path, self.repo_name, "python_logo_copy.png" 346 | ) 347 | 348 | RtpyTestMixin.assert_isinstance_dict(r) 349 | self.af.artifacts_and_storage.file_info(self.repo_name, "python_logo_copy.png") 350 | r = self.af.artifacts_and_storage.copy_item( 351 | self.repo_name, 352 | self.artifact_path, 353 | self.repo_name, 354 | "python_logo_copy.png", 355 | options="&dry=1", 356 | ) 357 | RtpyTestMixin.assert_isinstance_dict(r) 358 | 359 | def test_move_item( 360 | self, 361 | instantiate_af_objects_credentials_and_api_key, 362 | setup_then_destroy_test_env, 363 | ): 364 | """Move Item tests.""" 365 | r = self.af.artifacts_and_storage.move_item( 366 | self.repo_name, self.artifact_path, self.repo_name, "python_logo_copy.png" 367 | ) 368 | RtpyTestMixin.assert_isinstance_dict(r) 369 | self.af.artifacts_and_storage.file_info(self.repo_name, "python_logo_copy.png") 370 | r = self.af.artifacts_and_storage.move_item( 371 | self.repo_name, 372 | "python_logo_copy.png", 373 | self.repo_name, 374 | "python_logo_copy2.png", 375 | options="&dry=1", 376 | ) 377 | RtpyTestMixin.assert_isinstance_dict(r) 378 | 379 | def test_artifact_sync_download( 380 | self, 381 | instantiate_af_objects_credentials_and_api_key, 382 | setup_then_destroy_test_env, 383 | ): 384 | """Artifact Sync Download tests.""" 385 | r = self.af.artifacts_and_storage.artifact_sync_download( 386 | self.repo_name, self.artifact_path, options="?content=progress" 387 | ) 388 | 389 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 390 | 391 | # Unsupported methods 392 | # def test_get_repository_replication_configuration(): 393 | # def test_set_repository_replication_configuration(): 394 | # def test_update_repository_replication_configuration(): 395 | # def test_delete_repository_replication_configuration(): 396 | # def test_scheduled_replication_status(): 397 | # def test_pull_or_push_replication(): 398 | # def test_update_local_multi_push_replication(): 399 | # def test_delete_local_multi_push_replication(): 400 | # def test_enable_or_disable_multiple_replications(): 401 | # def test_global_system_replication_configuration(): 402 | # def test_block_system_replication(): 403 | # def test_unblock_system_replication(): 404 | 405 | def test_file_list( 406 | self, 407 | instantiate_af_objects_credentials_and_api_key, 408 | setup_then_destroy_test_env, 409 | ): 410 | """File List tests.""" 411 | r = self.af.artifacts_and_storage.file_list(self.repo_name, "") 412 | RtpyTestMixin.assert_isinstance_dict(r) 413 | r = self.af.artifacts_and_storage.file_list(self.repo_name, self.folder_name) 414 | RtpyTestMixin.assert_isinstance_dict(r) 415 | r = self.af.artifacts_and_storage.file_list( 416 | self.repo_name, self.folder_name, options="&mdTimestamps=0" 417 | ) 418 | RtpyTestMixin.assert_isinstance_dict(r) 419 | 420 | def test_get_background_tasks( 421 | self, 422 | instantiate_af_objects_credentials_and_api_key, 423 | setup_then_destroy_test_env, 424 | ): 425 | """Get Background Tasks tests.""" 426 | r = self.af.artifacts_and_storage.get_background_tasks() 427 | RtpyTestMixin.assert_isinstance_dict(r) 428 | 429 | def test_empty_trash_can( 430 | self, 431 | instantiate_af_objects_credentials_and_api_key, 432 | setup_then_destroy_test_env, 433 | ): 434 | """Empty Trash Can tests.""" 435 | r = self.af.artifacts_and_storage.empty_trash_can() 436 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 437 | 438 | def test_delete_item_from_trash_can( 439 | self, 440 | instantiate_af_objects_credentials_and_api_key, 441 | setup_then_destroy_test_env, 442 | ): 443 | """Delete Item From Trash Can tests.""" 444 | r = self.af.artifacts_and_storage.delete_item( 445 | self.repo_name, self.artifact_path 446 | ) 447 | path_in_trashcan = self.repo_name + "/" + self.artifact_path 448 | r = self.af.artifacts_and_storage.delete_item_from_trash_can(path_in_trashcan) 449 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 450 | 451 | def test_restore_item_from_trash_can( 452 | self, 453 | instantiate_af_objects_credentials_and_api_key, 454 | setup_then_destroy_test_env, 455 | ): 456 | """Restore Item From Trash Can tests.""" 457 | r = self.af.artifacts_and_storage.delete_item( 458 | self.repo_name, self.artifact_path 459 | ) 460 | path_in_trashcan = self.repo_name + "/" + self.artifact_path 461 | target_path = self.repo_name + "/" + "python_logo_restored.png" 462 | r = self.af.artifacts_and_storage.restore_item_from_trash_can( 463 | path_in_trashcan, target_path 464 | ) 465 | r = self.af.artifacts_and_storage.file_info( 466 | self.repo_name, "python_logo_restored.png" 467 | ) 468 | RtpyTestMixin.assert_isinstance_dict(r) 469 | 470 | def test_optimize_system_storage( 471 | self, 472 | instantiate_af_objects_credentials_and_api_key, 473 | setup_then_destroy_test_env, 474 | ): 475 | """Optimize System Storage tests.""" 476 | r = self.af.artifacts_and_storage.optimize_system_storage() 477 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 478 | -------------------------------------------------------------------------------- /tests/test_builds.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Definitions of the tests for the BUILDS REST API Methods category.""" 10 | 11 | from __future__ import unicode_literals 12 | 13 | import pytest 14 | 15 | from .mixins import RtpyTestMixin 16 | 17 | 18 | class TestsBuilds(RtpyTestMixin): 19 | """BUILDS methods category tests.""" 20 | 21 | @pytest.fixture 22 | def setup_and_teardown_test_env(self): 23 | """Create the test environement for each specific test.""" 24 | pass 25 | 26 | def test_all_builds( 27 | self, 28 | instantiate_af_objects_credentials_and_api_key, 29 | setup_and_teardown_test_env, 30 | ): 31 | """ 32 | All Builds tests. 33 | 34 | Currently returning 404 because no builds were previously created 35 | 36 | """ 37 | try: 38 | self.af.builds.all_builds() 39 | except self.af.AfApiError as error: 40 | if error.status_code != 404: 41 | raise error 42 | 43 | # Unsupported methods 44 | # def test_build_runs() 45 | # def test_build_upload() 46 | # def test_build_info() 47 | # def test_builds_diff() 48 | # def test_build_promotion() 49 | # def test_promote_docker_image() 50 | # def test_delete_builds() 51 | # def test_push_build_to_bintray() 52 | # def test_distribute_build() 53 | # def test_control_build_retention() 54 | -------------------------------------------------------------------------------- /tests/test_repositories.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Definitions of the tests for the REPOSITORIES REST API Methods category.""" 10 | 11 | from __future__ import unicode_literals 12 | import time 13 | 14 | import pytest 15 | 16 | from .mixins import RtpyTestMixin 17 | 18 | 19 | class TestsRepositories(RtpyTestMixin): 20 | """REPOSITORIES methods category tests.""" 21 | 22 | @pytest.fixture 23 | def create_debian_repo_push_artifacts_then_delete_repo(self): 24 | """Create the test environement for each specific test.""" 25 | # setup 26 | self.debian_repo_key = "rtpy_tests_repositories_debian_repo" 27 | self.artifact_path = "python_logo.png" 28 | params = { 29 | "key": self.debian_repo_key, 30 | "rclass": "local", 31 | "packageType": "debian", 32 | } 33 | self.af.repositories.create_repository(params) 34 | 35 | self.folder_name = RtpyTestMixin.generate_random_string() 36 | self.af.artifacts_and_storage.create_directory( 37 | self.debian_repo_key, self.folder_name 38 | ) 39 | 40 | self.af.artifacts_and_storage.deploy_artifact( 41 | self.debian_repo_key, "tests/assets/python_logo.png", self.artifact_path 42 | ) 43 | self.af.artifacts_and_storage.set_item_properties( 44 | self.debian_repo_key, "python_logo.png", "test=true" 45 | ) 46 | yield 47 | 48 | # teardown 49 | self.af.repositories.delete_repository(self.debian_repo_key) 50 | 51 | def test_get_repositories(self, instantiate_af_objects_credentials_and_api_key): 52 | """Get Repositories tests.""" 53 | r = self.af.repositories.get_repositories() 54 | RtpyTestMixin.assert_isinstance_list(r) 55 | r = self.af.repositories.get_repositories( 56 | options="?type=local&packageType=pypi" 57 | ) 58 | RtpyTestMixin.assert_isinstance_list(r) 59 | 60 | def test_repository_configuration( 61 | self, 62 | instantiate_af_objects_credentials_and_api_key, 63 | create_debian_repo_push_artifacts_then_delete_repo, 64 | ): 65 | """Repository Configuration tests.""" 66 | r = self.af.repositories.repository_configuration(self.debian_repo_key) 67 | RtpyTestMixin.assert_isinstance_dict(r) 68 | 69 | def test_create_repository(self, instantiate_af_objects_credentials_and_api_key): 70 | """Create Repository tests.""" 71 | params = { 72 | "key": "rtpy_tests_repositories_test_create_repository", 73 | "rclass": "local", 74 | "packageType": "generic", 75 | } 76 | r = self.af.repositories.create_repository(params) 77 | self.af.repositories.delete_repository( 78 | "rtpy_tests_repositories_test_create_repository" 79 | ) 80 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 81 | 82 | def test_update_repository_configuration( 83 | self, 84 | instantiate_af_objects_credentials_and_api_key, 85 | create_debian_repo_push_artifacts_then_delete_repo, 86 | ): 87 | """Update Repository Configuration tests.""" 88 | params = {"description": "new_description", "key": self.debian_repo_key} 89 | r = self.af.repositories.update_repository_configuration(params) 90 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 91 | 92 | def test_delete_repository(self, instantiate_af_objects_credentials_and_api_key): 93 | """Delete Repository tests.""" 94 | params = { 95 | "key": "rtpy_tests_repositories_delete_repository", 96 | "rclass": "local", 97 | "packageType": "generic", 98 | } 99 | self.af.repositories.create_repository(params) 100 | r = self.af.repositories.delete_repository( 101 | "rtpy_tests_repositories_delete_repository" 102 | ) 103 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 104 | 105 | def test_calculate_yum_repository_metadata( 106 | self, instantiate_af_objects_credentials_and_api_key, create_and_delete_yum_repo 107 | ): 108 | """Calculate YUM Repository Metadata tests.""" 109 | self.af.artifacts_and_storage.create_directory(self.yum_repo_key, "folder1") 110 | r = self.af.repositories.calculate_yum_repository_metadata(self.yum_repo_key) 111 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 112 | r = self.af.repositories.calculate_yum_repository_metadata( 113 | self.yum_repo_key, options="?path=folder1&async=0" 114 | ) 115 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 116 | r = self.af.repositories.calculate_yum_repository_metadata( 117 | self.yum_repo_key, options="?path=folder1&async=0", x_gpg_passphrase="abc" 118 | ) 119 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 120 | 121 | def test_calculate_nuget_repository_metadata( 122 | self, 123 | instantiate_af_objects_credentials_and_api_key, 124 | create_and_delete_nuget_repo, 125 | ): 126 | """Calculate Nuget Repository Metadata tests.""" 127 | r = self.af.repositories.calculate_nuget_repository_metadata( 128 | self.nuget_repo_key 129 | ) 130 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 131 | 132 | def test_calculate_npm_repository_metadata( 133 | self, instantiate_af_objects_credentials_and_api_key, create_and_delete_npm_repo 134 | ): 135 | """Calculate NPM Repository Metadata tests.""" 136 | r = self.af.repositories.calculate_npm_repository_metadata(self.npm_repo_key) 137 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 138 | 139 | def test_calculate_maven_index( 140 | self, 141 | instantiate_af_objects_credentials_and_api_key, 142 | create_and_delete_maven_repo, 143 | ): 144 | """Test Calculate Maven Index tests.""" 145 | # The test is run twice with credentials and api_key which can result 146 | # in the following error: 147 | # Status Code : 500 148 | # Message : Could not run indexer: Another manual 149 | # task org.artifactory.maven.index.MavenIndexerJob is still active! 150 | while True: 151 | try: 152 | r = self.af.repositories.calculate_maven_index( 153 | "repos=" + self.maven_repo_key 154 | ) 155 | break 156 | except self.af.AfApiError as error: 157 | if error.status_code == 500: 158 | time.sleep(1) 159 | pass 160 | 161 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 162 | 163 | def test_calculate_maven_metadata( 164 | self, 165 | instantiate_af_objects_credentials_and_api_key, 166 | create_and_delete_maven_repo, 167 | ): 168 | """Calculate Maven Metadata tests.""" 169 | self.af.artifacts_and_storage.create_directory(self.maven_repo_key, "folder1") 170 | r = self.af.repositories.calculate_maven_metadata( 171 | self.maven_repo_key, "folder1" 172 | ) 173 | r = self.af.repositories.calculate_maven_metadata( 174 | self.maven_repo_key, "folder1", options="?nonRecursive=true" 175 | ) 176 | 177 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 178 | 179 | def test_calculate_debian_repository_metadata( 180 | self, 181 | instantiate_af_objects_credentials_and_api_key, 182 | create_debian_repo_push_artifacts_then_delete_repo, 183 | ): 184 | """Calculate Debian Repository Metadata.""" 185 | r = self.af.repositories.calculate_debian_repository_metadata( 186 | self.debian_repo_key 187 | ) 188 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 189 | r = self.af.repositories.calculate_debian_repository_metadata( 190 | self.debian_repo_key, options="?writeProps=0" 191 | ) 192 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 193 | r = self.af.repositories.calculate_debian_repository_metadata( 194 | self.debian_repo_key, options="?writeProps=0", x_gpg_passphrase="abc" 195 | ) 196 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 197 | 198 | def test_calculate_opkg_repository_metadata( 199 | self, 200 | instantiate_af_objects_credentials_and_api_key, 201 | create_and_delete_opkg_repo, 202 | ): 203 | """Calculate Opkg Repository Metadata tests.""" 204 | r = self.af.repositories.calculate_opkg_repository_metadata(self.opkg_repo_key) 205 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 206 | r = self.af.repositories.calculate_opkg_repository_metadata( 207 | self.opkg_repo_key, options="?writeProps=0" 208 | ) 209 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 210 | r = self.af.repositories.calculate_opkg_repository_metadata( 211 | self.opkg_repo_key, options="?writeProps=0", x_gpg_passphrase="abc" 212 | ) 213 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 214 | 215 | def test_calculate_bower_index( 216 | self, 217 | instantiate_af_objects_credentials_and_api_key, 218 | create_and_delete_bower_repo, 219 | ): 220 | """Calculate Bower Index tests.""" 221 | r = self.af.repositories.calculate_bower_index(self.bower_repo_key) 222 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 223 | 224 | def test_calculate_helm_chart_index( 225 | self, 226 | instantiate_af_objects_credentials_and_api_key, 227 | create_and_delete_helm_repo, 228 | ): 229 | """Calculate Helm Chart Index tests.""" 230 | r = self.af.repositories.calculate_helm_chart_index(self.helm_repo_key) 231 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 232 | 233 | def test_calculate_cran_repository_metadata( 234 | self, 235 | instantiate_af_objects_credentials_and_api_key, 236 | create_and_delete_cran_repo, 237 | ): 238 | """Calculate CRAN Repository Metadata tests.""" 239 | r = self.af.repositories.calculate_cran_repository_metadata(self.cran_repo_key) 240 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 241 | 242 | def test_calculate_conda_repository_metadata( 243 | self, 244 | instantiate_af_objects_credentials_and_api_key, 245 | create_and_delete_conda_repo, 246 | ): 247 | """Calculate Conda Repository Metadata tests.""" 248 | # Currently returning HTTP 500 249 | # Probably because the test repository is empty? 250 | try: 251 | r = self.af.repositories.calculate_conda_repository_metadata( 252 | self.conda_repo_key 253 | ) 254 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 255 | except self.af.AfApiError as error: 256 | if error.status_code != 500: 257 | raise error 258 | -------------------------------------------------------------------------------- /tests/test_searches.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Definitions of the test for the SEARCHES REST API Methods category.""" 10 | 11 | from __future__ import unicode_literals 12 | 13 | import pytest 14 | 15 | from .mixins import RtpyTestMixin 16 | 17 | 18 | class TestsSearches(RtpyTestMixin): 19 | """SEARCHES methods category tests.""" 20 | 21 | @pytest.fixture 22 | def setup_then_destroy_test_env(self): 23 | """Create the test environement for each specific test.""" 24 | # setup 25 | self.repo_name = RtpyTestMixin.generate_random_string() 26 | self.artifact_path = "python_logo.png" 27 | self.params = {} 28 | self.params["key"] = self.repo_name 29 | self.params["rclass"] = "local" 30 | self.params["packageType"] = "debian" 31 | self.af.repositories.create_repository(self.params) 32 | 33 | self.folder_name = RtpyTestMixin.generate_random_string() 34 | self.af.artifacts_and_storage.create_directory(self.repo_name, self.folder_name) 35 | 36 | self.af.artifacts_and_storage.deploy_artifact( 37 | self.repo_name, "tests/assets/python_logo.png", self.artifact_path 38 | ) 39 | self.af.artifacts_and_storage.set_item_properties( 40 | self.repo_name, "python_logo.png", "test=true" 41 | ) 42 | yield 43 | 44 | # teardown 45 | self.af.repositories.delete_repository(self.repo_name) 46 | 47 | def test_artifactory_query_language( 48 | self, 49 | instantiate_af_objects_credentials_and_api_key, 50 | setup_then_destroy_test_env, 51 | ): 52 | """Artifactory Query Language tests.""" 53 | query = 'items.find({"repo":{"$eq":"' + self.repo_name + '"}})' 54 | r = self.af.searches.artifactory_query_language(query) 55 | RtpyTestMixin.assert_isinstance_dict(r) 56 | if not r["results"]: 57 | raise self.RtpyTestError("results shouldn't be an empty list!") 58 | 59 | # Unsupported methods 60 | # def test_artifact_search_quick_search(self) 61 | # def test_archive_entries_search_class_search() 62 | # def test_gavc_search() 63 | # def test_property_search(self): 64 | # def test_checksum_search(self): 65 | # def test_bad_checksum_search(self): 66 | # def test_artifacts_with_date_in_date_range() 67 | # def test_artifacts_created_in_date_range() 68 | # def test_pattern_search() 69 | # def test_builds_for_dependcy() 70 | # def test_license_search() 71 | # def test_artifact_version_search() 72 | # def test_artifact_latest_version_search_based_on_layout() 73 | # def test_artifact_latest_version_search_based_on_properties() 74 | # def test_build_artifacts_search() 75 | 76 | def test_list_docker_repositories( 77 | self, 78 | instantiate_af_objects_credentials_and_api_key, 79 | create_and_delete_docker_repo, 80 | ): 81 | """List Docker Repositories tests.""" 82 | # Currently not possible to push images to non http Artifactory 83 | # Using docker-py 84 | r = self.af.searches.list_docker_repositories(self.docker_repo_key) 85 | RtpyTestMixin.assert_isinstance_dict(r) 86 | 87 | r = self.af.searches.list_docker_repositories( 88 | self.docker_repo_key, options="?n=3" 89 | ) 90 | # RtpyTestMixin.assert_isinstance_dict(r) 91 | 92 | def test_list_docker_tags( 93 | self, 94 | instantiate_af_objects_credentials_and_api_key, 95 | create_and_delete_docker_repo, 96 | ): 97 | """List Docker Tags.""" 98 | # Currently 404 if no image exists 99 | # Output currently not verified as no image is pushed for the tests 100 | # See reason in the above test 101 | try: 102 | r = self.af.searches.list_docker_tags(self.docker_repo_key, "my_image") 103 | r = self.af.searches.list_docker_tags( 104 | self.docker_repo_key, "my_image", options="?n=3" 105 | ) 106 | RtpyTestMixin.assert_isinstance_dict(r) 107 | except self.af.AfApiError as error: 108 | if error.status_code != 404: 109 | raise error 110 | -------------------------------------------------------------------------------- /tests/test_security.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Definitions of the tests for the SECURITY REST API Methods category.""" 10 | 11 | from __future__ import unicode_literals 12 | 13 | from .mixins import RtpyTestMixin 14 | 15 | 16 | class TestsSecurity(RtpyTestMixin): 17 | """SECURITY methods category tests.""" 18 | 19 | def test_get_users(self, instantiate_af_objects_credentials_and_api_key): 20 | """Get Users tests.""" 21 | r = self.af.security.get_users() 22 | RtpyTestMixin.assert_isinstance_list(r) 23 | 24 | def test_get_user_details(self, instantiate_af_objects_credentials_and_api_key): 25 | """Get User Details tests.""" 26 | r = self.af.security.get_user_details("admin") 27 | RtpyTestMixin.assert_isinstance_dict(r) 28 | 29 | def test_get_user_encrypted_password( 30 | self, instantiate_af_objects_credentials_and_api_key 31 | ): 32 | """Get User Encrypted Password tests.""" 33 | r = self.af.security.get_user_encrypted_password() 34 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 35 | 36 | def test_create_or_replace_user( 37 | self, instantiate_af_objects_credentials_and_api_key 38 | ): 39 | """Create or Replace User tests.""" 40 | myparams = {} 41 | myparams["name"] = "rtpy_test_user1" 42 | myparams["admin"] = "true" 43 | myparams["email"] = "rtpy@mail.com" 44 | myparams["password"] = "password" 45 | 46 | r = self.af.security.create_or_replace_user(myparams) 47 | self.af.security.get_user_details(myparams["name"]) 48 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 49 | self.af.security.delete_user("rtpy_test_user1") 50 | 51 | def test_update_user(self, instantiate_af_objects_credentials_and_api_key): 52 | """Update User tests.""" 53 | myparams = {} 54 | myparams["name"] = "rtpy_test_user2" 55 | myparams["admin"] = "true" 56 | myparams["email"] = "rtpy@mail.com" 57 | myparams["password"] = "password" 58 | 59 | self.af.security.create_or_replace_user(myparams) 60 | r = self.af.security.get_user_details(myparams["name"]) 61 | admin_status_original = r["admin"] 62 | 63 | myparams = {} 64 | myparams["admin"] = "false" 65 | myparams["name"] = "rtpy_test_user2" 66 | r = self.af.security.update_user(myparams) 67 | r2 = self.af.security.get_user_details("rtpy_test_user2") 68 | admin_status = r2["admin"] 69 | self.af.security.delete_user("rtpy_test_user2") 70 | 71 | if admin_status == admin_status_original: 72 | message = "Field did not update properly!" 73 | raise self.RtpyTestError(message) 74 | 75 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 76 | 77 | def test_delete_user(self, instantiate_af_objects_credentials_and_api_key): 78 | """Delete User tests.""" 79 | myparams = {} 80 | myparams["name"] = "rtpy_test_user3" 81 | myparams["admin"] = "true" 82 | myparams["email"] = "rtpy@mail.com" 83 | myparams["password"] = "password" 84 | 85 | self.af.security.create_or_replace_user(myparams) 86 | r = self.af.security.delete_user("rtpy_test_user3") 87 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 88 | 89 | # Unsupported Methods 90 | # def test_expire_password_for_a_single_user() 91 | # def test_expire_password_for_multiple_users() 92 | # def test_expire_password_for_all_users() 93 | # def test_unexpire_password_for_a_single_user() 94 | # def test_change_password() 95 | # def test_get_password_expiration_policy() 96 | # def test_set_password_expiration_policy() 97 | # def test_configure_user_lock_policy() 98 | 99 | def test_get_locked_out_users(self, instantiate_af_objects_credentials_and_api_key): 100 | """Get locked Out Users tests.""" 101 | r = self.af.security.get_locked_out_users() 102 | RtpyTestMixin.assert_isinstance_list(r) 103 | 104 | def test_unlock_locked_out_user( 105 | self, instantiate_af_objects_credentials_and_api_key 106 | ): 107 | """Unlock Locked Out User tests.""" 108 | r = self.af.security.unlock_locked_out_user("admin") 109 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 110 | 111 | def test_unlock_locked_out_users( 112 | self, instantiate_af_objects_credentials_and_api_key 113 | ): 114 | """Unlock Locked Out Users tests.""" 115 | r = self.af.security.unlock_locked_out_users([]) 116 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 117 | myparams = {} 118 | myparams["name"] = "rtpy_test_user4" 119 | myparams["email"] = "rtpy@mail.com" 120 | myparams["password"] = "password" 121 | self.af.security.create_or_replace_user(myparams) 122 | myparams = {} 123 | myparams["name"] = "rtpy_test_user5" 124 | myparams["email"] = "rtpy@mail.com" 125 | myparams["password"] = "password" 126 | self.af.security.create_or_replace_user(myparams) 127 | 128 | # The method sometimes returns an error 500. 129 | try: 130 | r = self.af.security.unlock_locked_out_users( 131 | ["rtpy_test_user4", "rtpy_test_user5"] 132 | ) 133 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 134 | except self.af.AfApiError as error: 135 | if error.status_code != 500: 136 | raise error 137 | 138 | self.af.security.delete_user("rtpy_test_user4") 139 | self.af.security.delete_user("rtpy_test_user5") 140 | 141 | def test_unlock_all_locked_out_users( 142 | self, instantiate_af_objects_credentials_and_api_key 143 | ): 144 | """Unlock All Locked Out Users tests.""" 145 | r = self.af.security.unlock_all_locked_out_users() 146 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 147 | 148 | def test_create_api_key(self, instantiate_af_object_with_credentials): 149 | """Create API key tests.""" 150 | """ 151 | try: 152 | self.af.security.revoke_api_key() 153 | except (self.af.AfApiError, self.af.MalformedAfApiError): 154 | pass 155 | 156 | r = self.af.security.create_api_key() 157 | RtpyTestMixin.assert_isinstance_dict(r) 158 | """ 159 | pass 160 | 161 | def test_regenerate_api_key(self, instantiate_af_object_with_credentials): 162 | """Regenerate API key tests.""" 163 | """ 164 | try: 165 | self.af.security.revoke_api_key() 166 | self.af.security.create_api_key() 167 | except (self.af.AfApiError, self.af.MalformedAfApiError): 168 | pass 169 | r = self.af.security.regenerate_api_key() 170 | RtpyTestMixin.assert_isinstance_dict(r) 171 | """ 172 | pass 173 | 174 | def test_get_api_key(self, instantiate_af_object_with_credentials): 175 | """Get API key tests.""" 176 | """ 177 | try: 178 | self.af.security.revoke_api_key() 179 | except (self.af.AfApiError, self.af.MalformedAfApiError): 180 | pass 181 | 182 | self.af.security.create_api_key() 183 | r = self.af.security.get_api_key() 184 | RtpyTestMixin.assert_isinstance_dict(r) 185 | """ 186 | pass 187 | 188 | def test_revoke_api_key(self, instantiate_af_object_with_credentials): 189 | """Revoke API key tests.""" 190 | """ 191 | try: 192 | self.af.security.revoke_api_key() 193 | except (self.af.AfApiError, self.af.MalformedAfApiError): 194 | pass 195 | 196 | self.af.security.create_api_key() 197 | r = self.af.security.revoke_api_key() 198 | RtpyTestMixin.assert_isinstance_dict(r) 199 | """ 200 | pass 201 | 202 | def test_revoke_user_api_key(self, instantiate_af_object_with_credentials): 203 | """Revoke User API key tests.""" 204 | """ 205 | try: 206 | self.af.security.create_api_key() 207 | except (self.af.AfApiError, self.af.MalformedAfApiError): 208 | pass 209 | r = self.af.security.revoke_user_api_key("admin") 210 | RtpyTestMixin.assert_isinstance_dict(r) 211 | """ 212 | pass 213 | 214 | def test_get_groups(self, instantiate_af_objects_credentials_and_api_key): 215 | """Get Groups tests.""" 216 | r = self.af.security.get_groups() 217 | RtpyTestMixin.assert_isinstance_list(r) 218 | 219 | def test_get_group_details(self, instantiate_af_objects_credentials_and_api_key): 220 | """Get Group Details tests.""" 221 | r = self.af.security.get_group_details("readers") 222 | RtpyTestMixin.assert_isinstance_dict(r) 223 | 224 | def test_create_or_replace_group( 225 | self, instantiate_af_objects_credentials_and_api_key 226 | ): 227 | """Create Or Replace Group tests.""" 228 | group_name = "rtpy_tests_group1" 229 | params = {"description": "mydesc", "group_name": group_name} 230 | r = self.af.security.create_or_replace_group(params) 231 | self.af.security.delete_group(group_name) 232 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 233 | 234 | def test_update_group(self, instantiate_af_objects_credentials_and_api_key): 235 | """Update Group tests.""" 236 | """ 237 | currently not working for an unknown reason 238 | group_name = "rtpy_tests_group2" 239 | params = {"description": "mydesc", "group_name": group_name} 240 | r = self.af.security.create_or_replace_group( 241 | params, settings={"raw_response": True}) 242 | r = self.af.security.get_group_details(group_name) 243 | description_original = r["description"] 244 | params2 = {"description": "mydesc_new", "group_name": group_name} 245 | r = self.af.security.update_group(params2) 246 | r2 = self.af.security.get_group_details(group_name) 247 | self.af.security.delete_group(group_name) 248 | if r2["description"] == description_original: 249 | message = "Group did not update properly, check the fields!" 250 | # raise self.RtpyTestError(message) 251 | pass 252 | #RtpyTestMixin.assert_isinstance_str(self.py_version, r) 253 | """ 254 | pass 255 | 256 | def test_delete_group(self, instantiate_af_objects_credentials_and_api_key): 257 | """Delete Group tests.""" 258 | group_name = "rtpy_tests_group3" 259 | params = {"description": "mydesc", "group_name": group_name} 260 | self.af.security.create_or_replace_group(params) 261 | r = self.af.security.delete_group(group_name) 262 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 263 | 264 | def test_get_permission_targets( 265 | self, instantiate_af_objects_credentials_and_api_key 266 | ): 267 | """Get Permission Targets tests.""" 268 | r = self.af.security.get_permission_targets() 269 | RtpyTestMixin.assert_isinstance_list(r) 270 | 271 | def test_get_permission_target_details( 272 | self, instantiate_af_objects_credentials_and_api_key 273 | ): 274 | """Get Permission Target Details tests.""" 275 | r = self.af.security.get_permission_target_details("Anything") 276 | RtpyTestMixin.assert_isinstance_dict(r) 277 | 278 | def test_create_or_replace_permission_target( 279 | self, instantiate_af_objects_credentials_and_api_key 280 | ): 281 | """Create or Replace Permission Target.""" 282 | params = {} 283 | params["key"] = "rtpy_test_repo_security_1" 284 | params["rclass"] = "local" 285 | params["packageType"] = "generic" 286 | try: 287 | self.af.repositories.delete_repository("rtpy_test_repo_security_1") 288 | except self.af.AfApiError as error: 289 | if error.status_code not in [400, 404]: 290 | raise 291 | r = self.af.repositories.create_repository(params) 292 | 293 | params = {} 294 | params["repositories"] = ["rtpy_test_repo_security_1"] 295 | params["name"] = "rtpy_test_ptarget_1" 296 | r = self.af.security.create_or_replace_permission_target(params) 297 | self.af.security.delete_permission_target("rtpy_test_ptarget_1") 298 | try: 299 | self.af.repositories.delete_repository("rtpy_test_repo_security_1") 300 | except self.af.AfApiError as error: 301 | if error.status_code not in [400, 404]: 302 | raise 303 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 304 | 305 | def test_delete_permission_target( 306 | self, instantiate_af_objects_credentials_and_api_key 307 | ): 308 | """Delete Permission Target tests.""" 309 | params = {} 310 | params["key"] = "rtpy_test_repo_security_2" 311 | params["rclass"] = "local" 312 | params["packageType"] = "generic" 313 | try: 314 | self.af.repositories.delete_repository("rtpy_test_repo_security_2") 315 | except self.af.AfApiError as error: 316 | if error.status_code not in [400, 404]: 317 | raise 318 | self.af.repositories.create_repository(params) 319 | params = {} 320 | params["repositories"] = ["rtpy_test_repo_security_2"] 321 | params["name"] = "rtpy_test_ptarget_2" 322 | self.af.security.create_or_replace_permission_target(params) 323 | r = self.af.security.delete_permission_target("rtpy_test_ptarget_2") 324 | try: 325 | self.af.repositories.delete_repository("rtpy_test_repo_security_2") 326 | except self.af.AfApiError as error: 327 | if error.status_code not in [400, 404]: 328 | raise 329 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 330 | 331 | def test_effective_item_permissions( 332 | self, instantiate_af_objects_credentials_and_api_key 333 | ): 334 | """Effective Item Permissions tests.""" 335 | params = {} 336 | params["key"] = "rtpy_test_repo_security_3" 337 | params["rclass"] = "local" 338 | params["packageType"] = "generic" 339 | self.af.repositories.create_repository(params) 340 | r = self.af.security.effective_item_permissions("rtpy_test_repo_security_3", "") 341 | self.af.repositories.delete_repository("rtpy_test_repo_security_3") 342 | RtpyTestMixin.assert_isinstance_dict(r) 343 | -------------------------------------------------------------------------------- /tests/test_system_and_configuration.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """ 10 | Definitions of the tests for SYSTEM AND CONFIGURATION. 11 | 12 | (SYSTEM AND CONFIGURATION REST API Methods category.) 13 | """ 14 | 15 | from __future__ import unicode_literals 16 | import os 17 | 18 | from .mixins import RtpyTestMixin 19 | 20 | 21 | class TestsSystemAndConfiguration(RtpyTestMixin): 22 | """SYSTEM AND CONFIGURATION methods category tests.""" 23 | 24 | def test_system_info(self, instantiate_af_objects_credentials_and_api_key): 25 | """System Info tests.""" 26 | r = self.af.system_and_configuration.system_info() 27 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 28 | 29 | def test_system_health_ping(self, instantiate_af_objects_credentials_and_api_key): 30 | """System Health Ping tests.""" 31 | r = self.af.system_and_configuration.system_health_ping() 32 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 33 | 34 | # Unsupported method 35 | # def test_verify_connection(self): 36 | 37 | def test_general_configuration( 38 | self, instantiate_af_objects_credentials_and_api_key 39 | ): 40 | """General Configuration tests.""" 41 | r = self.af.system_and_configuration.general_configuration() 42 | RtpyTestMixin.assert_isinstance_str(self.py_version, r) 43 | 44 | def test_save_general_configuration( 45 | self, instantiate_af_objects_credentials_and_api_key 46 | ): 47 | """Save General Configuration tests.""" 48 | r = self.af.system_and_configuration.general_configuration() 49 | with open("new_conf.xml", "w") as xml_file: 50 | xml_file.write(r) 51 | 52 | r = self.af.system_and_configuration.save_general_configuration("new_conf.xml") 53 | os.remove("new_conf.xml") 54 | 55 | # Unsupported method 56 | # def test_update_custom_url_base(sielf): 57 | 58 | def test_licence_information(self, instantiate_af_objects_credentials_and_api_key): 59 | """License Information tests.""" 60 | r = self.af.system_and_configuration.license_information() 61 | RtpyTestMixin.assert_isinstance_dict(r) 62 | 63 | def test_install_license(self, instantiate_af_objects_credentials_and_api_key): 64 | """Install License tests.""" 65 | # Error output currently malformed 66 | # Error 400 is returned when license is invalid 67 | # Test will be fixed accordingly when a patch is realeased by JFrog 68 | # Licence currently supplied is also invalid 69 | try: 70 | params = {"licenseKey": "QWERTY123"} 71 | r = self.af.system_and_configuration.install_license(params) 72 | RtpyTestMixin.assert_isinstance_dict(r) 73 | except self.af.MalformedAfApiError: 74 | pass 75 | 76 | def test_version_and_addons_information( 77 | self, instantiate_af_objects_credentials_and_api_key 78 | ): 79 | """Version And Addons Information tests.""" 80 | r = self.af.system_and_configuration.version_and_addons_information() 81 | RtpyTestMixin.assert_isinstance_dict(r) 82 | 83 | def test_get_reverse_proxy_configuration( 84 | self, instantiate_af_objects_credentials_and_api_key 85 | ): 86 | """Get Reverse Proxy Configuration tests.""" 87 | r = self.af.system_and_configuration.get_reverse_proxy_configuration() 88 | RtpyTestMixin.assert_isinstance_dict(r) 89 | 90 | # Unsupported method 91 | # def test_update_reverse_proxy_configuration(self): 92 | 93 | def test_get_reverse_proxy_snippet( 94 | self, instantiate_af_objects_credentials_and_api_key 95 | ): 96 | """Get Reverse Proxy snippet tests.""" 97 | # No proxy configuration by default (400) 98 | try: 99 | r = self.af.system_and_configuration.get_reverse_proxy_snippet() 100 | RtpyTestMixin.assert_isinstance_dict(r) 101 | except self.af.AfApiError as error: 102 | if error.status_code != 400: 103 | raise error 104 | -------------------------------------------------------------------------------- /tests/test_tools.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (C) 2018 Orange 4 | # 5 | # This software is distributed under the terms and conditions of the 'Apache-2.0' 6 | # license which can be found in the 'LICENSE.md' file 7 | # or at 'http://www.apache.org/licenses/LICENSE-2.0'. 8 | 9 | """Definitions of the tests for the functions defined in rtpy/tools.py.""" 10 | 11 | from __future__ import unicode_literals 12 | import os 13 | 14 | import pytest 15 | import requests 16 | 17 | import rtpy 18 | from .mixins import RtpyTestMixin 19 | 20 | 21 | class TestsTools(RtpyTestMixin): 22 | """Tools class tests.""" 23 | 24 | def test_instantiate_rtpy_with_credentials(self): 25 | """Instantiate an Rtpy object with credential tests.""" 26 | self.instantiate_af_object_with_credentials_helpers() 27 | 28 | def test_instantiate_rtpy_with_api_key(self): 29 | """.Instantiate an Rtpy object with api key tests.""" 30 | self.instantiate_af_object_with_api_key_helpers() 31 | 32 | def test_json_to_dict(self): 33 | """Json to dict tests.""" 34 | original_dict = {"key": "my_value"} 35 | json_content = """ 36 | { 37 | "key" : "my_value" 38 | } 39 | """ 40 | 41 | with open("j_file.json", "w") as j_file: 42 | j_file.write(json_content) 43 | 44 | myparams = rtpy.json_to_dict("j_file.json") 45 | os.remove("j_file.json") 46 | 47 | if myparams != original_dict: 48 | message = "Created dictionary doesn't match original dictionary !" 49 | raise self.RtpyTestError(message) 50 | 51 | def test_raise_user_settings_error(self): 52 | """Raise UserSettingsError tests.""" 53 | my_settings = {} 54 | my_settings["af_url"] = os.environ["AF_TEST_URL"] 55 | 56 | # Missing authentication key(s) 57 | with pytest.raises(rtpy.UserSettingsError): 58 | my_settings["username"] = "admin" 59 | af = rtpy.Rtpy(my_settings) 60 | 61 | # Supplying api key and credentials 62 | with pytest.raises(rtpy.UserSettingsError): 63 | my_settings["password"] = "password" 64 | my_settings["api_key"] = "1234..." 65 | af = rtpy.Rtpy(my_settings) 66 | 67 | # Supplying incorrect keys 68 | with pytest.raises(rtpy.UserSettingsError): 69 | my_settings["my_custom_field"] = "custom_value" 70 | af = rtpy.Rtpy(my_settings) 71 | 72 | del my_settings["api_key"] 73 | del my_settings["my_custom_field"] 74 | 75 | # Incorrect raw response value 76 | with pytest.raises(rtpy.UserSettingsError): 77 | my_settings["raw_response"] = 2 78 | af = rtpy.Rtpy(my_settings) 79 | 80 | # Incorrect verbose level value 81 | with pytest.raises(rtpy.UserSettingsError): 82 | del my_settings["raw_response"] 83 | my_settings["verbose_level"] = 5 84 | af = rtpy.Rtpy(my_settings) 85 | 86 | def test_raise_malformed_af_api_error( 87 | self, instantiate_af_objects_credentials_and_api_key 88 | ): 89 | """Raise MalformedAfApiError tests.""" 90 | with pytest.raises(self.af.MalformedAfApiError): 91 | # Providing a bad license key to get an error from the API 92 | # This error is currently malformed 93 | params = {"licenseKey": "QWERTY123"} 94 | self.af.system_and_configuration.install_license(params) 95 | 96 | def test_raise_af_api_error( 97 | self, 98 | instantiate_af_objects_credentials_and_api_key, 99 | create_and_delete_docker_repo, 100 | ): 101 | """Raise AfApiError tests.""" 102 | try: 103 | r = self.af.searches.list_docker_tags(self.docker_repo_key, "my_image") 104 | except self.af.AfApiError as error: 105 | raised = True 106 | try: 107 | error.api_method, 108 | error.url, 109 | error.verb, 110 | error.status_code, 111 | error.message, 112 | error.print_message, 113 | all_attributes_exist = True 114 | except Exception as error: 115 | raise error 116 | if not raised and not all_attributes_exist: 117 | raise Exception("Error was not raised, or some attributes are missing") 118 | 119 | def test_raise_rtpy_error(self, instantiate_af_objects_credentials_and_api_key): 120 | """Raise RtpyError tests.""" 121 | # Empy path 122 | with pytest.raises(self.af.RtpyError): 123 | self.af.artifacts_and_storage.retrieve_artifact("repo_key", "") 124 | with pytest.raises(self.af.RtpyError): 125 | r = self.af.artifacts_and_storage.artifact_sync_download("repo_key", "") 126 | 127 | # Incorrect archive type 128 | with pytest.raises(self.af.RtpyError): 129 | self.af.artifacts_and_storage.retrieve_folder_or_repository_archive( 130 | "repo_key", "path", "easygz", include_checksums=True 131 | ) 132 | 133 | # Incorrect sha_type 134 | with pytest.raises(self.af.RtpyError): 135 | self.af.artifacts_and_storage.deploy_artifact_by_checksum( 136 | "my_repo", "my_remote_artifact", "sha_9k", "123" 137 | ) 138 | 139 | def test_optional_keys_in_settings( 140 | self, instantiate_af_objects_credentials_and_api_key 141 | ): 142 | """Optional keys in settings tests.""" 143 | my_settings = {"raw_response": True} 144 | r = self.af.system_and_configuration.system_health_ping(settings=my_settings) 145 | 146 | if not r.status_code: 147 | message = "raw response setting did not apply properly!" 148 | raise self.RtpyTestError(message) 149 | my_settings = {"verbose_level": 1} 150 | r = self.af.system_and_configuration.system_health_ping(settings=my_settings) 151 | session = requests.Session() 152 | my_settings = {"session": session} 153 | r = self.af.system_and_configuration.system_health_ping(settings=my_settings) 154 | 155 | def test_self_call(self, instantiate_af_objects_credentials_and_api_key): 156 | """Test the __call__ dunder (binding to System health ping).""" 157 | assert self.af() == "OK" 158 | --------------------------------------------------------------------------------