python-rundeck 0.3.0__py3-none-any.whl → 1.0.0__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-rundeck
3
- Version: 0.3.0
3
+ Version: 1.0.0
4
4
  Summary: Python client for the Rundeck API (v14-v56).
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -51,6 +51,7 @@ Contents
51
51
  - Quick start
52
52
  - Configuration
53
53
  - Available resources
54
+ - Return types policy
54
55
  - Examples by resource
55
56
  - Error handling
56
57
  - Development and testing
@@ -80,7 +81,8 @@ for p in projects:
80
81
  # Get a project and run a job
81
82
  project = rd.projects.get("demo")
82
83
  jobs = project.jobs.list()
83
- execu = jobs[0].run()
84
+ # Use as_execution=True to receive an Execution object.
85
+ execu = jobs[0].run(as_execution=True)
84
86
  print(execu.id)
85
87
  ```
86
88
 
@@ -122,6 +124,40 @@ Available resources
122
124
  - `config_management` (`ConfigManagementManager`): global configuration `/config`.
123
125
  - `scm` (via `project.scm` and `job.scm`): import/export plugins, setup, enable/disable, status, actions (commit/import/export...).
124
126
 
127
+ Return types policy
128
+ -------------------
129
+ - Resource managers use `list/get/create/update` to return objects (`RundeckObject`).
130
+ - Factory managers return the created resource object for actions that create it
131
+ (e.g., `project.adhoc` returns `Execution`).
132
+ - Utility managers return raw API responses (dict/str/bytes).
133
+ - `delete(...)` returns `None`.
134
+ - Object methods return raw API responses and update the object in place when needed.
135
+ - Explicit flags are used when an object return is requested (e.g., `as_execution=True`).
136
+
137
+ Resource managers:
138
+ - `projects`, `jobs`, `executions`, `tokens`, `users`, `plugins`, `features`, `key_storage`, `system.acl`, `project.webhooks`.
139
+
140
+ Factory managers:
141
+ - `adhoc` (via `project.adhoc`).
142
+ Returns `Execution` objects (execution factory).
143
+
144
+ Utility managers:
145
+ - `system`, `config_management`, `metrics`, `scheduler`, `webhooks` (event send).
146
+ - `scm` (via `project.scm` and `job.scm`), and project sub-managers like
147
+ `project.config`, `project.resources`, `project.sources`, `project.readme`,
148
+ `project.archive`, `project.acl`.
149
+
150
+ Note: `project.webhooks.create(...)` returns the raw API response (e.g., `{"msg": "Saved webhook"}`)
151
+ because the Rundeck API does not return the created webhook object.
152
+
153
+ Example:
154
+ ```python
155
+ job = rd.jobs.get("job-id") # -> Job (object)
156
+ resp = job.run() # -> dict (raw API response)
157
+ execu = job.run(as_execution=True) # -> Execution (object)
158
+ execution.refresh() # updates in place, returns None
159
+ ```
160
+
125
161
  Examples by resource (complete)
126
162
  -------------------------------
127
163
  Projects
@@ -240,7 +276,7 @@ project.jobs.import_jobs(content=open("jobs.json", "rb").read(), fileformat="jso
240
276
  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")(...)`.
241
277
 
242
278
  # Run and get the execution
243
- execution = job.run(argString="-option value")
279
+ execution = job.run(as_execution=True, argString="-option value")
244
280
 
245
281
  # Bulk actions
246
282
  rd.jobs.bulk.enable_execution(["id1", "id2"])
@@ -398,7 +434,8 @@ ks = rd.key_storage
398
434
  # Create a secret (password)
399
435
  ks.create("integration/secret1", content="s3cr3t", content_type="application/x-rundeck-data-password")
400
436
  resources = ks.list() # root listing
401
- meta = ks.get_metadata("integration/secret1")
437
+ meta = ks.get("integration/secret1")
438
+ content = meta.content() # raw bytes
402
439
  ks.delete("integration/secret1")
403
440
 
404
441
  # AdHoc commands/scripts
@@ -1,5 +1,5 @@
1
1
  rundeck/__init__.py,sha256=M413mLKjB08nWuo5godsAmk2ly9vEYw5Xzc-Pt4Pqds,951
2
- rundeck/base.py,sha256=8JAghfRDGMWAhCbwedfPjuPu7ahKF8cE_ndkM1WEyuM,7125
2
+ rundeck/base.py,sha256=TGqxLj9F4Y0x7nQGqkjuw7xitzxWOa2fn4apkjpckwE,7119
3
3
  rundeck/client.py,sha256=o1ScXT9AffgAVOhs5l8PFKqAFjSd1Dk0zWT_uzmQfVg,10949
