suite-py 1.41.2__py3-none-any.whl → 1.41.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- suite_py/__version__.py +1 -1
- suite_py/cli.py +107 -185
- suite_py/commands/aggregator.py +3 -5
- suite_py/commands/ask_review.py +3 -5
- suite_py/commands/batch_job.py +1 -2
- suite_py/commands/bump.py +1 -2
- suite_py/commands/check.py +3 -5
- suite_py/commands/context.py +26 -0
- suite_py/commands/create_branch.py +1 -2
- suite_py/commands/deploy.py +3 -5
- suite_py/commands/docker.py +1 -2
- suite_py/commands/generator.py +1 -2
- suite_py/commands/id.py +1 -2
- suite_py/commands/ip.py +1 -2
- suite_py/commands/login.py +4 -173
- suite_py/commands/merge_pr.py +3 -4
- suite_py/commands/open_pr.py +4 -5
- suite_py/commands/project_lock.py +3 -5
- suite_py/commands/release.py +3 -5
- suite_py/commands/secret.py +1 -2
- suite_py/commands/set_token.py +1 -2
- suite_py/commands/status.py +3 -4
- suite_py/lib/config.py +1 -3
- suite_py/lib/handler/captainhook_handler.py +44 -54
- suite_py/lib/handler/metrics_handler.py +8 -6
- suite_py/lib/handler/okta_handler.py +81 -0
- suite_py/lib/logger.py +1 -0
- suite_py/lib/metrics.py +4 -2
- suite_py/lib/oauth.py +156 -0
- suite_py/lib/tokens.py +4 -0
- {suite_py-1.41.2.dist-info → suite_py-1.41.4.dist-info}/METADATA +2 -4
- suite_py-1.41.4.dist-info/RECORD +54 -0
- suite_py/commands/qa.py +0 -424
- suite_py/lib/handler/qainit_handler.py +0 -259
- suite_py-1.41.2.dist-info/RECORD +0 -53
- /suite_py/{commands/templates → templates}/login.html +0 -0
- {suite_py-1.41.2.dist-info → suite_py-1.41.4.dist-info}/WHEEL +0 -0
- {suite_py-1.41.2.dist-info → suite_py-1.41.4.dist-info}/entry_points.txt +0 -0
suite_py/lib/logger.py
CHANGED
suite_py/lib/metrics.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
import functools
|
|
3
3
|
from suite_py.lib.config import Config
|
|
4
|
+
|
|
5
|
+
from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
4
6
|
from suite_py.lib.handler.metrics_handler import Metrics
|
|
5
7
|
|
|
6
8
|
_metrics_handler = None
|
|
@@ -15,9 +17,9 @@ def _metrics() -> Metrics:
|
|
|
15
17
|
)
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
def setup(config: Config):
|
|
20
|
+
def setup(config: Config, captainhook: CaptainHook):
|
|
19
21
|
global _metrics_handler
|
|
20
|
-
_metrics_handler = Metrics(config)
|
|
22
|
+
_metrics_handler = Metrics(config=config, captainhook=captainhook)
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def command_executed(command):
|
suite_py/lib/oauth.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import secrets
|
|
4
|
+
import typing
|
|
5
|
+
import urllib.parse
|
|
6
|
+
import webbrowser
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
9
|
+
from os import path
|
|
10
|
+
from urllib.parse import parse_qs, urlparse
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
import suite_py
|
|
15
|
+
from suite_py.lib import logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class OAuthTokenResponse:
|
|
20
|
+
access_token: str
|
|
21
|
+
id_token: typing.Optional[str]
|
|
22
|
+
refresh_token: typing.Optional[str]
|
|
23
|
+
expires_in: float
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def retrieve_token(base_url, params) -> OAuthTokenResponse:
|
|
27
|
+
url = f"{base_url}/token"
|
|
28
|
+
headers = {
|
|
29
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
30
|
+
"Accept": "application/json",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
data = requests.post(url, headers=headers, data=params, timeout=30).json()
|
|
34
|
+
logger.debug(data)
|
|
35
|
+
|
|
36
|
+
if error := data.get("error_description", None):
|
|
37
|
+
raise Exception(f"OAuth error: {error}")
|
|
38
|
+
|
|
39
|
+
return OAuthTokenResponse(
|
|
40
|
+
access_token=data["access_token"],
|
|
41
|
+
expires_in=data["expires_in"],
|
|
42
|
+
id_token=data.get("id_token", None),
|
|
43
|
+
refresh_token=data.get("refresh_token", None),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class OAuthCallbackServer(HTTPServer):
|
|
48
|
+
received_state: typing.Optional[str] = None
|
|
49
|
+
error_message: typing.Optional[str] = None
|
|
50
|
+
code: typing.Optional[str] = None
|
|
51
|
+
|
|
52
|
+
def __init__(self, server_address) -> None:
|
|
53
|
+
super().__init__(server_address, OAuthCallbackRequestHandler)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class OAuthCallbackRequestHandler(BaseHTTPRequestHandler):
|
|
57
|
+
def do_GET(self):
|
|
58
|
+
assert isinstance(self.server, OAuthCallbackServer)
|
|
59
|
+
|
|
60
|
+
server = self.server
|
|
61
|
+
args = parse_qs(urlparse(self.path).query)
|
|
62
|
+
|
|
63
|
+
server.received_state = args["state"][0]
|
|
64
|
+
if "error" in args:
|
|
65
|
+
error = args["error"][0]
|
|
66
|
+
error_description = args["error_description"][0]
|
|
67
|
+
server.error_message = f"{error}: {error_description}"
|
|
68
|
+
else:
|
|
69
|
+
server.code = args["code"][0]
|
|
70
|
+
|
|
71
|
+
self.send_response(200)
|
|
72
|
+
self.send_header("content-type", "text/html")
|
|
73
|
+
self.end_headers()
|
|
74
|
+
|
|
75
|
+
template = path.join(path.dirname(suite_py.__file__), "templates/login.html")
|
|
76
|
+
with open(template, "rb") as f:
|
|
77
|
+
self.wfile.write(f.read())
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _url_encode_no_padding(byte_data):
|
|
81
|
+
"""
|
|
82
|
+
Safe encoding handles + and /, and also replace = with nothing
|
|
83
|
+
"""
|
|
84
|
+
return base64.urlsafe_b64encode(byte_data).decode("utf-8").replace("=", "")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _generate_challenge(a_verifier):
|
|
88
|
+
return _url_encode_no_padding(hashlib.sha256(a_verifier.encode()).digest())
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def authorization_code_flow(
|
|
92
|
+
client_id,
|
|
93
|
+
base_url,
|
|
94
|
+
scope,
|
|
95
|
+
redirect_uri="http://127.0.0.1:5000/callback",
|
|
96
|
+
listen=("0.0.0.0", 5000),
|
|
97
|
+
):
|
|
98
|
+
# from https://auth0.com/docs/flows/add-login-using-the-authorization-code-flow-with-pkce
|
|
99
|
+
# Step1: Create code verifier: Generate a code_verifier that will be sent to Auth0 to request tokens.
|
|
100
|
+
verifier = _url_encode_no_padding(secrets.token_bytes(32))
|
|
101
|
+
# Step2: Create code challenge: Generate a code_challenge from the code_verifier that will be sent to Auth0 to request an authorization_code.
|
|
102
|
+
challenge = _generate_challenge(verifier)
|
|
103
|
+
state = _url_encode_no_padding(secrets.token_bytes(32))
|
|
104
|
+
|
|
105
|
+
# We generate a nonce (state) that is used to protect against attackers invoking the callback
|
|
106
|
+
url = f"{base_url}/authorize?"
|
|
107
|
+
url_parameters = {
|
|
108
|
+
"scope": scope,
|
|
109
|
+
"response_type": "code",
|
|
110
|
+
"redirect_uri": redirect_uri,
|
|
111
|
+
"client_id": client_id,
|
|
112
|
+
"code_challenge": challenge.replace("=", ""),
|
|
113
|
+
"code_challenge_method": "S256",
|
|
114
|
+
"state": state,
|
|
115
|
+
}
|
|
116
|
+
url = url + urllib.parse.urlencode(url_parameters)
|
|
117
|
+
|
|
118
|
+
# Step3: Authorize user: Request the user's authorization and redirect back to your app with an authorization_code.
|
|
119
|
+
# Open the browser window to the login url
|
|
120
|
+
# Start the server
|
|
121
|
+
logger.info("A browser tab should've opened. If not manually navigate to: " + url)
|
|
122
|
+
webbrowser.open(url)
|
|
123
|
+
|
|
124
|
+
server = OAuthCallbackServer(listen)
|
|
125
|
+
server.handle_request()
|
|
126
|
+
|
|
127
|
+
if state != server.received_state:
|
|
128
|
+
raise Exception(
|
|
129
|
+
"Error: session replay or similar attack in progress. Please log out of all connections."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if server.error_message:
|
|
133
|
+
raise Exception(server.error_message)
|
|
134
|
+
|
|
135
|
+
# Step4: Request tokens: Exchange your authorization_code and code_verifier for tokens.
|
|
136
|
+
body = {
|
|
137
|
+
"grant_type": "authorization_code",
|
|
138
|
+
"client_id": client_id,
|
|
139
|
+
"code_verifier": verifier,
|
|
140
|
+
"code": server.code,
|
|
141
|
+
"redirect_uri": redirect_uri,
|
|
142
|
+
}
|
|
143
|
+
return retrieve_token(base_url, body)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def do_refresh_token(
|
|
147
|
+
client_id: str, base_url: str, scope: str, refresh_token: str
|
|
148
|
+
) -> OAuthTokenResponse:
|
|
149
|
+
# See https://developer.okta.com/docs/guides/refresh-tokens/main/
|
|
150
|
+
params = {
|
|
151
|
+
"scope": scope,
|
|
152
|
+
"client_id": client_id,
|
|
153
|
+
"grant_type": "refresh_token",
|
|
154
|
+
"refresh_token": refresh_token,
|
|
155
|
+
}
|
|
156
|
+
return retrieve_token(base_url, params)
|
suite_py/lib/tokens.py
CHANGED
|
@@ -149,6 +149,7 @@ class Tokens:
|
|
|
149
149
|
|
|
150
150
|
def edit(self, service, token):
|
|
151
151
|
self._tokens[service] = token
|
|
152
|
+
self.save()
|
|
152
153
|
|
|
153
154
|
def keys(self):
|
|
154
155
|
return self._tokens.keys()
|
|
@@ -164,3 +165,6 @@ class Tokens:
|
|
|
164
165
|
@property
|
|
165
166
|
def drone(self):
|
|
166
167
|
return self._tokens["drone"]
|
|
168
|
+
|
|
169
|
+
def okta(self):
|
|
170
|
+
return self._tokens.get("okta", {})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: suite-py
|
|
3
|
-
Version: 1.41.
|
|
3
|
+
Version: 1.41.4
|
|
4
4
|
Summary:
|
|
5
5
|
Author: larrywax, EugenioLaghi, michelangelomo
|
|
6
6
|
Author-email: devops@prima.it
|
|
@@ -12,12 +12,10 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Requires-Dist: Click (>=7.0)
|
|
15
|
-
Requires-Dist: Flask (==1.1.2)
|
|
16
15
|
Requires-Dist: InquirerPy (>=0.2.0)
|
|
17
16
|
Requires-Dist: Jinja2 (>=2.11,<3.0.0)
|
|
18
17
|
Requires-Dist: PyGithub (>=1.57)
|
|
19
18
|
Requires-Dist: PyYaml (>=5.4)
|
|
20
|
-
Requires-Dist: Werkzeug (==2.0.2)
|
|
21
19
|
Requires-Dist: autoupgrade-prima (>=0.6)
|
|
22
20
|
Requires-Dist: black (>=22.6,<25.0)
|
|
23
21
|
Requires-Dist: boto3 (>=1.17.84)
|
|
@@ -27,7 +25,7 @@ Requires-Dist: cryptography (==42.0.5)
|
|
|
27
25
|
Requires-Dist: halo (>=0.0.28)
|
|
28
26
|
Requires-Dist: inquirer (==3.1.4)
|
|
29
27
|
Requires-Dist: itsdangerous (==2.0.1)
|
|
30
|
-
Requires-Dist: keyring (>=23.9.1,<
|
|
28
|
+
Requires-Dist: keyring (>=23.9.1,<26.0.0)
|
|
31
29
|
Requires-Dist: kubernetes (==29.0.0)
|
|
32
30
|
Requires-Dist: logzero (==1.7.0)
|
|
33
31
|
Requires-Dist: markupsafe (==2.0.1)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
suite_py/__init__.py,sha256=REmi3D0X2G1ZWnYpKs8Ffm3NIj-Hw6dMuvz2b9NW344,142
|
|
2
|
+
suite_py/__version__.py,sha256=AhkyH0YLIU7gH3Em5wzBcxR3qqCht93E136t28Bl6Yk,49
|
|
3
|
+
suite_py/cli.py,sha256=LDv8ppD4Sua-TKeNnAtdUvkQ9qql6kMzOqGueyIepYw,14961
|
|
4
|
+
suite_py/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
suite_py/commands/aggregator.py,sha256=_xyyX7MOMZzDEvzj2AI03OF3sut5PpSIyuRjUiCp5aI,5747
|
|
6
|
+
suite_py/commands/ask_review.py,sha256=yN__Ac-fiZBPShjRDhyCCQZGfVlQE16KozoJk4UtiNw,3788
|
|
7
|
+
suite_py/commands/batch_job.py,sha256=pcSpDov9uNY4z9MjQ8KDapEC9zhEJDe7XcJV7uidHyE,7450
|
|
8
|
+
suite_py/commands/bump.py,sha256=oFZU1hPfD11ujFC5G7wFyQOf2alY3xp2SO1h1ldjf3s,5406
|
|
9
|
+
suite_py/commands/check.py,sha256=0e2NsPi3cqvCwtNYzhR1UroT59bCojLlGo69vv3FiOA,4074
|
|
10
|
+
suite_py/commands/common.py,sha256=aWCEvO3hqdheuMUmZcHuc9EGZPQTk7VkzkHJk283MxQ,566
|
|
11
|
+
suite_py/commands/context.py,sha256=coK1O1XZ1nhtduEvZNpYJQ0RkTCXhrLYCbK3IBniDkQ,753
|
|
12
|
+
suite_py/commands/create_branch.py,sha256=cDpeDsQk1AK70GkWz-hTduaEeU65x1wck1b-5nKIMew,4424
|
|
13
|
+
suite_py/commands/deploy.py,sha256=kadgbVKUMtE_X4b8oWaQ1ufS4Qy9b2WuutPDXnjm4t4,8088
|
|
14
|
+
suite_py/commands/docker.py,sha256=POz_VXOXEQaFZCafkH-grgB2_HZFrckAc0CpB9IgOiU,2932
|
|
15
|
+
suite_py/commands/generator.py,sha256=-wQFRS0UNc-EvuYvnj3gk6DHTVSsg9lA-NMU2kQewb8,8510
|
|
16
|
+
suite_py/commands/id.py,sha256=qMQMSH_bGDInarYaGOpX2lEGf1tyHBeAV5GQiNr5Kiw,1998
|
|
17
|
+
suite_py/commands/ip.py,sha256=pbyVuee_cN507qUYSBv5gWcvKLYolOeuU_w_P7P7nVc,2396
|
|
18
|
+
suite_py/commands/login.py,sha256=A59e1HsbN7Ocv2L_2H0Eb7MZK7AzLkLb72QxBthnIqU,258
|
|
19
|
+
suite_py/commands/merge_pr.py,sha256=gUpoDx3q23X6gF9PLXCZnIL9DfRw_3D0LlqBlGVt7rA,5676
|
|
20
|
+
suite_py/commands/open_pr.py,sha256=U7MVl-JFKu1mdfxC_UvxUHtPLEln0g4kKl-SvP-6zd8,7159
|
|
21
|
+
suite_py/commands/project_lock.py,sha256=b7OkGysue_Sl13VIT7B5CTBppCvrB_Q6iC0IJRBSHp8,1909
|
|
22
|
+
suite_py/commands/release.py,sha256=clKgmsNgR9DdgBkYjXI3NqVaw8_mCe2TZHegby3ESn4,16634
|
|
23
|
+
suite_py/commands/secret.py,sha256=IOPQBTXsi8qFd84yxGe38U9b1x53dHguoLolCTOtRoU,7995
|
|
24
|
+
suite_py/commands/set_token.py,sha256=fehIqKjKhE-BJGFhgkPTo3Ntr0MvpgLd6EC5yjKuRs8,1508
|
|
25
|
+
suite_py/commands/status.py,sha256=0JUK53_d1-U3WNS742JD2QTiGmCGZONo3jJx8WR7q70,1122
|
|
26
|
+
suite_py/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
suite_py/lib/config.py,sha256=4uKXAav8E-VXlXGPnkYQ_fZYyi6058S0FM2JmyV8L2k,3970
|
|
28
|
+
suite_py/lib/handler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
suite_py/lib/handler/aws_handler.py,sha256=dRvRDicikfRbuFtCLPbevaX-yC-fO4LwXFdyqLPJ8OI,8815
|
|
30
|
+
suite_py/lib/handler/captainhook_handler.py,sha256=vS-RNhUgQ7FriCdBDEd2Ci04B5FJ0p7gi5woCaoEeY8,3254
|
|
31
|
+
suite_py/lib/handler/changelog_handler.py,sha256=-ppnRl3smBA_ys8tPqXmytS4eyntlwfawC2fiXFcwlw,4818
|
|
32
|
+
suite_py/lib/handler/drone_handler.py,sha256=rmtzu30OQyG3vRPlbZKsQhHN9zbguPtXO0RpDjYOTPA,8967
|
|
33
|
+
suite_py/lib/handler/frequent_reviewers_handler.py,sha256=EIJX5FEMWzrxuXS9A17hu1vfxgJSOHSBX_ahCEZ2FVA,2185
|
|
34
|
+
suite_py/lib/handler/git_handler.py,sha256=boxhl9lQz6fjEJ10ib1KrDW-geCVjhA_6nKwv2ll01g,11333
|
|
35
|
+
suite_py/lib/handler/github_handler.py,sha256=AnFL54yOZ5GDIU91wQat4s-d1WTcmg_B_5M7-Rop3wA,2900
|
|
36
|
+
suite_py/lib/handler/metrics_handler.py,sha256=-Tp62pFIiYsBkDga0nQG3lWU-gxH68wEjIIIJeU1jHk,3159
|
|
37
|
+
suite_py/lib/handler/okta_handler.py,sha256=3GEnJxbLXlu2zjFWniYwG0m1mFKJGfske1t-uUUdpZU,2545
|
|
38
|
+
suite_py/lib/handler/prompt_utils.py,sha256=vgk1O7h-iYEAZv1sXtMh8xIgH1djI398rzxRIgZWZcg,2474
|
|
39
|
+
suite_py/lib/handler/vault_handler.py,sha256=r4osw7qwz3ZFmLg2U1oFPdtRFcXzDXiaWBZC01cYK_w,871
|
|
40
|
+
suite_py/lib/handler/version_handler.py,sha256=DXTx4yCAbFVC6CdMqPJ-LiN5YM-dT2zklG8POyKTP5A,6774
|
|
41
|
+
suite_py/lib/handler/youtrack_handler.py,sha256=eTGBBXjlN_ay1cawtnZ2IG6l78dDyKdMN1x6PxcvtA0,7499
|
|
42
|
+
suite_py/lib/logger.py,sha256=q_qRtpg0Eh7r5tB-Rwz87dnWBwP-a2dIvggkiQikH8M,782
|
|
43
|
+
suite_py/lib/metrics.py,sha256=hGGrWg_c3uTz4xhpb7POGZh_xhIAYU3drRo-KgwpBvM,1626
|
|
44
|
+
suite_py/lib/oauth.py,sha256=IFJNrVlUXZ7GewgUmwpVP3aZYFLVSQZPramlYjzqDq4,5012
|
|
45
|
+
suite_py/lib/requests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
suite_py/lib/requests/auth.py,sha256=wN_WtGFmDUWRFilWzOmUaRBvP2n3EPpPMqex9Zjddko,228
|
|
47
|
+
suite_py/lib/requests/session.py,sha256=P32H3cWnCWunu91WIj2iDM5U3HzaBglg60VN_C9JBL4,267
|
|
48
|
+
suite_py/lib/symbol.py,sha256=z3QYBuNIwD3qQ3zF-cLOomIr_-C3bO_u5UIDAHMiyTo,60
|
|
49
|
+
suite_py/lib/tokens.py,sha256=kK4vatd5iKEFaue0BZwK1f66YOh9zLdLzP41ZOhjMCU,5534
|
|
50
|
+
suite_py/templates/login.html,sha256=fJLls2SB84oZTSrxTdA5q1PqfvIHcCD4fhVWfyco7Ig,861
|
|
51
|
+
suite_py-1.41.4.dist-info/METADATA,sha256=1DDdO0XjzywlKTQtz0oevgk8Ds0j6XsoR4iF69ccXrw,1531
|
|
52
|
+
suite_py-1.41.4.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
53
|
+
suite_py-1.41.4.dist-info/entry_points.txt,sha256=dVKLC-9Infy-dHJT_MkK6LcDjOgBCJ8lfPkURJhBjxE,46
|
|
54
|
+
suite_py-1.41.4.dist-info/RECORD,,
|