python-rundeck 0.1.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.
- python_rundeck-0.1.0.dist-info/METADATA +454 -0
- python_rundeck-0.1.0.dist-info/RECORD +28 -0
- python_rundeck-0.1.0.dist-info/WHEEL +4 -0
- python_rundeck-0.1.0.dist-info/licenses/LICENSE +21 -0
- rundeck/__init__.py +39 -0
- rundeck/base.py +231 -0
- rundeck/client.py +341 -0
- rundeck/config.py +166 -0
- rundeck/const.py +45 -0
- rundeck/exceptions.py +164 -0
- rundeck/py.typed +1 -0
- rundeck/v1/__init__.py +1 -0
- rundeck/v1/objects/__init__.py +1 -0
- rundeck/v1/objects/adhoc.py +93 -0
- rundeck/v1/objects/config_management.py +51 -0
- rundeck/v1/objects/executions.py +327 -0
- rundeck/v1/objects/features.py +44 -0
- rundeck/v1/objects/jobs.py +329 -0
- rundeck/v1/objects/key_storage.py +79 -0
- rundeck/v1/objects/metrics.py +37 -0
- rundeck/v1/objects/plugins.py +38 -0
- rundeck/v1/objects/projects.py +421 -0
- rundeck/v1/objects/scheduler.py +50 -0
- rundeck/v1/objects/scm.py +229 -0
- rundeck/v1/objects/system.py +140 -0
- rundeck/v1/objects/tokens.py +102 -0
- rundeck/v1/objects/users.py +137 -0
- rundeck/v1/objects/webhooks.py +104 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gestion du stockage des clés (/storage/keys).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from rundeck.base import RundeckObject, RundeckObjectManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StorageKey(RundeckObject):
|
|
13
|
+
"""Ressource de stockage (fichier ou dossier)."""
|
|
14
|
+
|
|
15
|
+
_id_attr = "path"
|
|
16
|
+
_repr_attr = "name"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StorageKeyManager(RundeckObjectManager[StorageKey]):
|
|
20
|
+
"""Manager pour les clés/ressources du storage."""
|
|
21
|
+
|
|
22
|
+
_path = "/storage/keys"
|
|
23
|
+
_obj_cls = StorageKey
|
|
24
|
+
|
|
25
|
+
def _dir_path(self, path: str | None = None) -> str:
|
|
26
|
+
base = self._build_path()
|
|
27
|
+
if path:
|
|
28
|
+
cleaned = path.strip("/")
|
|
29
|
+
return f"{base}/{cleaned}/"
|
|
30
|
+
return base
|
|
31
|
+
|
|
32
|
+
def _file_path(self, path: str) -> str:
|
|
33
|
+
base = self._build_path()
|
|
34
|
+
cleaned = path.strip("/")
|
|
35
|
+
return f"{base}/{cleaned}"
|
|
36
|
+
|
|
37
|
+
def list(self, path: str | None = None) -> list[StorageKey]:
|
|
38
|
+
"""Liste les ressources (fichiers/dossiers) sous le chemin donné."""
|
|
39
|
+
target = self._dir_path(path)
|
|
40
|
+
result = self.rd.http_get(target)
|
|
41
|
+
resources = []
|
|
42
|
+
if isinstance(result, dict):
|
|
43
|
+
resources = result.get("resources") or []
|
|
44
|
+
return self._wrap_list(resources)
|
|
45
|
+
|
|
46
|
+
def get_metadata(self, path: str) -> StorageKey:
|
|
47
|
+
"""Récupère les métadonnées d'une clé/fichier."""
|
|
48
|
+
target = self._file_path(path)
|
|
49
|
+
result = self.rd.http_get(target)
|
|
50
|
+
if isinstance(result, dict):
|
|
51
|
+
return self._wrap(result)
|
|
52
|
+
return self._wrap({"path": path, "raw": result})
|
|
53
|
+
|
|
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:
|
|
62
|
+
"""Crée une nouvelle clé/fichier à l'emplacement donné."""
|
|
63
|
+
target = self._file_path(path)
|
|
64
|
+
headers = {"Content-Type": content_type, "Accept": "application/json"}
|
|
65
|
+
return self.rd.http_post(target, data=content, headers=headers)
|
|
66
|
+
|
|
67
|
+
def update(self, path: str, content: bytes | str, content_type: str) -> Any:
|
|
68
|
+
"""Met à jour une clé/fichier existant."""
|
|
69
|
+
target = self._file_path(path)
|
|
70
|
+
headers = {"Content-Type": content_type, "Accept": "application/json"}
|
|
71
|
+
return self.rd.http_put(target, data=content, headers=headers)
|
|
72
|
+
|
|
73
|
+
def delete(self, path: str) -> None:
|
|
74
|
+
"""Supprime une clé/fichier."""
|
|
75
|
+
target = self._file_path(path)
|
|
76
|
+
self.rd.http_delete(target)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = ["StorageKey", "StorageKeyManager"]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gestion des métriques Rundeck
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rundeck.base import RundeckObjectManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MetricsManager(RundeckObjectManager):
|
|
11
|
+
"""Manager pour les endpoints de métriques."""
|
|
12
|
+
|
|
13
|
+
_path = "/metrics"
|
|
14
|
+
|
|
15
|
+
def list(self) -> dict[str, Any]:
|
|
16
|
+
"""Liste les endpoints de métriques disponibles."""
|
|
17
|
+
path = self._build_path()
|
|
18
|
+
return self.rd.http_get(path)
|
|
19
|
+
|
|
20
|
+
def data(self) -> dict[str, Any]:
|
|
21
|
+
"""Récupère les données de métriques."""
|
|
22
|
+
path = self._build_path("metrics")
|
|
23
|
+
return self.rd.http_get(path)
|
|
24
|
+
|
|
25
|
+
def healthcheck(self) -> dict[str, Any]:
|
|
26
|
+
"""Récupère les résultats des health checks."""
|
|
27
|
+
path = self._build_path("healthcheck")
|
|
28
|
+
return self.rd.http_get(path)
|
|
29
|
+
|
|
30
|
+
def ping(self) -> str:
|
|
31
|
+
"""Ping du serveur via l'endpoint métriques."""
|
|
32
|
+
path = self._build_path("ping")
|
|
33
|
+
response = self.rd.http_get(path, raw=True)
|
|
34
|
+
return response.text
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ["MetricsManager"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gestion des plugins installés (/plugin/list).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from rundeck.base import RundeckObject, RundeckObjectManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Plugin(RundeckObject):
|
|
9
|
+
"""Représente un plugin Rundeck installé."""
|
|
10
|
+
|
|
11
|
+
_id_attr = "id"
|
|
12
|
+
_repr_attr = "name"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PluginManager(RundeckObjectManager[Plugin]):
|
|
16
|
+
"""Manager pour lister les plugins installés."""
|
|
17
|
+
|
|
18
|
+
_path = "/plugin"
|
|
19
|
+
_obj_cls = Plugin
|
|
20
|
+
|
|
21
|
+
def list(self) -> list[Plugin]:
|
|
22
|
+
"""Liste les plugins installés sur l'instance."""
|
|
23
|
+
path = self._build_path("list")
|
|
24
|
+
return self._list(path=path)
|
|
25
|
+
|
|
26
|
+
def detail(self, service: str, provider: str) -> Plugin:
|
|
27
|
+
"""Détails d'un plugin spécifique (service/provider)."""
|
|
28
|
+
path = self._build_path(f"detail/{service}/{provider}")
|
|
29
|
+
result = self.rd.http_get(path)
|
|
30
|
+
# Certains retours ne contiennent pas le champ service ; on le re-injecte
|
|
31
|
+
# pour cohérence.
|
|
32
|
+
if isinstance(result, dict):
|
|
33
|
+
result.setdefault("service", service)
|
|
34
|
+
result.setdefault("provider", provider)
|
|
35
|
+
return self._wrap(result)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = ["Plugin", "PluginManager"]
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gestion des projets Rundeck
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rundeck.base import RundeckObject, RundeckObjectManager
|
|
8
|
+
from rundeck.v1.objects.adhoc import AdhocManager
|
|
9
|
+
from rundeck.v1.objects.executions import ExecutionManager
|
|
10
|
+
from rundeck.v1.objects.jobs import JobManager
|
|
11
|
+
from rundeck.v1.objects.scm import ProjectSCMManager
|
|
12
|
+
from rundeck.v1.objects.webhooks import ProjectWebhookManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Project(RundeckObject):
|
|
16
|
+
"""Représente un projet Rundeck."""
|
|
17
|
+
|
|
18
|
+
_id_attr = "name"
|
|
19
|
+
_repr_attr = "name"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def jobs(self) -> JobManager:
|
|
23
|
+
"""Accès aux jobs du projet via un manager parenté."""
|
|
24
|
+
return JobManager(self.rd, parent=self)
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def executions(self) -> ExecutionManager:
|
|
28
|
+
"""Accès aux exécutions du projet via un manager parenté."""
|
|
29
|
+
return ExecutionManager(self.rd, parent=self)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def config(self) -> "ProjectConfigManager":
|
|
33
|
+
"""Accès à la configuration du projet (clé/valeur)."""
|
|
34
|
+
return ProjectConfigManager(self.rd, parent=self)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def resources(self) -> "ProjectResourcesManager":
|
|
38
|
+
"""Accès aux ressources/nodes du projet."""
|
|
39
|
+
return ProjectResourcesManager(self.rd, parent=self)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def sources(self) -> "ProjectSourcesManager":
|
|
43
|
+
"""Accès aux sources de ressources du projet."""
|
|
44
|
+
return ProjectSourcesManager(self.rd, parent=self)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def acl(self) -> "ProjectACLManager":
|
|
48
|
+
"""Accès aux ACL du projet."""
|
|
49
|
+
return ProjectACLManager(self.rd, parent=self)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def archive(self) -> "ProjectArchiveManager":
|
|
53
|
+
"""Accès aux exports/imports d'archive projet."""
|
|
54
|
+
return ProjectArchiveManager(self.rd, parent=self)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def readme(self) -> "ProjectReadmeManager":
|
|
58
|
+
"""Accès aux fichiers readme/motd du projet."""
|
|
59
|
+
return ProjectReadmeManager(self.rd, parent=self)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def scm(self) -> ProjectSCMManager:
|
|
63
|
+
"""Accès aux opérations SCM du projet (import/export)."""
|
|
64
|
+
return ProjectSCMManager(self.rd, parent=self)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def webhooks(self) -> ProjectWebhookManager:
|
|
68
|
+
"""Accès aux webhooks du projet."""
|
|
69
|
+
return ProjectWebhookManager(self.rd, parent=self)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def adhoc(self) -> AdhocManager:
|
|
73
|
+
"""Accès aux commandes/scripts AdHoc du projet."""
|
|
74
|
+
return AdhocManager(self.rd, parent=self)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ProjectManager(RundeckObjectManager[Project]):
|
|
78
|
+
"""Manager pour les projets."""
|
|
79
|
+
|
|
80
|
+
_path = "/projects"
|
|
81
|
+
_obj_cls = Project
|
|
82
|
+
|
|
83
|
+
def list(self) -> list[Project]:
|
|
84
|
+
"""Liste tous les projets."""
|
|
85
|
+
return self._list()
|
|
86
|
+
|
|
87
|
+
def get(self, name: str) -> Project:
|
|
88
|
+
"""Récupère un projet par son nom."""
|
|
89
|
+
path = f"/project/{name}"
|
|
90
|
+
return self._get(name, path=path)
|
|
91
|
+
|
|
92
|
+
def create(self, name: str, config: dict[str, Any] | None = None) -> Project:
|
|
93
|
+
"""Crée un projet (optionnellement avec configuration)."""
|
|
94
|
+
payload: dict[str, Any] = {"name": name}
|
|
95
|
+
if config:
|
|
96
|
+
payload["config"] = config
|
|
97
|
+
path = self._build_path()
|
|
98
|
+
return self._create(json=payload, path=path)
|
|
99
|
+
|
|
100
|
+
def delete(self, name: str) -> None:
|
|
101
|
+
"""Supprime un projet par son nom."""
|
|
102
|
+
path = f"/project/{name}"
|
|
103
|
+
self._delete(name, path=path)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ProjectConfig(RundeckObject):
|
|
107
|
+
"""Placeholder, non utilisé pour wrapping (retours dict)."""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ProjectConfigManager(RundeckObjectManager[ProjectConfig]):
|
|
111
|
+
"""Manager pour la configuration d'un projet (clé/valeur)."""
|
|
112
|
+
|
|
113
|
+
_path = "/project/{parent}/config"
|
|
114
|
+
_obj_cls = ProjectConfig
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def keys(self) -> "ProjectConfigKeysManager":
|
|
118
|
+
"""Opérations clé/valeur (GET/PUT/POST/DELETE)."""
|
|
119
|
+
return ProjectConfigKeysManager(self.rd, parent=self.parent)
|
|
120
|
+
|
|
121
|
+
def get(self) -> dict[str, str]:
|
|
122
|
+
"""Récupère toute la configuration du projet parent."""
|
|
123
|
+
path = self._build_path()
|
|
124
|
+
return self.rd.http_get(path)
|
|
125
|
+
|
|
126
|
+
def replace(self, config: dict[str, str]) -> dict[str, str]:
|
|
127
|
+
"""Remplace toute la configuration du projet."""
|
|
128
|
+
path = self._build_path()
|
|
129
|
+
return self.rd.http_put(path, json=config)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ProjectConfigKeysManager(RundeckObjectManager[ProjectConfig]):
|
|
133
|
+
"""Opérations par clé sur la configuration."""
|
|
134
|
+
|
|
135
|
+
_path = "/project/{parent}/config"
|
|
136
|
+
_obj_cls = ProjectConfig
|
|
137
|
+
|
|
138
|
+
def get(self, key: str) -> dict[str, str]:
|
|
139
|
+
"""Récupère une propriété de configuration."""
|
|
140
|
+
path = self._build_path(key)
|
|
141
|
+
return self.rd.http_get(path)
|
|
142
|
+
|
|
143
|
+
def set(self, key: str, value: str) -> dict[str, str]:
|
|
144
|
+
"""Définit une propriété de configuration."""
|
|
145
|
+
path = self._build_path(key)
|
|
146
|
+
return self.rd.http_put(path, json={"value": value})
|
|
147
|
+
|
|
148
|
+
def update(self, config: dict[str, str]) -> dict[str, str]:
|
|
149
|
+
"""Met à jour plusieurs propriétés en une fois."""
|
|
150
|
+
path = self._build_path()
|
|
151
|
+
return self.rd.http_post(path, json=config)
|
|
152
|
+
|
|
153
|
+
def delete(self, key: str) -> None:
|
|
154
|
+
"""Supprime une propriété de configuration."""
|
|
155
|
+
path = self._build_path(key)
|
|
156
|
+
self.rd.http_delete(path)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ProjectACLManager(RundeckObjectManager):
|
|
160
|
+
"""Manager pour les ACL d'un projet (/project/{parent}/acl)."""
|
|
161
|
+
|
|
162
|
+
_path = "/project/{parent}/acl"
|
|
163
|
+
|
|
164
|
+
def list(self) -> dict[str, Any]:
|
|
165
|
+
"""Liste les politiques ACL du projet."""
|
|
166
|
+
path = self._build_path("")
|
|
167
|
+
return self.rd.http_get(path)
|
|
168
|
+
|
|
169
|
+
def get(self, policy_name: str) -> str:
|
|
170
|
+
"""Récupère une politique ACL projet (contenu texte)."""
|
|
171
|
+
path = self._build_path(policy_name)
|
|
172
|
+
response = self.rd.http_get(path, raw=True)
|
|
173
|
+
return response.text
|
|
174
|
+
|
|
175
|
+
def create(self, policy_name: str, content: str) -> dict[str, Any]:
|
|
176
|
+
"""Crée une politique ACL projet."""
|
|
177
|
+
path = self._build_path(policy_name)
|
|
178
|
+
headers = {"Content-Type": "application/yaml"}
|
|
179
|
+
return self.rd.http_post(path, data=content, headers=headers)
|
|
180
|
+
|
|
181
|
+
def update(self, policy_name: str, content: str) -> dict[str, Any]:
|
|
182
|
+
"""Met à jour une politique ACL projet."""
|
|
183
|
+
path = self._build_path(policy_name)
|
|
184
|
+
headers = {"Content-Type": "application/yaml"}
|
|
185
|
+
return self.rd.http_put(path, data=content, headers=headers)
|
|
186
|
+
|
|
187
|
+
def delete(self, policy_name: str) -> None:
|
|
188
|
+
"""Supprime une politique ACL projet."""
|
|
189
|
+
path = self._build_path(policy_name)
|
|
190
|
+
self.rd.http_delete(path)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class ProjectResourcesManager(RundeckObjectManager):
|
|
194
|
+
"""Manager pour les ressources (nodes) d'un projet."""
|
|
195
|
+
|
|
196
|
+
_path = "/project/{parent}/resources"
|
|
197
|
+
_obj_cls = RundeckObject # non utilisé, on retourne les données brutes
|
|
198
|
+
|
|
199
|
+
def list(self, format: str | None = "json", **filters: Any) -> Any:
|
|
200
|
+
"""Liste les ressources du projet (avec filtres de nodes éventuels)."""
|
|
201
|
+
params: dict[str, Any] = {}
|
|
202
|
+
if format:
|
|
203
|
+
params["format"] = format
|
|
204
|
+
params.update(filters)
|
|
205
|
+
path = self._build_path()
|
|
206
|
+
return self.rd.http_get(path, params=params or None)
|
|
207
|
+
|
|
208
|
+
def get(self, name: str, format: str | None = "json") -> Any:
|
|
209
|
+
"""Récupère une ressource spécifique par son nom."""
|
|
210
|
+
params: dict[str, Any] = {"format": format} if format else None
|
|
211
|
+
path = (
|
|
212
|
+
f"/project/{self.parent.id}/resource/{name}"
|
|
213
|
+
if self.parent
|
|
214
|
+
else f"/resource/{name}"
|
|
215
|
+
)
|
|
216
|
+
return self.rd.http_get(path, params=params)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class ProjectSourcesManager(RundeckObjectManager):
|
|
220
|
+
"""Manager pour les sources de ressources d'un projet."""
|
|
221
|
+
|
|
222
|
+
_path = "/project/{parent}/sources"
|
|
223
|
+
_obj_cls = RundeckObject # on retourne les dicts tels quels
|
|
224
|
+
|
|
225
|
+
def list(self) -> Any:
|
|
226
|
+
"""Liste les sources de ressources du projet."""
|
|
227
|
+
path = self._build_path()
|
|
228
|
+
return self.rd.http_get(path)
|
|
229
|
+
|
|
230
|
+
def get(self, index: int) -> Any:
|
|
231
|
+
"""Récupère une source spécifique par son index."""
|
|
232
|
+
path = self._build_path(f"{index}")
|
|
233
|
+
return self.rd.http_get(path)
|
|
234
|
+
|
|
235
|
+
def list_resources(self, index: int, accept: str | None = None) -> Any:
|
|
236
|
+
"""Liste les ressources d'une source donnée."""
|
|
237
|
+
path = self._build_path(f"{index}/resources")
|
|
238
|
+
headers = {"Accept": accept} if accept else None
|
|
239
|
+
return self.rd.http_get(path, headers=headers)
|
|
240
|
+
|
|
241
|
+
def update_resources(
|
|
242
|
+
self,
|
|
243
|
+
index: int,
|
|
244
|
+
content: Any,
|
|
245
|
+
content_type: str = "application/json",
|
|
246
|
+
accept: str | None = None,
|
|
247
|
+
) -> Any:
|
|
248
|
+
"""Met à jour les ressources d'une source via POST."""
|
|
249
|
+
path = self._build_path(f"{index}/resources")
|
|
250
|
+
headers: dict[str, str] = {"Content-Type": content_type}
|
|
251
|
+
if accept:
|
|
252
|
+
headers["Accept"] = accept
|
|
253
|
+
return self.rd.http_post(path, data=content, headers=headers)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class ProjectReadmeManager(RundeckObjectManager):
|
|
257
|
+
"""Gestion des fichiers readme.md / motd.md d'un projet."""
|
|
258
|
+
|
|
259
|
+
_path = "/project/{parent}"
|
|
260
|
+
|
|
261
|
+
def _get_file(self, filename: str, accept: str | None = "text/plain") -> Any:
|
|
262
|
+
headers = {"Accept": accept} if accept else None
|
|
263
|
+
path = self._build_path(filename)
|
|
264
|
+
return self.rd.http_get(path, headers=headers)
|
|
265
|
+
|
|
266
|
+
def _put_file(
|
|
267
|
+
self, filename: str, content: Any, content_type: str = "text/plain"
|
|
268
|
+
) -> Any:
|
|
269
|
+
path = self._build_path(filename)
|
|
270
|
+
headers = {"Content-Type": content_type}
|
|
271
|
+
if content_type == "application/json" and isinstance(content, str):
|
|
272
|
+
payload = {"contents": content}
|
|
273
|
+
return self.rd.http_put(path, json=payload, headers=headers)
|
|
274
|
+
return self.rd.http_put(path, data=content, headers=headers)
|
|
275
|
+
|
|
276
|
+
def _delete_file(self, filename: str) -> None:
|
|
277
|
+
path = self._build_path(filename)
|
|
278
|
+
self.rd.http_delete(path)
|
|
279
|
+
|
|
280
|
+
def get_readme(self, accept: str | None = "text/plain") -> Any:
|
|
281
|
+
"""Récupère le readme du projet (texte ou JSON selon l'Accept)."""
|
|
282
|
+
return self._get_file("readme.md", accept=accept)
|
|
283
|
+
|
|
284
|
+
def update_readme(self, content: Any, content_type: str = "text/plain") -> Any:
|
|
285
|
+
"""Crée ou met à jour le readme du projet."""
|
|
286
|
+
return self._put_file("readme.md", content=content, content_type=content_type)
|
|
287
|
+
|
|
288
|
+
def delete_readme(self) -> None:
|
|
289
|
+
"""Supprime le readme du projet."""
|
|
290
|
+
self._delete_file("readme.md")
|
|
291
|
+
|
|
292
|
+
def get_motd(self, accept: str | None = "text/plain") -> Any:
|
|
293
|
+
"""Récupère le motd (Message of the Day) du projet."""
|
|
294
|
+
return self._get_file("motd.md", accept=accept)
|
|
295
|
+
|
|
296
|
+
def update_motd(self, content: Any, content_type: str = "text/plain") -> Any:
|
|
297
|
+
"""Crée ou met à jour le motd du projet."""
|
|
298
|
+
return self._put_file("motd.md", content=content, content_type=content_type)
|
|
299
|
+
|
|
300
|
+
def delete_motd(self) -> None:
|
|
301
|
+
"""Supprime le motd du projet."""
|
|
302
|
+
self._delete_file("motd.md")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class ProjectArchiveManager(RundeckObjectManager):
|
|
306
|
+
"""Manager pour l'export/import d'un projet complet."""
|
|
307
|
+
|
|
308
|
+
_path = "/project/{parent}"
|
|
309
|
+
|
|
310
|
+
def export(
|
|
311
|
+
self,
|
|
312
|
+
execution_ids: list[str] | str | None = None,
|
|
313
|
+
export_all: bool | None = None,
|
|
314
|
+
export_jobs: bool | None = None,
|
|
315
|
+
export_executions: bool | None = None,
|
|
316
|
+
export_configs: bool | None = None,
|
|
317
|
+
export_readmes: bool | None = None,
|
|
318
|
+
export_acls: bool | None = None,
|
|
319
|
+
export_scm: bool | None = None,
|
|
320
|
+
export_webhooks: bool | None = None,
|
|
321
|
+
whkIncludeAuthTokens: bool | None = None,
|
|
322
|
+
**components: Any,
|
|
323
|
+
) -> Any:
|
|
324
|
+
"""
|
|
325
|
+
Exporte une archive ZIP du projet (synchrone).
|
|
326
|
+
|
|
327
|
+
Retourne la réponse brute (zip) pour laisser l'appelant gérer le flux.
|
|
328
|
+
"""
|
|
329
|
+
params: dict[str, Any] = {}
|
|
330
|
+
if execution_ids:
|
|
331
|
+
if isinstance(execution_ids, list):
|
|
332
|
+
params["executionIds"] = ",".join(execution_ids)
|
|
333
|
+
else:
|
|
334
|
+
params["executionIds"] = execution_ids
|
|
335
|
+
if export_all is not None:
|
|
336
|
+
params["exportAll"] = export_all
|
|
337
|
+
if export_jobs is not None:
|
|
338
|
+
params["exportJobs"] = export_jobs
|
|
339
|
+
if export_executions is not None:
|
|
340
|
+
params["exportExecutions"] = export_executions
|
|
341
|
+
if export_configs is not None:
|
|
342
|
+
params["exportConfigs"] = export_configs
|
|
343
|
+
if export_readmes is not None:
|
|
344
|
+
params["exportReadmes"] = export_readmes
|
|
345
|
+
if export_acls is not None:
|
|
346
|
+
params["exportAcls"] = export_acls
|
|
347
|
+
if export_scm is not None:
|
|
348
|
+
params["exportScm"] = export_scm
|
|
349
|
+
if export_webhooks is not None:
|
|
350
|
+
params["exportWebhooks"] = export_webhooks
|
|
351
|
+
if whkIncludeAuthTokens is not None:
|
|
352
|
+
params["whkIncludeAuthTokens"] = whkIncludeAuthTokens
|
|
353
|
+
if components:
|
|
354
|
+
params.update(components)
|
|
355
|
+
path = self._build_path("export")
|
|
356
|
+
return self.rd.http_get(path, params=params or None, raw=True)
|
|
357
|
+
|
|
358
|
+
def export_async(self, **params: Any) -> Any:
|
|
359
|
+
"""Lance une export async, retourne le token."""
|
|
360
|
+
path = self._build_path("export/async")
|
|
361
|
+
return self.rd.http_get(path, params=params or None)
|
|
362
|
+
|
|
363
|
+
def export_status(self, token: str) -> Any:
|
|
364
|
+
"""Vérifie le statut d'un export async."""
|
|
365
|
+
path = self._build_path(f"export/status/{token}")
|
|
366
|
+
return self.rd.http_get(path)
|
|
367
|
+
|
|
368
|
+
def export_download(self, token: str) -> Any:
|
|
369
|
+
"""Télécharge l'archive d'un export async prêt."""
|
|
370
|
+
path = self._build_path(f"export/download/{token}")
|
|
371
|
+
return self.rd.http_get(path, raw=True)
|
|
372
|
+
|
|
373
|
+
def import_archive(
|
|
374
|
+
self,
|
|
375
|
+
content: Any,
|
|
376
|
+
async_import: bool | None = None,
|
|
377
|
+
jobUuidOption: str | None = None,
|
|
378
|
+
importExecutions: bool | None = None,
|
|
379
|
+
importConfig: bool | None = None,
|
|
380
|
+
importACL: bool | None = None,
|
|
381
|
+
importScm: bool | None = None,
|
|
382
|
+
importWebhooks: bool | None = None,
|
|
383
|
+
whkRegenAuthTokens: bool | None = None,
|
|
384
|
+
importNodesSources: bool | None = None,
|
|
385
|
+
**component_options: Any,
|
|
386
|
+
) -> Any:
|
|
387
|
+
"""
|
|
388
|
+
Importe une archive ZIP dans le projet.
|
|
389
|
+
"""
|
|
390
|
+
params: dict[str, Any] = {}
|
|
391
|
+
if async_import is not None:
|
|
392
|
+
params["asyncImport"] = async_import
|
|
393
|
+
if jobUuidOption:
|
|
394
|
+
params["jobUuidOption"] = jobUuidOption
|
|
395
|
+
if importExecutions is not None:
|
|
396
|
+
params["importExecutions"] = importExecutions
|
|
397
|
+
if importConfig is not None:
|
|
398
|
+
params["importConfig"] = importConfig
|
|
399
|
+
if importACL is not None:
|
|
400
|
+
params["importACL"] = importACL
|
|
401
|
+
if importScm is not None:
|
|
402
|
+
params["importScm"] = importScm
|
|
403
|
+
if importWebhooks is not None:
|
|
404
|
+
params["importWebhooks"] = importWebhooks
|
|
405
|
+
if whkRegenAuthTokens is not None:
|
|
406
|
+
params["whkRegenAuthTokens"] = whkRegenAuthTokens
|
|
407
|
+
if importNodesSources is not None:
|
|
408
|
+
params["importNodesSources"] = importNodesSources
|
|
409
|
+
if component_options:
|
|
410
|
+
params.update(component_options)
|
|
411
|
+
|
|
412
|
+
path = self._build_path("import")
|
|
413
|
+
headers = {"Content-Type": "application/zip"}
|
|
414
|
+
return self.rd.http_put(
|
|
415
|
+
path, params=params or None, data=content, headers=headers
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
def import_status(self) -> Any:
|
|
419
|
+
"""Statut d'un import asynchrone."""
|
|
420
|
+
path = self._build_path("import/status")
|
|
421
|
+
return self.rd.http_get(path)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gestion des endpoints /scheduler
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rundeck.base import RundeckObjectManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SchedulerManager(RundeckObjectManager):
|
|
11
|
+
"""Manager pour les opérations du scheduler (/scheduler/*)."""
|
|
12
|
+
|
|
13
|
+
_path = "/scheduler"
|
|
14
|
+
|
|
15
|
+
def takeover(
|
|
16
|
+
self,
|
|
17
|
+
server_uuid: str | None = None,
|
|
18
|
+
all_servers: bool = False,
|
|
19
|
+
project: str | None = None,
|
|
20
|
+
job_id: str | None = None,
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
"""
|
|
23
|
+
Reprend le planning des jobs en mode cluster.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
server_uuid: UUID du serveur cible.
|
|
27
|
+
all_servers: True pour reprendre tous les serveurs.
|
|
28
|
+
project: Projet concerné (optionnel).
|
|
29
|
+
job_id: Job spécifique (optionnel).
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Résultat de la reprise.
|
|
33
|
+
"""
|
|
34
|
+
path = self._build_path("takeover")
|
|
35
|
+
|
|
36
|
+
data: dict[str, Any] = {}
|
|
37
|
+
if all_servers:
|
|
38
|
+
data["server"] = {"all": True}
|
|
39
|
+
elif server_uuid:
|
|
40
|
+
data["server"] = {"uuid": server_uuid}
|
|
41
|
+
|
|
42
|
+
if project:
|
|
43
|
+
data["project"] = project
|
|
44
|
+
if job_id:
|
|
45
|
+
data["job"] = {"id": job_id}
|
|
46
|
+
|
|
47
|
+
return self.rd.http_put(path, json=data)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = ["SchedulerManager"]
|