4
4
  rundeck/config.py,sha256=WZxhYDS2S4dZfvRgEMAzeGtM3EKll0CZMI_h9CZDzpA,5278
5
5
  rundeck/const.py,sha256=uK6LPGQfkq0y5ae1QUcQ7uyV7PSKdDKMRfpNoHNAT74,845
@@ -9,20 +9,20 @@ rundeck/v1/__init__.py,sha256=stKy-CO4DYAq_xa35LOgghZQ6LtrDcZ5WkuffNPGR0I,13
9
9
  rundeck/v1/objects/__init__.py,sha256=5n01WuyYDbuLLGGKoqO342Nb15Qgk3s5MUpJKp-o8Ko,21
10
10
  rundeck/v1/objects/adhoc.py,sha256=P_Ncn7ho-yEBXm-9e_JOF3ZX6MNAmpNyo_wX7QFwwM4,3447
11
11
  rundeck/v1/objects/config_management.py,sha256=Abeputc-VcHqgQgsE3HHOcUIVWNAB-wgLahpkfo0Wmc,1539
12
- rundeck/v1/objects/executions.py,sha256=v5ySlu4nOinvTtbM_1MpVF3bv8iNd8qbsckdqyVuJmQ,9628
12
+ rundeck/v1/objects/executions.py,sha256=BK1IvQGQJ_fMzpZgwZ2LJN6xslA2yIKprC6RZwLnGNI,9584
13
13
  rundeck/v1/objects/features.py,sha256=bVMLB86HIZF7dhN3EpqMVP_Yo_J2_hqtbJjZFb7BMxU,1283
14
- rundeck/v1/objects/jobs.py,sha256=fwhgxksV-i4_fA2jupeacvqlYNsKU5zBMWWZhbrWL8Q,11082
15
- rundeck/v1/objects/key_storage.py,sha256=mfFEUGcDnnDgTwnwCNNCXGuy_rmMi01fuXvcxinsWzI,2776
14
+ rundeck/v1/objects/jobs.py,sha256=fHG2iqN1b9UQevUcYNqd2qh7jQzbR4IKfs8-Keiuoj8,11582
15
+ rundeck/v1/objects/key_storage.py,sha256=TFa69t_jNZx1WbK0IQ5T1_x4tboHh3DOqSGNFX3CyVw,3673
16
16
  rundeck/v1/objects/metrics.py,sha256=R5SrySTHFznFgolJ3hezasSN7sZ3BSdTJs7PgN4w5p8,1004
17
17
  rundeck/v1/objects/plugins.py,sha256=a2vGsiQGz8V5gXNepj2KP98M926zzxRpksddKtZy5N8,1124
18
18
  rundeck/v1/objects/projects.py,sha256=FgqwbVpONjrOOVynoJenBtaJIj7x6dnAoNudQ6-nEVo,15512
19
19
  rundeck/v1/objects/scheduler.py,sha256=1lRGPdIej9OTNueSE9_p9PxS4b_M-ANTUJoztRo3IjE,1243
20
20
  rundeck/v1/objects/scm.py,sha256=f4y76CLF6J6q1ZE_Rrm9SXm2wlD3qaqXavt1DNzo_JE,7688
21
- rundeck/v1/objects/system.py,sha256=wTfIxs4zINXxJF3zaEckB75sTFeeUq31O864y-B_pdo,4629
21
+ rundeck/v1/objects/system.py,sha256=pEjGPcnr7LOlN0RpfPtaD0dRTJuy58GPtwB0k6EVV4Y,4876
22
22
  rundeck/v1/objects/tokens.py,sha256=OWnfs1QUTpJbsaKf7r0Y7w3VYxxGZxBDead8JAoPKck,2373
23
- rundeck/v1/objects/users.py,sha256=N7WjC2UqXrmd0PurI317lua5wMSZN-C1YfdjjPWpqgA,3278
23
+ rundeck/v1/objects/users.py,sha256=Ys3JE9rs1u7DIcIH3rG39rjZ--gwsfaXPHCsrxBXJaA,3330
24
24
  rundeck/v1/objects/webhooks.py,sha256=F_HzNvlBRPvS_viu2reCRc7ECqRSUhp5RfD_G3r-ZZk,3358
