semaphoreui-client 0.1.18__tar.gz → 0.2.0__tar.gz

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: semaphoreui-client
3
- Version: 0.1.18
3
+ Version: 0.2.0
4
4
  Summary: An api client for interacting with Semaphore UI
5
5
  Project-URL: Documentation, https://github.com/rockstar/semaphoreui-client#readme
6
6
  Project-URL: Issues, https://github.com/rockstar/semaphoreui-client/issues
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -43,10 +43,6 @@ class SemaphoreUIClient:
43
43
  assert response.status_code == 201
44
44
  return Token(**response.json(), client=self)
45
45
 
46
- def delete_token(self, id: str) -> None:
47
- response = self.http.delete(f"{self.api_endpoint}/user/tokens/{id}")
48
- assert response.status_code in (204, 404) # 404 if token was already expired
49
-
50
46
  def projects(self) -> typing.List["Project"]:
51
47
  response = self.http.get(f"{self.api_endpoint}/projects")
52
48
  assert response.status_code == 200
@@ -80,118 +76,175 @@ class SemaphoreUIClient:
80
76
  assert response.status_code == 201
81
77
  return Project(**response.json(), client=self)
82
78
 
83
- def delete_project(self, id: int) -> None:
84
- response = self.http.delete(f"{self.api_endpoint}/project/{id}")
85
- assert response.status_code == 204
86
79
 
