python-rundeck 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/PKG-INFO +135 -127
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/README.md +129 -126
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/pyproject.toml +8 -1
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/LICENSE +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/__init__.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/base.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/client.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/config.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/const.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/exceptions.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/py.typed +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/__init__.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/__init__.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/adhoc.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/config_management.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/executions.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/features.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/jobs.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/key_storage.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/metrics.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/plugins.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/projects.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/scheduler.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/scm.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/system.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/tokens.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/users.py +0 -0
- {python_rundeck-0.1.0 → python_rundeck-0.3.0}/src/rundeck/v1/objects/webhooks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-rundeck
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python client for the Rundeck API (v14-v56).
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -29,48 +29,55 @@ Requires-Dist: pytest-cov (>=5.0.0,<6.0.0) ; extra == "dev"
|
|
|
29
29
|
Requires-Dist: requests (>=2.32.5,<3.0.0)
|
|
30
30
|
Requires-Dist: ruff (>=0.7.0,<1.0.0) ; extra == "dev"
|
|
31
31
|
Requires-Dist: safety (>=3.2.0,<4.0.0) ; extra == "dev"
|
|
32
|
+
Project-URL: Changelog, https://gitlab.com/pascal.seckinger/python-rundeck/-/blob/main/CHANGELOG.md
|
|
33
|
+
Project-URL: Documentation, https://gitlab.com/pascal.seckinger/python-rundeck/-/blob/main/README.md
|
|
34
|
+
Project-URL: Homepage, https://gitlab.com/pascal.seckinger/python-rundeck
|
|
35
|
+
Project-URL: Issues, https://gitlab.com/pascal.seckinger/python-rundeck/-/issues
|
|
36
|
+
Project-URL: Repository, https://gitlab.com/pascal.seckinger/python-rundeck
|
|
32
37
|
Description-Content-Type: text/markdown
|
|
33
38
|
|
|
34
39
|
python-rundeck
|
|
35
40
|
===============
|
|
41
|
+