25
- python_rundeck-0.3.0.dist-info/METADATA,sha256=R2kNIAOkP26epzMRnUSZiRIwb7fvk4ayS0dmRwXM-Ug,16283
26
- python_rundeck-0.3.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
27
- python_rundeck-0.3.0.dist-info/licenses/LICENSE,sha256=3X-BjvCzow8iLfLMykDaEAXjE9JvDrEHPkH9UK7MMMA,1073
28
- python_rundeck-0.3.0.dist-info/RECORD,,
25
+ python_rundeck-1.0.0.dist-info/METADATA,sha256=aMBsZwnVx9C1nmFrTjcY7QMVzIJZSsr72cDxmFdNBEk,17935
26
+ python_rundeck-1.0.0.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
27
+ python_rundeck-1.0.0.dist-info/licenses/LICENSE,sha256=3X-BjvCzow8iLfLMykDaEAXjE9JvDrEHPkH9UK7MMMA,1073
28
+ python_rundeck-1.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.3.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
rundeck/base.py CHANGED
@@ -50,14 +50,16 @@ class RundeckObject(Generic[ResourceT]):
50
50
  def to_dict(self) -> dict[str, Any]:
51
51
  return dict(self._attrs)
52
52
 
53
- def refresh(self: ResourceT) -> ResourceT:
54
- """Recharge la ressource en interrogeant son manager."""
53
+ def refresh(self: ResourceT) -> None:
54
+ """
55
+ Recharge la ressource en interrogeant son manager.
56
+
57
+ """
55
58
  ident = self._attrs.get(self._id_attr)
56
59
  if ident is None:
57
60
  raise AttributeError(f"{self.__class__.__name__} has no identifier")
58
61
  fresh = self.manager.get(ident)
59
62
  self._attrs = fresh._attrs
60
- return self
61
63
 
62
64
  def pformat(self) -> str:
63
65
  """Retourne une représentation jolie du dictionnaire d'attributs."""
@@ -111,16 +111,15 @@ class Execution(RundeckObject):
111
111
  """
112
112
  return self._attrs.get("status") == "aborted"
113
113
 
114
- def refresh(self) -> "Execution":
114
+ def refresh(self) -> None:
115
115
  """
116
116
  Actualise les données de l'exécution
117
117
 
118
118
  Returns:
119
- Exécution actualisée
119
+ None.
120
120
  """
121
121
  updated = self.manager.get(self.id)
122
122
  self._attrs = updated._attrs
123
- return self
124
123
 
125
124
 
126
125
  class ExecutionManager(RundeckObjectManager):
@@ -22,22 +22,40 @@ class Job(RundeckObject):
22
22
  return f"{base}/{suffix.lstrip('/')}"
23
23
  return base
24
24
 
25
- def run(self, **kwargs: Any) -> Execution:
25
+ def run(
26
+ self, as_execution: bool = False, **kwargs: Any
27
+ ) -> Execution | dict[str, Any]:
26
28
  """
27
- Exécute le job et retourne l'exécution.
29
+ Exécute le job et retourne la réponse brute par défaut.
30
+
31
+ Args:
32
+ as_execution: True pour envelopper la réponse en Execution.
28
33
  """
29
34
  path = self._job_path("run")
30
35
  result = self.rd.http_post(path, json=kwargs or None)
31
- # Wrap avec le manager d'exécutions existant.
32
- return Execution(self.manager.rd.executions, result)
36
+ if as_execution:
37
+ # Wrap avec le manager d'exécutions existant.
38
+ return Execution(self.manager.rd.executions, result)
39
+ return result
33
40
 
34
- def retry(self, execution_id: str, **kwargs: Any) -> Execution:
41
+ def retry(
42
+ self,
43
+ execution_id: str,
44
+ as_execution: bool = False,
45
+ **kwargs: Any,
46
+ ) -> Execution | dict[str, Any]:
35
47
  """
36
48
  Relance une exécution (retry).
49
+
50
+ Args:
51
+ execution_id: ID de l'exécution à relancer.
52
+ as_execution: True pour envelopper la réponse en Execution.
37
53
  """
38
54
  path = self._job_path(f"retry/{execution_id}")
39
55
  result = self.rd.http_post(path, json=kwargs or None)
40
- return Execution(self.manager.rd.executions, result)
56
+ if as_execution:
57
+ return Execution(self.manager.rd.executions, result)
58
+ return result
41
59
 
42
60
  def delete(self) -> None:
43
61
  """Supprime le job courant."""
@@ -15,6 +15,28 @@ class StorageKey(RundeckObject):
15
15
  _id_attr = "path"
16
16
  _repr_attr = "name"
17
17
 
18
+ def _relative_path(self) -> str:
19
+ path = self.path
20
+ # API returns "keys/..." but endpoints expect the relative path.
21
+ if isinstance(path, str) and path.startswith("keys/"):
22
+ return path[len("keys/") :]
23
+ return path
24
+
25
+ def content(self, accept: str = "application/pgp-keys") -> bytes:
26
+ """Récupère le contenu d'une clé (ex: clé publique)."""
27
+ path = self.manager._file_path(self._relative_path())
28
+ headers = {"Accept": accept} if accept else None
29
+ response = self.manager.rd.http_get(path, headers=headers, raw=True)
30
+ return response.content
31
+
32
+ def update(self, content: bytes | str, content_type: str) -> dict[str, Any]:
33
+ """Met à jour le contenu de la clé et rafraîchit l'objet."""
34
+ updated = self.manager.update(
35
+ self._relative_path(), content=content, content_type=content_type
36
+ )
37
+ self._attrs = updated._attrs
38
+ return updated.to_dict()
39
+
18
40
 
