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.
@@ -0,0 +1,229 @@
1
+ """
2
+ Gestion des endpoints SCM (import/export) pour les projets et jobs.
3
+ """
4
+
5
+ from typing import Any
6
+
7
+ from rundeck.base import RundeckObjectManager
8
+
9
+
10
+ class _ProjectSCMBaseManager(RundeckObjectManager):
11
+ """Base pour les sous-managers SCM projet (intègre l'intégration import/export)."""
12
+
13
+ def __init__(self, rd: Any, parent: Any, integration: str) -> None:
14
+ super().__init__(rd, parent)
15
+ self.integration = integration
16
+
17
+ def _base_path(self) -> str:
18
+ if not self.parent:
19
+ raise ValueError("Parent projet requis pour SCM")
20
+ return f"/project/{self.parent.id}/scm/{self.integration}"
21
+
22
+
23
+ class ProjectSCMPluginsManager(_ProjectSCMBaseManager):
24
+ """Découverte des plugins SCM pour un projet."""
25
+
26
+ def list(self) -> Any:
27
+ """Liste les plugins SCM disponibles pour l'intégration."""
28
+ path = self._build_path("plugins")
29
+ return self.rd.http_get(path)
30
+
31
+ def input_fields(self, plugin_type: str) -> Any:
32
+ """Champs de saisie pour un plugin donné."""
33
+ path = self._build_path(f"plugin/{plugin_type}/input")
34
+ return self.rd.http_get(path)
35
+
36
+
37
+ class ProjectSCMConfigManager(_ProjectSCMBaseManager):
38
+ """Configuration et cycle de vie d'un plugin SCM pour un projet."""
39
+
40
+ def setup(self, plugin_type: str, config: dict[str, Any]) -> Any:
41
+ """Configure un plugin SCM pour le projet."""
42
+ path = self._build_path(f"plugin/{plugin_type}/setup")
43
+ payload = {"config": config}
44
+ return self.rd.http_post(path, json=payload)
45
+
46
+ def enable(self, plugin_type: str) -> Any:
47
+ """Active le plugin SCM (idempotent)."""
48
+ path = self._build_path(f"plugin/{plugin_type}/enable")
49
+ return self.rd.http_post(path)
50
+
51
+ def disable(self, plugin_type: str) -> Any:
52
+ """Désactive le plugin SCM (idempotent)."""
53
+ path = self._build_path(f"plugin/{plugin_type}/disable")
54
+ return self.rd.http_post(path)
55
+
56
+ def get(self) -> Any:
57
+ """Récupère la configuration SCM du projet pour l'intégration."""
58
+ path = self._build_path("config")
59
+ return self.rd.http_get(path)
60
+
61
+
62
+ class ProjectSCMActionsManager(_ProjectSCMBaseManager):
63
+ """Statut et actions SCM pour un projet."""
64
+
65
+ def status(self) -> Any:
66
+ """Statut SCM du projet pour l'intégration."""
67
+ path = self._build_path("status")
68
+ return self.rd.http_get(path)
69
+
70
+ def input_fields(self, action_id: str) -> Any:
71
+ """Champs attendus pour une action SCM projet."""
72
+ path = self._build_path(f"action/{action_id}/input")
73
+ return self.rd.http_get(path)
74
+
75
+ def perform(
76
+ self,
77
+ action_id: str,
78
+ input_values: dict[str, Any] | None = None,
79
+ jobs: list[str] | None = None,
80
+ items: list[str] | None = None,
81
+ deleted: list[str] | None = None,
82
+ deleted_jobs: list[str] | None = None,
83
+ ) -> Any:
84
+ """Exécute une action SCM projet (ex: commit/import/sync)."""
85
+ payload: dict[str, Any] = {}
86
+ if input_values is not None:
87
+ payload["input"] = input_values
88
+ if jobs:
89
+ payload["jobs"] = jobs
90
+ if items:
91
+ payload["items"] = items
92
+ if deleted:
93
+ payload["deleted"] = deleted
94
+ if deleted_jobs:
95
+ payload["deletedJobs"] = deleted_jobs
96
+
97
+ path = self._build_path(f"action/{action_id}")
98
+ return self.rd.http_post(path, json=payload or None)
99
+
100
+
101
+ class ProjectSCMIntegrationManager(RundeckObjectManager):
102
+ """Regroupe les sous-managers SCM projet pour une intégration (import/export)."""
103
+
104
+ def __init__(self, rd: Any, parent: Any, integration: str) -> None:
105
+ super().__init__(rd, parent)
106
+ self.integration = integration
107
+
108
+ @property
109
+ def plugins(self) -> ProjectSCMPluginsManager:
110
+ return ProjectSCMPluginsManager(
111
+ self.rd, parent=self.parent, integration=self.integration
112
+ )
113
+
114
+ @property
115
+ def config(self) -> ProjectSCMConfigManager:
116
+ return ProjectSCMConfigManager(
117
+ self.rd, parent=self.parent, integration=self.integration
118
+ )
119
+
120
+ @property
121
+ def actions(self) -> ProjectSCMActionsManager:
122
+ return ProjectSCMActionsManager(
123
+ self.rd, parent=self.parent, integration=self.integration
124
+ )
125
+
126
+
127
+ class ProjectSCMManager(RundeckObjectManager):
128
+ """Manager racine SCM pour un projet, expose import/export."""
129
+
130
+ def __init__(self, rd: Any, parent: Any) -> None:
131
+ if parent is None:
132
+ raise ValueError("Parent projet requis pour SCM")
133
+ super().__init__(rd, parent)
134
+
135
+ @property
136
+ def import_(self) -> ProjectSCMIntegrationManager:
137
+ """SCM import pour le projet."""
138
+ return ProjectSCMIntegrationManager(
139
+ self.rd, parent=self.parent, integration="import"
140
+ )
141
+
142
+ @property
143
+ def export(self) -> ProjectSCMIntegrationManager:
144
+ """SCM export pour le projet."""
145
+ return ProjectSCMIntegrationManager(
146
+ self.rd, parent=self.parent, integration="export"
147
+ )
148
+
149
+ def __getattr__(self, name: str) -> Any:
150
+ if name == "import":
151
+ return self.import_
152
+ return super().__getattribute__(name)
153
+
154
+
155
+ class JobSCMManager(RundeckObjectManager):
156
+ """Manager racine SCM pour un job, expose import/export."""
157
+
158
+ def __init__(self, rd: Any, parent: Any) -> None:
159
+ if parent is None:
160
+ raise ValueError("Parent job requis pour SCM")
161
+ super().__init__(rd, parent)
162
+
163
+ @property
164
+ def import_(self) -> "JobSCMIntegrationManager":
165
+ """SCM import pour le job."""
166
+ return JobSCMIntegrationManager(
167
+ self.rd, parent=self.parent, integration="import"
168
+ )
169
+
170
+ @property
171
+ def export(self) -> "JobSCMIntegrationManager":
172
+ """SCM export pour le job."""
173
+ return JobSCMIntegrationManager(
174
+ self.rd, parent=self.parent, integration="export"
175
+ )
176
+
177
+ def __getattr__(self, name: str) -> Any:
178
+ if name == "import":
179
+ return self.import_
180
+ return super().__getattribute__(name)
181
+
182
+
183
+ class JobSCMIntegrationManager(RundeckObjectManager):
184
+ """Opérations SCM pour un job donné et une intégration (import/export)."""
185
+
186
+ def __init__(self, rd: Any, parent: Any, integration: str) -> None:
187
+ super().__init__(rd, parent)
188
+ self.integration = integration
189
+
190
+ def _base_path(self) -> str:
191
+ if not self.parent:
192
+ raise ValueError("Parent job requis pour SCM")
193
+ return f"/job/{self.parent.id}/scm/{self.integration}"
194
+
195
+ def status(self) -> Any:
196
+ """Statut SCM du job pour l'intégration."""
197
+ path = self._build_path("status")
198
+ return self.rd.http_get(path)
199
+
200
+ def diff(self) -> Any:
201
+ """Diff SCM pour le job (si applicable)."""
202
+ path = self._build_path("diff")
203
+ return self.rd.http_get(path)
204
+
205
+ def input_fields(self, action_id: str) -> Any:
206
+ """Champs attendus pour une action SCM job."""
207
+ path = self._build_path(f"action/{action_id}/input")
208
+ return self.rd.http_get(path)
209
+
210
+ def perform(
211
+ self, action_id: str, input_values: dict[str, Any] | None = None
212
+ ) -> Any:
213
+ """Exécute une action SCM job."""
214
+ payload: dict[str, Any] = {}
215
+ if input_values is not None:
216
+ payload["input"] = input_values
217
+ path = self._build_path(f"action/{action_id}")
218
+ return self.rd.http_post(path, json=payload or None)
219
+
220
+
221
+ __all__ = [
222
+ "ProjectSCMManager",
223
+ "ProjectSCMIntegrationManager",
224
+ "ProjectSCMPluginsManager",
225
+ "ProjectSCMConfigManager",
226
+ "ProjectSCMActionsManager",
227
+ "JobSCMManager",
228
+ "JobSCMIntegrationManager",
229
+ ]
@@ -0,0 +1,140 @@
1
+ """
2
+ Gestion du système Rundeck
3
+ """
4
+
5
+ from typing import Any
6
+
7
+ from rundeck.base import RundeckObject, RundeckObjectManager
8
+
9
+
10
+ class SystemExecutionsManager(RundeckObjectManager):
11
+ """Sous-manager pour les endpoints executions du système."""
12
+
13
+ _path = "/system/executions"
14
+
15
+ def enable(self) -> dict[str, Any]:
16
+ """Active le mode d'exécution."""
17
+ path = self._build_path("enable")
18
+ return self.rd.http_post(path)
19
+
20
+ def disable(self) -> dict[str, Any]:
21
+ """Désactive le mode d'exécution (mode passif)."""
22
+ path = self._build_path("disable")
23
+ return self.rd.http_post(path)
24
+
25
+ def status(self) -> dict[str, Any]:
26
+ """Statut actuel du mode d'exécution."""
27
+ path = self._build_path("status")
28
+ return self.rd.http_get(path)
29
+
30
+
31
+ class SystemACL(RundeckObject):
32
+ """Représente une policy ACL système."""
33
+
34
+ _id_attr = "name"
35
+ _repr_attr = "name"
36
+
37
+
38
+ class SystemACLManager(RundeckObjectManager[SystemACL]):
39
+ """Sous-manager pour les endpoints ACL système."""
40
+
41
+ _path = "/system/acl"
42
+ _obj_cls = SystemACL
43
+
44
+ def list(self) -> list[SystemACL]:
45
+ """Liste les politiques ACL système."""
46
+ path = self._build_path("")
47
+ result = self.rd.http_get(path)
48
+ if isinstance(result, dict):
49
+ resources = result.get("resources") or []
50
+ items = []
51
+ for entry in resources:
52
+ name = entry.get("name") or entry.get("path")
53
+ if name:
54
+ data = dict(entry)
55
+ data.setdefault("name", name)
56
+ items.append(data)
57
+ result = items
58
+ return self._wrap_list(result or [])
59
+
60
+ def get(self, policy_name: str) -> SystemACL:
61
+ """Récupère une politique ACL (contenu texte)."""
62
+ path = self._build_path(policy_name)
63
+ response = self.rd.http_get(path, raw=True)
64
+ return self._wrap({"name": policy_name, "content": response.text})
65
+
66
+ def create(self, policy_name: str, content: str) -> SystemACL:
67
+ """Crée une politique ACL système."""
68
+ path = self._build_path(policy_name)
69
+ headers = {"Content-Type": "application/yaml"}
70
+ result = self.rd.http_post(path, data=content, headers=headers) or {}
71
+ if "name" not in result:
72
+ result["name"] = policy_name
73
+ return self._wrap(result)
74
+
75
+ def update(self, policy_name: str, content: str) -> SystemACL:
76
+ """Met à jour une politique ACL système."""
77
+ path = self._build_path(policy_name)
78
+ headers = {"Content-Type": "application/yaml"}
79
+ result = self.rd.http_put(path, data=content, headers=headers) or {}
80
+ if "name" not in result:
81
+ result["name"] = policy_name
82
+ return self._wrap(result)
83
+
84
+ def delete(self, policy_name: str) -> None:
85
+ """Supprime une politique ACL système."""
86
+ path = self._build_path(policy_name)
87
+ self.rd.http_delete(path)
88
+
89
+
90
+ class SystemLogStorageManager(RundeckObjectManager):
91
+ """Sous-manager pour les endpoints logstorage du système."""
92
+
93
+ _path = "/system/logstorage"
94
+
95
+ def info(self) -> dict[str, Any]:
96
+ """Informations sur le stockage des logs."""
97
+ path = self._build_path()
98
+ return self.rd.http_get(path)
99
+
100
+ def incomplete(self, max: int = 20, offset: int = 0) -> dict[str, Any]:
101
+ """Liste des exécutions avec stockage incomplet."""
102
+ params = {"max": max, "offset": offset}
103
+ path = self._build_path("incomplete")
104
+ return self.rd.http_get(path, params=params)
105
+
106
+ def incomplete_resume(self) -> dict[str, Any]:
107
+ """Reprise du traitement des logs incomplets."""
108
+ path = self._build_path("incomplete/resume")
109
+ return self.rd.http_post(path)
110
+
111
+
112
+ class SystemManager(RundeckObjectManager):
113
+ """Manager pour les opérations système"""
114
+
115
+ _path = "/system"
116
+
117
+ @property
118
+ def executions(self) -> SystemExecutionsManager:
119
+ """Accès aux endpoints /system/executions/*."""
120
+ return SystemExecutionsManager(self.rd)
121
+
122
+ @property
123
+ def acl(self) -> SystemACLManager:
124
+ """Accès aux endpoints ACL système /system/acl/*."""
125
+ return SystemACLManager(self.rd)
126
+
127
+ @property
128
+ def logstorage(self) -> SystemLogStorageManager:
129
+ """Accès aux endpoints /system/logstorage/*."""
130
+ return SystemLogStorageManager(self.rd)
131
+
132
+ def info(self) -> dict[str, Any]:
133
+ """
134
+ Récupère les informations système
135
+
136
+ Returns:
137
+ Informations système
138
+ """
139
+ path = self._build_path("info")
140
+ return self.rd.http_get(path)
@@ -0,0 +1,102 @@
1
+ """
2
+ Gestion des tokens Rundeck
3
+ """
4
+
5
+ from typing import Any
6
+
7
+ from rundeck.base import RundeckObject, RundeckObjectManager
8
+
9
+
10
+ class Token(RundeckObject):
11
+ """Représente un token API Rundeck"""
12
+
13
+ _id_attr = "id"
14
+ _repr_attr = "user"
15
+
16
+ def delete(self) -> None:
17
+ """Supprime le token"""
18
+ self.manager.delete(self.id)
19
+
20
+
21
+ class TokenManager(RundeckObjectManager):
22
+ """Manager pour les tokens"""
23
+
24
+ _path = "/tokens"
25
+ _obj_cls = Token
26
+
27
+ _single_path = "/token/{id}"
28
+
29
+ def _token_path(self, token_id: str) -> str:
30
+ """
31
+ Chemin pour un token unitaire (/token/{id}).
32
+ L'API Rundeck mélange /tokens (collection) et /token/{id} (single),
33
+ on centralise ici pour rester cohérent.
34
+ """
35
+ return self._single_path.format(id=token_id)
36
+
37
+ def list(self, user: str | None = None) -> "list[Token]":
38
+ """
39
+ Liste les tokens
40
+
41
+ Args:
42
+ user: Filtre par utilisateur
43
+
44
+ Returns:
45
+ Liste de tokens
46
+ """
47
+ path = self._build_path(user) if user else self._build_path()
48
+ return self._list(path=path)
49
+
50
+ def get(self, token_id: str) -> Token:
51
+ """
52
+ Récupère un token par son ID
53
+
54
+ Args:
55
+ token_id: ID du token
56
+
57
+ Returns:
58
+ Token
59
+ """
60
+ path = self._token_path(token_id)
61
+ return self._get(token_id, path=path)
62
+
63
+ def create(
64
+ self,
65
+ user: str,
66
+ roles: "list[str]",
67
+ duration: str | None = None,
68
+ name: str | None = None,
69
+ ) -> Token:
70
+ """
71
+ Crée un nouveau token
72
+
73
+ Args:
74
+ user: Nom d'utilisateur
75
+ roles: Liste des rôles
76
+ duration: Durée de validité (ex: "120d")
77
+ name: Nom du token (API v37+)
78
+
79
+ Returns:
80
+ Token créé
81
+ """
82
+ data: dict[str, Any] = {
83
+ "user": user,
84
+ "roles": roles,
85
+ }
86
+ if duration:
87
+ data["duration"] = duration
88
+ if name:
89
+ data["name"] = name
90
+
91
+ path = self._build_path(user)
92
+ return self._create(json=data, path=path)
93
+
94
+ def delete(self, token_id: str) -> None:
95
+ """
96
+ Supprime un token
97
+
98
+ Args:
99
+ token_id: ID du token
100
+ """
101
+ path = self._token_path(token_id)
102
+ self._delete(token_id, path=path)
@@ -0,0 +1,137 @@
1
+ """
2
+ Gestion des utilisateurs Rundeck
3
+ """
4
+
5
+ from typing import Any
6
+
7
+ from rundeck.base import RundeckObject, RundeckObjectManager
8
+
9
+
10
+ class User(RundeckObject):
11
+ """Représente un utilisateur Rundeck"""
12
+
13
+ _id_attr = "login"
14
+ _repr_attr = "login"
15
+
16
+ def update(
17
+ self,
18
+ firstName: str | None = None,
19
+ lastName: str | None = None,
20
+ email: str | None = None,
21
+ ) -> "User":
22
+ """
23
+ Met à jour les informations de l'utilisateur
24
+
25
+ Args:
26
+ firstName: Prénom
27
+ lastName: Nom
28
+ email: Email
29
+
30
+ Returns:
31
+ Utilisateur mis à jour
32
+ """
33
+ data: dict[str, Any] = {}
34
+ if firstName:
35
+ data["firstName"] = firstName
36
+ if lastName:
37
+ data["lastName"] = lastName
38
+ if email:
39
+ data["email"] = email
40
+
41
+ path = self.manager._build_path(f"info/{self.login}")
42
+ result = self.manager.rd.http_post(path, json=data)
43
+ self._attrs.update(result)
44
+ return self
45
+
46
+ def roles(self) -> list[str]:
47
+ """
48
+ Récupère les rôles de l'utilisateur
49
+
50
+ Returns:
51
+ Liste des rôles
52
+ """
53
+ path = self.manager._build_path("roles")
54
+ result = self.manager.rd.http_get(path)
55
+ return result.get("roles", [])
56
+
57
+
58
+ class UserManager(RundeckObjectManager):
59
+ """Manager pour les utilisateurs"""
60
+
61
+ _path = "/user"
62
+ _obj_cls = User
63
+
64
+ def list(self) -> "list[User]":
65
+ """
66
+ Liste tous les utilisateurs
67
+
68
+ Returns:
69
+ Liste d'utilisateurs
70
+ """
71
+ path = self._build_path("list")
72
+ return self._list(path=path)
73
+
74
+ def get(self, login: str) -> User:
75
+ """
76
+ Récupère un utilisateur par son login
77
+
78
+ Args:
79
+ login: Login de l'utilisateur
80
+
81
+ Returns:
82
+ Utilisateur
83
+ """
84
+ path = self._build_path(f"info/{login}")
85
+ return self._get(login, path=path)
86
+
87
+ def get_current(self) -> User:
88
+ """
89
+ Récupère l'utilisateur courant
90
+
91
+ Returns:
92
+ Utilisateur courant
93
+ """
94
+ path = self._build_path("info")
95
+ return self._get("current", path=path)
96
+
97
+ def update(
98
+ self,
99
+ login: str,
100
+ firstName: str | None = None,
101
+ lastName: str | None = None,
102
+ email: str | None = None,
103
+ ) -> User:
104
+ """
105
+ Met à jour un utilisateur
106
+
107
+ Args:
108
+ login: Login de l'utilisateur
109
+ firstName: Prénom
110
+ lastName: Nom
111
+ email: Email
112
+
113
+ Returns:
114
+ Utilisateur mis à jour
115
+ """
116
+ data: dict[str, Any] = {}
117
+ if firstName:
118
+ data["firstName"] = firstName
119
+ if lastName:
120
+ data["lastName"] = lastName
121
+ if email:
122
+ data["email"] = email
123
+
124
+ path = self._build_path(f"info/{login}")
125
+ result = self.rd.http_post(path, json=data)
126
+ return self._obj_cls(self, result)
127
+
128
+ def current_roles(self) -> "list[str]":
129
+ """
130
+ Récupère les rôles de l'utilisateur courant
131
+
132
+ Returns:
133
+ Liste des rôles
134
+ """
135
+ path = self._build_path("roles")
136
+ result = self.rd.http_get(path)
137
+ return result.get("roles", [])