github-org-manager 0.7.3__tar.gz → 0.7.5__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.
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/PKG-INFO +12 -5
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/gh_org_mgr/_config.py +93 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/gh_org_mgr/_gh_org.py +99 -25
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/gh_org_mgr/_helpers.py +1 -1
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/gh_org_mgr/manage.py +4 -2
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/pyproject.toml +12 -10
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/LICENSE.txt +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/LICENSES/Apache-2.0.txt +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/LICENSES/CC-BY-4.0.txt +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/LICENSES/CC0-1.0.txt +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/LICENSES/MIT.txt +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/README.md +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/gh_org_mgr/__init__.py +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/gh_org_mgr/_gh_api.py +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/gh_org_mgr/_setup_team.py +0 -0
- {github_org_manager-0.7.3 → github_org_manager-0.7.5}/gh_org_mgr/_stats.py +0 -0
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: github-org-manager
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.5
|
|
4
4
|
Summary: Manage a GitHub Organization, its teams, repository permissions, and more
|
|
5
5
|
License: Apache-2.0
|
|
6
|
+
License-File: LICENSE.txt
|
|
7
|
+
License-File: LICENSES/Apache-2.0.txt
|
|
8
|
+
License-File: LICENSES/CC-BY-4.0.txt
|
|
9
|
+
License-File: LICENSES/CC0-1.0.txt
|
|
10
|
+
License-File: LICENSES/MIT.txt
|
|
6
11
|
Keywords: github,github-management,permissions,access-control
|
|
7
12
|
Author: Max Mehl
|
|
8
13
|
Author-email: max.mehl@deutschebahn.com
|
|
@@ -17,12 +22,14 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
26
|
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
21
27
|
Classifier: Topic :: Utilities
|
|
22
|
-
Requires-Dist:
|
|
28
|
+
Requires-Dist: jsonschema (>=4.25.1,<5.0.0)
|
|
29
|
+
Requires-Dist: pygithub (>=2.8.1,<3.0.0)
|
|
23
30
|
Requires-Dist: python-slugify (>=8.0.4,<9.0.0)
|
|
24
|
-
Requires-Dist: pyyaml (>=6.0.
|
|
25
|
-
Requires-Dist: requests (>=2.32.
|
|
31
|
+
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
32
|
+
Requires-Dist: requests (>=2.32.5,<3.0.0)
|
|
26
33
|
Project-URL: Repository, https://github.com/OpenRailAssociation/github-org-manager
|
|
27
34
|
Description-Content-Type: text/markdown
|
|
28
35
|
|
|
@@ -11,6 +11,8 @@ import sys
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
import yaml
|
|
14
|
+
from jsonschema import FormatChecker, validate
|
|
15
|
+
from jsonschema.exceptions import ValidationError
|
|
14
16
|
|
|
15
17
|
# Global files with settings for the app and org, e.g. GitHub token and org name
|
|
16
18
|
ORG_CONFIG_FILE = r"org\.ya?ml"
|
|
@@ -18,6 +20,84 @@ APP_CONFIG_FILE = r"app\.ya?ml"
|
|
|
18
20
|
TEAM_CONFIG_DIR = "teams"
|
|
19
21
|
TEAM_CONFIG_FILES = r".+\.ya?ml"
|
|
20
22
|
|
|
23
|
+
# Schemas for config validation
|
|
24
|
+
APP_CONFIG_SCHEMA = {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"github_token": {"type": "string"},
|
|
28
|
+
"github_app_id": {"type": "integer"},
|
|
29
|
+
"github_app_private_key": {"type": "string"},
|
|
30
|
+
"remove_members_without_team": {"type": "boolean"},
|
|
31
|
+
"delete_unconfigured_teams": {"type": "boolean"},
|
|
32
|
+
},
|
|
33
|
+
"additionalProperties": False,
|
|
34
|
+
}
|
|
35
|
+
ORG_CONFIG_SCHEMA = {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"org_name": {"type": "string"},
|
|
39
|
+
"org_owners": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"items": {"type": "string"},
|
|
42
|
+
"minItems": 1,
|
|
43
|
+
},
|
|
44
|
+
"defaults": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"properties": {
|
|
47
|
+
"team": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"description": {"type": "string"},
|
|
51
|
+
"privacy": {"type": "string", "enum": ["secret", "closed"]},
|
|
52
|
+
"notification_setting": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"enum": ["notifications_enabled", "notifications_disabled"],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
"additionalProperties": False,
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"additionalProperties": False,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
"additionalProperties": False,
|
|
64
|
+
"required": ["org_name", "org_owners"],
|
|
65
|
+
}
|
|
66
|
+
TEAM_CONFIG_SCHEMA = {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"patternProperties": {
|
|
69
|
+
"^[a-zA-Z0-9 _\\-]+$": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"properties": {
|
|
72
|
+
"description": {"type": "string"},
|
|
73
|
+
"privacy": {"type": "string", "enum": ["secret", "closed"]},
|
|
74
|
+
"notification_setting": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"enum": ["notifications_enabled", "notifications_disabled"],
|
|
77
|
+
},
|
|
78
|
+
"maintainer": {
|
|
79
|
+
"oneOf": [{"type": "null"}, {"type": "array", "items": {"type": "string"}}]
|
|
80
|
+
},
|
|
81
|
+
"member": {
|
|
82
|
+
"oneOf": [{"type": "null"}, {"type": "array", "items": {"type": "string"}}]
|
|
83
|
+
},
|
|
84
|
+
"parent": {"type": "string"},
|
|
85
|
+
"repos": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"propertyNames": {"type": "string"},
|
|
88
|
+
"additionalProperties": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"enum": ["pull", "triage", "push", "maintain", "admin"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
"additionalProperties": False,
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"additionalProperties": False,
|
|
98
|
+
"required": [],
|
|
99
|
+
}
|
|
100
|
+
|
|
21
101
|
|
|
22
102
|
def _find_matching_files(directory: str, pattern: str, only_one: bool = False) -> list[str]:
|
|
23
103
|
"""
|
|
@@ -85,6 +165,16 @@ def _read_config_file(file: str) -> dict:
|
|
|
85
165
|
return config
|
|
86
166
|
|
|
87
167
|
|
|
168
|
+
def _validate_config_schema(file: str, cfg: dict, schema: dict) -> None:
|
|
169
|
+
"""Validate the config against a JSON schema"""
|
|
170
|
+
try:
|
|
171
|
+
validate(instance=cfg, schema=schema, format_checker=FormatChecker())
|
|
172
|
+
except ValidationError as e:
|
|
173
|
+
logging.critical("Config validation of file %s failed: %s", file, e.message)
|
|
174
|
+
raise ValueError(e) from None
|
|
175
|
+
logging.debug("Config in file %s validated successfully against schema.", file)
|
|
176
|
+
|
|
177
|
+
|
|
88
178
|
def parse_config_files(path: str) -> tuple[dict[str, str | dict[str, str]], dict, dict]:
|
|
89
179
|
"""Parse all relevant files in the configuration directory. Returns a tuple
|
|
90
180
|
of org config, app config, and merged teams config"""
|
|
@@ -95,7 +185,9 @@ def parse_config_files(path: str) -> tuple[dict[str, str | dict[str, str]], dict
|
|
|
95
185
|
|
|
96
186
|
# Read and parse config files for app and org
|
|
97
187
|
cfg_app = _read_config_file(cfg_app_files[0])
|
|
188
|
+
_validate_config_schema(file=cfg_app_files[0], cfg=cfg_app, schema=APP_CONFIG_SCHEMA)
|
|
98
189
|
cfg_org = _read_config_file(cfg_org_files[0])
|
|
190
|
+
_validate_config_schema(file=cfg_org_files[0], cfg=cfg_org, schema=ORG_CONFIG_SCHEMA)
|
|
99
191
|
|
|
100
192
|
# For the teams config files, we parse and combine them as there may be multiple
|
|
101
193
|
cfg_teams: dict[str, Any] = {}
|
|
@@ -103,6 +195,7 @@ def parse_config_files(path: str) -> tuple[dict[str, str | dict[str, str]], dict
|
|
|
103
195
|
# Compare their keys (team names). They must not be defined multiple times!
|
|
104
196
|
for cfg_team_file in cfg_teams_files:
|
|
105
197
|
cfg = _read_config_file(cfg_team_file)
|
|
198
|
+
_validate_config_schema(file=cfg_team_file, cfg=cfg, schema=TEAM_CONFIG_SCHEMA)
|
|
106
199
|
if overlap := set(cfg_teams.keys()) & set(cfg.keys()):
|
|
107
200
|
logging.critical(
|
|
108
201
|
"The config file '%s' contains keys that are also defined in "
|
|
@@ -8,14 +8,12 @@ import logging
|
|
|
8
8
|
import sys
|
|
9
9
|
from dataclasses import asdict, dataclass, field
|
|
10
10
|
|
|
11
|
-
from github import
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
from github import Auth, Github, GithubIntegration
|
|
12
|
+
from github.GithubException import (
|
|
13
|
+
BadCredentialsException,
|
|
14
14
|
GithubException,
|
|
15
|
-
GithubIntegration,
|
|
16
15
|
UnknownObjectException,
|
|
17
16
|
)
|
|
18
|
-
from github.GithubException import BadCredentialsException
|
|
19
17
|
from github.NamedUser import NamedUser
|
|
20
18
|
from github.Organization import Organization
|
|
21
19
|
from github.Repository import Repository
|
|
@@ -47,6 +45,7 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
47
45
|
configured_org_owners: list[str] = field(default_factory=list)
|
|
48
46
|
org_members: list[NamedUser] = field(default_factory=list)
|
|
49
47
|
current_teams: dict[Team, dict] = field(default_factory=dict)
|
|
48
|
+
current_teams_str: list[str] = field(default_factory=list)
|
|
50
49
|
configured_teams: dict[str, dict | None] = field(default_factory=dict)
|
|
51
50
|
newly_added_users: list[NamedUser] = field(default_factory=list)
|
|
52
51
|
current_repos_teams: dict[Repository, dict[Team, str]] = field(default_factory=dict)
|
|
@@ -54,6 +53,7 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
54
53
|
current_repos_collaborators: dict[Repository, dict[str, str]] = field(default_factory=dict)
|
|
55
54
|
configured_repos_collaborators: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
56
55
|
archived_repos: list[Repository] = field(default_factory=list)
|
|
56
|
+
unconfigured_teams: list[Team] = field(default_factory=list)
|
|
57
57
|
unconfigured_team_repo_permissions: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
58
58
|
stats: OrgChanges = field(default_factory=OrgChanges)
|
|
59
59
|
|
|
@@ -84,8 +84,9 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
84
84
|
|
|
85
85
|
# Decide how to login. If app set, prefer this
|
|
86
86
|
if self.gh_app_id and self.gh_app_private_key:
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
gh_app_id_sanitized = str(self.gh_app_id).strip("'").strip('"')
|
|
88
|
+
logging.debug("Logging in via app %s", gh_app_id_sanitized)
|
|
89
|
+
auth = Auth.AppAuth(app_id=gh_app_id_sanitized, private_key=self.gh_app_private_key)
|
|
89
90
|
app = GithubIntegration(auth=auth)
|
|
90
91
|
try:
|
|
91
92
|
installation = app.get_org_installation(org=orgname)
|
|
@@ -202,9 +203,16 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
202
203
|
return True
|
|
203
204
|
|
|
204
205
|
def _is_user_authenticated_user(self, user: NamedUser) -> bool:
|
|
205
|
-
"""Check if a given NamedUser is the authenticated user
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
"""Check if a given NamedUser is the authenticated user. If logging in via App, this will
|
|
207
|
+
always return False, as the authenticated user is the App itself"""
|
|
208
|
+
try:
|
|
209
|
+
if user.login == self.gh.get_user().login:
|
|
210
|
+
return True
|
|
211
|
+
except GithubException as e:
|
|
212
|
+
if e.status == 403 and "Resource not accessible by integration" in str(e):
|
|
213
|
+
logging.debug("Cannot check if user is authenticated, as this is an App login")
|
|
214
|
+
return False
|
|
215
|
+
raise
|
|
208
216
|
return False
|
|
209
217
|
|
|
210
218
|
def sync_org_owners(self, dry: bool = False, force: bool = False) -> None:
|
|
@@ -283,20 +291,78 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
283
291
|
"""Get teams of the existing organisation"""
|
|
284
292
|
for team in list(self.org.get_teams()):
|
|
285
293
|
self.current_teams[team] = {"members": {}, "repos": {}}
|
|
294
|
+
self.current_teams_str = [team.name for team in self.current_teams]
|
|
286
295
|
|
|
287
|
-
def
|
|
288
|
-
"""
|
|
296
|
+
def ensure_team_hierarchy(self) -> None:
|
|
297
|
+
"""Check if all configured parent teams make sense: either they exist already or will be
|
|
298
|
+
created during this run"""
|
|
289
299
|
|
|
290
300
|
# Get list of current teams
|
|
291
301
|
self._get_current_teams()
|
|
292
302
|
|
|
293
|
-
#
|
|
294
|
-
|
|
303
|
+
# First, check whether all configured parent teams exist or will be created
|
|
304
|
+
for team, attributes in self.configured_teams.items():
|
|
305
|
+
if parent := attributes.get("parent"): # type: ignore
|
|
306
|
+
if parent not in self.configured_teams:
|
|
307
|
+
if parent not in self.current_teams_str:
|
|
308
|
+
logging.critical(
|
|
309
|
+
"The team '%s' is configured with parent team '%s', but this parent "
|
|
310
|
+
"team does not exist and is not configured to be created. "
|
|
311
|
+
"Cannot continue.",
|
|
312
|
+
team,
|
|
313
|
+
parent,
|
|
314
|
+
)
|
|
315
|
+
sys.exit(1)
|
|
316
|
+
else:
|
|
317
|
+
logging.debug(
|
|
318
|
+
"The team '%s' is configured with parent team '%s', "
|
|
319
|
+
"which already exists",
|
|
320
|
+
team,
|
|
321
|
+
parent,
|
|
322
|
+
)
|
|
323
|
+
else:
|
|
324
|
+
logging.debug(
|
|
325
|
+
"The team '%s' is configured with parent team '%s', "
|
|
326
|
+
"which will be created during this run",
|
|
327
|
+
team,
|
|
328
|
+
parent,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Second, order the teams in a way that parent teams are created before child teams
|
|
332
|
+
ordered_teams: dict[str, dict | None] = {}
|
|
333
|
+
while len(ordered_teams) < len(self.configured_teams):
|
|
334
|
+
for team, attributes in self.configured_teams.items():
|
|
335
|
+
# Team already ordered
|
|
336
|
+
if team in ordered_teams:
|
|
337
|
+
continue
|
|
338
|
+
# Team has parent, but parent not ordered yet
|
|
339
|
+
if parent := attributes.get("parent"): # type: ignore
|
|
340
|
+
if parent not in ordered_teams:
|
|
341
|
+
continue
|
|
342
|
+
# Team has no parent, or parent already ordered
|
|
343
|
+
ordered_teams[team] = attributes
|
|
344
|
+
# Overwrite configured teams with ordered ones
|
|
345
|
+
self.configured_teams = ordered_teams
|
|
346
|
+
|
|
347
|
+
def create_missing_teams(self, dry: bool = False) -> None:
|
|
348
|
+
"""Find out which teams are configured but not part of the org yet"""
|
|
295
349
|
|
|
296
350
|
for team, attributes in self.configured_teams.items():
|
|
297
|
-
if team not in
|
|
351
|
+
if team not in self.current_teams_str:
|
|
352
|
+
# If a parent team is configured, try to get its ID
|
|
298
353
|
if parent := attributes.get("parent"): # type: ignore
|
|
299
|
-
|
|
354
|
+
try:
|
|
355
|
+
parent_id = self.org.get_team_by_slug(sluggify_teamname(parent)).id
|
|
356
|
+
except UnknownObjectException:
|
|
357
|
+
if dry:
|
|
358
|
+
logging.debug(
|
|
359
|
+
"For team %s, the configured parent team's ('%s') ID wasn't found, "
|
|
360
|
+
"probably because it should be created but it's a dry-run. "
|
|
361
|
+
"We set a default ID of 424242",
|
|
362
|
+
team,
|
|
363
|
+
parent,
|
|
364
|
+
)
|
|
365
|
+
parent_id = 424242
|
|
300
366
|
|
|
301
367
|
logging.info("Creating team '%s' with parent ID '%s'", team, parent_id)
|
|
302
368
|
self.stats.create_team(team)
|
|
@@ -310,6 +376,7 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
310
376
|
privacy="closed",
|
|
311
377
|
)
|
|
312
378
|
|
|
379
|
+
# No parent team configured
|
|
313
380
|
else:
|
|
314
381
|
logging.info("Creating team '%s' without parent", team)
|
|
315
382
|
self.stats.create_team(team)
|
|
@@ -594,31 +661,38 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
594
661
|
team.name,
|
|
595
662
|
)
|
|
596
663
|
|
|
597
|
-
def
|
|
664
|
+
def get_and_delete_unconfigured_teams(
|
|
598
665
|
self, dry: bool = False, delete_unconfigured_teams: bool = False
|
|
599
666
|
) -> None:
|
|
600
667
|
"""Get all teams that are not configured locally and optionally remove them"""
|
|
601
668
|
# Get all teams that are not configured locally
|
|
602
|
-
unconfigured_teams: list[Team] = []
|
|
603
669
|
for team in self.current_teams:
|
|
604
670
|
if team.name not in self.configured_teams:
|
|
605
|
-
unconfigured_teams.append(team)
|
|
671
|
+
self.unconfigured_teams.append(team)
|
|
606
672
|
|
|
607
|
-
if unconfigured_teams:
|
|
673
|
+
if self.unconfigured_teams:
|
|
608
674
|
if delete_unconfigured_teams:
|
|
609
|
-
for team in unconfigured_teams:
|
|
675
|
+
for team in self.unconfigured_teams:
|
|
610
676
|
logging.info("Deleting team '%s' as it is not configured locally", team.name)
|
|
611
677
|
self.stats.delete_team(team=team.name, deleted=True)
|
|
612
678
|
if not dry:
|
|
613
|
-
|
|
679
|
+
try:
|
|
680
|
+
team.delete()
|
|
681
|
+
except UnknownObjectException as e:
|
|
682
|
+
logging.info(
|
|
683
|
+
"Team '%s' could not be deleted, probably because it was already "
|
|
684
|
+
"deleted as part of a parent team. "
|
|
685
|
+
"Error: %s",
|
|
686
|
+
team.name,
|
|
687
|
+
e,
|
|
688
|
+
)
|
|
614
689
|
else:
|
|
615
|
-
unconfigured_teams_str = [team.name for team in unconfigured_teams]
|
|
616
690
|
logging.warning(
|
|
617
691
|
"The following teams of your GitHub organisation are not "
|
|
618
692
|
"configured locally: %s. Taking no action about these teams.",
|
|
619
|
-
", ".join(
|
|
693
|
+
", ".join([team.name for team in self.unconfigured_teams]),
|
|
620
694
|
)
|
|
621
|
-
for team in unconfigured_teams:
|
|
695
|
+
for team in self.unconfigured_teams:
|
|
622
696
|
self.stats.delete_team(team=team.name, deleted=False)
|
|
623
697
|
|
|
624
698
|
def get_members_without_team(
|
|
@@ -134,6 +134,9 @@ def main():
|
|
|
134
134
|
# Synchronise organisation owners
|
|
135
135
|
log_progress("Synchronising organisation owners...")
|
|
136
136
|
org.sync_org_owners(dry=args.dry, force=args.force)
|
|
137
|
+
# Validate parent/child team relationships
|
|
138
|
+
log_progress("Validating team hierarchy...")
|
|
139
|
+
org.ensure_team_hierarchy()
|
|
137
140
|
# Create teams that aren't present at Github yet
|
|
138
141
|
log_progress("Creating missing teams...")
|
|
139
142
|
org.create_missing_teams(dry=args.dry)
|
|
@@ -145,7 +148,7 @@ def main():
|
|
|
145
148
|
org.sync_teams_members(dry=args.dry)
|
|
146
149
|
# Report and act on teams that are not configured locally
|
|
147
150
|
log_progress("Checking for unconfigured teams...")
|
|
148
|
-
org.
|
|
151
|
+
org.get_and_delete_unconfigured_teams(
|
|
149
152
|
dry=args.dry,
|
|
150
153
|
delete_unconfigured_teams=cfg_app.get("delete_unconfigured_teams", False),
|
|
151
154
|
)
|
|
@@ -164,7 +167,6 @@ def main():
|
|
|
164
167
|
org.sync_repo_collaborator_permissions(dry=args.dry)
|
|
165
168
|
|
|
166
169
|
# Debug output
|
|
167
|
-
log_progress("") # clear progress
|
|
168
170
|
logging.debug("Final dataclass:\n%s", org.pretty_print_dataclass())
|
|
169
171
|
org.ratelimit()
|
|
170
172
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "github-org-manager"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.5"
|
|
8
8
|
description = "Manage a GitHub Organization, its teams, repository permissions, and more"
|
|
9
9
|
authors = ["Max Mehl <max.mehl@deutschebahn.com>"]
|
|
10
10
|
readme = "README.md"
|
|
@@ -27,19 +27,21 @@ gh-org-mgr = 'gh_org_mgr.manage:main'
|
|
|
27
27
|
|
|
28
28
|
[tool.poetry.dependencies]
|
|
29
29
|
python = "^3.10"
|
|
30
|
-
pygithub = "^2.
|
|
31
|
-
pyyaml = "^6.0.
|
|
32
|
-
requests = "^2.32.
|
|
30
|
+
pygithub = "^2.8.1"
|
|
31
|
+
pyyaml = "^6.0.3"
|
|
32
|
+
requests = "^2.32.5"
|
|
33
33
|
python-slugify = "^8.0.4"
|
|
34
|
+
jsonschema = "^4.25.1"
|
|
34
35
|
|
|
35
36
|
[tool.poetry.group.dev.dependencies]
|
|
36
|
-
black = "^25.
|
|
37
|
+
black = "^25.9.0"
|
|
37
38
|
isort = ">=5.13.2,<7.0.0"
|
|
38
|
-
mypy = "^1.
|
|
39
|
-
pylint = "^3.
|
|
40
|
-
types-pyyaml = "^6.0.12.
|
|
41
|
-
types-requests = "^2.32.
|
|
42
|
-
bump-my-version = "^1.
|
|
39
|
+
mypy = "^1.18.2"
|
|
40
|
+
pylint = "^3.3.8"
|
|
41
|
+
types-pyyaml = "^6.0.12.20250915"
|
|
42
|
+
types-requests = "^2.32.4.20250913"
|
|
43
|
+
bump-my-version = "^1.2.3"
|
|
44
|
+
types-jsonschema = "^4.25.1.20250822"
|
|
43
45
|
|
|
44
46
|
[build-system]
|
|
45
47
|
requires = ["poetry-core"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|