19
41
  class StorageKeyManager(RundeckObjectManager[StorageKey]):
20
42
  """Manager pour les clés/ressources du storage."""
@@ -43,7 +65,7 @@ class StorageKeyManager(RundeckObjectManager[StorageKey]):
43
65
  resources = result.get("resources") or []
44
66
  return self._wrap_list(resources)
45
67
 
46
- def get_metadata(self, path: str) -> StorageKey:
68
+ def get(self, path: str) -> StorageKey:
47
69
  """Récupère les métadonnées d'une clé/fichier."""
48
70
  target = self._file_path(path)
49
71
  result = self.rd.http_get(target)
@@ -51,24 +73,23 @@ class StorageKeyManager(RundeckObjectManager[StorageKey]):
51
73
  return self._wrap(result)
52
74
  return self._wrap({"path": path, "raw": result})
53
75
 
54
- def get_content(self, path: str, accept: str = "application/pgp-keys") -> bytes:
55
- """Récupère le contenu d'une clé (ex: clé publique)."""
56
- target = self._file_path(path)
57
- headers = {"Accept": accept} if accept else None
58
- response = self.rd.http_get(target, headers=headers, raw=True)
59
- return response.content
60
-
61
- def create(self, path: str, content: bytes | str, content_type: str) -> Any:
76
+ def create(self, path: str, content: bytes | str, content_type: str) -> StorageKey:
62
77
  """Crée une nouvelle clé/fichier à l'emplacement donné."""
63
78
  target = self._file_path(path)
64
79
  headers = {"Content-Type": content_type, "Accept": "application/json"}
65
- return self.rd.http_post(target, data=content, headers=headers)
80
+ result = self.rd.http_post(target, data=content, headers=headers)
81
+ if isinstance(result, dict):
82
+ return self._wrap(result)
83
+ return self._wrap({"path": path, "raw": result})
66
84
 
67
- def update(self, path: str, content: bytes | str, content_type: str) -> Any:
85
+ def update(self, path: str, content: bytes | str, content_type: str) -> StorageKey:
68
86
  """Met à jour une clé/fichier existant."""
69
87
  target = self._file_path(path)
70
88
  headers = {"Content-Type": content_type, "Accept": "application/json"}
71
- return self.rd.http_put(target, data=content, headers=headers)
89
+ result = self.rd.http_put(target, data=content, headers=headers)
90
+ if isinstance(result, dict):
91
+ return self._wrap(result)
92
+ return self._wrap({"path": path, "raw": result})
72
93
 
73
94
  def delete(self, path: str) -> None:
74
95
  """Supprime une clé/fichier."""
@@ -34,6 +34,12 @@ class SystemACL(RundeckObject):
34
34
  _id_attr = "name"
35
35
  _repr_attr = "name"
36
36
 
37
+ def update(self, content: str) -> dict[str, Any]:
38
+ """Met à jour la policy ACL et rafraîchit l'objet."""
39
+ updated = self.manager.update(self.name, content)
40
+ self._attrs = updated._attrs
41
+ return updated.to_dict()
42
+
37
43
 
38
44
  class SystemACLManager(RundeckObjectManager[SystemACL]):
39
45
  """Sous-manager pour les endpoints ACL système."""
@@ -18,7 +18,7 @@ class User(RundeckObject):
18
18
  firstName: str | None = None,
19
19
  lastName: str | None = None,
20
20
  email: str | None = None,
21
- ) -> "User":
21
+ ) -> dict[str, Any]:
22
22
  """
23
23
  Met à jour les informations de l'utilisateur
24
24
 
@@ -28,7 +28,7 @@ class User(RundeckObject):
28
28
  email: Email
29
29
 
30
30
  Returns:
31
- Utilisateur mis à jour
31
+ Réponse brute de l'API.
32
32
  """
33
33
  data: dict[str, Any] = {}
34
34
  if firstName:
@@ -40,8 +40,9 @@ class User(RundeckObject):
40
40
 
41
41
  path = self.manager._build_path(f"info/{self.login}")
42
42
  result = self.manager.rd.http_post(path, json=data)
43
- self._attrs.update(result)
44
- return self
43
+ if isinstance(result, dict):
44
+ self._attrs.update(result)
45
+ return result
45
46
 
46
47
  def roles(self) -> list[str]:
47
48
  """