├── .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 | [](https://pypi.org/project/rtpy/)
4 | [](https://pypi.org/project/rtpy/)
5 | [](https://github.com/ambv/black)
6 | [](https://rtpy.readthedocs.io/en/latest/?badge=latest)
7 | [](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 |
--------------------------------------------------------------------------------