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.
Files changed (38) hide show
  1. suite_py/__version__.py +1 -1
  2. suite_py/cli.py +107 -185
  3. suite_py/commands/aggregator.py +3 -5
  4. suite_py/commands/ask_review.py +3 -5
  5. suite_py/commands/batch_job.py +1 -2
  6. suite_py/commands/bump.py +1 -2
  7. suite_py/commands/check.py +3 -5
  8. suite_py/commands/context.py +26 -0
  9. suite_py/commands/create_branch.py +1 -2
  10. suite_py/commands/deploy.py +3 -5
  11. suite_py/commands/docker.py +1 -2
  12. suite_py/commands/generator.py +1 -2
  13. suite_py/commands/id.py +1 -2
  14. suite_py/commands/ip.py +1 -2
  15. suite_py/commands/login.py +4 -173
  16. suite_py/commands/merge_pr.py +3 -4
  17. suite_py/commands/open_pr.py +4 -5
  18. suite_py/commands/project_lock.py +3 -5
  19. suite_py/commands/release.py +3 -5
  20. suite_py/commands/secret.py +1 -2
  21. suite_py/commands/set_token.py +1 -2
  22. suite_py/commands/status.py +3 -4
  23. suite_py/lib/config.py +1 -3
  24. suite_py/lib/handler/captainhook_handler.py +44 -54
  25. suite_py/lib/handler/metrics_handler.py +8 -6
  26. suite_py/lib/handler/okta_handler.py +81 -0
  27. suite_py/lib/logger.py +1 -0
  28. suite_py/lib/metrics.py +4 -2
  29. suite_py/lib/oauth.py +156 -0
  30. suite_py/lib/tokens.py +4 -0
  31. {suite_py-1.41.2.dist-info → suite_py-1.41.4.dist-info}/METADATA +2 -4
  32. suite_py-1.41.4.dist-info/RECORD +54 -0
  33. suite_py/commands/qa.py +0 -424
  34. suite_py/lib/handler/qainit_handler.py +0 -259
  35. suite_py-1.41.2.dist-info/RECORD +0 -53
  36. /suite_py/{commands/templates → templates}/login.html +0 -0
  37. {suite_py-1.41.2.dist-info → suite_py-1.41.4.dist-info}/WHEEL +0 -0
  38. {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
@@ -1,5 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  import logging
3
+
3
4
  import logzero
4
5
  from logzero import logger as _logger
5
6
 
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.2
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,<25.0.0)
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,,