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
rundeck/base.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Bases objet/manager alignées sur le pattern python-gitlab,
|
|
5
|
+
adossées au client `_request`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pprint as _pprint
|
|
9
|
+
from typing import Any, Generic, Iterable, Iterator, TypeVar
|
|
10
|
+
|
|
11
|
+
from rundeck.const import HTTP_DELETE, HTTP_GET, HTTP_POST, HTTP_PUT
|
|
12
|
+
|
|
13
|
+
ResourceT = TypeVar("ResourceT", bound="RundeckObject")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RundeckObject(Generic[ResourceT]):
|
|
17
|
+
"""Objet de base représentant une ressource Rundeck."""
|
|
18
|
+
|
|
19
|
+
_id_attr: str = "id"
|
|
20
|
+
_repr_attr: str | None = None
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
manager: "RundeckObjectManager[ResourceT]",
|
|
25
|
+
attrs: dict[str, Any],
|
|
26
|
+
) -> None:
|
|
27
|
+
self.manager = manager
|
|
28
|
+
self.rd = manager.rd
|
|
29
|
+
self._attrs = attrs or {}
|
|
30
|
+
|
|
31
|
+
def __getattr__(self, name: str) -> Any:
|
|
32
|
+
try:
|
|
33
|
+
return self._attrs[name]
|
|
34
|
+
except KeyError:
|
|
35
|
+
raise AttributeError(name) from None
|
|
36
|
+
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
value = None
|
|
39
|
+
if self._repr_attr and self._repr_attr in self._attrs:
|
|
40
|
+
value = self._attrs[self._repr_attr]
|
|
41
|
+
elif self._id_attr in self._attrs:
|
|
42
|
+
value = self._attrs[self._id_attr]
|
|
43
|
+
suffix = f" {value}" if value is not None else ""
|
|
44
|
+
return f"<{self.__class__.__name__}{suffix}>"
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def id(self) -> Any:
|
|
48
|
+
return self._attrs.get(self._id_attr)
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> dict[str, Any]:
|
|
51
|
+
return dict(self._attrs)
|
|
52
|
+
|
|
53
|
+
def refresh(self: ResourceT) -> ResourceT:
|
|
54
|
+
"""Recharge la ressource en interrogeant son manager."""
|
|
55
|
+
ident = self._attrs.get(self._id_attr)
|
|
56
|
+
if ident is None:
|
|
57
|
+
raise AttributeError(f"{self.__class__.__name__} has no identifier")
|
|
58
|
+
fresh = self.manager.get(ident)
|
|
59
|
+
self._attrs = fresh._attrs
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def pformat(self) -> str:
|
|
63
|
+
"""Retourne une représentation jolie du dictionnaire d'attributs."""
|
|
64
|
+
return _pprint.pformat(self.to_dict())
|
|
65
|
+
|
|
66
|
+
def pprint(self) -> None:
|
|
67
|
+
"""Affiche la représentation jolie des attributs."""
|
|
68
|
+
_pprint.pprint(self.to_dict())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RundeckObjectManager(Generic[ResourceT]):
|
|
72
|
+
"""Manager de base fournissant helpers CRUD et wrapping."""
|
|
73
|
+
|
|
74
|
+
_path: str = ""
|
|
75
|
+
_obj_cls: type[ResourceT] = RundeckObject # type: ignore[assignment]
|
|
76
|
+
|
|
77
|
+
def __init__(self, rd: Any, parent: RundeckObject | None = None) -> None:
|
|
78
|
+
self.rd = rd
|
|
79
|
+
self.parent = parent
|
|
80
|
+
|
|
81
|
+
def _base_path(self) -> str:
|
|
82
|
+
base = self._path or ""
|
|
83
|
+
if "{parent}" in base:
|
|
84
|
+
if not self.parent:
|
|
85
|
+
raise ValueError("Parent requis pour ce manager")
|
|
86
|
+
base = base.format(parent=self.parent.id)
|
|
87
|
+
elif (
|
|
88
|
+
base
|
|
89
|
+
and not base.startswith("/")
|
|
90
|
+
and self.parent
|
|
91
|
+
and getattr(self.parent, "manager", None)
|
|
92
|
+
):
|
|
93
|
+
# Cascade automatique : si _path est relatif, préfixer avec le chemin
|
|
94
|
+
# du manager parent.
|
|
95
|
+
parent_path = self.parent.manager._build_path(
|
|
96
|
+
getattr(self.parent, getattr(self.parent, "_id_attr", "id"), None)
|
|
97
|
+
)
|
|
98
|
+
base = (
|
|
99
|
+
f"{parent_path.rstrip('/')}/{base.lstrip('/')}" if base else parent_path
|
|
100
|
+
)
|
|
101
|
+
return base
|
|
102
|
+
|
|
103
|
+
def _build_path(self, suffix: str | None = None) -> str:
|
|
104
|
+
base = self._base_path()
|
|
105
|
+
if suffix:
|
|
106
|
+
if base.endswith("/"):
|
|
107
|
+
return f"{base}{suffix.lstrip('/')}"
|
|
108
|
+
if base:
|
|
109
|
+
return f"{base}/{suffix.lstrip('/')}"
|
|
110
|
+
return suffix
|
|
111
|
+
return base
|
|
112
|
+
|
|
113
|
+
def _request(
|
|
114
|
+
self,
|
|
115
|
+
method: str,
|
|
116
|
+
path: str,
|
|
117
|
+
params: dict[str, Any] | None = None,
|
|
118
|
+
json: dict[str, Any] | None = None,
|
|
119
|
+
data: dict[str, Any] | None = None,
|
|
120
|
+
headers: dict[str, str] | None = None,
|
|
121
|
+
**kwargs: Any,
|
|
122
|
+
) -> Any:
|
|
123
|
+
return self.rd._request(
|
|
124
|
+
method=method,
|
|
125
|
+
path=path,
|
|
126
|
+
params=params,
|
|
127
|
+
json=json,
|
|
128
|
+
data=data,
|
|
129
|
+
headers=headers,
|
|
130
|
+
**kwargs,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _wrap(self, data: dict[str, Any]) -> ResourceT:
|
|
134
|
+
return self._obj_cls(self, data)
|
|
135
|
+
|
|
136
|
+
def _wrap_list(self, items: Iterable[dict[str, Any]]) -> list[ResourceT]:
|
|
137
|
+
return [self._wrap(item) for item in items]
|
|
138
|
+
|
|
139
|
+
def iter(
|
|
140
|
+
self,
|
|
141
|
+
path: str | None = None,
|
|
142
|
+
params: dict[str, Any] | None = None,
|
|
143
|
+
page_size: int | None = None,
|
|
144
|
+
**kwargs: Any,
|
|
145
|
+
) -> Iterator[ResourceT]:
|
|
146
|
+
"""
|
|
147
|
+
Générateur paginé sur les ressources (offset/max).
|
|
148
|
+
"""
|
|
149
|
+
target = path or self._build_path()
|
|
150
|
+
query: dict[str, Any] = dict(params or {})
|
|
151
|
+
offset = int(query.get("offset", 0))
|
|
152
|
+
if page_size:
|
|
153
|
+
query["max"] = page_size
|
|
154
|
+
|
|
155
|
+
while True:
|
|
156
|
+
current_params = dict(query)
|
|
157
|
+
current_params["offset"] = offset
|
|
158
|
+
page = self.rd.http_list(target, params=current_params, **kwargs)
|
|
159
|
+
if not page:
|
|
160
|
+
break
|
|
161
|
+
for item in page:
|
|
162
|
+
yield self._wrap(item)
|
|
163
|
+
if page_size and len(page) < page_size:
|
|
164
|
+
break
|
|
165
|
+
offset += len(page)
|
|
166
|
+
|
|
167
|
+
def _list(
|
|
168
|
+
self,
|
|
169
|
+
params: dict[str, Any] | None = None,
|
|
170
|
+
path: str | None = None,
|
|
171
|
+
**kwargs: Any,
|
|
172
|
+
) -> list[ResourceT]:
|
|
173
|
+
target = path or self._build_path()
|
|
174
|
+
result = self.rd.http_list(target, params=params, **kwargs)
|
|
175
|
+
return self._wrap_list(result)
|
|
176
|
+
|
|
177
|
+
def _get(
|
|
178
|
+
self,
|
|
179
|
+
ident: str,
|
|
180
|
+
params: dict[str, Any] | None = None,
|
|
181
|
+
path: str | None = None,
|
|
182
|
+
**kwargs: Any,
|
|
183
|
+
) -> ResourceT:
|
|
184
|
+
target = path or self._build_path(str(ident))
|
|
185
|
+
result = self.rd._request(HTTP_GET, target, params=params, **kwargs)
|
|
186
|
+
return self._wrap(result)
|
|
187
|
+
|
|
188
|
+
def _create(
|
|
189
|
+
self,
|
|
190
|
+
json: dict[str, Any] | None = None,
|
|
191
|
+
data: dict[str, Any] | None = None,
|
|
192
|
+
params: dict[str, Any] | None = None,
|
|
193
|
+
path: str | None = None,
|
|
194
|
+
**kwargs: Any,
|
|
195
|
+
) -> ResourceT:
|
|
196
|
+
target = path or self._build_path()
|
|
197
|
+
result = self.rd._request(
|
|
198
|
+
HTTP_POST, target, json=json, data=data, params=params, **kwargs
|
|
199
|
+
)
|
|
200
|
+
return self._wrap(result)
|
|
201
|
+
|
|
202
|
+
def _update(
|
|
203
|
+
self,
|
|
204
|
+
ident: str,
|
|
205
|
+
json: dict[str, Any] | None = None,
|
|
206
|
+
data: dict[str, Any] | None = None,
|
|
207
|
+
params: dict[str, Any] | None = None,
|
|
208
|
+
path: str | None = None,
|
|
209
|
+
**kwargs: Any,
|
|
210
|
+
) -> ResourceT:
|
|
211
|
+
target = path or self._build_path(str(ident))
|
|
212
|
+
result = self.rd._request(
|
|
213
|
+
HTTP_PUT, target, json=json, data=data, params=params, **kwargs
|
|
214
|
+
)
|
|
215
|
+
return self._wrap(result)
|
|
216
|
+
|
|
217
|
+
def _delete(
|
|
218
|
+
self,
|
|
219
|
+
ident: str,
|
|
220
|
+
params: dict[str, Any] | None = None,
|
|
221
|
+
path: str | None = None,
|
|
222
|
+
**kwargs: Any,
|
|
223
|
+
) -> None:
|
|
224
|
+
target = path or self._build_path(str(ident))
|
|
225
|
+
self.rd._request(HTTP_DELETE, target, params=params, **kwargs)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
__all__ = [
|
|
229
|
+
"RundeckObject",
|
|
230
|
+
"RundeckObjectManager",
|
|
231
|
+
]
|
rundeck/client.py
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Client principal pour l'API Rundeck
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.parse import urljoin
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from rundeck.config import RundeckConfig
|
|
13
|
+
from rundeck.const import (
|
|
14
|
+
HTTP_DELETE,
|
|
15
|
+
HTTP_GET,
|
|
16
|
+
HTTP_POST,
|
|
17
|
+
HTTP_PUT,
|
|
18
|
+
RUNDECK_AUTH_HEADER,
|
|
19
|
+
)
|
|
20
|
+
from rundeck.exceptions import (
|
|
21
|
+
RundeckAuthenticationError,
|
|
22
|
+
RundeckConnectionError,
|
|
23
|
+
RundeckTimeoutError,
|
|
24
|
+
raise_for_status,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Rundeck:
|
|
29
|
+
"""Wrapper pour interagir avec l'API Rundeck."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
url: str | None = None,
|
|
34
|
+
token: str | None = None,
|
|
35
|
+
username: str | None = None,
|
|
36
|
+
password: str | None = None,
|
|
37
|
+
api_version: int | None = None,
|
|
38
|
+
timeout: float | None = None,
|
|
39
|
+
ssl_verify: bool | None = None,
|
|
40
|
+
session: requests.Session | None = None,
|
|
41
|
+
config_files: list[str] | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
self.config = RundeckConfig(
|
|
44
|
+
url=url,
|
|
45
|
+
token=token,
|
|
46
|
+
username=username,
|
|
47
|
+
password=password,
|
|
48
|
+
api_version=api_version,
|
|
49
|
+
timeout=timeout,
|
|
50
|
+
ssl_verify=ssl_verify,
|
|
51
|
+
config_files=config_files,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if not self.config.url:
|
|
55
|
+
raise ValueError("URL Rundeck manquante (config ou argument requis).")
|
|
56
|
+
|
|
57
|
+
self._base_url = self.config.url.rstrip("/")
|
|
58
|
+
self._api_version = str(self.config.api_version)
|
|
59
|
+
self._api_url = f"{self._base_url}/api/{self._api_version}"
|
|
60
|
+
|
|
61
|
+
self.timeout = self.config.timeout
|
|
62
|
+
self.ssl_verify = self.config.ssl_verify
|
|
63
|
+
|
|
64
|
+
self.session = session or requests.Session()
|
|
65
|
+
self.headers = {
|
|
66
|
+
"User-Agent": getattr(self.config, "user_agent", None) or "python-rundeck",
|
|
67
|
+
"Accept": "application/json",
|
|
68
|
+
}
|
|
69
|
+
if self.config.token:
|
|
70
|
+
self.headers[RUNDECK_AUTH_HEADER] = self.config.token
|
|
71
|
+
self.session.headers.update(self.headers)
|
|
72
|
+
self.session.verify = self.ssl_verify
|
|
73
|
+
|
|
74
|
+
if not self.config.token and self.config.username and self.config.password:
|
|
75
|
+
self._authenticate_with_password()
|
|
76
|
+
|
|
77
|
+
self._init_managers()
|
|
78
|
+
|
|
79
|
+
def _init_managers(self) -> None:
|
|
80
|
+
"""Initialise les managers d'objets."""
|
|
81
|
+
# Import paresseux pour éviter les boucles d'import.
|
|
82
|
+
from rundeck.v1.objects.config_management import ConfigManagementManager
|
|
83
|
+
from rundeck.v1.objects.executions import ExecutionManager
|
|
84
|
+
from rundeck.v1.objects.features import FeatureManager
|
|
85
|
+
from rundeck.v1.objects.jobs import JobManager
|
|
86
|
+
from rundeck.v1.objects.key_storage import StorageKeyManager
|
|
87
|
+
from rundeck.v1.objects.metrics import MetricsManager
|
|
88
|
+
from rundeck.v1.objects.plugins import PluginManager
|
|
89
|
+
from rundeck.v1.objects.projects import ProjectManager
|
|
90
|
+
from rundeck.v1.objects.scheduler import SchedulerManager
|
|
91
|
+
from rundeck.v1.objects.system import SystemManager
|
|
92
|
+
from rundeck.v1.objects.tokens import TokenManager
|
|
93
|
+
from rundeck.v1.objects.users import UserManager
|
|
94
|
+
from rundeck.v1.objects.webhooks import WebhookEventManager
|
|
95
|
+
|
|
96
|
+
self.projects = ProjectManager(self)
|
|
97
|
+
self.jobs = JobManager(self)
|
|
98
|
+
self.executions = ExecutionManager(self)
|
|
99
|
+
self.config_management = ConfigManagementManager(self)
|
|
100
|
+
self.metrics = MetricsManager(self)
|
|
101
|
+
self.plugins = PluginManager(self)
|
|
102
|
+
self.webhooks = WebhookEventManager(self)
|
|
103
|
+
self.key_storage = StorageKeyManager(self)
|
|
104
|
+
self.features = FeatureManager(self)
|
|
105
|
+
self.system = SystemManager(self)
|
|
106
|
+
self.scheduler = SchedulerManager(self)
|
|
107
|
+
self.tokens = TokenManager(self)
|
|
108
|
+
self.users = UserManager(self)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def url(self) -> str:
|
|
112
|
+
"""URL fournie par l'utilisateur."""
|
|
113
|
+
return self._base_url
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def api_url(self) -> str:
|
|
117
|
+
"""URL de base de l'API."""
|
|
118
|
+
return self._api_url
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def api_version(self) -> str:
|
|
122
|
+
"""Version de l'API utilisée."""
|
|
123
|
+
return self._api_version
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def from_config(
|
|
127
|
+
cls,
|
|
128
|
+
config_section: str | None = None,
|
|
129
|
+
config_files: list[str] | None = None,
|
|
130
|
+
**kwargs: Any,
|
|
131
|
+
) -> "Rundeck":
|
|
132
|
+
"""
|
|
133
|
+
Construit un client Rundeck à partir des fichiers de configuration/env.
|
|
134
|
+
"""
|
|
135
|
+
conf = RundeckConfig(
|
|
136
|
+
config_files=config_files,
|
|
137
|
+
config_section=config_section or "rundeck",
|
|
138
|
+
)
|
|
139
|
+
return cls(
|
|
140
|
+
url=conf.url,
|
|
141
|
+
token=conf.token,
|
|
142
|
+
username=conf.username,
|
|
143
|
+
password=conf.password,
|
|
144
|
+
api_version=conf.api_version,
|
|
145
|
+
timeout=conf.timeout,
|
|
146
|
+
ssl_verify=conf.ssl_verify,
|
|
147
|
+
session=kwargs.get("session"),
|
|
148
|
+
config_files=config_files,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def _authenticate_with_password(self) -> None:
|
|
152
|
+
"""Authentification via j_security_check + cookie de session."""
|
|
153
|
+
login_url = urljoin(f"{self._base_url}/", "j_security_check")
|
|
154
|
+
try:
|
|
155
|
+
response = self.session.post(
|
|
156
|
+
login_url,
|
|
157
|
+
data={
|
|
158
|
+
"j_username": self.config.username,
|
|
159
|
+
"j_password": self.config.password,
|
|
160
|
+
},
|
|
161
|
+
allow_redirects=True,
|
|
162
|
+
timeout=self.timeout,
|
|
163
|
+
verify=self.ssl_verify,
|
|
164
|
+
headers={
|
|
165
|
+
"Accept": "application/json",
|
|
166
|
+
"User-Agent": self.headers.get("User-Agent", "python-rundeck"),
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
except requests.exceptions.Timeout as exc:
|
|
170
|
+
raise RundeckTimeoutError(
|
|
171
|
+
f"Timeout lors de l'authentification: {exc}"
|
|
172
|
+
) from exc
|
|
173
|
+
except requests.exceptions.ConnectionError as exc:
|
|
174
|
+
raise RundeckConnectionError(
|
|
175
|
+
f"Erreur de connexion lors de l'authentification: {exc}"
|
|
176
|
+
) from exc
|
|
177
|
+
|
|
178
|
+
urls = [response.url] + [r.url for r in response.history]
|
|
179
|
+
if any("/user/login" in (u or "") or "/user/error" in (u or "") for u in urls):
|
|
180
|
+
raise RundeckAuthenticationError(
|
|
181
|
+
"Authentification échouée (login/error)", None, response
|
|
182
|
+
)
|
|
183
|
+
if response.status_code >= 400:
|
|
184
|
+
raise RundeckAuthenticationError(
|
|
185
|
+
f"Authentification échouée (HTTP {response.status_code})",
|
|
186
|
+
None,
|
|
187
|
+
response,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
cookies = self.session.cookies.get_dict()
|
|
191
|
+
if "JSESSIONID" not in cookies:
|
|
192
|
+
raise RundeckAuthenticationError(
|
|
193
|
+
"Cookie de session JSESSIONID manquant après authentification",
|
|
194
|
+
None,
|
|
195
|
+
response,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _make_request(
|
|
199
|
+
self,
|
|
200
|
+
method: str,
|
|
201
|
+
url: str,
|
|
202
|
+
params: dict[str, Any] | None = None,
|
|
203
|
+
data: Any = None,
|
|
204
|
+
json: Any = None,
|
|
205
|
+
headers: dict[str, str] | None = None,
|
|
206
|
+
files: dict[str, Any] | None = None,
|
|
207
|
+
**kwargs: Any,
|
|
208
|
+
) -> requests.Response:
|
|
209
|
+
if headers:
|
|
210
|
+
request_headers = self.session.headers.copy()
|
|
211
|
+
request_headers.update(headers)
|
|
212
|
+
else:
|
|
213
|
+
request_headers = None
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
response = self.session.request(
|
|
217
|
+
method=method,
|
|
218
|
+
url=url,
|
|
219
|
+
params=params,
|
|
220
|
+
data=data,
|
|
221
|
+
json=json,
|
|
222
|
+
files=files,
|
|
223
|
+
headers=request_headers,
|
|
224
|
+
timeout=self.timeout,
|
|
225
|
+
verify=self.ssl_verify,
|
|
226
|
+
**kwargs,
|
|
227
|
+
)
|
|
228
|
+
raise_for_status(response)
|
|
229
|
+
return response
|
|
230
|
+
except requests.exceptions.Timeout as exc:
|
|
231
|
+
raise RundeckTimeoutError(f"Timeout lors de la requête: {exc}") from exc
|
|
232
|
+
except requests.exceptions.ConnectionError as exc:
|
|
233
|
+
raise RundeckConnectionError(f"Erreur de connexion: {exc}") from exc
|
|
234
|
+
|
|
235
|
+
def _request(
|
|
236
|
+
self,
|
|
237
|
+
method: str,
|
|
238
|
+
path: str,
|
|
239
|
+
params: dict[str, Any] | None = None,
|
|
240
|
+
data: Any = None,
|
|
241
|
+
json: Any = None,
|
|
242
|
+
headers: dict[str, str] | None = None,
|
|
243
|
+
files: dict[str, Any] | None = None,
|
|
244
|
+
raw: bool = False,
|
|
245
|
+
**kwargs: Any,
|
|
246
|
+
) -> Any:
|
|
247
|
+
"""
|
|
248
|
+
Effectue une requête sur le chemin donné et retourne le contenu déjà parsé.
|
|
249
|
+
"""
|
|
250
|
+
url = path
|
|
251
|
+
if not path.startswith("http"):
|
|
252
|
+
url = urljoin(f"{self.api_url}/", path.lstrip("/"))
|
|
253
|
+
|
|
254
|
+
response = self._make_request(
|
|
255
|
+
method=method,
|
|
256
|
+
url=url,
|
|
257
|
+
params=params,
|
|
258
|
+
data=data,
|
|
259
|
+
json=json,
|
|
260
|
+
headers=headers,
|
|
261
|
+
files=files,
|
|
262
|
+
**kwargs,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if raw:
|
|
266
|
+
return response
|
|
267
|
+
|
|
268
|
+
if method == HTTP_DELETE:
|
|
269
|
+
return None
|
|
270
|
+
if response.text:
|
|
271
|
+
try:
|
|
272
|
+
return response.json()
|
|
273
|
+
except ValueError:
|
|
274
|
+
return response.text
|
|
275
|
+
return {}
|
|
276
|
+
|
|
277
|
+
def http_get(
|
|
278
|
+
self,
|
|
279
|
+
url: str,
|
|
280
|
+
params: dict[str, Any] | None = None,
|
|
281
|
+
raw: bool = False,
|
|
282
|
+
**kwargs: Any,
|
|
283
|
+
) -> Any:
|
|
284
|
+
return self._request(HTTP_GET, url, params=params, raw=raw, **kwargs)
|
|
285
|
+
|
|
286
|
+
def http_list(
|
|
287
|
+
self,
|
|
288
|
+
url: str,
|
|
289
|
+
params: dict[str, Any] | None = None,
|
|
290
|
+
**kwargs: Any,
|
|
291
|
+
) -> list[Any]:
|
|
292
|
+
result = self.http_get(url, params=params, **kwargs)
|
|
293
|
+
|
|
294
|
+
if isinstance(result, list):
|
|
295
|
+
return result
|
|
296
|
+
if isinstance(result, dict):
|
|
297
|
+
for key in ["items", "data", "results", "executions", "jobs", "projects"]:
|
|
298
|
+
if key in result and isinstance(result[key], list):
|
|
299
|
+
return result[key]
|
|
300
|
+
return [result] if result else []
|
|
301
|
+
return []
|
|
302
|
+
|
|
303
|
+
def http_post(
|
|
304
|
+
self,
|
|
305
|
+
url: str,
|
|
306
|
+
data: dict[str, Any] | None = None,
|
|
307
|
+
json: dict[str, Any] | None = None,
|
|
308
|
+
files: dict[str, Any] | None = None,
|
|
309
|
+
raw: bool = False,
|
|
310
|
+
**kwargs: Any,
|
|
311
|
+
) -> Any:
|
|
312
|
+
return self._request(
|
|
313
|
+
HTTP_POST, url, data=data, json=json, files=files, raw=raw, **kwargs
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def http_put(
|
|
317
|
+
self,
|
|
318
|
+
url: str,
|
|
319
|
+
data: dict[str, Any] | None = None,
|
|
320
|
+
json: dict[str, Any] | None = None,
|
|
321
|
+
files: dict[str, Any] | None = None,
|
|
322
|
+
raw: bool = False,
|
|
323
|
+
**kwargs: Any,
|
|
324
|
+
) -> Any:
|
|
325
|
+
return self._request(
|
|
326
|
+
HTTP_PUT, url, data=data, json=json, files=files, raw=raw, **kwargs
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def http_delete(
|
|
330
|
+
self,
|
|
331
|
+
url: str,
|
|
332
|
+
raw: bool = False,
|
|
333
|
+
**kwargs: Any,
|
|
334
|
+
) -> None:
|
|
335
|
+
self._request(HTTP_DELETE, url, raw=raw, **kwargs)
|
|
336
|
+
|
|
337
|
+
def __enter__(self) -> "Rundeck":
|
|
338
|
+
return self
|
|
339
|
+
|
|
340
|
+
def __exit__(self, *args: Any) -> None:
|
|
341
|
+
self.session.close()
|