|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
Python client for the Rundeck API (v14-v56), inspired by the python-gitlab architecture. Provides typed managers for key resources (projects, jobs, executions, tokens, users, system, configuration) and SCM (import/export).
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
Contents
|
|
40
49
|
--------
|
|
41
50
|
- Installation
|
|
42
|
-
-
|
|
51
|
+
- Quick start
|
|
43
52
|
- Configuration
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
53
|
+
- Available resources
|
|
54
|
+
- Examples by resource
|
|
55
|
+
- Error handling
|
|
56
|
+
- Development and testing
|
|
48
57
|
|
|
49
58
|
Installation
|
|
50
59
|
------------
|
|
51
|
-
|
|
60
|
+
Prerequisites: Python 3.11+. Install from PyPI.
|
|
52
61
|
|
|
53
62
|
```bash
|
|
54
|
-
|
|
55
|
-
# ou en editable avec pip
|
|
56
|
-
pip install -e .
|
|
63
|
+
pip install python-rundeck
|
|
57
64
|
```
|
|
58
65
|
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
Quick start
|
|
67
|
+
-----------
|
|
61
68
|
```python
|
|
62
69
|
from rundeck.client import Rundeck
|
|
63
70
|
|
|
64
71
|
rd = Rundeck(url="https://rundeck.example.com", token="MY_TOKEN", api_version=56)
|
|
65
|
-
#
|
|
72
|
+
# Password auth (session cookie) if no token
|
|
66
73
|
rd = Rundeck(url="https://rundeck.example.com", username="admin", password="admin", api_version=56)
|
|
67
74
|
|
|
68
|
-
#
|
|
75
|
+
# List projects
|
|
69
76
|
projects = rd.projects.list()
|
|
70
77
|
for p in projects:
|
|
71
78
|
print(p.name)
|
|
72
79
|
|
|
73
|
-
#
|
|
80
|
+
# Get a project and run a job
|
|
74
81
|
project = rd.projects.get("demo")
|
|
75
82
|
jobs = project.jobs.list()
|
|
76
83
|
execu = jobs[0].run()
|
|
@@ -79,64 +86,64 @@ print(execu.id)
|
|
|
79
86
|
|
|
80
87
|
Configuration
|
|
81
88
|
-------------
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
- `url
|
|
86
|
-
- `token
|
|
87
|
-
- `username` / `password
|
|
88
|
-
- `api_version
|
|
89
|
-
- `timeout
|
|
90
|
-
- `ssl_verify
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
- `RUNDECK_URL
|
|
95
|
-
- `RUNDECK_TOKEN
|
|
96
|
-
- `RUNDECK_USERNAME` / `RUNDECK_PASSWORD
|
|
97
|
-
- `RUNDECK_API_VERSION
|
|
98
|
-
- `RUNDECK_TIMEOUT
|
|
99
|
-
- `RUNDECK_SSL_VERIFY
|
|
100
|
-
- `RUNDECK_USER_AGENT
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- `projects` (`ProjectManager`)
|
|
105
|
-
- `jobs` (`JobManager`)
|
|
106
|
-
- `executions` (`ExecutionManager`)
|
|
107
|
-
- `tokens` (`TokenManager`)
|
|
108
|
-
- `users` (`UserManager`)
|
|
109
|
-
- `metrics` (`MetricsManager`)
|
|
110
|
-
- `plugins` (`PluginManager`)
|
|
111
|
-
- `webhooks` (`WebhookEventManager` + `ProjectWebhookManager`)
|
|
112
|
-
- `key_storage` (`StorageKeyManager`)
|
|
113
|
-
- `adhoc` (`AdhocManager`, via `project.adhoc`)
|
|
114
|
-
- `system` (`SystemManager`)
|
|
115
|
-
- `config_management` (`ConfigManagementManager`)
|
|
116
|
-
- `scm` (via `project.scm`
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
89
|
+
Configuration follows a cascading model (args > env > files > defaults) via `RundeckConfig`.
|
|
90
|
+
|
|
91
|
+
Main parameters:
|
|
92
|
+
- `url`: Rundeck URL (e.g. `https://rundeck.example.com`)
|
|
93
|
+
- `token`: API token (header `X-Rundeck-Auth-Token`)
|
|
94
|
+
- `username` / `password`: Session authentication (j_security_check) if no token is provided.
|
|
95
|
+
- `api_version`: API version (e.g. `56`)
|
|
96
|
+
- `timeout`: Request timeout (float, seconds)
|
|
97
|
+
- `ssl_verify`: TLS verification (bool or CA path)
|
|
98
|
+
|
|
99
|
+
Config files: if needed, pass `config_files` or `Rundeck.from_config(config_section=...)`.
|
|
100
|
+
Useful env vars (client config):
|
|
101
|
+
- `RUNDECK_URL`: Base URL
|
|
102
|
+
- `RUNDECK_TOKEN`: API token (header `X-Rundeck-Auth-Token`)
|
|
103
|
+
- `RUNDECK_USERNAME` / `RUNDECK_PASSWORD`: Session auth (if no token)
|
|
104
|
+
- `RUNDECK_API_VERSION`: API version (e.g. 56)
|
|
105
|
+
- `RUNDECK_TIMEOUT`: Request timeout (seconds)
|
|
106
|
+
- `RUNDECK_SSL_VERIFY`: TLS verification (bool or CA path)
|
|
107
|
+
- `RUNDECK_USER_AGENT`: HTTP User-Agent
|
|
108
|
+
|
|
109
|
+
Available resources
|
|
110
|
+
-------------------
|
|
111
|
+
- `projects` (`ProjectManager`): CRUD projects, job export/import, project config, archive (export/import).
|
|
112
|
+
- `jobs` (`JobManager`): list, get, deletions, bulk actions, execution.
|
|
113
|
+
- `executions` (`ExecutionManager`): list/filter, running, get/delete, advanced query.
|
|
114
|
+
- `tokens` (`TokenManager`): list, get, create, delete.
|
|
115
|
+
- `users` (`UserManager`): user operations (per current implementation).
|
|
116
|
+
- `metrics` (`MetricsManager`): `/metrics` endpoints (list/data/healthcheck/ping).
|
|
117
|
+
- `plugins` (`PluginManager`): list installed plugins (`/plugin/list`).
|
|
118
|
+
- `webhooks` (`WebhookEventManager` + `ProjectWebhookManager`): project webhook management and sending via token.
|
|
119
|
+
- `key_storage` (`StorageKeyManager`): key storage management `/storage/keys`.
|
|
120
|
+
- `adhoc` (`AdhocManager`, via `project.adhoc`): run AdHoc commands/scripts.
|
|
121
|
+
- `system` (`SystemManager`): system info, executions enable/disable, logstorage, ACL.
|
|
122
|
+
- `config_management` (`ConfigManagementManager`): global configuration `/config`.
|
|
123
|
+
- `scm` (via `project.scm` and `job.scm`): import/export plugins, setup, enable/disable, status, actions (commit/import/export...).
|
|
124
|
+
|
|
125
|
+
Examples by resource (complete)
|
|
126
|
+
-------------------------------
|
|
127
|
+
Projects
|
|
121
128
|
```python
|
|
122
|
-
# CRUD
|
|
129
|
+
# CRUD project
|
|
123
130
|
p = rd.projects.create("demo")
|
|
124
131
|
p = rd.projects.get("demo")
|
|
125
132
|
rd.projects.delete("demo")
|
|
126
133
|
projects = rd.projects.list()
|
|
127
134
|
|
|
128
|
-
# Export / import
|
|
135
|
+
# Export / import jobs from a project (via job manager)
|
|
129
136
|
project.jobs.export(format="json", idlist="id1,id2", groupPath="group/sub")
|
|
130
137
|
project.jobs.import_jobs(content=open("jobs.json").read(), format="json", dupeOption="update", uuidOption="remove")
|
|
131
138
|
|
|
132
|
-
#
|
|
139
|
+
# Project export / import (ZIP archive)
|
|
133
140
|
archive = project.archive
|
|
134
|
-
resp = archive.export(export_all=False, export_webhooks=True) #
|
|
141
|
+
resp = archive.export(export_all=False, export_webhooks=True) # Raw response (zip)
|
|
135
142
|
token_info = archive.export_async(exportAll=True)
|
|
136
143
|
status = archive.export_status(token_info.get("token", ""))
|
|
137
144
|
zip_resp = archive.export_download(token_info.get("token", ""))
|
|
138
145
|
|
|
139
|
-
#
|
|
146
|
+
# Archive import (sync or async)
|
|
140
147
|
archive.import_archive(
|
|
141
148
|
content=open("project-export.zip", "rb").read(),
|
|
142
149
|
jobUuidOption="preserve",
|
|
@@ -146,16 +153,16 @@ archive.import_archive(
|
|
|
146
153
|
archive.import_archive(content=open("project-export.zip", "rb").read(), async_import=True)
|
|
147
154
|
archive.import_status()
|
|
148
155
|
|
|
149
|
-
#
|
|
150
|
-
project.readme.get_readme() #
|
|
156
|
+
# Project README / MOTD
|
|
157
|
+
project.readme.get_readme() # default text
|
|
151
158
|
project.readme.get_readme(accept="application/json")
|
|
152
|
-
project.readme.update_readme("
|
|
159
|
+
project.readme.update_readme("New content", content_type="text/plain")
|
|
153
160
|
project.readme.delete_readme()
|
|
154
161
|
project.readme.get_motd()
|
|
155
|
-
project.readme.update_motd("Message
|
|
162
|
+
project.readme.update_motd("Message of the day", content_type="text/plain")
|
|
156
163
|
project.readme.delete_motd()
|
|
157
164
|
|
|
158
|
-
#
|
|
165
|
+
# Project config (key/value)
|
|
159
166
|
conf = p.config.get()
|
|
160
167
|
p.config.keys.get("project.label")
|
|
161
168
|
p.config.keys.set("project.label", "Demo")
|
|
@@ -163,27 +170,27 @@ p.config.keys.update({"project.description": "Sample"})
|
|
|
163
170
|
p.config.replace({"project.label": "Demo", "project.description": "Sample"})
|
|
164
171
|
p.config.keys.delete("project.label")
|
|
165
172
|
|
|
166
|
-
# SCM import/export (
|
|
167
|
-
scm_import = project.scm.import_ #
|
|
173
|
+
# SCM import/export (on a project)
|
|
174
|
+
scm_import = project.scm.import_ # or getattr(project.scm, "import")
|
|
168
175
|
scm_export = project.scm.export
|
|
169
176
|
|
|
170
|
-
#
|
|
177
|
+
# Plugin discovery
|
|
171
178
|
scm_import.plugins.list()
|
|
172
179
|
scm_export.plugins.list()
|
|
173
180
|
|
|
174
|
-
#
|
|
181
|
+
# Input fields for a plugin and setup (explicit plugin_type)
|
|
175
182
|
fields = scm_import.plugins.input_fields("git-import")
|
|
176
183
|
scm_import.config.setup("git-import", {"url": "ssh://git@example.com/repo.git", "dir": "/tmp/repo"})
|
|
177
184
|
|
|
178
|
-
#
|
|
185
|
+
# Enable/disable a plugin
|
|
179
186
|
scm_import.config.enable("git-import")
|
|
180
187
|
scm_export.config.disable("custom-export")
|
|
181
188
|
|
|
182
|
-
#
|
|
189
|
+
# SCM status/config
|
|
183
190
|
import_status = scm_import.actions.status()
|
|
184
191
|
export_conf = scm_export.config.get()
|
|
185
192
|
|
|
186
|
-
#
|
|
193
|
+
# Project-side SCM actions (e.g., commit/pull/push depending on plugin)
|
|
187
194
|
action_fields = scm_export.actions.input_fields("commit")
|
|
188
195
|
scm_export.actions.perform(
|
|
189
196
|
"commit",
|
|
@@ -196,14 +203,14 @@ scm_export.actions.perform(
|
|
|
196
203
|
|
|
197
204
|
Jobs
|
|
198
205
|
```python
|
|
199
|
-
#
|
|
206
|
+
# List jobs for a project
|
|
200
207
|
jobs = rd.jobs.list(project="demo", groupPath="ops")
|
|
201
208
|
|
|
202
|
-
#
|
|
209
|
+
# From a parent project
|
|
203
210
|
project = rd.projects.get("demo")
|
|
204
211
|
jobs = project.jobs.list()
|
|
205
212
|
|
|
206
|
-
#
|
|
213
|
+
# Direct access to a job
|
|
207
214
|
job = rd.jobs.get("job-id")
|
|
208
215
|
job.delete()
|
|
209
216
|
job.definition(format="yaml")
|
|
@@ -218,7 +225,7 @@ tags = job.tags()
|
|
|
218
225
|
workflow = job.workflow()
|
|
219
226
|
forecast = job.forecast(time="2024-05-01T10:00:00Z", max=5)
|
|
220
227
|
|
|
221
|
-
#
|
|
228
|
+
# Export/import jobs via manager (project parameter or parent)
|
|
222
229
|
rd.jobs.export(project="demo", format="xml", idlist="id1,id2", groupPath="group")
|
|
223
230
|
rd.jobs.import_jobs(
|
|
224
231
|
project="demo",
|
|
@@ -226,30 +233,30 @@ rd.jobs.import_jobs(
|
|
|
226
233
|
fileformat="xml",
|
|
227
234
|
dupeOption="update",
|
|
228
235
|
)
|
|
229
|
-
#
|
|
236
|
+
# Or via a parent project
|
|
230
237
|
project.jobs.export(format="json")
|
|
231
238
|
project.jobs.import_jobs(content=open("jobs.json", "rb").read(), fileformat="json")
|
|
232
239
|
|
|
233
|
-
Note:
|
|
240
|
+
Note: import remains exposed as `import_jobs(...)` (the Python keyword prevents a direct call to `.import`). If you prefer the alias, use `getattr(rd.jobs, "import")(...)`.
|
|
234
241
|
|
|
235
|
-
#
|
|
242
|
+
# Run and get the execution
|
|
236
243
|
execution = job.run(argString="-option value")
|
|
237
244
|
|
|
238
|
-
#
|
|
245
|
+
# Bulk actions
|
|
239
246
|
rd.jobs.bulk.enable_execution(["id1", "id2"])
|
|
240
247
|
rd.jobs.bulk.disable_execution(["id1", "id2"])
|
|
241
248
|
rd.jobs.bulk.delete(["id1", "id2"])
|
|
242
249
|
rd.jobs.bulk.enable_schedule(["id1", "id2"])
|
|
243
250
|
rd.jobs.bulk.disable_schedule(["id1", "id2"])
|
|
244
251
|
|
|
245
|
-
# Upload
|
|
252
|
+
# Upload option files and uploaded files
|
|
246
253
|
job.upload_option_file("csvfile", open("data.csv", "rb").read(), file_name="data.csv")
|
|
247
254
|
job.list_uploaded_files(max=20)
|
|
248
255
|
rd.jobs.get_uploaded_file_info("file-id")
|
|
249
256
|
|
|
250
|
-
# SCM import/export
|
|
257
|
+
# SCM import/export on a job
|
|
251
258
|
job_scm_export = job.scm.export
|
|
252
|
-
job_scm_import = job.scm.import_ #
|
|
259
|
+
job_scm_import = job.scm.import_ # or getattr(job.scm, "import")
|
|
253
260
|
|
|
254
261
|
job_scm_export.status()
|
|
255
262
|
job_scm_export.diff()
|
|
@@ -259,7 +266,7 @@ job_scm_import.status()
|
|
|
259
266
|
job_scm_import.input_fields("pull")
|
|
260
267
|
job_scm_import.perform("pull", input_values={"message": "Update from repo"})
|
|
261
268
|
|
|
262
|
-
#
|
|
269
|
+
# Project resources
|
|
263
270
|
resources = project.resources.list(format="json", groupPath="ops")
|
|
264
271
|
node = project.resources.get("node1")
|
|
265
272
|
sources = project.sources.list()
|
|
@@ -273,18 +280,18 @@ project.acl.update("policy.aclpolicy", content="...yaml...")
|
|
|
273
280
|
project.acl.delete("policy.aclpolicy")
|
|
274
281
|
```
|
|
275
282
|
|
|
276
|
-
|
|
283
|
+
Executions
|
|
277
284
|
```python
|
|
278
|
-
#
|
|
285
|
+
# Simple or paginated list
|
|
279
286
|
execs = rd.executions.list(project="demo", status="running", max=50, offset=0)
|
|
280
|
-
running = rd.executions.running(project="demo") #
|
|
287
|
+
running = rd.executions.running(project="demo") # or "*" for all
|
|
281
288
|
|
|
282
|
-
#
|
|
289
|
+
# Details / deletion
|
|
283
290
|
e = rd.executions.get("123")
|
|
284
291
|
rd.executions.delete("123")
|
|
285
292
|
rd.executions.bulk_delete(["123", "124"])
|
|
286
293
|
|
|
287
|
-
#
|
|
294
|
+
# Advanced query
|
|
288
295
|
advanced = rd.executions.query(
|
|
289
296
|
project="demo",
|
|
290
297
|
statusFilter="failed",
|
|
@@ -294,12 +301,12 @@ advanced = rd.executions.query(
|
|
|
294
301
|
max=100,
|
|
295
302
|
)
|
|
296
303
|
|
|
297
|
-
#
|
|
304
|
+
# Execution methods
|
|
298
305
|
e.abort(asUser="admin")
|
|
299
306
|
output = e.get_output(offset=0, maxlines=100)
|
|
300
307
|
state = e.get_state()
|
|
301
308
|
is_running = e.is_running()
|
|
302
|
-
e.refresh() #
|
|
309
|
+
e.refresh() # reload data
|
|
303
310
|
```
|
|
304
311
|
|
|
305
312
|
Tokens
|
|
@@ -311,20 +318,20 @@ new_token = rd.tokens.create(user="alice", roles=["admin"], duration="90d", name
|
|
|
311
318
|
rd.tokens.delete(new_token.id)
|
|
312
319
|
```
|
|
313
320
|
|
|
314
|
-
|
|
321
|
+
Users
|
|
315
322
|
```python
|
|
316
323
|
users = rd.users.list()
|
|
317
324
|
me = rd.users.get_current()
|
|
318
325
|
u = rd.users.get("bob")
|
|
319
326
|
roles = rd.users.current_roles()
|
|
320
327
|
|
|
321
|
-
#
|
|
328
|
+
# Update via manager or object
|
|
322
329
|
u = rd.users.update("bob", firstName="Bob", lastName="Builder", email="bob@example.com")
|
|
323
|
-
u.roles() # via
|
|
324
|
-
u.update(email="new@example.com") #
|
|
330
|
+
u.roles() # via the object
|
|
331
|
+
u.update(email="new@example.com") # updates and refreshes the object
|
|
325
332
|
```
|
|
326
333
|
|
|
327
|
-
|
|
334
|
+
System
|
|
328
335
|
```python
|
|
329
336
|
system = rd.system
|
|
330
337
|
info = system.info()
|
|
@@ -334,7 +341,7 @@ system.logstorage.info()
|
|
|
334
341
|
system.logstorage.incomplete(max=50, offset=0)
|
|
335
342
|
system.logstorage.incomplete_resume()
|
|
336
343
|
|
|
337
|
-
#
|
|
344
|
+
# Execution mode (sub-manager)
|
|
338
345
|
system.executions.enable()
|
|
339
346
|
system.executions.disable()
|
|
340
347
|
system.executions.status()
|
|
@@ -351,7 +358,7 @@ rd.scheduler.takeover(all_servers=True)
|
|
|
351
358
|
rd.scheduler.takeover(server_uuid="uuid-123", project="demo", job_id="job-1")
|
|
352
359
|
```
|
|
353
360
|
|
|
354
|
-
|
|
361
|
+
Metrics
|
|
355
362
|
```python
|
|
356
363
|
metrics = rd.metrics
|
|
357
364
|
metrics.list()
|
|
@@ -359,16 +366,16 @@ metrics.data()
|
|
|
359
366
|
metrics.healthcheck()
|
|
360
367
|
metrics.ping()
|
|
361
368
|
|
|
362
|
-
#
|
|
369
|
+
# Installed plugins
|
|
363
370
|
plugins = rd.plugins.list()
|
|
364
371
|
for plugin in plugins:
|
|
365
372
|
print(plugin.name, plugin.service)
|
|
366
373
|
|
|
367
|
-
#
|
|
374
|
+
# Plugin details
|
|
368
375
|
first = plugins[0]
|
|
369
376
|
detail = rd.plugins.detail(first.service, first.name)
|
|
370
377
|
|
|
371
|
-
#
|
|
378
|
+
# Project webhooks and send
|
|
372
379
|
project = rd.projects.get("demo")
|
|
373
380
|
wh = project.webhooks
|
|
374
381
|
wh.create(
|
|
@@ -388,9 +395,9 @@ wh.delete(first_hook.id)
|
|
|
388
395
|
|
|
389
396
|
# Key storage (/storage/keys)
|
|
390
397
|
ks = rd.key_storage
|
|
391
|
-
#
|
|
398
|
+
# Create a secret (password)
|
|
392
399
|
ks.create("integration/secret1", content="s3cr3t", content_type="application/x-rundeck-data-password")
|
|
393
|
-
resources = ks.list() #
|
|
400
|
+
resources = ks.list() # root listing
|
|
394
401
|
meta = ks.get_metadata("integration/secret1")
|
|
395
402
|
ks.delete("integration/secret1")
|
|
396
403
|
|
|
@@ -398,17 +405,17 @@ ks.delete("integration/secret1")
|
|
|
398
405
|
project = rd.projects.get("demo")
|
|
399
406
|
exec1 = project.adhoc.run_command("echo 'hello world'")
|
|
400
407
|
exec2 = project.adhoc.run_script("echo 'from script'")
|
|
401
|
-
# exec2.id
|
|
402
|
-
#
|
|
408
|
+
# exec2.id to track execution
|
|
409
|
+
# Stub mode (no immediate refresh) then manual refresh
|
|
403
410
|
exec_stub = project.adhoc.run_command("echo stub", refresh=False)
|
|
404
|
-
exec_stub.refresh() #
|
|
411
|
+
exec_stub.refresh() # load the full object
|
|
405
412
|
# Script via multipart (upload)
|
|
406
413
|
exec3 = project.adhoc.run_script(
|
|
407
414
|
script_file=("hello.sh", "echo multipart", "text/plain"),
|
|
408
415
|
refresh=False,
|
|
409
416
|
)
|
|
410
417
|
|
|
411
|
-
#
|
|
418
|
+
# System features
|
|
412
419
|
features = rd.features.list()
|
|
413
420
|
if features:
|
|
414
421
|
first = features[0]
|
|
@@ -416,7 +423,7 @@ if features:
|
|
|
416
423
|
print(first.name, status.enabled)
|
|
417
424
|
```
|
|
418
425
|
|
|
419
|
-
|
|
426
|
+
Global configuration `/config`
|
|
420
427
|
```python
|
|
421
428
|
cfg = rd.config_management
|
|
422
429
|
all_configs = cfg.list()
|
|
@@ -428,27 +435,28 @@ cfg.restart()
|
|
|
428
435
|
|
|
429
436
|
Pagination
|
|
430
437
|
----------
|
|
431
|
-
|
|
438
|
+
Managers inherit `RundeckObjectManager.iter(...)` (offset/max). Example:
|
|
432
439
|
```python
|
|
433
440
|
for job in rd.jobs.iter(project="demo", page_size=100):
|
|
434
441
|
print(job.id)
|
|
435
442
|
```
|
|
436
443
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
-
|
|
444
|
-
-
|
|
445
|
-
-
|
|
446
|
-
- Tests
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
- `src/rundeck/
|
|
452
|
-
- `src/rundeck/
|
|
453
|
-
-
|
|
444
|
+
Error handling
|
|
445
|
+
--------------
|
|
446
|
+
HTTP errors go through `raise_for_status` and raise dedicated exceptions (e.g., `RundeckAuthenticationError`, `RundeckNotFoundError`, `RundeckValidationError`, `RundeckConflictError`, `RundeckServerError`). Handle them with a `try/except` block around client calls.
|
|
447
|
+
|
|
448
|
+
Development and testing
|
|
449
|
+
-----------------------
|
|
450
|
+
- Development setup (contributors): clone the repo and run `poetry install`.
|
|
451
|
+
- Formatting/Lint: `black src/rundeck tests/`, `ruff check src/rundeck tests/`
|
|
452
|
+
- Typing: `mypy src/rundeck/`
|
|
453
|
+
- Tests: `pytest`
|
|
454
|
+
- Local integration tests: `scripts/run-integration.sh` (starts docker compose at repo root, exports by default `RUNDECK_URL=http://localhost:4440`, `RUNDECK_TOKEN=adminToken`, `RUNDECK_API_VERSION=56`, waits for the healthcheck, runs `poetry run pytest -m integration`). You can override env vars before running. Add `KEEP_STACK=1` to keep the instance running after tests. Dedicated teardown script: `scripts/stop-integration.sh`.
|
|
455
|
+
|
|
456
|
+
Code structure
|
|
457
|
+
--------------
|
|
458
|
+
- `src/rundeck/base.py`: generic objects/managers, `_build_path` helpers, pagination, CRUD.
|
|
459
|
+
- `src/rundeck/client.py`: HTTP client, `http_get/post/put/delete/list` methods.
|
|
460
|
+
- `src/rundeck/v1/objects/`: domain managers and objects (projects, jobs, executions, system, tokens, users, config_management).
|
|
461
|
+
- Managers/objects schema: we apply the manager -> object pattern wherever the API exposes an identifiable resource (jobs, projects, users, tokens, executions, etc.). For purely global or utility endpoints without a resource (e.g., `/system` and its subdomains, `/config`, `/metrics`), we use dedicated sub-managers rather than forcing an artificial object.
|
|
454
462
|
|
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
python-rundeck
|
|
2
2
|
===============
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
3
7
|
|
|
4
|
-
|
|
8
|
+
Python client for the Rundeck API (v14-v56), inspired by the python-gitlab architecture. Provides typed managers for key resources (projects, jobs, executions, tokens, users, system, configuration) and SCM (import/export).
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
Contents
|
|
7
11
|
--------
|
|
8
12
|
- Installation
|
|
9
|
-
-
|
|
13
|
+
- Quick start
|
|
10
14
|
- Configuration
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
+
- Available resources
|
|
16
|
+
- Examples by resource
|
|
17
|
+
- Error handling
|
|
18
|
+
- Development and testing
|
|
15
19
|
|
|
16
20
|
Installation
|
|
17
21
|
------------
|
|
18
|
-
|
|
22
|
+
Prerequisites: Python 3.11+. Install from PyPI.
|
|
19
23
|
|
|
20
24
|
```bash
|
|
21
|
-
|
|
22
|
-
# ou en editable avec pip
|
|
23
|
-
pip install -e .
|
|
25
|
+
pip install python-rundeck
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
Quick start
|
|
29
|
+
-----------
|
|
28
30
|
```python
|
|
29
31
|
from rundeck.client import Rundeck
|
|
30
32
|
|
|
31
33
|
rd = Rundeck(url="https://rundeck.example.com", token="MY_TOKEN", api_version=56)
|
|
32
|
-
#
|
|
34
|
+
# Password auth (session cookie) if no token
|
|
33
35
|
rd = Rundeck(url="https://rundeck.example.com", username="admin", password="admin", api_version=56)
|
|
34
36
|
|
|
35
|
-
#
|
|
37
|
+
# List projects
|
|
36
38
|
projects = rd.projects.list()
|
|
37
39
|
for p in projects:
|
|
38
40
|
print(p.name)
|
|
39
41
|
|
|
40
|
-
#
|
|
42
|
+
# Get a project and run a job
|
|
41
43
|
project = rd.projects.get("demo")
|
|
42
44
|
jobs = project.jobs.list()
|
|
43
45
|
execu = jobs[0].run()
|
|
@@ -46,64 +48,64 @@ print(execu.id)
|
|
|
46
48
|
|
|
47
49
|
Configuration
|
|
48
50
|
-------------
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
- `url
|
|
53
|
-
- `token
|
|
54
|
-
- `username` / `password
|
|
55
|
-
- `api_version
|
|
56
|
-
- `timeout
|
|
57
|
-
- `ssl_verify
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- `RUNDECK_URL
|
|
62
|
-
- `RUNDECK_TOKEN
|
|
63
|
-
- `RUNDECK_USERNAME` / `RUNDECK_PASSWORD
|
|
64
|
-
- `RUNDECK_API_VERSION
|
|
65
|
-
- `RUNDECK_TIMEOUT
|
|
66
|
-
- `RUNDECK_SSL_VERIFY
|
|
67
|
-
- `RUNDECK_USER_AGENT
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- `projects` (`ProjectManager`)
|
|
72
|
-
- `jobs` (`JobManager`)
|
|
73
|
-
- `executions` (`ExecutionManager`)
|
|
74
|
-
- `tokens` (`TokenManager`)
|
|
75
|
-
- `users` (`UserManager`)
|
|
76
|
-
- `metrics` (`MetricsManager`)
|
|
77
|
-
- `plugins` (`PluginManager`)
|
|
78
|
-
- `webhooks` (`WebhookEventManager` + `ProjectWebhookManager`)
|
|
79
|
-
- `key_storage` (`StorageKeyManager`)
|
|
80
|
-
- `adhoc` (`AdhocManager`, via `project.adhoc`)
|
|
81
|
-
- `system` (`SystemManager`)
|
|
82
|
-
- `config_management` (`ConfigManagementManager`)
|
|
83
|
-
- `scm` (via `project.scm`
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
51
|
+
Configuration follows a cascading model (args > env > files > defaults) via `RundeckConfig`.
|
|
52
|
+
|
|
53
|
+
Main parameters:
|
|
54
|
+
- `url`: Rundeck URL (e.g. `https://rundeck.example.com`)
|
|
55
|
+
- `token`: API token (header `X-Rundeck-Auth-Token`)
|
|
56
|
+
- `username` / `password`: Session authentication (j_security_check) if no token is provided.
|
|
57
|
+
- `api_version`: API version (e.g. `56`)
|
|
58
|
+
- `timeout`: Request timeout (float, seconds)
|
|
59
|
+
- `ssl_verify`: TLS verification (bool or CA path)
|
|
60
|
+
|
|
61
|
+
Config files: if needed, pass `config_files` or `Rundeck.from_config(config_section=...)`.
|
|
62
|
+
Useful env vars (client config):
|
|
63
|
+
- `RUNDECK_URL`: Base URL
|
|
64
|
+
- `RUNDECK_TOKEN`: API token (header `X-Rundeck-Auth-Token`)
|
|
65
|
+
- `RUNDECK_USERNAME` / `RUNDECK_PASSWORD`: Session auth (if no token)
|
|
66
|
+
- `RUNDECK_API_VERSION`: API version (e.g. 56)
|
|
67
|
+
- `RUNDECK_TIMEOUT`: Request timeout (seconds)
|
|
68
|
+
- `RUNDECK_SSL_VERIFY`: TLS verification (bool or CA path)
|
|
69
|
+
- `RUNDECK_USER_AGENT`: HTTP User-Agent
|
|
70
|
+
|
|
71
|
+
Available resources
|
|
72
|
+
-------------------
|
|
73
|
+
- `projects` (`ProjectManager`): CRUD projects, job export/import, project config, archive (export/import).
|
|
74
|
+
- `jobs` (`JobManager`): list, get, deletions, bulk actions, execution.
|
|
75
|
+
- `executions` (`ExecutionManager`): list/filter, running, get/delete, advanced query.
|
|
76
|
+
- `tokens` (`TokenManager`): list, get, create, delete.
|
|
77
|
+
- `users` (`UserManager`): user operations (per current implementation).
|
|
78
|
+
- `metrics` (`MetricsManager`): `/metrics` endpoints (list/data/healthcheck/ping).
|
|
79
|
+
- `plugins` (`PluginManager`): list installed plugins (`/plugin/list`).
|
|
80
|
+
- `webhooks` (`WebhookEventManager` + `ProjectWebhookManager`): project webhook management and sending via token.
|
|
81
|
+
- `key_storage` (`StorageKeyManager`): key storage management `/storage/keys`.
|
|
82
|
+
- `adhoc` (`AdhocManager`, via `project.adhoc`): run AdHoc commands/scripts.
|
|
83
|
+
- `system` (`SystemManager`): system info, executions enable/disable, logstorage, ACL.
|
|
84
|
+
- `config_management` (`ConfigManagementManager`): global configuration `/config`.
|
|
85
|
+
- `scm` (via `project.scm` and `job.scm`): import/export plugins, setup, enable/disable, status, actions (commit/import/export...).
|
|
86
|
+
|
|
87
|
+
Examples by resource (complete)
|
|
88
|
+
-------------------------------
|
|
89
|
+
Projects
|
|
88
90
|
```python
|
|
89
|
-
# CRUD
|
|
91
|
+
# CRUD project
|
|
90
92
|
p = rd.projects.create("demo")
|
|
91
93
|
p = rd.projects.get("demo")
|
|
92
94
|
rd.projects.delete("demo")
|
|
93
95
|
projects = rd.projects.list()
|
|
94
96
|
|
|
95
|
-
# Export / import
|
|
97
|
+
# Export / import jobs from a project (via job manager)
|
|
96
98
|
project.jobs.export(format="json", idlist="id1,id2", groupPath="group/sub")
|
|
97
99
|
project.jobs.import_jobs(content=open("jobs.json").read(), format="json", dupeOption="update", uuidOption="remove")
|
|
98
100
|
|
|
99
|
-
#
|
|
101
|
+
# Project export / import (ZIP archive)
|
|
100
102
|
archive = project.archive
|
|
101
|
-
resp = archive.export(export_all=False, export_webhooks=True) #
|
|
103
|
+
resp = archive.export(export_all=False, export_webhooks=True) # Raw response (zip)
|
|
102
104
|
token_info = archive.export_async(exportAll=True)
|
|
103
105
|
status = archive.export_status(token_info.get("token", ""))
|
|
104
106
|
zip_resp = archive.export_download(token_info.get("token", ""))
|
|
105
107
|
|
|
106
|
-
#
|
|
108
|
+
# Archive import (sync or async)
|
|
107
109
|
archive.import_archive(
|
|
108
110
|
content=open("project-export.zip", "rb").read(),
|
|
109
111
|
jobUuidOption="preserve",
|
|
@@ -113,16 +115,16 @@ archive.import_archive(
|
|
|
113
115
|
archive.import_archive(content=open("project-export.zip", "rb").read(), async_import=True)
|
|
114
116
|
archive.import_status()
|
|
115
117
|
|
|
116
|
-
#
|
|
117
|
-
project.readme.get_readme() #
|
|
118
|
+
# Project README / MOTD
|
|
119
|
+
project.readme.get_readme() # default text
|
|
118
120
|
project.readme.get_readme(accept="application/json")
|
|
119
|
-
project.readme.update_readme("
|
|
121
|
+
project.readme.update_readme("New content", content_type="text/plain")
|
|
120
122
|
project.readme.delete_readme()
|
|
121
123
|
project.readme.get_motd()
|
|
122
|
-
project.readme.update_motd("Message
|
|
124
|
+
project.readme.update_motd("Message of the day", content_type="text/plain")
|
|
123
125
|
project.readme.delete_motd()
|
|
124
126
|
|
|
125
|
-
#
|
|
127
|
+
# Project config (key/value)
|
|
126
128
|
conf = p.config.get()
|
|
127
129
|
p.config.keys.get("project.label")
|
|
128
130
|
p.config.keys.set("project.label", "Demo")
|
|
@@ -130,27 +132,27 @@ p.config.keys.update({"project.description": "Sample"})
|
|
|
130
132
|
p.config.replace({"project.label": "Demo", "project.description": "Sample"})
|
|
131
133
|
p.config.keys.delete("project.label")
|
|
132
134
|
|
|
133
|
-
# SCM import/export (
|
|
134
|
-
scm_import = project.scm.import_ #
|
|
135
|
+
# SCM import/export (on a project)
|
|
136
|
+
scm_import = project.scm.import_ # or getattr(project.scm, "import")
|
|
135
137
|
scm_export = project.scm.export
|
|
136
138
|
|
|
137
|
-
#
|
|
139
|
+
# Plugin discovery
|
|
138
140
|
scm_import.plugins.list()
|
|
139
141
|
scm_export.plugins.list()
|
|
140
142
|
|
|
141
|
-
#
|
|
143
|
+
# Input fields for a plugin and setup (explicit plugin_type)
|
|
142
144
|
fields = scm_import.plugins.input_fields("git-import")
|
|
143
145
|
scm_import.config.setup("git-import", {"url": "ssh://git@example.com/repo.git", "dir": "/tmp/repo"})
|
|
144
146
|
|
|
145
|
-
#
|
|
147
|
+
# Enable/disable a plugin
|
|
146
148
|
scm_import.config.enable("git-import")
|
|
147
149
|
scm_export.config.disable("custom-export")
|
|
148
150
|
|
|
149
|
-
#
|
|
151
|
+
# SCM status/config
|
|
150
152
|
import_status = scm_import.actions.status()
|
|
151
153
|
export_conf = scm_export.config.get()
|
|
152
154
|
|
|
153
|
-
#
|
|
155
|
+
# Project-side SCM actions (e.g., commit/pull/push depending on plugin)
|
|
154
156
|
action_fields = scm_export.actions.input_fields("commit")
|
|
155
157
|
scm_export.actions.perform(
|
|
156
158
|
"commit",
|
|
@@ -163,14 +165,14 @@ scm_export.actions.perform(
|
|
|
163
165
|
|
|
164
166
|
Jobs
|
|
165
167
|
```python
|
|
166
|
-
#
|
|
168
|
+
# List jobs for a project
|
|
167
169
|
jobs = rd.jobs.list(project="demo", groupPath="ops")
|
|
168
170
|
|
|
169
|
-
#
|
|
171
|
+
# From a parent project
|
|
170
172
|
project = rd.projects.get("demo")
|
|
171
173
|
jobs = project.jobs.list()
|
|
172
174
|
|
|
173
|
-
#
|
|
175
|
+
# Direct access to a job
|
|
174
176
|
job = rd.jobs.get("job-id")
|
|
175
177
|
job.delete()
|
|
176
178
|
job.definition(format="yaml")
|
|
@@ -185,7 +187,7 @@ tags = job.tags()
|
|
|
185
187
|
workflow = job.workflow()
|
|
186
188
|
forecast = job.forecast(time="2024-05-01T10:00:00Z", max=5)
|
|
187
189
|
|
|
188
|
-
#
|
|
190
|
+
# Export/import jobs via manager (project parameter or parent)
|
|
189
191
|
rd.jobs.export(project="demo", format="xml", idlist="id1,id2", groupPath="group")
|
|
190
192
|
rd.jobs.import_jobs(
|
|
191
193
|
project="demo",
|
|
@@ -193,30 +195,30 @@ rd.jobs.import_jobs(
|
|
|
193
195
|
fileformat="xml",
|
|
194
196
|
dupeOption="update",
|
|
195
197
|
)
|
|
196
|
-
#
|
|
198
|
+
# Or via a parent project
|
|
197
199
|
project.jobs.export(format="json")
|
|
198
200
|
project.jobs.import_jobs(content=open("jobs.json", "rb").read(), fileformat="json")
|
|
199
201
|
|
|
200
|
-
Note:
|
|
202
|
+
Note: import remains exposed as `import_jobs(...)` (the Python keyword prevents a direct call to `.import`). If you prefer the alias, use `getattr(rd.jobs, "import")(...)`.
|
|
201
203
|
|
|
202
|
-
#
|
|
204
|
+
# Run and get the execution
|
|
203
205
|
execution = job.run(argString="-option value")
|
|
204
206
|
|
|
205
|
-
#
|
|
207
|
+
# Bulk actions
|
|
206
208
|
rd.jobs.bulk.enable_execution(["id1", "id2"])
|
|
207
209
|
rd.jobs.bulk.disable_execution(["id1", "id2"])
|
|
208
210
|
rd.jobs.bulk.delete(["id1", "id2"])
|
|
209
211
|
rd.jobs.bulk.enable_schedule(["id1", "id2"])
|
|
210
212
|
rd.jobs.bulk.disable_schedule(["id1", "id2"])
|
|
211
213
|
|
|
212
|
-
# Upload
|
|
214
|
+
# Upload option files and uploaded files
|
|
213
215
|
job.upload_option_file("csvfile", open("data.csv", "rb").read(), file_name="data.csv")
|
|
214
216
|
job.list_uploaded_files(max=20)
|
|
215
217
|
rd.jobs.get_uploaded_file_info("file-id")
|
|
216
218
|
|
|
217
|
-
# SCM import/export
|
|
219
|
+
# SCM import/export on a job
|
|
218
220
|
job_scm_export = job.scm.export
|
|
219
|
-
job_scm_import = job.scm.import_ #
|
|
221
|
+
job_scm_import = job.scm.import_ # or getattr(job.scm, "import")
|
|
220
222
|
|
|
221
223
|
job_scm_export.status()
|
|
222
224
|
job_scm_export.diff()
|
|
@@ -226,7 +228,7 @@ job_scm_import.status()
|
|
|
226
228
|
job_scm_import.input_fields("pull")
|
|
227
229
|
job_scm_import.perform("pull", input_values={"message": "Update from repo"})
|
|
228
230
|
|
|
229
|
-
#
|
|
231
|
+
# Project resources
|
|
230
232
|
resources = project.resources.list(format="json", groupPath="ops")
|
|
231
233
|
node = project.resources.get("node1")
|
|
232
234
|
sources = project.sources.list()
|
|
@@ -240,18 +242,18 @@ project.acl.update("policy.aclpolicy", content="...yaml...")
|
|
|
240
242
|
project.acl.delete("policy.aclpolicy")
|
|
241
243
|
```
|
|
242
244
|
|
|
243
|
-
|
|
245
|
+
Executions
|
|
244
246
|
```python
|
|
245
|
-
#
|
|
247
|
+
# Simple or paginated list
|
|
246
248
|
execs = rd.executions.list(project="demo", status="running", max=50, offset=0)
|
|
247
|
-
running = rd.executions.running(project="demo") #
|
|
249
|
+
running = rd.executions.running(project="demo") # or "*" for all
|
|
248
250
|
|
|
249
|
-
#
|
|
251
|
+
# Details / deletion
|
|
250
252
|
e = rd.executions.get("123")
|
|
251
253
|
rd.executions.delete("123")
|
|
252
254
|
rd.executions.bulk_delete(["123", "124"])
|
|
253
255
|
|
|
254
|
-
#
|
|
256
|
+
# Advanced query
|
|
255
257
|
advanced = rd.executions.query(
|
|
256
258
|
project="demo",
|
|
257
259
|
statusFilter="failed",
|
|
@@ -261,12 +263,12 @@ advanced = rd.executions.query(
|
|
|
261
263
|
max=100,
|
|
262
264
|
)
|
|
263
265
|
|
|
264
|
-
#
|
|
266
|
+
# Execution methods
|
|
265
267
|
e.abort(asUser="admin")
|
|
266
268
|
output = e.get_output(offset=0, maxlines=100)
|
|
267
269
|
state = e.get_state()
|
|
268
270
|
is_running = e.is_running()
|
|
269
|
-
e.refresh() #
|
|
271
|
+
e.refresh() # reload data
|
|
270
272
|
```
|
|
271
273
|
|
|
272
274
|
Tokens
|
|
@@ -278,20 +280,20 @@ new_token = rd.tokens.create(user="alice", roles=["admin"], duration="90d", name
|
|
|
278
280
|
rd.tokens.delete(new_token.id)
|
|
279
281
|
```
|
|
280
282
|
|
|
281
|
-
|
|
283
|
+
Users
|
|
282
284
|
```python
|
|
283
285
|
users = rd.users.list()
|
|
284
286
|
me = rd.users.get_current()
|
|
285
287
|
u = rd.users.get("bob")
|
|
286
288
|
roles = rd.users.current_roles()
|
|
287
289
|
|
|
288
|
-
#
|
|
290
|
+
# Update via manager or object
|
|
289
291
|
u = rd.users.update("bob", firstName="Bob", lastName="Builder", email="bob@example.com")
|
|
290
|
-
u.roles() # via
|
|
291
|
-
u.update(email="new@example.com") #
|
|
292
|
+
u.roles() # via the object
|
|
293
|
+
u.update(email="new@example.com") # updates and refreshes the object
|
|
292
294
|
```
|
|
293
295
|
|
|
294
|
-
|
|
296
|
+
System
|
|
295
297
|
```python
|
|
296
298
|
system = rd.system
|
|
297
299
|
info = system.info()
|
|
@@ -301,7 +303,7 @@ system.logstorage.info()
|
|
|
301
303
|
system.logstorage.incomplete(max=50, offset=0)
|
|
302
304
|
system.logstorage.incomplete_resume()
|
|
303
305
|
|
|
304
|
-
#
|
|
306
|
+
# Execution mode (sub-manager)
|
|
305
307
|
system.executions.enable()
|
|
306
308
|
system.executions.disable()
|
|
307
309
|
system.executions.status()
|
|
@@ -318,7 +320,7 @@ rd.scheduler.takeover(all_servers=True)
|
|
|
318
320
|
rd.scheduler.takeover(server_uuid="uuid-123", project="demo", job_id="job-1")
|
|
319
321
|
```
|
|
320
322
|
|
|
321
|
-
|
|
323
|
+
Metrics
|
|
322
324
|
```python
|
|
323
325
|
metrics = rd.metrics
|
|
324
326
|
metrics.list()
|
|
@@ -326,16 +328,16 @@ metrics.data()
|
|
|
326
328
|
metrics.healthcheck()
|
|
327
329
|
metrics.ping()
|
|
328
330
|
|
|
329
|
-
#
|
|
331
|
+
# Installed plugins
|
|
330
332
|
plugins = rd.plugins.list()
|
|
331
333
|
for plugin in plugins:
|
|
332
334
|
print(plugin.name, plugin.service)
|
|
333
335
|
|
|
334
|
-
#
|
|
336
|
+
# Plugin details
|
|
335
337
|
first = plugins[0]
|
|
336
338
|
detail = rd.plugins.detail(first.service, first.name)
|
|
337
339
|
|
|
338
|
-
#
|
|
340
|
+
# Project webhooks and send
|
|
339
341
|
project = rd.projects.get("demo")
|
|
340
342
|
wh = project.webhooks
|
|
341
343
|
wh.create(
|
|
@@ -355,9 +357,9 @@ wh.delete(first_hook.id)
|
|
|
355
357
|
|
|
356
358
|
# Key storage (/storage/keys)
|
|
357
359
|
ks = rd.key_storage
|
|
358
|
-
#
|
|
360
|
+
# Create a secret (password)
|
|
359
361
|
ks.create("integration/secret1", content="s3cr3t", content_type="application/x-rundeck-data-password")
|
|
360
|
-
resources = ks.list() #
|
|
362
|
+
resources = ks.list() # root listing
|
|
361
363
|
meta = ks.get_metadata("integration/secret1")
|
|
362
364
|
ks.delete("integration/secret1")
|
|
363
365
|
|
|
@@ -365,17 +367,17 @@ ks.delete("integration/secret1")
|
|
|
365
367
|
project = rd.projects.get("demo")
|
|
366
368
|
exec1 = project.adhoc.run_command("echo 'hello world'")
|
|
367
369
|
exec2 = project.adhoc.run_script("echo 'from script'")
|
|
368
|
-
# exec2.id
|
|
369
|
-
#
|
|
370
|
+
# exec2.id to track execution
|
|
371
|
+
# Stub mode (no immediate refresh) then manual refresh
|
|
370
372
|
exec_stub = project.adhoc.run_command("echo stub", refresh=False)
|
|
371
|
-
exec_stub.refresh() #
|
|
373
|
+
exec_stub.refresh() # load the full object
|
|
372
374
|
# Script via multipart (upload)
|
|
373
375
|
exec3 = project.adhoc.run_script(
|
|
374
376
|
script_file=("hello.sh", "echo multipart", "text/plain"),
|
|
375
377
|
refresh=False,
|
|
376
378
|
)
|
|
377
379
|
|
|
378
|
-
#
|
|
380
|
+
# System features
|
|
379
381
|
features = rd.features.list()
|
|
380
382
|
if features:
|
|
381
383
|
first = features[0]
|
|
@@ -383,7 +385,7 @@ if features:
|
|
|
383
385
|
print(first.name, status.enabled)
|
|
384
386
|
```
|
|
385
387
|
|
|
386
|
-
|
|
388
|
+
Global configuration `/config`
|
|
387
389
|
```python
|
|
388
390
|
cfg = rd.config_management
|
|
389
391
|
all_configs = cfg.list()
|
|
@@ -395,26 +397,27 @@ cfg.restart()
|
|
|
395
397
|
|
|
396
398
|
Pagination
|
|
397
399
|
----------
|
|
398
|
-
|
|
400
|
+
Managers inherit `RundeckObjectManager.iter(...)` (offset/max). Example:
|
|
399
401
|
```python
|
|
400
402
|
for job in rd.jobs.iter(project="demo", page_size=100):
|
|
401
403
|
print(job.id)
|
|
402
404
|
```
|
|
403
405
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
-
|
|
411
|
-
-
|
|
412
|
-
-
|
|
413
|
-
- Tests
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
- `src/rundeck/
|
|
419
|
-
- `src/rundeck/
|
|
420
|
-
-
|
|
406
|
+
Error handling
|
|
407
|
+
--------------
|
|
408
|
+
HTTP errors go through `raise_for_status` and raise dedicated exceptions (e.g., `RundeckAuthenticationError`, `RundeckNotFoundError`, `RundeckValidationError`, `RundeckConflictError`, `RundeckServerError`). Handle them with a `try/except` block around client calls.
|
|
409
|
+
|
|
410
|
+
Development and testing
|
|
411
|
+
-----------------------
|
|
412
|
+
- Development setup (contributors): clone the repo and run `poetry install`.
|
|
413
|
+
- Formatting/Lint: `black src/rundeck tests/`, `ruff check src/rundeck tests/`
|
|
414
|
+
- Typing: `mypy src/rundeck/`
|
|
415
|
+
- Tests: `pytest`
|
|
416
|
+
- Local integration tests: `scripts/run-integration.sh` (starts docker compose at repo root, exports by default `RUNDECK_URL=http://localhost:4440`, `RUNDECK_TOKEN=adminToken`, `RUNDECK_API_VERSION=56`, waits for the healthcheck, runs `poetry run pytest -m integration`). You can override env vars before running. Add `KEEP_STACK=1` to keep the instance running after tests. Dedicated teardown script: `scripts/stop-integration.sh`.
|
|
417
|
+
|
|
418
|
+
Code structure
|
|
419
|
+
--------------
|
|
420
|
+
- `src/rundeck/base.py`: generic objects/managers, `_build_path` helpers, pagination, CRUD.
|
|
421
|
+
- `src/rundeck/client.py`: HTTP client, `http_get/post/put/delete/list` methods.
|
|
422
|
+
- `src/rundeck/v1/objects/`: domain managers and objects (projects, jobs, executions, system, tokens, users, config_management).
|
|
423
|
+
- Managers/objects schema: we apply the manager -> object pattern wherever the API exposes an identifiable resource (jobs, projects, users, tokens, executions, etc.). For purely global or utility endpoints without a resource (e.g., `/system` and its subdomains, `/config`, `/metrics`), we use dedicated sub-managers rather than forcing an artificial object.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-rundeck"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Python client for the Rundeck API (v14-v56)."
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Pascal Seckinger",email = "pascal.seckinger@protonmail.com"}
|
|
@@ -26,6 +26,13 @@ dependencies = [
|
|
|
26
26
|
"requests (>=2.32.5,<3.0.0)"
|
|
27
27
|
]
|
|
28
28
|
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://gitlab.com/pascal.seckinger/python-rundeck"
|
|
31
|
+
Repository = "https://gitlab.com/pascal.seckinger/python-rundeck"
|
|
32
|
+
Documentation = "https://gitlab.com/pascal.seckinger/python-rundeck/-/blob/main/README.md"
|
|
33
|
+
Issues = "https://gitlab.com/pascal.seckinger/python-rundeck/-/issues"
|
|
34
|
+
Changelog = "https://gitlab.com/pascal.seckinger/python-rundeck/-/blob/main/CHANGELOG.md"
|
|
35
|
+
|
|
29
36
|
[project.optional-dependencies]
|
|
30
37
|
dev = [
|
|
31
38
|
"bandit (>=1.7.9,<2.0.0)",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|