87
- def update_project(
88
- self,
89
- id: int,
90
- name: str,
91
- alert: bool,
92
- alert_chat: str,
93
- max_parallel_tasks: int,
94
- type: typing.Optional[str] = None,
95
- ) -> None:
96
- response = self.http.put(
97
- f"{self.api_endpoint}/project/{id}",
80
+ @dataclass
81
+ class Integration:
82
+ """A project integration"""
83
+
84
+ id: int
85
+ name: str
86
+ project_id: int
87
+ template_id: int
88
+
89
+ client: SemaphoreUIClient
90
+
91
+ def save(self) -> None:
92
+ response = self.client.http.put(
93
+ f"{self.client.api_endpoint}/project/{self.project_id}/integrations/{self.id}",
98
94
  json={
99
- "name": name,
100
- "alert": alert,
101
- "alert_chat": alert_chat,
102
- "max_parallel_tasks": max_parallel_tasks,
103
- "type": type,
95
+ "project_id": self.project_id,
96
+ "name": self.name,
97
+ "template_id": self.template_id,
104
98
  },
105
99
  )
106
100
  assert response.status_code == 204
107
101
 
108
- def backup_project(self, id: int) -> "ProjectBackup":
109
- response = self.http.get(f"{self.api_endpoint}/project/{id}/backup")
110
- assert response.status_code == 200
111
- return ProjectBackup(**response.json())
102
+ def delete(self) -> None:
103
+ response = self.client.http.delete(
104
+ f"{self.client.api_endpoint}/project/{self.project_id}/integrations/{self.id}"
105
+ )
106
+ assert response.status_code == 204
112
107
 
113
- def get_project_role(self, id: int) -> "Permissions":
114
- response = self.http.get(f"{self.api_endpoint}/project/{id}/role")
115
- assert response.status_code == 200
116
- return Permissions(**response.json())
117
108
 
118
- def get_project_events(self, id: int) -> typing.List["Event"]:
119
- response = self.http.get(f"{self.api_endpoint}/project/{id}/events")
120
- assert response.status_code == 200
121
- return [Event(**data) for data in response.json()]
109
+ @dataclass
110
+ class Token:
111
+ """An authorization token."""
122
112
 
123
- def get_project_users(self, id: int, sort: str, order: str) -> typing.List["User"]:
124
- response = self.http.get(f"{self.api_endpoint}/project/{id}/")
125
- assert response.status_code == 200
126
- return [User(**data) for data in response.json()]
113
+ id: str
114
+ created: str
115
+ expired: bool
116
+ user_id: int
127
117
 
128
- def add_project_user(self, id: int, user: "User") -> None:
129
- response = self.http.post(
130
- f"{self.api_endpoint}/project/{id}/users",
131
- json=user.to_json(), # type: ignore
118
+ client: SemaphoreUIClient
119
+
120
+ def delete(self) -> None:
121
+ response = self.client.http.delete(
122
+ f"{self.client.api_endpoint}/user/tokens/{self.id}"
132
123
  )
133
- assert response.status_code == 204
124
+ assert response.status_code in (204, 404) # 404 if token was already expired
134
125
 
135
- def update_project_user(self, id: int, user: "User") -> None:
136
- response = self.http.put(
137
- f"{self.api_endpoint}/project/{id}/users/{user.id}",
138
- json=user.to_json(), # type: ignore
126
+
127
+ @dataclass
128
+ class Project:
129
+ """A Semaphore UI project."""
130
+
131
+ id: int
132
+ name: str
133
+ created: str
134
+ alert: bool
135
+ alert_chat: str
136
+ max_parallel_tasks: int
137
+ type: str # This might be an enum?
138
+
139
+ client: SemaphoreUIClient
140
+
141
+ @property
142
+ def url(self) -> str:
143
+ return f"{self.client.api_endpoint}/project/{self.id}"
144
+
145
+ def delete(self) -> None:
146
+ response = self.client.http.delete(
147
+ f"{self.client.api_endpoint}/project/{self.id}"
139
148
  )
140
149
  assert response.status_code == 204
141
150
 
142
- def remove_project_user(self, id: int, user_id: int) -> None:
143
- response = self.http.delete(f"{self.api_endpoint}/project/{id}/users/{user_id}")
151
+ def save(self) -> None:
152
+ response = self.client.http.put(
153
+ f"{self.client.api_endpoint}/project/{self.id}",
154
+ json={
155
+ "name": self.name,
156
+ "alert": self.alert,
157
+ "alert_chat": self.alert_chat,
158
+ "max_parallel_tasks": self.max_parallel_tasks,
159
+ "type": self.type,
160
+ },
161
+ )
144
162
  assert response.status_code == 204
145
163
 
146
- def get_project_integrations(self, id: int) -> typing.List["Integration"]:
147
- response = self.http.get(f"{self.api_endpoint}/project/{id}/integrations")
164
+ def backup(self) -> "ProjectBackup":
165
+ response = self.client.http.get(
166
+ f"{self.client.api_endpoint}/project/{self.id}/backup"
167
+ )
148
168
  assert response.status_code == 200
149
- return [Integration(**data, client=self) for data in response.json()]
169
+ return ProjectBackup(**response.json())
150
170
 
151
- def create_project_integrations(
152
- self, project_id: int, name: str, template_id: int
153
- ) -> "Integration":
154
- response = self.http.post(
155
- f"{self.api_endpoint}/project/{id}integrations",
156
- json={"project_id": project_id, "name": name, "template_id": template_id},
171
+ def role(self) -> "Permissions":
172
+ response = self.client.http.get(
173
+ f"{self.client.api_endpoint}/project/{self.id}/role"
157
174
  )
158
175
  assert response.status_code == 200
159
- return Integration(**response.json(), client=self)
160
-
161
- def update_project_integration(
162
- self, project_id: int, id: int, name: str, template_id: int
163
- ) -> None:
164
- response = self.http.put(
165
- f"{self.api_endpoint}/project/{project_id}/integrations/{id}",
166
- json={"project_id": project_id, "name": name, "template_id": template_id},
176
+ return Permissions(**response.json())
177
+
178
+ def events(self) -> typing.List["Event"]:
179
+ response = self.client.http.get(
180
+ f"{self.client.api_endpoint}/project/{self.id}/events"
181
+ )
182
+ assert response.status_code == 200
183
+ return [Event(**data) for data in response.json()]
184
+
185
+ def users(
186
+ self, sort: typing.Optional[str] = None, order: typing.Optional[str] = None
187
+ ) -> typing.List["ProjectUser"]:
188
+ params = {}
189
+ if sort is not None:
190
+ params["sort"] = sort
191
+ if order is not None:
192
+ params["order"] = order
193
+ response = self.client.http.get(
194
+ f"{self.client.api_endpoint}/project/{self.id}/users", params=params
195
+ )
196
+ assert response.status_code == 200
197
+ return [
198
+ ProjectUser(**data, client=self.client, project_id=self.id)
199
+ for data in response.json()
200
+ ]
201
+
202
+ def add_user(self, user: "ProjectUser") -> None:
203
+ response = self.client.http.post(
204
+ f"{self.client.api_endpoint}/project/{self.id}/users",
205
+ json=user.to_json(), # type: ignore
167
206
  )
168
207
  assert response.status_code == 204
169
208
 
170
- def delete_project_integration(self, project_id: int, id: int) -> None:
171
- response = self.http.delete(
172
- f"{self.api_endpoint}/project/{project_id}/integrations/{id}"
209
+ def remove_user(self, user_id: int) -> None:
210
+ response = self.client.http.delete(
211
+ f"{self.client.api_endpoint}/project/{self.id}/users/{user_id}"
173
212
  )
174
213
  assert response.status_code == 204
175
214
 
176
- def get_project_keys(
215
+ def update_user(self, user: "ProjectUser") -> None:
216
+ response = self.client.http.put(
217
+ f"{self.client.api_endpoint}/project/{self.id}/users/{user.id}",
218
+ json=user.to_json(), # type: ignore
219
+ )
220
+ assert response.status_code == 204
221
+
222
+ def keys(
177
223
  self,
178
- project_id: int,
179
224
  key_type: typing.Optional[str] = None,
180
225
  sort: typing.Optional[str] = None,
181
226
  order: typing.Optional[str] = None,
182
227
  ) -> typing.List["Key"]:
183
- response = self.http.get(f"{self.api_endpoint}/project/{project_id}/keys")
228
+ params = {}
229
+ if key_type is not None:
230
+ params["key_type"] = key_type
231
+ if sort is not None:
232
+ params["sort"] = sort
233
+ if order is not None:
234
+ params["order"] = order
235
+ response = self.client.http.get(
236
+ f"{self.client.api_endpoint}/project/{self.id}/keys", params=params
237
+ )
184
238
  assert response.status_code == 200
185
- return [Key(**data, client=self) for data in response.json()]
239
+ return [Key(**data, client=self.client) for data in response.json()]
186
240
 
187
- def create_project_key(
241
+ def create_key(
188
242
  self,
189
- project_id: int,
190
243
  name: str,
191
244
  key_type: str,
192
- override_secret: bool,
193
- login_password: typing.Optional[typing.Tuple[str, str]],
194
- ssh: typing.Optional[typing.Tuple[str, str, str]],
245
+ override_secret: bool = False,
246
+ login_password: typing.Optional[typing.Tuple[str, str]] = None,
247
+ ssh: typing.Optional[typing.Tuple[str, str, str]] = None,
195
248
  ) -> "Key":
196
249
  if key_type not in ("ssh", "login_password"):
197
250
  raise ValueError(
@@ -202,7 +255,7 @@ class SemaphoreUIClient:
202
255
  raise ValueError("ssh parameter must be set on key_type: ssh")
203
256
  json_data = {
204
257
  "id": 0,
205
- "project_id": project_id,
258
+ "project_id": self.id,
206
259
  "name": name,
207
260
  "type": key_type,
208
261
  "override_secret": override_secret,
@@ -223,7 +276,7 @@ class SemaphoreUIClient:
223
276
  )
224
277
  json_data = {
225
278
  "id": 0,
226
- "project_id": project_id,
279
+ "project_id": self.id,
227
280
  "name": name,
228
281
  "type": key_type,
229
282
  "override_secret": override_secret,
@@ -233,40 +286,32 @@ class SemaphoreUIClient:
233
286
  },
234
287
  "ssh": {"login": "", "passphrase": "", "private_key": ""},
235
288
  }
236
- response = self.http.post(
237
- f"{self.api_endpoint}/project/{project_id}/keys", json=json_data
289
+ response = self.client.http.post(
290
+ f"{self.client.api_endpoint}/project/{self.id}/keys", json=json_data
238
291
  )
239
292
  assert response.status_code == 204
240
293
 
241
294
  try:
242
- return Key(**response.json(), client=self)
295
+ return Key(**response.json(), client=self.client)
243
296
  except ValueError:
244
297
  # Sporadically, the response is an empty string. Get the actual key from the API
245
- return [
246
- key for key in self.get_project_keys(project_id) if key.name == name
247
- ][0]
248
-
249
- def delete_project_key(self, project_id: int, id: int) -> None:
250
- response = self.http.delete(
251
- f"{self.api_endpoint}/project/{project_id}/keys/{id}"
252
- )
253
- assert response.status_code == 204
298
+ return [key for key in self.keys() if key.name == name][0]
254
299
 
255
- def get_project_repositories(self, project_id: int) -> typing.List["Repository"]:
256
- response = self.http.get(
257
- f"{self.api_endpoint}/project/{project_id}/repositories"
300
+ def repositories(self) -> typing.List["Repository"]:
301
+ response = self.client.http.get(
302
+ f"{self.client.api_endpoint}/project/{self.id}/repositories"
258
303
  )
259
304
  assert response.status_code == 200
260
- return [Repository(**data, client=self) for data in response.json()]
305
+ return [Repository(**data, client=self.client) for data in response.json()]
261
306
 
262
- def create_project_repository(
263
- self, project_id: int, name: str, git_url: str, git_branch: str, ssh_key_id: int
307
+ def create_repository(
308
+ self, name: str, git_url: str, git_branch: str, ssh_key_id: int
264
309
  ) -> "Repository":
265
- response = self.http.post(
266
- f"{self.api_endpoint}/project/{project_id}/repositories",
310
+ response = self.client.http.post(
311
+ f"{self.client.api_endpoint}/project/{self.id}/repositories",
267
312
  json={
268
313
  "name": name,
269
- "project_id": project_id,
314
+ "project_id": self.id,
270
315
  "git_url": git_url,
271
316
  "git_branch": git_branch,
272
317
  "ssh_key_id": ssh_key_id,
@@ -274,41 +319,30 @@ class SemaphoreUIClient:
274
319
  )
275
320
  assert response.status_code == 204
276
321
  try:
277
- return Repository(**response.json(), client=self)
322
+ return Repository(**response.json(), client=self.client)
278
323
  except ValueError:
279
- return [
280
- repo
281
- for repo in self.get_project_repositories(project_id)
282
- if repo.name == name
283
- ][0]
284
-
285
- def delete_project_repository(self, project_id: int, id: int) -> None:
286
- response = self.http.delete(
287
- f"{self.api_endpoint}/project/{project_id}/repositories/{id}"
288
- )
289
- assert response.status_code == 204
324
+ return [repo for repo in self.repositories() if repo.name == name][0]
290
325
 
291
- def get_project_environments(self, project_id: int) -> typing.List["Environment"]:
292
- response = self.http.get(
293
- f"{self.api_endpoint}/project/{project_id}/environment"
326
+ def environments(self) -> typing.List["Environment"]:
327
+ response = self.client.http.get(
328
+ f"{self.client.api_endpoint}/project/{self.id}/environment"
294
329
  )
295
330
  assert response.status_code == 200
296
- return [Environment(**data, client=self) for data in response.json()]
331
+ return [Environment(**data, client=self.client) for data in response.json()]
297
332
 
298
- def create_project_environment(
333
+ def create_environment(
299
334
  self,
300
- project_id: int,
301
335
  name: str,
302
336
  password: str,
303
337
  json: str,
304
338
  env: str,
305
339
  secrets: typing.List[typing.Dict[typing.Any, typing.Any]],
306
340
  ) -> "Environment":
307
- response = self.http.post(
308
- f"{self.api_endpoint}/project/{project_id}/environment",
341
+ response = self.client.http.post(
342
+ f"{self.url}/environment",
309
343
  json={
310
344
  "name": name,
311
- "project_id": project_id,
345
+ "project_id": self.id,
312
346
  "password": None,
313
347
  "json": json,
314
348
  "env": env,
@@ -317,89 +351,67 @@ class SemaphoreUIClient:
317
351
  )
318
352
  assert response.status_code == 204
319
353
  try:
320
- return Environment(**response.json(), client=self)
354
+ return Environment(**response.json(), client=self.client)
321
355
  except ValueError:
322
- return [
323
- env
324
- for env in self.get_project_environments(project_id)
325
- if env.name == name
326
- ][0]
327
-
328
- def delete_project_environment(self, project_id: int, id: int) -> None:
329
- response = self.http.delete(
330
- f"{self.api_endpoint}/project/{project_id}/environment/{id}"
331
- )
332
- assert response.status_code == 204
356
+ return [env for env in self.environments() if env.name == name][0]
333
357
 
334
- def get_project_views(self, project_id: int) -> typing.List["View"]:
335
- response = self.http.get(f"{self.api_endpoint}/project/{project_id}/views")
358
+ def views(self) -> typing.List["View"]:
359
+ response = self.client.http.get(f"{self.url}/views")
336
360
  assert response.status_code == 200
337
- return [View(**data, client=self) for data in response.json()]
361
+ return [View(**data, client=self.client) for data in response.json()]
338
362
 
339
- def create_project_view(self, project_id: int, title: str, position: int) -> "View":
340
- response = self.http.post(
341
- f"{self.api_endpoint}/project/{project_id}/views",
342
- json={"position": position, "title": title, "project_id": project_id},
363
+ def create_view(self, title: str, position: int) -> "View":
364
+ response = self.client.http.post(
365
+ f"{self.url}/views",
366
+ json={"position": position, "title": title, "project_id": self.id},
343
367
  )
344
368
  assert response.status_code == 201
345
- return View(**response.json(), client=self)
346
-
347
- def delete_project_view(self, project_id: int, id: int) -> None:
348
- response = self.http.delete(
349
- f"{self.api_endpoint}/project/{project_id}/views/{id}"
350
- )
351
- assert response.status_code == 204
369
+ return View(**response.json(), client=self.client)
352
370
 
353
- def get_project_inventories(self, project_id: int) -> typing.List["Inventory"]:
354
- response = self.http.get(f"{self.api_endpoint}/project/{project_id}/inventory")
371
+ def inventories(self) -> typing.List["Inventory"]:
372
+ response = self.client.http.get(f"{self.url}/inventory")
355
373
  assert response.status_code == 200
356
- return [Inventory(**data, client=self) for data in response.json()]
374
+ return [Inventory(**data, client=self.client) for data in response.json()]
357
375
 
358
- def create_project_inventory(
376
+ def create_inventory(
359
377
  self,
360
- project_id: int,
361
378
  name: str,
362
379
  inventory: str,
363
380
  ssh_key_id: int,
364
381
  become_key_id: int,
365
382
  type: str,
366
- repostory_id: int,
383
+ repository_id: int,
367
384
  ) -> "Inventory":
368
- response = self.http.post(
369
- f"{self.api_endpoint}/project/{project_id}/inventory",
385
+ response = self.client.http.post(
386
+ f"{self.url}/inventory",
370
387
  json={
371
388
  "id": 0,
372
389
  "name": name,
373
- "project_id": project_id,
390
+ "project_id": self.id,
374
391
  "inventory": inventory,
375
392
  "ssh_key_id": ssh_key_id,
376
393
  "become_key_id": become_key_id,
377
394
  "type": type,
378
- "repository_id": repostory_id,
395
+ "repository_id": repository_id,
379
396
  },
380
397
  )
381
398
  assert response.status_code == 201
382
- return Inventory(**response.json(), client=self)
383
-
384
- def delete_project_inventory(self, project_id: int, id: int) -> None:
385
- response = self.http.delete(
386
- f"{self.api_endpoint}/project/{project_id}/inventory/{id}"
387
- )
388
- assert response.status_code == 204
399
+ return Inventory(**response.json(), client=self.client)
389
400
 
390
- def get_project_templates(self, project_id: int) -> typing.List["Template"]:
391
- response = self.http.get(f"{self.api_endpoint}/project/{project_id}/templates")
401
+ def templates(self) -> typing.List["Template"]:
402
+ response = self.client.http.get(f"{self.url}/templates")
392
403
  assert response.status_code == 200
393
404
  templates: typing.List["Template"] = []
394
405
  for template in response.json():
395
406
  if template["last_task"] is not None:
396
- template["last_task"] = Task(**template["last_task"], client=self)
397
- templates.append(Template(**template, client=self))
407
+ template["last_task"] = Task(
408
+ **template["last_task"], client=self.client
409
+ )
410
+ templates.append(Template(**template, client=self.client))
398
411
  return templates
399
412
 
400
- def create_project_template(
413
+ def create_template(
401
414
  self,
402
- project_id: int,
403
415
  name: str,
404
416
  repository_id: int,
405
417
  inventory_id: int,
@@ -420,11 +432,11 @@ class SemaphoreUIClient:
420
432
  autorun: bool,
421
433
  build_template_id: typing.Optional[int] = None,
422
434
  ) -> "Template":
423
- response = self.http.post(
424
- f"{self.api_endpoint}/project/{project_id}/templates",
435
+ response = self.client.http.post(
436
+ f"{self.url}/templates",
425
437
  json={
426
438
  "id": 0,
427
- "project_id": project_id,
439
+ "project_id": self.id,
428
440
  "inventory_id": inventory_id,
429
441
  "repository_id": repository_id,
430
442
  "environment_id": environment_id,
@@ -449,63 +461,23 @@ class SemaphoreUIClient:
449
461
  assert response.status_code == 201, (
450
462
  f"Expected response code 201, got {response.status_code}"
451
463
  )
452
- return Template(**response.json(), client=self)
453
-
454
- def run_template(
455
- self,
456
- template_id: int,
457
- project_id: int,
458
- debug: bool,
459
- dry_run: bool,
460
- diff: bool,
461
- message: str,
462
- git_branch: str,
463
- limit: str,
464
- environment: str,
465
- playbook: str,
466
- ) -> "Task":
467
- response = self.http.post(
468
- f"{self.api_endpoint}/project/{project_id}/tasks",
469
- json={
470
- "template_id": template_id,
471
- "debug": debug,
472
- "dry_run": dry_run,
473
- "diff": diff,
474
- "playbook": playbook,
475
- "environment": environment,
476
- "limit": limit,
477
- "git_branch": git_branch,
478
- "message": message,
479
- },
480
- )
481
- assert response.status_code == 201
482
- # The response is not quite a full task, so re-fetch it.
483
- return self.get_project_task(project_id, response.json()["id"])
484
-
485
- def delete_project_template(self, project_id: int, id: int) -> None:
486
- response = self.http.delete(
487
- f"{self.api_endpoint}/project/{project_id}/templates/{id}"
488
- )
489
- assert response.status_code == 204
464
+ return Template(**response.json(), client=self.client)
490
465
 
491
- def get_project_schedules(self, project_id: int) -> typing.List["Schedule"]:
492
- response = self.http.get(f"{self.api_endpoint}/project/{project_id}/schedules")
466
+ def schedules(self) -> typing.List["Schedule"]:
467
+ response = self.client.http.get(f"{self.url}/schedules")
493
468
  assert response.status_code == 200
494
- return [Schedule(**schedule, client=self) for schedule in response.json()]
469
+ return [
470
+ Schedule(**schedule, client=self.client) for schedule in response.json()
471
+ ]
495
472
 
496
- def create_project_schedule(
497
- self,
498
- project_id: int,
499
- template_id: int,
500
- name: str,
501
- cron_format: str,
502
- active: bool = True,
473
+ def create_schedule(
474
+ self, template_id: int, name: str, cron_format: str, active: bool = True
503
475
  ) -> "Schedule":
504
- response = self.http.post(
505
- f"{self.api_endpoint}/project/{project_id}/schedules",
476
+ response = self.client.http.post(
477
+ f"{self.url}/schedules",
506
478
  json={
507
479
  "id": 0,
508
- "project_id": project_id,
480
+ "project_id": self.id,
509
481
  "template_id": template_id,
510
482
  "name": name,
511
483
  "cron_format": cron_format,
@@ -513,274 +485,34 @@ class SemaphoreUIClient:
513
485
  },
514
486
  )
515
487
  assert response.status_code == 201
516
- return Schedule(**response.json(), client=self)
488
+ return Schedule(**response.json(), client=self.client)
517
489
 
518
- def update_project_schedule(
519
- self,
520
- project_id: int,
521
- schedule_id: int,
522
- template_id: int,
523
- name: str,
524
- cron_format: str,
525
- active: bool,
526
- ) -> None:
527
- response = self.http.post(
528
- f"{self.api_endpoint}/project/{project_id}/schedules",
529
- json={
530
- "id": schedule_id,
531
- "project_id": project_id,
532
- "template_id": template_id,
533
- "name": name,
534
- "cron_format": cron_format,
535
- "active": active,
536
- },
537
- )
538
- assert response.status_code == 201
539
-
540
- def delete_project_schedule(self, project_id: int, schedule_id: int) -> None:
541
- response = self.http.delete(
542
- f"{self.api_endpoint}/project/{project_id}/schedules/{schedule_id}"
543
- )
544
- assert response.status_code == 204
545
-
546
- def get_project_tasks(self, project_id: int) -> typing.List["Task"]:
547
- response = self.http.get(f"{self.api_endpoint}/project/{project_id}/tasks")
490
+ def tasks(self) -> typing.List["Task"]:
491
+ response = self.client.http.get(f"{self.url}/tasks")
548
492
  assert response.status_code == 200
549
- return [Task(**task, client=self) for task in response.json()]
493
+ return [Task(**task, client=self.client) for task in response.json()]
550
494
 
551
- def stop_project_task(self, project_id: int, id: int) -> None:
552
- response = self.http.post(
553
- f"{self.api_endpoint}/project/{project_id}/tasks/{id}/stop"
495
+ def get_task(self, task_id: int) -> "Task":
496
+ response = self.client.http.get(
497
+ f"{self.client.api_endpoint}/project/{self.id}/tasks/{task_id}"
554
498
  )
555
- assert response.status_code == 204
556
-
557
- def get_project_task(self, project_id: int, id: int) -> "Task":
558
- response = self.http.get(f"{self.api_endpoint}/project/{project_id}/tasks/{id}")
559
499
  assert response.status_code == 200
560
- return Task(**response.json(), client=self)
561
-
562
- def delete_project_task(self, project_id: int, id: int) -> None:
563
- response = self.http.delete(
564
- f"{self.api_endpoint}/project/{project_id}/tasks/{id}"
565
- )
566
- assert response.status_code == 204
500
+ return Task(**response.json(), client=self.client)
567
501
 
568
- def get_project_task_output(self, project_id: int, id: int) -> str:
569
- response = self.http.get(
570
- f"{self.api_endpoint}/project/{project_id}/tasks/{id}/output"
502
+ def integrations(self) -> typing.List["Integration"]:
503
+ response = self.client.http.get(
504
+ f"{self.client.api_endpoint}/project/{self.id}/integrations"
571
505
  )
572
506
  assert response.status_code == 200
573
- return "\n".join(data["output"] for data in response.json())
574
-
575
-
576
- @dataclass
577
- class Integration:
578
- """A project integration"""
579
-
580
- id: int
581
- name: str
582
- project_id: int
583
- template_id: int
584
-
585
- client: SemaphoreUIClient
586
-
587
- def save(self) -> None:
588
- self.client.update_project_integration(
589
- self.project_id, self.id, self.name, self.template_id
590
- )
591
-
592
- def delete(self) -> None:
593
- self.client.delete_project_integration(self.project_id, self.id)
594
-
595
-
596
- @dataclass
597
- class Token:
598
- """An authorization token."""
599
-
600
- id: str
601
- created: str
602
- expired: bool
603
- user_id: int
604
-
605
- client: SemaphoreUIClient
606
-
607
- def delete(self) -> None:
608
- self.client.delete_token(self.id)
609
-
610
-
611
- @dataclass
612
- class Project:
613
- """A Semaphore UI project."""
614
-
615
- id: int
616
- name: str
617
- created: str
618
- alert: bool
619
- alert_chat: str
620
- max_parallel_tasks: int
621
- type: str # This might be an enum?
622
-
623
- client: SemaphoreUIClient
624
-
625
- def delete(self) -> None:
626
- self.client.delete_project(self.id)
507
+ return [Integration(**data, client=self.client) for data in response.json()]
627
508
 
628
- def save(self) -> None:
629
- self.client.update_project(
630
- self.id,
631
- self.name,
632
- self.alert,
633
- self.alert_chat,
634
- self.max_parallel_tasks,
635
- self.type,
509
+ def create_integration(self, name: str, template_id: int) -> "Integration":
510
+ response = self.client.http.post(
511
+ f"{self.client.api_endpoint}/project/{self.id}integrations",
512
+ json={"project_id": self.id, "name": name, "template_id": template_id},
636
513
  )
637
-
638
- def backup(self) -> "ProjectBackup":
639
- return self.client.backup_project(self.id)
640
-
641
- def role(self) -> "Permissions":
642
- return self.client.get_project_role(self.id)
643
-
644
- def events(self) -> typing.List["Event"]:
645
- return self.client.get_project_events(self.id)
646
-
647
- def users(self, sort: str, order: str) -> typing.List["User"]:
648
- return self.client.get_project_users(self.id, sort, order)
649
-
650
- def add_user(self, user: "User") -> None:
651
- return self.client.add_project_user(self.id, user)
652
-
653
- def remove_user(self, user_id: int) -> None:
654
- return self.client.remove_project_user(self.id, user_id)
655
-
656
- def update_user(self, user: "User") -> None:
657
- return self.client.update_project_user(self.id, user)
658
-
659
- def keys(self) -> typing.List["Key"]:
660
- return self.client.get_project_keys(self.id)
661
-
662
- def create_key(
663
- self,
664
- name: str,
665
- key_type: str,
666
- override_secret: bool = False,
667
- login_password: typing.Optional[typing.Tuple[str, str]] = None,
668
- ssh: typing.Optional[typing.Tuple[str, str, str]] = None,
669
- ) -> "Key":
670
- return self.client.create_project_key(
671
- self.id, name, key_type, override_secret, login_password, ssh
672
- )
673
-
674
- def repositories(self) -> typing.List["Repository"]:
675
- return self.client.get_project_repositories(self.id)
676
-
677
- def create_repository(
678
- self, name: str, git_url: str, git_branch: str, ssh_key_id: int
679
- ) -> "Repository":
680
- return self.client.create_project_repository(
681
- self.id, name, git_url, git_branch, ssh_key_id
682
- )
683
-
684
- def environments(self) -> typing.List["Environment"]:
685
- return self.client.get_project_environments(self.id)
686
-
687
- def create_environment(
688
- self,
689
- name: str,
690
- password: str,
691
- json: str,
692
- env: str,
693
- secrets: typing.List[typing.Dict[typing.Any, typing.Any]],
694
- ) -> "Environment":
695
- return self.client.create_project_environment(
696
- self.id, name, password, json, env, secrets
697
- )
698
-
699
- def views(self) -> typing.List["View"]:
700
- return self.client.get_project_views(self.id)
701
-
702
- def create_view(self, title: str, position: int) -> "View":
703
- return self.client.create_project_view(self.id, title, position)
704
-
705
- def inventories(self) -> typing.List["Inventory"]:
706
- return self.client.get_project_inventories(self.id)
707
-
708
- def create_inventory(
709
- self,
710
- name: str,
711
- inventory: str,
712
- ssh_key_id: int,
713
- become_key_id: int,
714
- type: str,
715
- repository_id: int,
716
- ) -> "Inventory":
717
- return self.client.create_project_inventory(
718
- self.id, name, inventory, ssh_key_id, become_key_id, type, repository_id
719
- )
720
-
721
- def templates(self) -> typing.List["Template"]:
722
- return self.client.get_project_templates(self.id)
723
-
724
- def create_template(
725
- self,
726
- name: str,
727
- repository_id: int,
728
- inventory_id: int,
729
- environment_id: int,
730
- view_id: int,
731
- vault_id: int,
732
- playbook: str,
733
- arguments: str,
734
- description: str,
735
- allow_override_args_in_task: bool,
736
- limit: int,
737
- suppress_success_alerts: bool,
738
- app: str,
739
- git_branch: str,
740
- survey_vars: typing.List[typing.Dict[str, typing.Any]],
741
- type: str,
742
- start_version: str,
743
- autorun: bool,
744
- build_template_id: typing.Optional[int] = None,
745
- ) -> "Template":
746
- return self.client.create_project_template(
747
- self.id,
748
- name,
749
- repository_id,
750
- inventory_id,
751
- environment_id,
752
- view_id,
753
- vault_id,
754
- playbook,
755
- arguments,
756
- description,
757
- allow_override_args_in_task,
758
- limit,
759
- suppress_success_alerts,
760
- app,
761
- git_branch,
762
- survey_vars,
763
- type,
764
- start_version,
765
- autorun,
766
- build_template_id,
767
- )
768
-
769
- def schedules(self) -> typing.List["Schedule"]:
770
- return self.client.get_project_schedules(self.id)
771
-
772
- def create_schedule(
773
- self, template_id: int, name: str, cron_format: str, active: bool = True
774
- ) -> "Schedule":
775
- return self.client.create_project_schedule(
776
- self.id, template_id, name, cron_format, active
777
- )
778
-
779
- def tasks(self) -> typing.List["Task"]:
780
- return self.client.get_project_tasks(self.id)
781
-
782
- def get_task(self, task_id: int) -> "Task":
783
- return self.client.get_project_task(self.id, task_id)
514
+ assert response.status_code == 200
515
+ return Integration(**response.json(), client=self.client)
784
516
 
785
517
 
786
518
  @dataclass
@@ -804,13 +536,19 @@ class Event:
804
536
 
805
537
  @dataclass_json
806
538
  @dataclass
807
- class User:
808
- user_id: int
539
+ class ProjectUser:
540
+ id: int
541
+ name: str
542
+ username: str
809
543
  role: str
810
544
 
545
+ project_id: int
546
+
547
+ client: SemaphoreUIClient
548
+
811
549
  @property
812
- def id(self) -> int:
813
- return self.user_id
550
+ def url(self) -> str:
551
+ return f"{self.client.api_endpoint}/project/{self.project_id}/users/{self.id}"
814
552
 
815
553
 
816
554
  @dataclass
@@ -840,8 +578,13 @@ class Key:
840
578
 
841
579
  client: SemaphoreUIClient
842
580
 
581
+ @property
582
+ def url(self) -> str:
583
+ return f"{self.client.api_endpoint}/project/{self.project_id}/keys/{self.id}"
584
+
843
585
  def delete(self) -> None:
844
- self.client.delete_project_key(self.project_id, self.id)
586
+ response = self.client.http.delete(self.url)
587
+ assert response.status_code == 204
845
588
 
846
589
 
847
590
  @dataclass
@@ -855,8 +598,13 @@ class Repository:
855
598
 
856
599
  client: SemaphoreUIClient
857
600
 
601
+ @property
602
+ def url(self) -> str:
603
+ return f"{self.client.api_endpoint}/project/{self.project_id}/repositories/{self.id}"
604
+
858
605
  def delete(self) -> None:
859
- self.client.delete_project_repository(self.project_id, self.id)
606
+ response = self.client.http.delete(self.url)
607
+ assert response.status_code == 204
860
608
 
861
609
 
862
610
  @dataclass
@@ -878,8 +626,13 @@ class Environment:
878
626
 
879
627
  client: SemaphoreUIClient
880
628
 
629
+ @property
630
+ def url(self) -> str:
631
+ return f"{self.client.api_endpoint}/project/{self.project_id}/environment/{self.id}"
632
+
881
633
  def delete(self) -> None:
882
- self.client.delete_project_environment(self.project_id, self.id)
634
+ response = self.client.http.delete(self.url)
635
+ assert response.status_code == 204
883
636
 
884
637
 
885
638
  @dataclass
@@ -891,8 +644,13 @@ class View:
891
644
 
892
645
  client: SemaphoreUIClient
893
646
 
647
+ @property
648
+ def url(self) -> str:
649
+ return f"{self.client.api_endpoint}/project/{self.project_id}/views/{self.id}"
650
+
894
651
  def delete(self) -> None:
895
- self.client.delete_project_view(self.project_id, self.id)
652
+ response = self.client.http.delete(self.url)
653
+ assert response.status_code == 204
896
654
 
897
655
 
898
656
  @dataclass
@@ -910,8 +668,16 @@ class Inventory:
910
668
 
911
669
  client: SemaphoreUIClient
912
670
 
671
+ def url(self) -> str:
672
+ return (
673
+ f"{self.client.api_endpoint}/project/{self.project_id}/inventory/{self.id}"
674
+ )
675
+
913
676
  def delete(self) -> None:
914
- self.client.delete_project_inventory(self.project_id, self.id)
677
+ response = self.client.http.delete(
678
+ f"{self.client.api_endpoint}/project/{self.project_id}/inventory/{self.id}"
679
+ )
680
+ assert response.status_code == 204
915
681
 
916
682
 
917
683
  @dataclass
@@ -940,6 +706,12 @@ class Template:
940
706
 
941
707
  client: SemaphoreUIClient
942
708
 
709
+ @property
710
+ def url(self) -> str:
711
+ return (
712
+ f"{self.client.api_endpoint}/project/{self.project_id}/templates/{self.id}"
713
+ )
714
+
943
715
  def run(
944
716
  self,
945
717
  debug: bool = False,
@@ -949,27 +721,33 @@ class Template:
949
721
  limit: str = "",
950
722
  environment: str = "",
951
723
  ) -> "Task":
724
+ project = self.client.get_project(self.project_id)
952
725
  repo = [
953
- repo
954
- for repo in self.client.get_project_repositories(self.project_id)
955
- if repo.id == self.repository_id
726
+ repo for repo in project.repositories() if repo.id == self.repository_id
956
727
  ][0]
957
728
  git_branch = repo.git_branch
958
- return self.client.run_template(
959
- self.id,
960
- self.project_id,
961
- debug,
962
- dry_run,
963
- diff,
964
- message,
965
- git_branch,
966
- limit,
967
- environment,
968
- self.playbook,
729
+ response = self.client.http.post(
730
+ f"{self.client.api_endpoint}/project/{self.project_id}/tasks",
731
+ json={
732
+ "template_id": self.id,
733
+ "debug": debug,
734
+ "dry_run": dry_run,
735
+ "diff": diff,
736
+ "playbook": self.playbook,
737
+ "environment": environment,
738
+ "limit": limit,
739
+ "git_branch": git_branch,
740
+ "message": message,
741
+ },
969
742
  )
743
+ assert response.status_code == 201
744
+ # The response is not quite a full task, so re-fetch it.
745
+ project = self.client.get_project(self.project_id)
746
+ return project.get_task(response.json()["id"])
970
747
 
971
748
  def delete(self) -> None:
972
- self.client.delete_project_template(self.project_id, self.id)
749
+ response = self.client.http.delete(self.url)
750
+ assert response.status_code == 204
973
751
 
974
752
 
975
753
  @dataclass
@@ -984,18 +762,29 @@ class Schedule:
984
762
 
985
763
  client: SemaphoreUIClient
986
764
 
765
+ @property
766
+ def url(self) -> str:
767
+ return (
768
+ f"{self.client.api_endpoint}/project/{self.project_id}/schedules/{self.id}"
769
+ )
770
+
987
771
  def save(self) -> None:
988
- self.client.update_project_schedule(
989
- self.project_id,
990
- self.id,
991
- self.template_id,
992
- self.name,
993
- self.cron_format,
994
- self.active,
772
+ response = self.client.http.post(
773
+ self.url,
774
+ json={
775
+ "id": self.id,
776
+ "project_id": self.project_id,
777
+ "template_id": self.template_id,
778
+ "name": self.name,
779
+ "cron_format": self.cron_format,
780
+ "active": self.active,
781
+ },
995
782
  )
783
+ assert response.status_code == 201
996
784
 
997
785
  def delete(self) -> None:
998
- self.client.delete_project_schedule(self.project_id, self.id)
786
+ response = self.client.http.delete(self.url)
787
+ assert response.status_code == 204
999
788
 
1000
789
 
1001
790
  @dataclass
@@ -1037,11 +826,19 @@ class Task:
1037
826
  user_name: typing.Optional[str] = field(default=None)
1038
827
  version: typing.Optional[str] = field(default=None)
1039
828
 
829
+ @property
830
+ def url(self) -> str:
831
+ return f"{self.client.api_endpoint}/project/{self.project_id}/tasks/{self.id}"
832
+
1040
833
  def stop(self) -> None:
1041
- self.client.stop_project_task(self.project_id, self.id)
834
+ response = self.client.http.post(f"{self.url}/stop")
835
+ assert response.status_code == 204
1042
836
 
1043
837
  def delete(self) -> None:
1044
- self.client.delete_project_task(self.project_id, self.id)
838
+ response = self.client.http.delete(self.url)
839
+ assert response.status_code == 204
1045
840
 
1046
- def output(self) -> str:
1047
- return self.client.get_project_task_output(self.project_id, self.id)
841
+ def output(self) -> typing.List[str]:
842
+ response = self.client.http.get(f"{self.url}/output")
843
+ assert response.status_code == 200
844
+ return [data["output"] for data in response.json()]
@@ -1 +0,0 @@
1
- __version__ = "0.1.18"