suite-py 1.52.0__tar.gz → 1.53.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.
- {suite_py-1.52.0 → suite_py-1.53.0}/PKG-INFO +1 -1
- {suite_py-1.52.0 → suite_py-1.53.0}/pyproject.toml +1 -1
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/__version__.py +1 -1
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/ask_review.py +4 -6
- suite_py-1.53.0/suite_py/lib/handler/backstage_handler.py +167 -0
- suite_py-1.52.0/suite_py/lib/handler/backstage_handler.py +0 -82
- {suite_py-1.52.0 → suite_py-1.53.0}/LICENSE-APACHE +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/LICENSE-MIT +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/__init__.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/cli.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/__init__.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/bump.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/check.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/common.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/context.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/create_branch.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/estimate_cone.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/login.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/merge_pr.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/open_pr.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/project_lock.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/release.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/set_token.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/commands/status.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/__init__.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/config.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/__init__.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/aws_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/captainhook_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/changelog_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/frequent_reviewers_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/git_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/github_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/metrics_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/okta_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/pre_commit_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/prompt_utils.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/version_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/handler/youtrack_handler.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/logger.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/metrics.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/oauth.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/requests/__init__.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/requests/auth.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/requests/session.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/symbol.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/lib/tokens.py +0 -0
- {suite_py-1.52.0 → suite_py-1.53.0}/suite_py/templates/login.html +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.53.0"
|
|
@@ -10,11 +10,11 @@ from suite_py.lib.handler.youtrack_handler import YoutrackHandler
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class AskReview:
|
|
13
|
-
def __init__(self, project,
|
|
13
|
+
def __init__(self, project, backstage, config, tokens):
|
|
14
14
|
self._project = project
|
|
15
15
|
self._config = config
|
|
16
16
|
self._youtrack = YoutrackHandler(config, tokens)
|
|
17
|
-
self.
|
|
17
|
+
self._backstage = backstage
|
|
18
18
|
self._git = GitHandler(project, config)
|
|
19
19
|
self._github = GithubHandler(tokens)
|
|
20
20
|
self._frequent_reviewers = FrequentReviewersHandler(config)
|
|
@@ -32,13 +32,11 @@ class AskReview:
|
|
|
32
32
|
|
|
33
33
|
def _maybe_get_users_list(self):
|
|
34
34
|
try:
|
|
35
|
-
users = self.
|
|
35
|
+
users = self._backstage.get_users_list()
|
|
36
36
|
self._config.put_cache("users", users)
|
|
37
37
|
return users
|
|
38
38
|
except Exception:
|
|
39
|
-
logger.warning(
|
|
40
|
-
"Can't get users list from Captainhook. Using cached version."
|
|
41
|
-
)
|
|
39
|
+
logger.warning("Can't get users list from Backstage. Using cached version.")
|
|
42
40
|
return self._config.get_cache("users")
|
|
43
41
|
|
|
44
42
|
def _get_pr(self):
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
import ssl
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from suite_py.__version__ import __version__
|
|
7
|
+
from suite_py.lib.handler.github_handler import GithubHandler
|
|
8
|
+
from suite_py.lib.handler.okta_handler import Okta
|
|
9
|
+
from suite_py.lib import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BackstageError(Exception): ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Backstage:
|
|
16
|
+
_okta: Okta
|
|
17
|
+
_github: GithubHandler
|
|
18
|
+
|
|
19
|
+
def __init__(self, config, okta: Okta, tokens=None):
|
|
20
|
+
self._backstage_url = config.user["backstage_url"]
|
|
21
|
+
self._timeout = config.user["backstage_timeout"]
|
|
22
|
+
self._backstage_identity_token = None
|
|
23
|
+
|
|
24
|
+
# Under mise cacert are loaded from ~/.local/share/mise
|
|
25
|
+
# so we need to force system defaults to make warp happy
|
|
26
|
+
self._verify = ssl.get_default_verify_paths().capath
|
|
27
|
+
self._okta = okta
|
|
28
|
+
|
|
29
|
+
if tokens is not None:
|
|
30
|
+
self._github = GithubHandler(tokens)
|
|
31
|
+
|
|
32
|
+
def lock_project(self, project, env):
|
|
33
|
+
return self._request(
|
|
34
|
+
method="post",
|
|
35
|
+
url=self._entity_locking_endpoint("/projects/manage-lock"),
|
|
36
|
+
headers=self._okta_headers(),
|
|
37
|
+
raise_for_status=True,
|
|
38
|
+
json={
|
|
39
|
+
"project": project,
|
|
40
|
+
"status": "locked",
|
|
41
|
+
"user": self._get_user(),
|
|
42
|
+
"environment": env,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def unlock_project(self, project, env):
|
|
47
|
+
return self._request(
|
|
48
|
+
method="post",
|
|
49
|
+
url=self._entity_locking_endpoint("/projects/manage-lock"),
|
|
50
|
+
headers=self._okta_headers(),
|
|
51
|
+
raise_for_status=True,
|
|
52
|
+
json={
|
|
53
|
+
"project": project,
|
|
54
|
+
"status": "unlocked",
|
|
55
|
+
"user": self._get_user(),
|
|
56
|
+
"environment": env,
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def status(self, project, env):
|
|
61
|
+
return self._request(
|
|
62
|
+
method="get",
|
|
63
|
+
url=self._entity_locking_endpoint(
|
|
64
|
+
f"/projects/check?project={project}&environment={env}"
|
|
65
|
+
),
|
|
66
|
+
raise_for_status=True,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def check(self):
|
|
70
|
+
response = self._request(
|
|
71
|
+
method="get",
|
|
72
|
+
url=self._entity_locking_endpoint("/ping"),
|
|
73
|
+
headers=self._okta_headers(),
|
|
74
|
+
timeout=(2, self._timeout),
|
|
75
|
+
raise_for_status=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert response.text == "pong"
|
|
79
|
+
|
|
80
|
+
def get_users_list(self):
|
|
81
|
+
response = self._fetch_users()
|
|
82
|
+
|
|
83
|
+
if response.status_code == 401:
|
|
84
|
+
self._refresh_backstage_identity_token()
|
|
85
|
+
response = self._fetch_users()
|
|
86
|
+
|
|
87
|
+
response.raise_for_status()
|
|
88
|
+
|
|
89
|
+
return [
|
|
90
|
+
{"name": u.pop("displayName"), **u}
|
|
91
|
+
for u in (item["spec"]["profile"] for item in response.json()["items"])
|
|
92
|
+
if "github" in u and "youtrack" in u and "displayName" in u
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
def _request(self, method, url, **kwargs):
|
|
96
|
+
timeout = kwargs.pop("timeout", self._timeout)
|
|
97
|
+
verify = kwargs.pop("verify", self._verify)
|
|
98
|
+
allow_redirects = kwargs.pop("allow_redirects", False)
|
|
99
|
+
raise_for_status = kwargs.pop("raise_for_status", False)
|
|
100
|
+
|
|
101
|
+
response = requests.request(
|
|
102
|
+
method=method,
|
|
103
|
+
url=url,
|
|
104
|
+
timeout=timeout,
|
|
105
|
+
verify=verify,
|
|
106
|
+
allow_redirects=allow_redirects,
|
|
107
|
+
**kwargs,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
redirect = (
|
|
111
|
+
urlparse(response.headers["Location"])
|
|
112
|
+
if response.status_code == 302
|
|
113
|
+
else None
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if redirect is not None and redirect.netloc == "prima.cloudflareaccess.com":
|
|
117
|
+
logger.error(
|
|
118
|
+
"It looks like Cloudflare WARP is not active, please enable it and try again"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
raise BackstageError("Cloudflare Access authentication redirect")
|
|
122
|
+
|
|
123
|
+
if raise_for_status:
|
|
124
|
+
response.raise_for_status()
|
|
125
|
+
|
|
126
|
+
return response
|
|
127
|
+
|
|
128
|
+
def _entity_locking_endpoint(self, path):
|
|
129
|
+
return f"{self._backstage_url}{path}"
|
|
130
|
+
|
|
131
|
+
def _get_user(self):
|
|
132
|
+
return self._github.get_user().login
|
|
133
|
+
|
|
134
|
+
def _okta_headers(self):
|
|
135
|
+
return {
|
|
136
|
+
"User-Agent": f"suite-py/{__version__}",
|
|
137
|
+
"Authorization": f"Bearer {self._okta.get_id_token()}",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
def _backstage_headers(self):
|
|
141
|
+
if self._backstage_identity_token is None:
|
|
142
|
+
self._refresh_backstage_identity_token()
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
"User-Agent": f"suite-py/{__version__}",
|
|
146
|
+
"Authorization": f"Bearer {self._backstage_identity_token}",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
def _fetch_users(self):
|
|
150
|
+
fields = ",".join(
|
|
151
|
+
f"spec.profile.{f}" for f in ("displayName", "youtrack", "slack", "github")
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return self._request(
|
|
155
|
+
method="GET",
|
|
156
|
+
url=f"{self._backstage_url}/api/catalog/entities/by-query?filter=kind=user&fields={fields}&limit=500",
|
|
157
|
+
headers=self._backstage_headers(),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def _refresh_backstage_identity_token(self):
|
|
161
|
+
response = self._request(
|
|
162
|
+
method="GET",
|
|
163
|
+
url=f"{self._backstage_url}/api/auth/cfaccess/refresh",
|
|
164
|
+
raise_for_status=True,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
self._backstage_identity_token = response.json()["backstageIdentity"]["token"]
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
import requests
|
|
3
|
-
|
|
4
|
-
from suite_py.__version__ import __version__
|
|
5
|
-
from suite_py.lib.handler.github_handler import GithubHandler
|
|
6
|
-
from suite_py.lib.handler.okta_handler import Okta
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class BackstageResponseError(Exception): ...
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Backstage:
|
|
13
|
-
_okta: Okta
|
|
14
|
-
_github: GithubHandler
|
|
15
|
-
|
|
16
|
-
def __init__(self, config, okta: Okta, tokens=None):
|
|
17
|
-
backstage_url = config.user["backstage_url"]
|
|
18
|
-
self._baseurl = f"{backstage_url}/api/entity-locking"
|
|
19
|
-
self._timeout = config.user["backstage_timeout"]
|
|
20
|
-
self._okta = okta
|
|
21
|
-
|
|
22
|
-
if tokens is not None:
|
|
23
|
-
self._github = GithubHandler(tokens)
|
|
24
|
-
|
|
25
|
-
def lock_project(self, project, env):
|
|
26
|
-
return self.send_request(
|
|
27
|
-
"post",
|
|
28
|
-
"/projects/manage-lock",
|
|
29
|
-
json={
|
|
30
|
-
"project": project,
|
|
31
|
-
"status": "locked",
|
|
32
|
-
"user": self._get_user(),
|
|
33
|
-
"environment": env,
|
|
34
|
-
},
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
def unlock_project(self, project, env):
|
|
38
|
-
return self.send_request(
|
|
39
|
-
"post",
|
|
40
|
-
"/projects/manage-lock",
|
|
41
|
-
json={
|
|
42
|
-
"project": project,
|
|
43
|
-
"status": "unlocked",
|
|
44
|
-
"user": self._get_user(),
|
|
45
|
-
"environment": env,
|
|
46
|
-
},
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
def status(self, project, env):
|
|
50
|
-
return self.send_request(
|
|
51
|
-
"get", f"/projects/check?project={project}&environment={env}"
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
def check(self):
|
|
55
|
-
response = self.send_request("get", "/ping", timeout=(2, self._timeout))
|
|
56
|
-
assert response.text == "pong"
|
|
57
|
-
|
|
58
|
-
def send_request(self, method, endpoint, data=None, json=None, timeout=None):
|
|
59
|
-
response = requests.request(
|
|
60
|
-
method,
|
|
61
|
-
f"{self._baseurl}{endpoint}",
|
|
62
|
-
headers=self._headers(),
|
|
63
|
-
data=data,
|
|
64
|
-
json=json,
|
|
65
|
-
timeout=(timeout or self._timeout),
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
if response.status_code == 200:
|
|
69
|
-
return response
|
|
70
|
-
|
|
71
|
-
raise BackstageResponseError(
|
|
72
|
-
f"Got unexpected status code from Backstage: {response.status_code}"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
def _get_user(self):
|
|
76
|
-
return self._github.get_user().login
|
|
77
|
-
|
|
78
|
-
def _headers(self):
|
|
79
|
-
return {
|
|
80
|
-
"User-Agent": f"suite-py/{__version__}",
|
|
81
|
-
"Authorization": f"Bearer {self._okta.get_id_token()}",
|
|
82
|
-
}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|