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/config.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Gestion de la configuration Rundeck (arguments, variables d'env, fichiers).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import configparser
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from rundeck.const import DEFAULT_API_VERSION, DEFAULT_TIMEOUT, USER_AGENT
|
|
13
|
+
|
|
14
|
+
DEFAULT_CONFIG_FILES: list[str] = [
|
|
15
|
+
"/etc/rundeck.cfg",
|
|
16
|
+
str(Path.home() / ".rundeck.cfg"),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RundeckConfigError(Exception):
|
|
21
|
+
"""Erreur de configuration Rundeck."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _resolve_existing_files(config_files: list[str]) -> list[str]:
|
|
25
|
+
existing: list[str] = []
|
|
26
|
+
for file in config_files:
|
|
27
|
+
path = Path(file).expanduser()
|
|
28
|
+
if path.exists():
|
|
29
|
+
existing.append(str(path.resolve()))
|
|
30
|
+
return existing
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _coerce_timeout(value: Any) -> float:
|
|
34
|
+
try:
|
|
35
|
+
return float(value)
|
|
36
|
+
except (TypeError, ValueError):
|
|
37
|
+
return DEFAULT_TIMEOUT
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _coerce_ssl_verify(value: Any) -> bool | str:
|
|
41
|
+
if isinstance(value, bool):
|
|
42
|
+
return value
|
|
43
|
+
if value is None:
|
|
44
|
+
return True
|
|
45
|
+
text = str(value).strip().lower()
|
|
46
|
+
if text in {"true", "1", "yes", "y"}:
|
|
47
|
+
return True
|
|
48
|
+
if text in {"false", "0", "no", "n"}:
|
|
49
|
+
return False
|
|
50
|
+
# Assume path to CA bundle
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RundeckConfig:
|
|
55
|
+
"""Charge la configuration depuis args/env/fichiers avec priorité décroissante."""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
url: str | None = None,
|
|
60
|
+
token: str | None = None,
|
|
61
|
+
username: str | None = None,
|
|
62
|
+
password: str | None = None,
|
|
63
|
+
api_version: int | str | None = None,
|
|
64
|
+
timeout: float | None = None,
|
|
65
|
+
ssl_verify: bool | str | None = None,
|
|
66
|
+
config_files: list[str] | None = None,
|
|
67
|
+
config_section: str = "rundeck",
|
|
68
|
+
) -> None:
|
|
69
|
+
env_url = os.getenv("RUNDECK_URL")
|
|
70
|
+
env_token = os.getenv("RUNDECK_TOKEN")
|
|
71
|
+
env_username = os.getenv("RUNDECK_USERNAME")
|
|
72
|
+
env_password = os.getenv("RUNDECK_PASSWORD")
|
|
73
|
+
env_api_version = os.getenv("RUNDECK_API_VERSION")
|
|
74
|
+
env_timeout = os.getenv("RUNDECK_TIMEOUT")
|
|
75
|
+
env_ssl_verify = os.getenv("RUNDECK_SSL_VERIFY")
|
|
76
|
+
env_user_agent = os.getenv("RUNDECK_USER_AGENT")
|
|
77
|
+
|
|
78
|
+
files = self._resolve_files(config_files)
|
|
79
|
+
file_conf = self._parse_files(files, config_section) if files else {}
|
|
80
|
+
|
|
81
|
+
self.url: str | None = url or env_url or file_conf.get("url")
|
|
82
|
+
self.token: str | None = token or env_token or file_conf.get("token")
|
|
83
|
+
self.username: str | None = (
|
|
84
|
+
username or env_username or file_conf.get("username")
|
|
85
|
+
)
|
|
86
|
+
self.password: str | None = (
|
|
87
|
+
password or env_password or file_conf.get("password")
|
|
88
|
+
)
|
|
89
|
+
self.api_version: str = str(
|
|
90
|
+
api_version
|
|
91
|
+
or env_api_version
|
|
92
|
+
or file_conf.get("api_version")
|
|
93
|
+
or DEFAULT_API_VERSION
|
|
94
|
+
)
|
|
95
|
+
self.timeout: float = (
|
|
96
|
+
float(timeout)
|
|
97
|
+
if timeout is not None
|
|
98
|
+
else _coerce_timeout(env_timeout or file_conf.get("timeout"))
|
|
99
|
+
)
|
|
100
|
+
self.ssl_verify: bool | str = (
|
|
101
|
+
ssl_verify
|
|
102
|
+
if ssl_verify is not None
|
|
103
|
+
else _coerce_ssl_verify(env_ssl_verify or file_conf.get("ssl_verify"))
|
|
104
|
+
)
|
|
105
|
+
self.user_agent: str = (
|
|
106
|
+
env_user_agent or file_conf.get("user_agent") or USER_AGENT
|
|
107
|
+
)
|
|
108
|
+
self.config_files = files
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _resolve_files(config_files: list[str] | None) -> list[str]:
|
|
112
|
+
if config_files:
|
|
113
|
+
resolved = _resolve_existing_files(config_files)
|
|
114
|
+
if not resolved:
|
|
115
|
+
raise RundeckConfigError("Aucun fichier de configuration trouvé.")
|
|
116
|
+
return resolved
|
|
117
|
+
|
|
118
|
+
env_cfg = os.getenv("RUNDECK_CFG")
|
|
119
|
+
if env_cfg:
|
|
120
|
+
resolved = _resolve_existing_files([env_cfg])
|
|
121
|
+
if not resolved:
|
|
122
|
+
raise RundeckConfigError(
|
|
123
|
+
f"Fichier de configuration introuvable: {env_cfg}"
|
|
124
|
+
)
|
|
125
|
+
return resolved
|
|
126
|
+
|
|
127
|
+
return _resolve_existing_files(DEFAULT_CONFIG_FILES)
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def _parse_files(
|
|
131
|
+
files: list[str],
|
|
132
|
+
section: str,
|
|
133
|
+
) -> dict[str, Any]:
|
|
134
|
+
parser = configparser.ConfigParser()
|
|
135
|
+
parser.read(files, encoding="utf-8")
|
|
136
|
+
if not parser.has_section(section):
|
|
137
|
+
return {}
|
|
138
|
+
|
|
139
|
+
def _get(option: str) -> Any:
|
|
140
|
+
try:
|
|
141
|
+
return parser.get(section, option)
|
|
142
|
+
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
"url": _get("url"),
|
|
147
|
+
"token": _get("token"),
|
|
148
|
+
"username": _get("username"),
|
|
149
|
+
"password": _get("password"),
|
|
150
|
+
"api_version": _get("api_version"),
|
|
151
|
+
"timeout": _get("timeout"),
|
|
152
|
+
"ssl_verify": _get("ssl_verify"),
|
|
153
|
+
"user_agent": _get("user_agent"),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def from_config(
|
|
158
|
+
cls, config_section: str | None = None, config_files: list[str] | None = None
|
|
159
|
+
) -> "RundeckConfig":
|
|
160
|
+
"""
|
|
161
|
+
Charge uniquement depuis fichiers/env (sans arguments explicites).
|
|
162
|
+
"""
|
|
163
|
+
return cls(
|
|
164
|
+
config_files=config_files,
|
|
165
|
+
config_section=config_section or "rundeck",
|
|
166
|
+
)
|
rundeck/const.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constantes partagées pour le client Rundeck.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Package metadata
|
|
6
|
+
__version__ = "0.1.0"
|
|
7
|
+
|
|
8
|
+
# Auth
|
|
9
|
+
RUNDECK_AUTH_HEADER = "X-Rundeck-Auth-Token"
|
|
10
|
+
|
|
11
|
+
# HTTP methods
|
|
12
|
+
HTTP_GET = "GET"
|
|
13
|
+
HTTP_POST = "POST"
|
|
14
|
+
HTTP_PUT = "PUT"
|
|
15
|
+
HTTP_DELETE = "DELETE"
|
|
16
|
+
|
|
17
|
+
# Content / formats
|
|
18
|
+
FORMAT_JSON = "json"
|
|
19
|
+
|
|
20
|
+
# API versions (cf. docs Rundeck: current 56, minimum 14, dépréciation 17)
|
|
21
|
+
API_VERSION_CURRENT = "56"
|
|
22
|
+
API_VERSION_MIN = "14"
|
|
23
|
+
API_VERSION_DEPRECATION = "17"
|
|
24
|
+
|
|
25
|
+
# Defaults
|
|
26
|
+
DEFAULT_API_VERSION = API_VERSION_CURRENT
|
|
27
|
+
DEFAULT_TIMEOUT = 30
|
|
28
|
+
USER_AGENT = "python-rundeck"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"__version__",
|
|
33
|
+
"API_VERSION_CURRENT",
|
|
34
|
+
"API_VERSION_DEPRECATION",
|
|
35
|
+
"API_VERSION_MIN",
|
|
36
|
+
"DEFAULT_API_VERSION",
|
|
37
|
+
"DEFAULT_TIMEOUT",
|
|
38
|
+
"FORMAT_JSON",
|
|
39
|
+
"HTTP_DELETE",
|
|
40
|
+
"HTTP_GET",
|
|
41
|
+
"HTTP_POST",
|
|
42
|
+
"HTTP_PUT",
|
|
43
|
+
"RUNDECK_AUTH_HEADER",
|
|
44
|
+
"USER_AGENT",
|
|
45
|
+
]
|
rundeck/exceptions.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Exceptions et helpers pour l'API Rundeck.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RundeckError(Exception):
|
|
13
|
+
"""Exception de base pour toutes les erreurs Rundeck."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
message: str = "",
|
|
18
|
+
error_code: str | None = None,
|
|
19
|
+
response: requests.Response | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
self.message = message
|
|
23
|
+
self.error_code = error_code
|
|
24
|
+
self.response = response
|
|
25
|
+
self.response_code = response.status_code if response is not None else None
|
|
26
|
+
|
|
27
|
+
# Stocke le corps de la réponse (JSON ou texte brut)
|
|
28
|
+
if response is None or not response.text:
|
|
29
|
+
self.response_body: Any | None = None
|
|
30
|
+
else:
|
|
31
|
+
try:
|
|
32
|
+
self.response_body = response.json()
|
|
33
|
+
except ValueError:
|
|
34
|
+
self.response_body = response.text
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
code = f"{self.response_code}: " if self.response_code is not None else ""
|
|
38
|
+
error = f" ({self.error_code})" if self.error_code else ""
|
|
39
|
+
return f"{code}{self.message}{error}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RundeckHTTPError(RundeckError):
|
|
43
|
+
"""Erreur HTTP générique renvoyée par Rundeck."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RundeckOperationError(RundeckHTTPError):
|
|
47
|
+
"""Erreur lors d'une opération métier."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RundeckAuthenticationError(RundeckOperationError):
|
|
51
|
+
"""Authentification ou autorisation refusée."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RundeckNotFoundError(RundeckOperationError):
|
|
55
|
+
"""Ressource introuvable."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RundeckValidationError(RundeckOperationError):
|
|
59
|
+
"""Entrée invalide (erreur 400)."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RundeckApiVersionUnsupportedError(RundeckValidationError):
|
|
63
|
+
"""Version d'API non supportée pour la ressource demandée."""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class RundeckConflictError(RundeckOperationError):
|
|
67
|
+
"""Conflit (erreur 409)."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RundeckQuotaExceededError(RundeckOperationError):
|
|
71
|
+
"""Quota dépassé (erreur 429)."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class RundeckServerError(RundeckHTTPError):
|
|
75
|
+
"""Erreur serveur (5xx)."""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class RundeckConnectionError(RundeckError):
|
|
79
|
+
"""Erreur réseau ou connexion refusée."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RundeckTimeoutError(RundeckError):
|
|
83
|
+
"""Timeout réseau."""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _extract_error(response: requests.Response) -> tuple[str, str | None, Any | None]:
|
|
87
|
+
"""
|
|
88
|
+
Extrait le message et le code d'erreur depuis la réponse HTTP.
|
|
89
|
+
"""
|
|
90
|
+
message = f"HTTP {response.status_code}"
|
|
91
|
+
error_code: str | None = None
|
|
92
|
+
body: Any | None = None
|
|
93
|
+
|
|
94
|
+
if response.text:
|
|
95
|
+
try:
|
|
96
|
+
body = response.json()
|
|
97
|
+
except ValueError:
|
|
98
|
+
body = response.text
|
|
99
|
+
|
|
100
|
+
if isinstance(body, dict):
|
|
101
|
+
message = (
|
|
102
|
+
body.get("message")
|
|
103
|
+
or body.get("error")
|
|
104
|
+
or body.get("errorMessage")
|
|
105
|
+
or message
|
|
106
|
+
)
|
|
107
|
+
error_code = body.get("errorCode") or body.get("error_code")
|
|
108
|
+
elif isinstance(body, str) and body.strip():
|
|
109
|
+
message = body.strip()
|
|
110
|
+
|
|
111
|
+
return message, error_code, body
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def raise_for_status(response: requests.Response) -> None:
|
|
115
|
+
"""
|
|
116
|
+
Lève une exception spécialisée en fonction du code HTTP.
|
|
117
|
+
"""
|
|
118
|
+
message, error_code, body = _extract_error(response)
|
|
119
|
+
status = response.status_code
|
|
120
|
+
code_lower = error_code.lower() if isinstance(error_code, str) else None
|
|
121
|
+
|
|
122
|
+
# Certains retours peuvent être HTTP 200 mais contenir un errorCode explicite
|
|
123
|
+
if (
|
|
124
|
+
response.ok
|
|
125
|
+
and not code_lower
|
|
126
|
+
and not (isinstance(body, dict) and body.get("error") is True)
|
|
127
|
+
):
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if code_lower == "unauthorized":
|
|
131
|
+
raise RundeckAuthenticationError(message, error_code, response)
|
|
132
|
+
if code_lower == "api-version-unsupported":
|
|
133
|
+
raise RundeckApiVersionUnsupportedError(message, error_code, response)
|
|
134
|
+
if status == 400:
|
|
135
|
+
raise RundeckValidationError(message, error_code, response)
|
|
136
|
+
if status in (401, 403):
|
|
137
|
+
raise RundeckAuthenticationError(message, error_code, response)
|
|
138
|
+
if status == 404:
|
|
139
|
+
raise RundeckNotFoundError(message, error_code, response)
|
|
140
|
+
if status == 409:
|
|
141
|
+
raise RundeckConflictError(message, error_code, response)
|
|
142
|
+
if status == 429:
|
|
143
|
+
raise RundeckQuotaExceededError(message, error_code, response)
|
|
144
|
+
if status >= 500:
|
|
145
|
+
raise RundeckServerError(message, error_code, response)
|
|
146
|
+
|
|
147
|
+
raise RundeckHTTPError(message, error_code, response)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
__all__ = [
|
|
151
|
+
"RundeckApiVersionUnsupportedError",
|
|
152
|
+
"RundeckAuthenticationError",
|
|
153
|
+
"RundeckConflictError",
|
|
154
|
+
"RundeckConnectionError",
|
|
155
|
+
"RundeckError",
|
|
156
|
+
"RundeckHTTPError",
|
|
157
|
+
"RundeckNotFoundError",
|
|
158
|
+
"RundeckOperationError",
|
|
159
|
+
"RundeckQuotaExceededError",
|
|
160
|
+
"RundeckServerError",
|
|
161
|
+
"RundeckTimeoutError",
|
|
162
|
+
"RundeckValidationError",
|
|
163
|
+
"raise_for_status",
|
|
164
|
+
]
|
rundeck/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker file for PEP 561 type hints.
|
rundeck/v1/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package v1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package v1.objects
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exécution AdHoc (commandes, scripts, URLs) pour un projet.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rundeck.base import RundeckObjectManager
|
|
8
|
+
from rundeck.v1.objects.executions import Execution
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AdhocManager(RundeckObjectManager[Execution]):
|
|
12
|
+
"""Manager pour les exécutions AdHoc d'un projet."""
|
|
13
|
+
|
|
14
|
+
_obj_cls = Execution
|
|
15
|
+
|
|
16
|
+
def _project_name(self, project: str | None) -> str:
|
|
17
|
+
project_name = project or (self.parent.id if self.parent else None)
|
|
18
|
+
if not project_name:
|
|
19
|
+
raise ValueError("Le nom du projet est requis pour les commandes AdHoc")
|
|
20
|
+
return project_name
|
|
21
|
+
|
|
22
|
+
def _wrap_execution(self, result: Any, refresh: bool = True) -> Execution:
|
|
23
|
+
data = (
|
|
24
|
+
result.get("execution")
|
|
25
|
+
if isinstance(result, dict) and "execution" in result
|
|
26
|
+
else result
|
|
27
|
+
)
|
|
28
|
+
if isinstance(data, dict):
|
|
29
|
+
exec_id = data.get("id")
|
|
30
|
+
# Les réponses AdHoc ne sont pas complètes,
|
|
31
|
+
# Le rafraîchissement permet d'obtenir toutes les données
|
|
32
|
+
if refresh and exec_id:
|
|
33
|
+
try:
|
|
34
|
+
return self.rd.executions.get(exec_id)
|
|
35
|
+
except Exception:
|
|
36
|
+
return Execution(self.rd.executions, data)
|
|
37
|
+
return Execution(self.rd.executions, data)
|
|
38
|
+
|
|
39
|
+
def run_command(
|
|
40
|
+
self,
|
|
41
|
+
exec: str,
|
|
42
|
+
project: str | None = None,
|
|
43
|
+
refresh: bool = True,
|
|
44
|
+
**options: Any,
|
|
45
|
+
) -> Execution:
|
|
46
|
+
"""Exécute une commande shell AdHoc."""
|
|
47
|
+
project_name = self._project_name(project)
|
|
48
|
+
payload: dict[str, Any] = {"exec": exec, "project": project_name}
|
|
49
|
+
payload.update(options)
|
|
50
|
+
path = f"/project/{project_name}/run/command"
|
|
51
|
+
result = self.rd.http_post(path, json=payload)
|
|
52
|
+
return self._wrap_execution(result, refresh=refresh)
|
|
53
|
+
|
|
54
|
+
def run_script(
|
|
55
|
+
self,
|
|
56
|
+
script: str | None = None,
|
|
57
|
+
project: str | None = None,
|
|
58
|
+
refresh: bool = True,
|
|
59
|
+
script_file: tuple[str, Any, str | None] | None = None,
|
|
60
|
+
**options: Any,
|
|
61
|
+
) -> Execution:
|
|
62
|
+
"""Exécute un script AdHoc (contenu inline ou multipart)."""
|
|
63
|
+
project_name = self._project_name(project)
|
|
64
|
+
path = f"/project/{project_name}/run/script"
|
|
65
|
+
if script_file:
|
|
66
|
+
filename, content, content_type = script_file
|
|
67
|
+
payload: dict[str, Any] = {"project": project_name}
|
|
68
|
+
payload.update(options)
|
|
69
|
+
files = {"scriptFile": (filename, content, content_type or "text/plain")}
|
|
70
|
+
result = self.rd.http_post(path, data=payload, files=files)
|
|
71
|
+
else:
|
|
72
|
+
payload: dict[str, Any] = {"script": script or "", "project": project_name}
|
|
73
|
+
payload.update(options)
|
|
74
|
+
result = self.rd.http_post(path, json=payload)
|
|
75
|
+
return self._wrap_execution(result, refresh=refresh)
|
|
76
|
+
|
|
77
|
+
def run_url(
|
|
78
|
+
self,
|
|
79
|
+
script_url: str,
|
|
80
|
+
project: str | None = None,
|
|
81
|
+
refresh: bool = True,
|
|
82
|
+
**options: Any,
|
|
83
|
+
) -> Execution:
|
|
84
|
+
"""Exécute un script AdHoc depuis une URL."""
|
|
85
|
+
project_name = self._project_name(project)
|
|
86
|
+
payload: dict[str, Any] = {"scriptURL": script_url, "project": project_name}
|
|
87
|
+
payload.update(options)
|
|
88
|
+
path = f"/project/{project_name}/run/url"
|
|
89
|
+
result = self.rd.http_post(path, json=payload)
|
|
90
|
+
return self._wrap_execution(result, refresh=refresh)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__all__ = ["AdhocManager"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Gestion de la configuration Rundeck (instance globale).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from rundeck.base import RundeckObjectManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConfigManagementManager(RundeckObjectManager):
|
|
11
|
+
"""
|
|
12
|
+
Gestion des configurations globales (plugin/custom) via /config.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_path = "/config"
|
|
16
|
+
|
|
17
|
+
def list(self) -> list[dict[str, str]]:
|
|
18
|
+
"""Liste toutes les configs et propriétés."""
|
|
19
|
+
path = self._build_path("list")
|
|
20
|
+
return self.rd.http_get(path)
|
|
21
|
+
|
|
22
|
+
def save(self, entries: list[dict[str, str]]) -> dict[str, str | list[str]]:
|
|
23
|
+
"""
|
|
24
|
+
Crée ou met à jour des configs (payload: liste de dicts {key, value, strata?}).
|
|
25
|
+
"""
|
|
26
|
+
path = self._build_path("save")
|
|
27
|
+
return self.rd.http_post(path, json=entries)
|
|
28
|
+
|
|
29
|
+
def delete(self, key: str, strata: str | None = None) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Supprime une config via /config/delete avec payload JSON.
|
|
32
|
+
"""
|
|
33
|
+
payload: dict[str, str] = {"key": key}
|
|
34
|
+
if strata:
|
|
35
|
+
payload["strata"] = strata
|
|
36
|
+
path = self._build_path("delete")
|
|
37
|
+
self.rd.http_delete(path, json=payload)
|
|
38
|
+
|
|
39
|
+
def refresh(self) -> dict[str, str]:
|
|
40
|
+
"""
|
|
41
|
+
Recharge les configurations depuis les fichiers de propriétés.
|
|
42
|
+
"""
|
|
43
|
+
path = self._build_path("refresh")
|
|
44
|
+
return self.rd.http_post(path)
|
|
45
|
+
|
|
46
|
+
def restart(self) -> dict[str, str]:
|
|
47
|
+
"""
|
|
48
|
+
Redémarre le serveur Rundeck.
|
|
49
|
+
"""
|
|
50
|
+
path = self._build_path("restart")
|
|
51
|
+
return self.rd.http_post(path)
|