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
|
@@ -4,8 +4,7 @@ import sys
|
|
|
4
4
|
|
|
5
5
|
import requests
|
|
6
6
|
|
|
7
|
-
from suite_py.lib import logger
|
|
8
|
-
from suite_py.lib import metrics
|
|
7
|
+
from suite_py.lib import logger, metrics
|
|
9
8
|
from suite_py.lib.handler import prompt_utils
|
|
10
9
|
from suite_py.lib.handler.git_handler import GitHandler, is_branch_name_valid
|
|
11
10
|
from suite_py.lib.handler.youtrack_handler import YoutrackHandler
|
suite_py/commands/deploy.py
CHANGED
|
@@ -4,11 +4,9 @@ import sys
|
|
|
4
4
|
|
|
5
5
|
from suite_py.commands import common
|
|
6
6
|
from suite_py.commands.release import _parse_available_countries
|
|
7
|
-
from suite_py.lib import logger
|
|
8
|
-
from suite_py.lib import metrics
|
|
7
|
+
from suite_py.lib import logger, metrics
|
|
9
8
|
from suite_py.lib.handler import git_handler as git
|
|
10
9
|
from suite_py.lib.handler import prompt_utils
|
|
11
|
-
from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
12
10
|
from suite_py.lib.handler.changelog_handler import ChangelogHandler
|
|
13
11
|
from suite_py.lib.handler.drone_handler import DroneHandler
|
|
14
12
|
from suite_py.lib.handler.git_handler import GitHandler
|
|
@@ -19,11 +17,11 @@ from suite_py.lib.handler.youtrack_handler import YoutrackHandler
|
|
|
19
17
|
|
|
20
18
|
class Deploy:
|
|
21
19
|
# pylint: disable=too-many-instance-attributes
|
|
22
|
-
def __init__(self, project, config, tokens):
|
|
20
|
+
def __init__(self, project, captainhook, config, tokens):
|
|
23
21
|
self._project = project
|
|
24
22
|
self._config = config
|
|
25
23
|
self._youtrack = YoutrackHandler(config, tokens)
|
|
26
|
-
self._captainhook =
|
|
24
|
+
self._captainhook = captainhook
|
|
27
25
|
self._changelog_handler = ChangelogHandler()
|
|
28
26
|
self._github = GithubHandler(tokens)
|
|
29
27
|
self._repo = self._github.get_repo(project)
|
suite_py/commands/docker.py
CHANGED
|
@@ -3,8 +3,7 @@ import sys
|
|
|
3
3
|
|
|
4
4
|
from halo import Halo
|
|
5
5
|
|
|
6
|
-
from suite_py.lib import logger
|
|
7
|
-
from suite_py.lib import metrics
|
|
6
|
+
from suite_py.lib import logger, metrics
|
|
8
7
|
from suite_py.lib.handler import prompt_utils
|
|
9
8
|
from suite_py.lib.handler.drone_handler import DroneHandler
|
|
10
9
|
from suite_py.lib.handler.git_handler import GitHandler
|
suite_py/commands/generator.py
CHANGED
|
@@ -9,8 +9,7 @@ from os.path import isfile, join
|
|
|
9
9
|
import yaml
|
|
10
10
|
from jinja2 import DebugUndefined, Environment, FileSystemLoader
|
|
11
11
|
|
|
12
|
-
from suite_py.lib import logger
|
|
13
|
-
from suite_py.lib import metrics
|
|
12
|
+
from suite_py.lib import logger, metrics
|
|
14
13
|
from suite_py.lib.handler import prompt_utils
|
|
15
14
|
from suite_py.lib.handler.drone_handler import DroneHandler
|
|
16
15
|
from suite_py.lib.handler.git_handler import GitHandler
|
suite_py/commands/id.py
CHANGED
suite_py/commands/ip.py
CHANGED
suite_py/commands/login.py
CHANGED
|
@@ -1,180 +1,11 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import hashlib
|
|
3
|
-
import logging
|
|
4
|
-
import secrets
|
|
5
|
-
import sys
|
|
6
|
-
import threading
|
|
7
|
-
import urllib
|
|
8
|
-
import webbrowser
|
|
9
|
-
from time import sleep
|
|
10
|
-
|
|
11
|
-
import requests
|
|
12
|
-
from flask import Flask, render_template, request
|
|
13
|
-
from werkzeug.serving import make_server
|
|
14
|
-
|
|
15
|
-
from suite_py.lib import logger
|
|
16
1
|
from suite_py.lib import metrics
|
|
17
|
-
|
|
18
|
-
# Global vars for comunication between flask thread and main cli function
|
|
19
|
-
received_callback = None
|
|
20
|
-
code = None
|
|
21
|
-
error_message = None
|
|
22
|
-
received_state = None
|
|
23
|
-
|
|
24
|
-
# Global Flask App
|
|
25
|
-
app = Flask(__name__, template_folder="templates")
|
|
26
|
-
log = logging.getLogger("werkzeug")
|
|
27
|
-
log.disabled = True
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@app.route("/callback")
|
|
31
|
-
def callback():
|
|
32
|
-
"""# pylint: disable-next=missing-timeout
|
|
33
|
-
The callback is invoked after a completed login attempt (succesful or otherwise).
|
|
34
|
-
It sets global variables with the auth code or error messages, then sets the
|
|
35
|
-
polling flag received_callback.
|
|
36
|
-
:return:
|
|
37
|
-
"""
|
|
38
|
-
global received_callback, code, error_message, received_state
|
|
39
|
-
error_message = None
|
|
40
|
-
code = None
|
|
41
|
-
if "error" in request.args:
|
|
42
|
-
error_message = request.args["error"] + ": " + request.args["error_description"]
|
|
43
|
-
else:
|
|
44
|
-
code = request.args["code"]
|
|
45
|
-
received_state = request.args["state"]
|
|
46
|
-
received_callback = True
|
|
47
|
-
return render_template("login.html")
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class ServerThread(threading.Thread):
|
|
51
|
-
"""
|
|
52
|
-
The Flask server is done this way to allow shutting down after a single request has been received.
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
def __init__(self, app):
|
|
56
|
-
threading.Thread.__init__(self)
|
|
57
|
-
self.srv = make_server("127.0.0.1", 5000, app)
|
|
58
|
-
self.ctx = app.app_context()
|
|
59
|
-
self.ctx.push()
|
|
60
|
-
|
|
61
|
-
def run(self):
|
|
62
|
-
# logger.debug('starting server')
|
|
63
|
-
self.srv.serve_forever()
|
|
64
|
-
|
|
65
|
-
def shutdown(self):
|
|
66
|
-
self.srv.shutdown()
|
|
2
|
+
from suite_py.lib.handler.okta_handler import Okta
|
|
67
3
|
|
|
68
4
|
|
|
69
5
|
class Login:
|
|
70
|
-
def __init__(self, config):
|
|
71
|
-
|
|
72
|
-
try:
|
|
73
|
-
if not config.okta["client_id"] or not config.okta["base_url"]:
|
|
74
|
-
self.usage()
|
|
75
|
-
sys.exit(-1)
|
|
76
|
-
except AttributeError:
|
|
77
|
-
self.usage()
|
|
78
|
-
sys.exit(-1)
|
|
79
|
-
|
|
80
|
-
self._config = config
|
|
81
|
-
self.okta_client_id = config.okta["client_id"]
|
|
82
|
-
self.base_url = config.okta["base_url"]
|
|
83
|
-
self.okta_scope = "openid"
|
|
84
|
-
self.redirect_uri = "http://127.0.0.1:5000/callback"
|
|
85
|
-
|
|
86
|
-
def usage(self):
|
|
87
|
-
logger.warning("Unable to login: missing config")
|
|
88
|
-
logger.warning(
|
|
89
|
-
"please check docs: https://github.com/primait/suite_py/blob/master/README.md"
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
def url_encode_no_padding(self, byte_data):
|
|
93
|
-
"""
|
|
94
|
-
Safe encoding handles + and /, and also replace = with nothing
|
|
95
|
-
:param byte_data:
|
|
96
|
-
:return:
|
|
97
|
-
"""
|
|
98
|
-
return base64.urlsafe_b64encode(byte_data).decode("utf-8").replace("=", "")
|
|
99
|
-
|
|
100
|
-
def generate_challenge(self, a_verifier):
|
|
101
|
-
return self.url_encode_no_padding(hashlib.sha256(a_verifier.encode()).digest())
|
|
6
|
+
def __init__(self, config, tokens):
|
|
7
|
+
self._okta = Okta(config, tokens)
|
|
102
8
|
|
|
103
9
|
@metrics.command("login")
|
|
104
10
|
def run(self):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# from https://auth0.com/docs/flows/add-login-using-the-authorization-code-flow-with-pkce
|
|
108
|
-
# Step1: Create code verifier: Generate a code_verifier that will be sent to Auth0 to request tokens.
|
|
109
|
-
verifier = self.url_encode_no_padding(secrets.token_bytes(32))
|
|
110
|
-
# Step2: Create code challenge: Generate a code_challenge from the code_verifier that will be sent to Auth0 to request an authorization_code.
|
|
111
|
-
challenge = self.generate_challenge(verifier)
|
|
112
|
-
state = self.url_encode_no_padding(secrets.token_bytes(32))
|
|
113
|
-
client_id = self.okta_client_id
|
|
114
|
-
base_url = self.base_url
|
|
115
|
-
scope = self.okta_scope
|
|
116
|
-
redirect_uri = self.redirect_uri
|
|
117
|
-
#
|
|
118
|
-
# We generate a nonce (state) that is used to protect against attackers invoking the callback
|
|
119
|
-
url = f"{base_url}/authorize?"
|
|
120
|
-
url_parameters = {
|
|
121
|
-
"scope": scope,
|
|
122
|
-
"response_type": "code",
|
|
123
|
-
"redirect_uri": redirect_uri,
|
|
124
|
-
"client_id": client_id,
|
|
125
|
-
"code_challenge": challenge.replace("=", ""),
|
|
126
|
-
"code_challenge_method": "S256",
|
|
127
|
-
"state": state,
|
|
128
|
-
}
|
|
129
|
-
url = url + urllib.parse.urlencode(url_parameters)
|
|
130
|
-
|
|
131
|
-
# Step3: Authorize user: Request the user's authorization and redirect back to your app with an authorization_code.
|
|
132
|
-
# Open the browser window to the login url
|
|
133
|
-
# Start the server
|
|
134
|
-
# Poll til the callback has been invoked
|
|
135
|
-
received_callback = False
|
|
136
|
-
logger.info(
|
|
137
|
-
"A browser tab should've opened. If not manually navigate to: " + url
|
|
138
|
-
)
|
|
139
|
-
webbrowser.open(url)
|
|
140
|
-
server = ServerThread(app)
|
|
141
|
-
server.start()
|
|
142
|
-
while not received_callback:
|
|
143
|
-
sleep(1)
|
|
144
|
-
server.shutdown()
|
|
145
|
-
|
|
146
|
-
if state != received_state:
|
|
147
|
-
logger.error(
|
|
148
|
-
"Error: session replay or similar attack in progress. Please log out of all connections."
|
|
149
|
-
)
|
|
150
|
-
sys.exit(1)
|
|
151
|
-
|
|
152
|
-
if error_message:
|
|
153
|
-
logger.error("An error occurred:")
|
|
154
|
-
logger.error(error_message)
|
|
155
|
-
sys.exit(1)
|
|
156
|
-
|
|
157
|
-
# Step4: Request tokens: Exchange your authorization_code and code_verifier for tokens.
|
|
158
|
-
url = f"{base_url}/token"
|
|
159
|
-
headers = {
|
|
160
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
161
|
-
"Accept": "application/json",
|
|
162
|
-
}
|
|
163
|
-
body = {
|
|
164
|
-
"grant_type": "authorization_code",
|
|
165
|
-
"client_id": client_id,
|
|
166
|
-
"code_verifier": verifier,
|
|
167
|
-
"code": code,
|
|
168
|
-
"redirect_uri": redirect_uri,
|
|
169
|
-
}
|
|
170
|
-
# pylint: disable-next=missing-timeout
|
|
171
|
-
r = requests.post(url, headers=headers, data=body)
|
|
172
|
-
data = r.json()
|
|
173
|
-
logger.debug(data)
|
|
174
|
-
if "id_token" in data:
|
|
175
|
-
token = str(data["id_token"])
|
|
176
|
-
else:
|
|
177
|
-
logger.error("id_token not found in okta response")
|
|
178
|
-
sys.exit(1)
|
|
179
|
-
logger.info("login succeded")
|
|
180
|
-
self._config.put_cache(f"{base_url}_token", token)
|
|
11
|
+
self._okta.login()
|
suite_py/commands/merge_pr.py
CHANGED
|
@@ -3,8 +3,7 @@ import sys
|
|
|
3
3
|
|
|
4
4
|
from halo import Halo
|
|
5
5
|
|
|
6
|
-
from suite_py.lib import logger
|
|
7
|
-
from suite_py.lib import metrics
|
|
6
|
+
from suite_py.lib import logger, metrics
|
|
8
7
|
from suite_py.lib.handler import git_handler as git
|
|
9
8
|
from suite_py.lib.handler import prompt_utils
|
|
10
9
|
from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
@@ -16,11 +15,11 @@ from suite_py.lib.symbol import CHECKMARK, CROSSMARK
|
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class MergePR:
|
|
19
|
-
def __init__(self, project, config, tokens):
|
|
18
|
+
def __init__(self, project, captainhook: CaptainHook, config, tokens):
|
|
20
19
|
self._project = project
|
|
21
20
|
self._config = config
|
|
22
21
|
self._youtrack = YoutrackHandler(config, tokens)
|
|
23
|
-
self._captainhook =
|
|
22
|
+
self._captainhook = captainhook
|
|
24
23
|
self._git = GitHandler(project, config)
|
|
25
24
|
self._github = GithubHandler(tokens)
|
|
26
25
|
self._drone = DroneHandler(config, tokens, repo=project)
|
suite_py/commands/open_pr.py
CHANGED
|
@@ -3,9 +3,7 @@ import sys
|
|
|
3
3
|
|
|
4
4
|
from github import GithubException
|
|
5
5
|
|
|
6
|
-
from suite_py.
|
|
7
|
-
from suite_py.lib import logger
|
|
8
|
-
from suite_py.lib import metrics
|
|
6
|
+
from suite_py.lib import logger, metrics
|
|
9
7
|
from suite_py.lib.handler import prompt_utils
|
|
10
8
|
from suite_py.lib.handler.git_handler import GitHandler, get_commit_logs
|
|
11
9
|
from suite_py.lib.handler.github_handler import GithubHandler
|
|
@@ -13,7 +11,8 @@ from suite_py.lib.handler.youtrack_handler import YoutrackHandler
|
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class OpenPR:
|
|
16
|
-
def __init__(self, project, config, tokens):
|
|
14
|
+
def __init__(self, ask_review, project, config, tokens):
|
|
15
|
+
self._ask_review = ask_review
|
|
17
16
|
self._project = project
|
|
18
17
|
self._config = config
|
|
19
18
|
self._tokens = tokens
|
|
@@ -115,7 +114,7 @@ class OpenPR:
|
|
|
115
114
|
return
|
|
116
115
|
|
|
117
116
|
if prompt_utils.ask_confirm("Do you want to insert reviewers?"):
|
|
118
|
-
|
|
117
|
+
self._ask_review.run()
|
|
119
118
|
elif youtrack_id:
|
|
120
119
|
self._detect_and_move_new_pr_with_reviewers(youtrack_id, pr)
|
|
121
120
|
|
|
@@ -3,17 +3,15 @@ import sys
|
|
|
3
3
|
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
|
-
from suite_py.lib import logger
|
|
7
|
-
from suite_py.lib import metrics
|
|
8
|
-
from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
6
|
+
from suite_py.lib import logger, metrics
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
class ProjectLock:
|
|
12
|
-
def __init__(self, project, env, action,
|
|
10
|
+
def __init__(self, project, env, action, captainhook):
|
|
13
11
|
self._project = project
|
|
14
12
|
self._env = _parse_env(env)
|
|
15
13
|
self._action = action
|
|
16
|
-
self._captainhook =
|
|
14
|
+
self._captainhook = captainhook
|
|
17
15
|
|
|
18
16
|
@metrics.command("manage-project-lock")
|
|
19
17
|
def run(self):
|
suite_py/commands/release.py
CHANGED
|
@@ -6,11 +6,9 @@ import semver
|
|
|
6
6
|
from halo import Halo
|
|
7
7
|
|
|
8
8
|
from suite_py.commands import common
|
|
9
|
-
from suite_py.lib import logger
|
|
10
|
-
from suite_py.lib import metrics
|
|
9
|
+
from suite_py.lib import logger, metrics
|
|
11
10
|
from suite_py.lib.handler import git_handler as git
|
|
12
11
|
from suite_py.lib.handler import prompt_utils
|
|
13
|
-
from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
14
12
|
from suite_py.lib.handler.changelog_handler import ChangelogHandler
|
|
15
13
|
from suite_py.lib.handler.drone_handler import DroneHandler
|
|
16
14
|
from suite_py.lib.handler.git_handler import GitHandler
|
|
@@ -21,7 +19,7 @@ from suite_py.lib.handler.youtrack_handler import YoutrackHandler
|
|
|
21
19
|
|
|
22
20
|
class Release:
|
|
23
21
|
# pylint: disable=too-many-instance-attributes
|
|
24
|
-
def __init__(self, action, project, config, tokens, flags=None):
|
|
22
|
+
def __init__(self, action, project, captainhook, config, tokens, flags=None):
|
|
25
23
|
self._action = action
|
|
26
24
|
self._project = project
|
|
27
25
|
self._flags = flags
|
|
@@ -29,7 +27,7 @@ class Release:
|
|
|
29
27
|
self._tokens = tokens
|
|
30
28
|
self._changelog_handler = ChangelogHandler()
|
|
31
29
|
self._youtrack = YoutrackHandler(config, tokens)
|
|
32
|
-
self._captainhook =
|
|
30
|
+
self._captainhook = captainhook
|
|
33
31
|
self._github = GithubHandler(tokens)
|
|
34
32
|
self._repo = self._github.get_repo(project)
|
|
35
33
|
self._git = GitHandler(project, config)
|
suite_py/commands/secret.py
CHANGED
suite_py/commands/set_token.py
CHANGED
suite_py/commands/status.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from halo import Halo
|
|
3
3
|
|
|
4
|
-
from suite_py.lib import logger
|
|
5
|
-
from suite_py.lib import metrics
|
|
4
|
+
from suite_py.lib import logger, metrics
|
|
6
5
|
from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
7
6
|
from suite_py.lib.symbol import CHECKMARK, CROSSMARK
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class Status:
|
|
11
|
-
def __init__(self, project,
|
|
10
|
+
def __init__(self, project, captainhook: CaptainHook):
|
|
12
11
|
self._project = project
|
|
13
|
-
self._captainhook =
|
|
12
|
+
self._captainhook = captainhook
|
|
14
13
|
|
|
15
14
|
@metrics.command("status")
|
|
16
15
|
def run(self):
|
suite_py/lib/config.py
CHANGED
|
@@ -78,9 +78,7 @@ class Config:
|
|
|
78
78
|
) as cache_file:
|
|
79
79
|
return json.load(cache_file)
|
|
80
80
|
except Exception:
|
|
81
|
-
logger.
|
|
82
|
-
f"I couldn't find any cached version for the key {key}. Turn on the VPN."
|
|
83
|
-
)
|
|
81
|
+
logger.debug(f"I couldn't find any cached version for the key {key}.")
|
|
84
82
|
return ""
|
|
85
83
|
# sys.exit(-1)
|
|
86
84
|
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
import sys
|
|
3
|
-
|
|
4
2
|
import requests
|
|
5
3
|
|
|
6
|
-
from suite_py.lib import logger
|
|
7
4
|
from suite_py.lib.handler.github_handler import GithubHandler
|
|
5
|
+
from suite_py.lib.handler.okta_handler import Okta
|
|
6
|
+
from suite_py.__version__ import __version__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UnauthorizedError(Exception):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
super().__init__("Unauthorized with captainhook")
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
class CaptainHook:
|
|
11
|
-
|
|
15
|
+
_okta: Okta
|
|
16
|
+
|
|
17
|
+
def __init__(self, config, okta: Okta, tokens=None):
|
|
12
18
|
self._baseurl = config.user["captainhook_url"]
|
|
13
19
|
self._timeout = config.user["captainhook_timeout"]
|
|
14
|
-
self.
|
|
15
|
-
self._okta_token = config.get_cache(f"{self._okta_base_url}_token")
|
|
16
|
-
self._headers = {
|
|
17
|
-
"Authorization": f"Bearer {self._okta_token}",
|
|
18
|
-
}
|
|
20
|
+
self._okta = okta
|
|
19
21
|
|
|
20
22
|
if tokens is not None:
|
|
21
23
|
self._github = GithubHandler(tokens)
|
|
@@ -60,58 +62,39 @@ class CaptainHook:
|
|
|
60
62
|
self.send_post_request("/suite_py/metrics/", json=metrics).raise_for_status()
|
|
61
63
|
|
|
62
64
|
def send_post_request(self, endpoint, data=None, json=None):
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return self._response(r)
|
|
73
|
-
except Exception as e:
|
|
74
|
-
logger.error(
|
|
75
|
-
"Unable to contact Captainhook, are you logged in/using the VPN?"
|
|
76
|
-
)
|
|
77
|
-
raise e
|
|
65
|
+
r = requests.post(
|
|
66
|
+
f"{self._baseurl}{endpoint}",
|
|
67
|
+
headers=self._headers(),
|
|
68
|
+
data=data,
|
|
69
|
+
json=json,
|
|
70
|
+
timeout=self._timeout,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return self._response(r)
|
|
78
74
|
|
|
79
75
|
def send_put_request(self, endpoint, data=None, json=None):
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return self._response(r)
|
|
90
|
-
except Exception as e:
|
|
91
|
-
logger.error(
|
|
92
|
-
"Unable to contact Captainhook, are you logged in/using the VPN?"
|
|
93
|
-
)
|
|
94
|
-
raise e
|
|
76
|
+
r = requests.put(
|
|
77
|
+
f"{self._baseurl}{endpoint}",
|
|
78
|
+
headers=self._headers(),
|
|
79
|
+
data=data,
|
|
80
|
+
json=json,
|
|
81
|
+
timeout=self._timeout,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return self._response(r)
|
|
95
85
|
|
|
96
86
|
def send_get_request(self, endpoint):
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return self._response(r)
|
|
105
|
-
except Exception as e:
|
|
106
|
-
logger.error(
|
|
107
|
-
"Unable to contact Captainhook, are you logged in/using the VPN?"
|
|
108
|
-
)
|
|
109
|
-
raise e
|
|
87
|
+
r = requests.get(
|
|
88
|
+
f"{self._baseurl}{endpoint}",
|
|
89
|
+
headers=self._headers(),
|
|
90
|
+
timeout=(2, self._timeout),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return self._response(r)
|
|
110
94
|
|
|
111
95
|
def _response(self, response):
|
|
112
96
|
if response.status_code == 401:
|
|
113
|
-
|
|
114
|
-
sys.exit(-1)
|
|
97
|
+
raise UnauthorizedError()
|
|
115
98
|
|
|
116
99
|
return response
|
|
117
100
|
|
|
@@ -120,3 +103,10 @@ class CaptainHook:
|
|
|
120
103
|
|
|
121
104
|
def _get_user(self):
|
|
122
105
|
return self._github.get_user().login
|
|
106
|
+
|
|
107
|
+
def _headers(self):
|
|
108
|
+
id_token = self._okta.get_id_token()
|
|
109
|
+
return {
|
|
110
|
+
"User-Agent": f"suite-py/{__version__}",
|
|
111
|
+
"Authorization": f"Bearer {id_token}",
|
|
112
|
+
}
|
|
@@ -12,7 +12,8 @@ from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
|
12
12
|
|
|
13
13
|
# Collects metrics and sends them to datadog through captainhook
|
|
14
14
|
class Metrics:
|
|
15
|
-
def __init__(self, config: Config):
|
|
15
|
+
def __init__(self, captainhook: CaptainHook, config: Config):
|
|
16
|
+
self._captainhook = captainhook
|
|
16
17
|
self._config = config
|
|
17
18
|
|
|
18
19
|
# Emits the command_executed metric
|
|
@@ -37,8 +38,11 @@ class Metrics:
|
|
|
37
38
|
metrics = []
|
|
38
39
|
|
|
39
40
|
logger.debug(f"creating metric: {metric}")
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
if self._config.user.get("disable_metrics_creation", False):
|
|
42
|
+
logger.debug("skipping metric creation")
|
|
43
|
+
else:
|
|
44
|
+
metrics.append(metric)
|
|
45
|
+
self._config.put_cookie("metrics", metrics)
|
|
42
46
|
|
|
43
47
|
# Upload metrics in a detached background process
|
|
44
48
|
def async_upload(self):
|
|
@@ -69,8 +73,6 @@ class Metrics:
|
|
|
69
73
|
self.upload()
|
|
70
74
|
|
|
71
75
|
def upload(self):
|
|
72
|
-
captainhook = CaptainHook(self._config)
|
|
73
|
-
|
|
74
76
|
metrics = self._config.get_cookie("metrics", [])
|
|
75
77
|
# Prevent double uploads
|
|
76
78
|
#
|
|
@@ -79,7 +81,7 @@ class Metrics:
|
|
|
79
81
|
self._config.put_cookie("metrics", [])
|
|
80
82
|
try:
|
|
81
83
|
if len(metrics) != 0:
|
|
82
|
-
|
|
84
|
+
self._captainhook.send_metrics(metrics)
|
|
83
85
|
except Exception:
|
|
84
86
|
# Upload failed, try again later
|
|
85
87
|
self._config.put_cookie("metrics", metrics)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from suite_py.lib import logger, oauth
|
|
5
|
+
from suite_py.lib.config import Config
|
|
6
|
+
from suite_py.lib.tokens import Tokens
|
|
7
|
+
|
|
8
|
+
_SCOPE = "openid offline_access"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Okta:
|
|
12
|
+
def __init__(self, config: Config, tokens: Tokens) -> None:
|
|
13
|
+
self._config = config
|
|
14
|
+
self._tokens = tokens
|
|
15
|
+
|
|
16
|
+
def login(self):
|
|
17
|
+
res = oauth.authorization_code_flow(
|
|
18
|
+
self._config.okta["client_id"],
|
|
19
|
+
self._config.okta["base_url"],
|
|
20
|
+
_SCOPE,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
self._update_tokens(res)
|
|
24
|
+
|
|
25
|
+
def get_id_token(self) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Returns an id_token, performing a token refresh if needed
|
|
28
|
+
Raises on an invalid or missing refresh token
|
|
29
|
+
"""
|
|
30
|
+
return self._get_id_token() or self._refresh()
|
|
31
|
+
|
|
32
|
+
def _refresh(self) -> str:
|
|
33
|
+
logger.debug("Refreshing id_token")
|
|
34
|
+
|
|
35
|
+
refresh_token = self._get_refresh_token()
|
|
36
|
+
if not isinstance(refresh_token, str):
|
|
37
|
+
raise Exception(
|
|
38
|
+
"Invalid okta refresh token. Try logging in with `suite_py login`"
|
|
39
|
+
)
|
|
40
|
+
res = oauth.do_refresh_token(
|
|
41
|
+
self._config.okta["client_id"],
|
|
42
|
+
self._config.okta["base_url"],
|
|
43
|
+
_SCOPE,
|
|
44
|
+
refresh_token,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return self._update_tokens(res)
|
|
48
|
+
|
|
49
|
+
def _update_tokens(self, tokens: oauth.OAuthTokenResponse) -> str:
|
|
50
|
+
if not tokens.id_token:
|
|
51
|
+
raise Exception("Okta didn't return a new id_token. This shouldn't happen.")
|
|
52
|
+
if not tokens.refresh_token:
|
|
53
|
+
raise Exception(
|
|
54
|
+
"Okta didn't return a new refresh_token. This shouldn't happen."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
self._set_refresh_token(tokens.refresh_token)
|
|
58
|
+
|
|
59
|
+
expires_at = time.time() + tokens.expires_in
|
|
60
|
+
self._set_id_token(tokens.id_token, expires_at)
|
|
61
|
+
|
|
62
|
+
return tokens.id_token
|
|
63
|
+
|
|
64
|
+
def _get_refresh_token(self) -> typing.Optional[str]:
|
|
65
|
+
return self._tokens.okta().get("refresh_token", None)
|
|
66
|
+
|
|
67
|
+
def _set_refresh_token(self, token: str):
|
|
68
|
+
okta = self._tokens.okta()
|
|
69
|
+
okta["refresh_token"] = token
|
|
70
|
+
self._tokens.edit("okta", okta)
|
|
71
|
+
|
|
72
|
+
def _get_id_token(self) -> typing.Optional[str]:
|
|
73
|
+
(token, expiration) = self._tokens.okta().get("id_token", (None, None))
|
|
74
|
+
if token is not None and time.time() < expiration:
|
|
75
|
+
return token
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
def _set_id_token(self, token: str, expiration: float):
|
|
79
|
+
okta = self._tokens.okta()
|
|
80
|
+
okta["id_token"] = (token, expiration)
|
|
81
|
+
self._tokens.edit("okta", okta)
|