zabel-elements 1.47.2__tar.gz → 1.50.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.
- {zabel_elements-1.47.2/zabel_elements.egg-info → zabel_elements-1.50.0}/PKG-INFO +2 -2
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/pyproject.toml +2 -2
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/atlassian.py +258 -67
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/confluence.py +107 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/github.py +0 -2
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/githubcloud.py +540 -88
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/jira.py +616 -37
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/jiracloud.py +45 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0/zabel_elements.egg-info}/PKG-INFO +2 -2
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/LICENSE.txt +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/README.md +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/setup.cfg +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/__init__.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/actito.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/artifactory.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/atlassian.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/__init__.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/actito.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/artifactory.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/confluencecloud.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/gitlab.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/jenkins.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/kubernetes.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/okta.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/sonarqube.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/sonatypenexus.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/base/squashtm.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/confluence.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/confluencecloud.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/github.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/githubcloud.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/gitlab.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/jenkins.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/jira.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/jiracloud.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/kubehelpers.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/kubernetes.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/okta.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/sonarqube.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/sonatypenexus.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/clients/squashtm.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/images/__init__.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/images/managedserviceapp.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel/elements/images/utilityapp.py +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel_elements.egg-info/SOURCES.txt +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel_elements.egg-info/dependency_links.txt +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel_elements.egg-info/requires.txt +0 -0
- {zabel_elements-1.47.2 → zabel_elements-1.50.0}/zabel_elements.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zabel-elements
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.50.0
|
|
4
|
+
Summary: The Zabel default clients and images
|
|
5
5
|
Author-email: Martin Lafaix <martin.lafaix@external.engie.com>
|
|
6
6
|
License-Expression: EPL-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/engie-group/zabel-elements
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "zabel-elements"
|
|
7
|
-
version = "1.
|
|
8
|
-
description = "
|
|
7
|
+
version = "1.50.0"
|
|
8
|
+
description = "The Zabel default clients and images"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "EPL-2.0"
|
|
11
11
|
authors = [
|
|
@@ -11,7 +11,7 @@ pagination if appropriate, but does not process the results or compose
|
|
|
11
11
|
API requests.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from typing import Any, Iterable, Mapping
|
|
14
|
+
from typing import Any, Iterable, Mapping, Optional
|
|
15
15
|
|
|
16
16
|
import random
|
|
17
17
|
import time
|
|
@@ -87,11 +87,12 @@ class Atlassian:
|
|
|
87
87
|
```python
|
|
88
88
|
from zabel.elements.clients import Atlassian
|
|
89
89
|
|
|
90
|
-
url = 'https://api.atlassian.com
|
|
90
|
+
url = 'https://api.atlassian.com'
|
|
91
91
|
token = '...'
|
|
92
92
|
atlassian = Atlassian(url, bearer_auth=token)
|
|
93
93
|
atlassian.list_organization_users('your-organization-id')
|
|
94
94
|
```
|
|
95
|
+
|
|
95
96
|
"""
|
|
96
97
|
|
|
97
98
|
def __init__(
|
|
@@ -121,21 +122,28 @@ class Atlassian:
|
|
|
121
122
|
|
|
122
123
|
`url` is the top-level API endpoint. For example:
|
|
123
124
|
|
|
124
|
-
'https://api.atlassian.com
|
|
125
|
+
'https://api.atlassian.com'
|
|
125
126
|
"""
|
|
126
127
|
ensure_nonemptystring('url')
|
|
127
128
|
ensure_noneornonemptystring('bearer_auth')
|
|
128
129
|
ensure_noneorinstance('basic_auth', tuple)
|
|
129
|
-
if not url.endswith('/v1/'):
|
|
130
|
-
raise ValueError(f'url must end with /v1/: {url}')
|
|
131
130
|
|
|
132
131
|
if basic_auth is None and bearer_auth is None:
|
|
133
132
|
raise ValueError(
|
|
134
133
|
'Provide at least one of bearer_auth or basic_auth'
|
|
135
134
|
)
|
|
136
135
|
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
# Normalize url: strip legacy /admin/v1 or /admin suffix so
|
|
137
|
+
# both old inputs (e.g. 'https://api.atlassian.com/admin/v1/')
|
|
138
|
+
# and new inputs (e.g. 'https://api.atlassian.com') work.
|
|
139
|
+
normalized = url.rstrip('/')
|
|
140
|
+
for suffix in ('/admin/v1', '/admin'):
|
|
141
|
+
if normalized.endswith(suffix):
|
|
142
|
+
normalized = normalized[: -len(suffix)]
|
|
143
|
+
break
|
|
144
|
+
self.url = normalized
|
|
145
|
+
self.admin_url = join_url(self.url, 'admin')
|
|
146
|
+
self.v1_url = join_url(self.url, 'admin/v1')
|
|
139
147
|
self.basic_auth = basic_auth
|
|
140
148
|
self.bearer_auth = bearer_auth
|
|
141
149
|
|
|
@@ -183,21 +191,21 @@ class Atlassian:
|
|
|
183
191
|
A list of _users_. Each user is a dictionary with the
|
|
184
192
|
following entries:
|
|
185
193
|
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
194
|
+
- account_id: a string
|
|
195
|
+
- account_type: a string
|
|
196
|
+
- account_status: a string
|
|
197
|
+
- name: a string
|
|
198
|
+
- picture: a string
|
|
199
|
+
- email: a string
|
|
200
|
+
- access_billable: a boolean
|
|
201
|
+
- last_active: a string
|
|
202
|
+
- product_access: a list of dictionaries
|
|
203
|
+
- links: a dictionary (with self)
|
|
196
204
|
"""
|
|
197
205
|
ensure_nonemptystring('org_id')
|
|
198
206
|
|
|
199
207
|
session = self._get_session()
|
|
200
|
-
api_url = join_url(self.
|
|
208
|
+
api_url = join_url(self.v1_url, f'orgs/{org_id}/users')
|
|
201
209
|
|
|
202
210
|
collected: list[dict[str, Any]] = []
|
|
203
211
|
while True:
|
|
@@ -235,7 +243,7 @@ class Atlassian:
|
|
|
235
243
|
# Returned value
|
|
236
244
|
|
|
237
245
|
A list of _directories_. Each directory is a dictionary with
|
|
238
|
-
entries such as
|
|
246
|
+
entries such as id, name, type, managementAccess.
|
|
239
247
|
"""
|
|
240
248
|
ensure_nonemptystring('org_id')
|
|
241
249
|
|
|
@@ -255,8 +263,8 @@ class Atlassian:
|
|
|
255
263
|
# Returned value
|
|
256
264
|
|
|
257
265
|
A list of _groups_. Each group is a dictionary with entries
|
|
258
|
-
such as
|
|
259
|
-
|
|
266
|
+
such as id, name, description, directoryId,
|
|
267
|
+
managementAccess, externalSynced.
|
|
260
268
|
"""
|
|
261
269
|
ensure_nonemptystring('org_id')
|
|
262
270
|
ensure_nonemptystring('directory_id')
|
|
@@ -287,11 +295,11 @@ class Atlassian:
|
|
|
287
295
|
A list of _role assignments_. Each role assignment is a
|
|
288
296
|
dictionary with:
|
|
289
297
|
|
|
290
|
-
-
|
|
291
|
-
-
|
|
292
|
-
-
|
|
293
|
-
|
|
294
|
-
-
|
|
298
|
+
- resourceId: a string (e.g., ari:cloud:confluence::site/<id>)
|
|
299
|
+
- resourceOwner: a string (jira, confluence, cmdb, ...)
|
|
300
|
+
- roles: a list of strings (atlassian/user, atlassian/admin,
|
|
301
|
+
atlassian/guest)
|
|
302
|
+
- defaultRole: a string
|
|
295
303
|
"""
|
|
296
304
|
ensure_nonemptystring('org_id')
|
|
297
305
|
ensure_nonemptystring('directory_id')
|
|
@@ -311,11 +319,11 @@ class Atlassian:
|
|
|
311
319
|
) -> list[dict[str, Any]]:
|
|
312
320
|
"""List users in a directory of an organization.
|
|
313
321
|
|
|
314
|
-
Returns full user info including the org-level
|
|
315
|
-
(
|
|
316
|
-
|
|
317
|
-
Note the difference with
|
|
318
|
-
APIs: the latter can report
|
|
322
|
+
Returns full user info including the org-level status field
|
|
323
|
+
(active / suspended / deactivated / not_invited /
|
|
324
|
+
for_deletion), which is the source of truth for billing.
|
|
325
|
+
Note the difference with accountStatus returned by site-level
|
|
326
|
+
APIs: the latter can report active for users actually
|
|
319
327
|
suspended at the org level.
|
|
320
328
|
|
|
321
329
|
# Required parameters
|
|
@@ -333,14 +341,14 @@ class Atlassian:
|
|
|
333
341
|
A list of _users_. Each user is a dictionary with entries
|
|
334
342
|
such as:
|
|
335
343
|
|
|
336
|
-
-
|
|
337
|
-
-
|
|
338
|
-
-
|
|
339
|
-
-
|
|
340
|
-
suspended -- prefer
|
|
341
|
-
-
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
+
- accountId: a string
|
|
345
|
+
- accountType: a string (atlassian, app, customer)
|
|
346
|
+
- status: a string (active, suspended, deactivated)
|
|
347
|
+
- accountStatus: a string (often active even when status is
|
|
348
|
+
suspended -- prefer status for billing decisions)
|
|
349
|
+
- membershipStatus: a string (per-directory membership)
|
|
350
|
+
- name, nickname, email, claimStatus, addedToOrg,
|
|
351
|
+
deactivatedOn, ...
|
|
344
352
|
|
|
345
353
|
# Usage
|
|
346
354
|
|
|
@@ -354,7 +362,8 @@ class Atlassian:
|
|
|
354
362
|
users = atlassian.list_directory_users(org_id, directory_id, params)
|
|
355
363
|
```
|
|
356
364
|
|
|
357
|
-
|
|
365
|
+
|
|
366
|
+
For billing, combining roleIds + resourceIds returns the
|
|
358
367
|
exact billable user set for a given (site, product) couple --
|
|
359
368
|
matching admin UI counts.
|
|
360
369
|
"""
|
|
@@ -375,20 +384,20 @@ class Atlassian:
|
|
|
375
384
|
|
|
376
385
|
# Required parameters
|
|
377
386
|
|
|
378
|
-
- site_url: a non-empty string (of the form
|
|
387
|
+
- site_url: a non-empty string (of the form https://...)
|
|
379
388
|
|
|
380
389
|
# Returned value
|
|
381
390
|
|
|
382
391
|
A list of _users_. Each user is a dictionary with the
|
|
383
392
|
following entries:
|
|
384
393
|
|
|
385
|
-
-
|
|
386
|
-
-
|
|
387
|
-
-
|
|
388
|
-
-
|
|
389
|
-
-
|
|
390
|
-
-
|
|
391
|
-
-
|
|
394
|
+
- accountId: a string
|
|
395
|
+
- accountType: a string
|
|
396
|
+
- emailAddress: a string
|
|
397
|
+
- avatarUrls: a dictionary
|
|
398
|
+
- displayName: a string
|
|
399
|
+
- active: a boolean
|
|
400
|
+
- locale: a string
|
|
392
401
|
"""
|
|
393
402
|
ensure_nonemptystring('site_url')
|
|
394
403
|
|
|
@@ -452,8 +461,8 @@ class Atlassian:
|
|
|
452
461
|
"""List Jira application roles on a site.
|
|
453
462
|
|
|
454
463
|
Each role describes one Jira product available on the site
|
|
455
|
-
(e.g.
|
|
456
|
-
|
|
464
|
+
(e.g. jira-software, jira-servicedesk, jira-core,
|
|
465
|
+
jira-product-discovery) and exposes the groups whose members
|
|
457
466
|
are granted access to that product.
|
|
458
467
|
|
|
459
468
|
# Required parameters
|
|
@@ -465,14 +474,14 @@ class Atlassian:
|
|
|
465
474
|
A list of _roles_. Each role is a dictionary with entries
|
|
466
475
|
such as:
|
|
467
476
|
|
|
468
|
-
-
|
|
469
|
-
-
|
|
470
|
-
-
|
|
471
|
-
-
|
|
472
|
-
-
|
|
473
|
-
-
|
|
474
|
-
-
|
|
475
|
-
-
|
|
477
|
+
- key: a string (the product key)
|
|
478
|
+
- name: a string
|
|
479
|
+
- groups: a list of strings (group names)
|
|
480
|
+
- groupDetails: a list of dictionaries (with name, groupId)
|
|
481
|
+
- defaultGroups: a list of strings
|
|
482
|
+
- numberOfSeats: an integer
|
|
483
|
+
- userCount: an integer
|
|
484
|
+
- hasUnlimitedSeats: a boolean
|
|
476
485
|
"""
|
|
477
486
|
ensure_nonemptystring('site_url')
|
|
478
487
|
|
|
@@ -492,7 +501,7 @@ class Atlassian:
|
|
|
492
501
|
"""List members of a group on a Jira site.
|
|
493
502
|
|
|
494
503
|
Works for any group on the site, including the groups that
|
|
495
|
-
gate Confluence access (e.g.
|
|
504
|
+
gate Confluence access (e.g. confluence-users).
|
|
496
505
|
|
|
497
506
|
# Required parameters
|
|
498
507
|
|
|
@@ -507,11 +516,11 @@ class Atlassian:
|
|
|
507
516
|
|
|
508
517
|
A list of _users_. Each user is a dictionary with entries:
|
|
509
518
|
|
|
510
|
-
-
|
|
511
|
-
-
|
|
512
|
-
-
|
|
513
|
-
-
|
|
514
|
-
-
|
|
519
|
+
- accountId: a string
|
|
520
|
+
- accountType: a string
|
|
521
|
+
- displayName: a string
|
|
522
|
+
- active: a boolean
|
|
523
|
+
- emailAddress: a string (omitted when the caller does not
|
|
515
524
|
have the relevant privacy permission)
|
|
516
525
|
"""
|
|
517
526
|
ensure_nonemptystring('site_url')
|
|
@@ -538,9 +547,181 @@ class Atlassian:
|
|
|
538
547
|
|
|
539
548
|
return collected
|
|
540
549
|
|
|
550
|
+
####################################################################
|
|
551
|
+
# Jira Rules
|
|
552
|
+
#
|
|
553
|
+
# list_rules
|
|
554
|
+
# get_rule
|
|
555
|
+
# enable_rule
|
|
556
|
+
# disable_rule
|
|
557
|
+
|
|
558
|
+
@api_call
|
|
559
|
+
@require_basic_auth
|
|
560
|
+
def list_rules(
|
|
561
|
+
self,
|
|
562
|
+
product: str,
|
|
563
|
+
site_id: str,
|
|
564
|
+
) -> list[dict[str, Any]]:
|
|
565
|
+
"""List rules for a Jira product on a site.
|
|
566
|
+
# Required parameters
|
|
567
|
+
|
|
568
|
+
- product: a non-empty string (e.g. jira, confluence)
|
|
569
|
+
- site_id: a non-empty string (the site ID, not the URL)
|
|
570
|
+
|
|
571
|
+
# Returned value
|
|
572
|
+
A list of _rules_. Each rule is a dictionary with entries such as:
|
|
573
|
+
- uuid: a string
|
|
574
|
+
- name: a string
|
|
575
|
+
- state: a string (ENABLED, DISABLED)
|
|
576
|
+
- description: a string
|
|
577
|
+
- authorAccountId: a string
|
|
578
|
+
- actorAccountId: a string
|
|
579
|
+
- created: a float (Unix timestamp)
|
|
580
|
+
- updated: a float (Unix timestamp)
|
|
581
|
+
- labels: a list of strings
|
|
582
|
+
- ruleScopeARIs: a list of strings
|
|
583
|
+
"""
|
|
584
|
+
ensure_nonemptystring('product')
|
|
585
|
+
ensure_nonemptystring('site_id')
|
|
586
|
+
|
|
587
|
+
session = self._get_session()
|
|
588
|
+
next_cursor = ''
|
|
589
|
+
api = join_url(
|
|
590
|
+
self.url,
|
|
591
|
+
f'automation/public/{product}/{site_id}/rest/v1/rule/summary',
|
|
592
|
+
)
|
|
593
|
+
collected: list[dict[str, Any]] = []
|
|
594
|
+
while True:
|
|
595
|
+
url = api if not next_cursor else join_url(api, next_cursor)
|
|
596
|
+
response = session.get(url)
|
|
597
|
+
data = response.json()
|
|
598
|
+
values = data.get('data', [])
|
|
599
|
+
collected.extend(values)
|
|
600
|
+
links = data.get('links', {})
|
|
601
|
+
next_link = links.get('next')
|
|
602
|
+
if not values or not next_link:
|
|
603
|
+
break
|
|
604
|
+
next_cursor = next_link
|
|
605
|
+
return collected
|
|
606
|
+
|
|
607
|
+
@api_call
|
|
608
|
+
@require_basic_auth
|
|
609
|
+
def get_rule(
|
|
610
|
+
self,
|
|
611
|
+
product: str,
|
|
612
|
+
site_id: str,
|
|
613
|
+
rule_id: str,
|
|
614
|
+
) -> dict[str, Any]:
|
|
615
|
+
"""Get a rule for a Jira product on a site.
|
|
616
|
+
# Required parameters
|
|
617
|
+
|
|
618
|
+
- product: a non-empty string (e.g. jira, confluence)
|
|
619
|
+
- site_id: a non-empty string (the site ID, not the URL)
|
|
620
|
+
- rule_id: a non-empty string
|
|
621
|
+
|
|
622
|
+
# Returned value
|
|
623
|
+
|
|
624
|
+
A dictionary with two top-level keys:
|
|
625
|
+
|
|
626
|
+
- rule: a dictionary with entries such as:
|
|
627
|
+
- uuid: a string
|
|
628
|
+
- name: a string
|
|
629
|
+
- state: a string (ENABLED or DISABLED)
|
|
630
|
+
- description: a string or None
|
|
631
|
+
- canOtherRuleTrigger: a boolean
|
|
632
|
+
- notifyOnError: a string (e.g. FIRSTERROR)
|
|
633
|
+
- authorAccountId: a string
|
|
634
|
+
- actor: a dictionary with keys type and actor (account ID)
|
|
635
|
+
- trigger: a dictionary describing the trigger component
|
|
636
|
+
- components: a list of dictionaries (conditions and actions)
|
|
637
|
+
- ruleScopeARIs: a list of strings (project ARIs)
|
|
638
|
+
- labels: a list of strings
|
|
639
|
+
- writeAccessType: a string (e.g. UNRESTRICTED)
|
|
640
|
+
- collaborators: a list
|
|
641
|
+
- created: a float (Unix timestamp)
|
|
642
|
+
- updated: a float (Unix timestamp)
|
|
643
|
+
|
|
644
|
+
- connections: a list of _connections_. Each connection is a
|
|
645
|
+
dictionary with entries such as:
|
|
646
|
+
- id: a string
|
|
647
|
+
- accountId: a string
|
|
648
|
+
- connectionTargetKey: a string
|
|
649
|
+
- targetConfigJson: a string
|
|
650
|
+
- authType: a string
|
|
651
|
+
- createdAt: a float (Unix timestamp)
|
|
652
|
+
- updatedAt: a float (Unix timestamp)
|
|
653
|
+
- container: a dictionary
|
|
654
|
+
"""
|
|
655
|
+
ensure_nonemptystring('product')
|
|
656
|
+
ensure_nonemptystring('site_id')
|
|
657
|
+
ensure_nonemptystring('rule_id')
|
|
658
|
+
|
|
659
|
+
session = self._get_session()
|
|
660
|
+
api = join_url(
|
|
661
|
+
self.url,
|
|
662
|
+
f'automation/public/{product}/{site_id}/rest/v1/rule/{rule_id}',
|
|
663
|
+
)
|
|
664
|
+
response = session.get(api)
|
|
665
|
+
return response.json()
|
|
666
|
+
|
|
667
|
+
@api_call
|
|
668
|
+
@require_basic_auth
|
|
669
|
+
def enable_rule(
|
|
670
|
+
self,
|
|
671
|
+
product: str,
|
|
672
|
+
site_id: str,
|
|
673
|
+
rule_id: str,
|
|
674
|
+
):
|
|
675
|
+
"""Enable a rule for a Jira product on a site.
|
|
676
|
+
# Required parameters
|
|
677
|
+
|
|
678
|
+
- product: a non-empty string (e.g. jira, confluence)
|
|
679
|
+
- site_id: a non-empty string (the site ID, not the URL)
|
|
680
|
+
- rule_id: a non-empty string
|
|
681
|
+
"""
|
|
682
|
+
ensure_nonemptystring('product')
|
|
683
|
+
ensure_nonemptystring('site_id')
|
|
684
|
+
ensure_nonemptystring('rule_id')
|
|
685
|
+
|
|
686
|
+
self._set_rule_state(product, site_id, rule_id, 'ENABLED')
|
|
687
|
+
|
|
688
|
+
@api_call
|
|
689
|
+
@require_basic_auth
|
|
690
|
+
def disable_rule(
|
|
691
|
+
self,
|
|
692
|
+
product: str,
|
|
693
|
+
site_id: str,
|
|
694
|
+
rule_id: str,
|
|
695
|
+
):
|
|
696
|
+
"""Disable a rule for a Jira product on a site.
|
|
697
|
+
|
|
698
|
+
# Required parameters
|
|
699
|
+
|
|
700
|
+
- product: a non-empty string (e.g. jira, confluence)
|
|
701
|
+
- site_id: a non-empty string (the site ID, not the URL)
|
|
702
|
+
- rule_id: a non-empty string
|
|
703
|
+
"""
|
|
704
|
+
ensure_nonemptystring('product')
|
|
705
|
+
ensure_nonemptystring('site_id')
|
|
706
|
+
ensure_nonemptystring('rule_id')
|
|
707
|
+
|
|
708
|
+
self._set_rule_state(product, site_id, rule_id, 'DISABLED')
|
|
709
|
+
|
|
541
710
|
####################################################################
|
|
542
711
|
# atlassian private helpers
|
|
543
712
|
|
|
713
|
+
def _set_rule_state(
|
|
714
|
+
self,
|
|
715
|
+
product: str,
|
|
716
|
+
site_id: str,
|
|
717
|
+
rule_id: str,
|
|
718
|
+
state: str,
|
|
719
|
+
) -> None:
|
|
720
|
+
self._put(
|
|
721
|
+
f'automation/public/{product}/{site_id}/rest/v1/rule/{rule_id}/state',
|
|
722
|
+
json={'value': state},
|
|
723
|
+
)
|
|
724
|
+
|
|
544
725
|
def _get(
|
|
545
726
|
self,
|
|
546
727
|
api: str,
|
|
@@ -551,6 +732,16 @@ class Atlassian:
|
|
|
551
732
|
api_url = join_url(self.url, api)
|
|
552
733
|
return session.get(api_url, params=params)
|
|
553
734
|
|
|
735
|
+
def _put(
|
|
736
|
+
self, api: str, json: Optional[Mapping[str, Any]] = None
|
|
737
|
+
) -> requests.Response:
|
|
738
|
+
session = self._get_session()
|
|
739
|
+
api_url = join_url(self.url, api)
|
|
740
|
+
return session.put(
|
|
741
|
+
api_url,
|
|
742
|
+
json=json,
|
|
743
|
+
)
|
|
744
|
+
|
|
554
745
|
def _collect_v2(
|
|
555
746
|
self,
|
|
556
747
|
api: str,
|
|
@@ -558,13 +749,13 @@ class Atlassian:
|
|
|
558
749
|
) -> list[dict[str, Any]]:
|
|
559
750
|
"""Return admin/v2 GET api call results, collected.
|
|
560
751
|
|
|
561
|
-
First page sends
|
|
752
|
+
First page sends params (server-side filters); subsequent
|
|
562
753
|
pages send the cursor alone -- it encodes the filters, and
|
|
563
754
|
mixing them again causes inconsistent results on some
|
|
564
|
-
endpoints (e.g. directory
|
|
755
|
+
endpoints (e.g. directory users/). Retries on HTTP 429.
|
|
565
756
|
"""
|
|
566
757
|
session = self._get_session()
|
|
567
|
-
api_url = join_url(join_url(self.
|
|
758
|
+
api_url = join_url(join_url(self.admin_url, '/v2'), api)
|
|
568
759
|
|
|
569
760
|
_params: dict[str, Any] = {
|
|
570
761
|
'limit': _V2_PAGE_SIZE,
|
|
@@ -249,6 +249,77 @@ class Confluence:
|
|
|
249
249
|
|
|
250
250
|
return self._collect_data('content/search', params=params)
|
|
251
251
|
|
|
252
|
+
@api_call
|
|
253
|
+
def audit(
|
|
254
|
+
self,
|
|
255
|
+
group: str,
|
|
256
|
+
actions: Optional[str] = None,
|
|
257
|
+
from_date: Optional[str] = None,
|
|
258
|
+
to_date: Optional[str] = None,
|
|
259
|
+
limit: int = 100,
|
|
260
|
+
scan_limit: Optional[int] = None,
|
|
261
|
+
) -> List[Dict[str, Any]]:
|
|
262
|
+
"""Return all audit log events matching a group filter.
|
|
263
|
+
|
|
264
|
+
# Required parameters
|
|
265
|
+
|
|
266
|
+
- group: a non-empty string
|
|
267
|
+
|
|
268
|
+
# Optional parameters
|
|
269
|
+
|
|
270
|
+
- actions: a string or None (None by default)
|
|
271
|
+
- from_date: a string or None (None by default)
|
|
272
|
+
- to_date: a string or None (None by default)
|
|
273
|
+
- limit: an integer (`100` by default)
|
|
274
|
+
- scan_limit: an integer or None (None by default)
|
|
275
|
+
|
|
276
|
+
# Returned value
|
|
277
|
+
|
|
278
|
+
A possibly empty list of audit log event dictionaries.
|
|
279
|
+
|
|
280
|
+
Handles pagination (i.e., it returns all matching events, not
|
|
281
|
+
only the first n events).
|
|
282
|
+
|
|
283
|
+
# Raised exceptions
|
|
284
|
+
|
|
285
|
+
If the request fails, an _ApiError_ is raised.
|
|
286
|
+
"""
|
|
287
|
+
ensure_nonemptystring('group')
|
|
288
|
+
ensure_noneorinstance('actions', str)
|
|
289
|
+
ensure_noneorinstance('from_date', str)
|
|
290
|
+
ensure_noneorinstance('to_date', str)
|
|
291
|
+
ensure_instance('limit', int)
|
|
292
|
+
ensure_noneorinstance('scan_limit', int)
|
|
293
|
+
|
|
294
|
+
params: Dict[str, Any] = {
|
|
295
|
+
'search': group,
|
|
296
|
+
'limit': limit,
|
|
297
|
+
}
|
|
298
|
+
add_if_specified(params, 'actions', actions)
|
|
299
|
+
add_if_specified(params, 'scanLimit', scan_limit)
|
|
300
|
+
add_if_specified(params, 'from', from_date)
|
|
301
|
+
add_if_specified(params, 'to', to_date)
|
|
302
|
+
|
|
303
|
+
all_events: List[Dict[str, Any]] = []
|
|
304
|
+
while True:
|
|
305
|
+
response = self.session().get(
|
|
306
|
+
join_url(self.url, '/rest/auditing/1.0/events'),
|
|
307
|
+
params=params,
|
|
308
|
+
)
|
|
309
|
+
if response.status_code // 100 != 2:
|
|
310
|
+
raise ApiError(response.text)
|
|
311
|
+
data = response.json()
|
|
312
|
+
entities = data.get('entities', [])
|
|
313
|
+
all_events.extend(entities)
|
|
314
|
+
paging = data.get('pagingInfo', {})
|
|
315
|
+
if paging.get('lastPage', True) or not entities:
|
|
316
|
+
break
|
|
317
|
+
cursor = paging.get('nextPageCursor')
|
|
318
|
+
if not cursor:
|
|
319
|
+
break
|
|
320
|
+
params['pageCursor'] = cursor
|
|
321
|
+
return all_events
|
|
322
|
+
|
|
252
323
|
####################################################################
|
|
253
324
|
# Confluence groups&users
|
|
254
325
|
#
|
|
@@ -957,6 +1028,8 @@ class Confluence:
|
|
|
957
1028
|
# list_space_permissionsets*
|
|
958
1029
|
# create_space
|
|
959
1030
|
# delete_space
|
|
1031
|
+
# archive_space
|
|
1032
|
+
# restore_space
|
|
960
1033
|
# add_space_label*
|
|
961
1034
|
# remove_space_permission*
|
|
962
1035
|
# add_space_permissions*
|
|
@@ -1262,6 +1335,40 @@ class Confluence:
|
|
|
1262
1335
|
response = self._delete(f'/space/{space_key}')
|
|
1263
1336
|
return response
|
|
1264
1337
|
|
|
1338
|
+
@api_call
|
|
1339
|
+
def archive_space(self, space_key: str) -> bool:
|
|
1340
|
+
"""Archive a space.
|
|
1341
|
+
|
|
1342
|
+
# Required parameters
|
|
1343
|
+
|
|
1344
|
+
- space_key: a non-empty string
|
|
1345
|
+
|
|
1346
|
+
# Returned value
|
|
1347
|
+
|
|
1348
|
+
A boolean, True if successful
|
|
1349
|
+
"""
|
|
1350
|
+
ensure_nonemptystring('space_key')
|
|
1351
|
+
|
|
1352
|
+
response = self._put(f'/space/{space_key}/archive')
|
|
1353
|
+
return response.status_code == 204
|
|
1354
|
+
|
|
1355
|
+
@api_call
|
|
1356
|
+
def restore_space(self, space_key: str) -> bool:
|
|
1357
|
+
"""Restore a space.
|
|
1358
|
+
|
|
1359
|
+
# Required parameters
|
|
1360
|
+
|
|
1361
|
+
- space_key: a non-empty string
|
|
1362
|
+
|
|
1363
|
+
# Returned value
|
|
1364
|
+
|
|
1365
|
+
A boolean, True if successful
|
|
1366
|
+
"""
|
|
1367
|
+
ensure_nonemptystring('space_key')
|
|
1368
|
+
|
|
1369
|
+
response = self._put(f'/space/{space_key}/restore')
|
|
1370
|
+
return response.status_code == 204
|
|
1371
|
+
|
|
1265
1372
|
@api_call
|
|
1266
1373
|
def add_space_label(self, space_key: str, label: str) -> bool:
|
|
1267
1374
|
"""Add label to space.
|
|
@@ -4582,12 +4582,10 @@ class GitHub:
|
|
|
4582
4582
|
|
|
4583
4583
|
remaining = response.headers.get('X-RateLimit-Remaining')
|
|
4584
4584
|
retry_after = response.headers.get('Retry-After')
|
|
4585
|
-
reset = response.headers.get('X-RateLimit-Reset')
|
|
4586
4585
|
if (
|
|
4587
4586
|
response.status_code == 403
|
|
4588
4587
|
and remaining != '0'
|
|
4589
4588
|
and retry_after is None
|
|
4590
|
-
and reset is None
|
|
4591
4589
|
):
|
|
4592
4590
|
return response
|
|
4593
4591
|
|