suite-py 1.41.10__py3-none-any.whl → 1.42.0__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 +6 -194
- suite_py/commands/check.py +0 -18
- suite_py/commands/merge_pr.py +1 -12
- suite_py/commands/open_pr.py +1 -6
- suite_py/commands/release.py +2 -248
- suite_py/lib/config.py +0 -1
- suite_py/lib/handler/captainhook_handler.py +0 -7
- suite_py/lib/handler/okta_handler.py +8 -4
- suite_py/lib/metrics.py +1 -1
- suite_py/lib/oauth.py +7 -3
- suite_py/lib/tokens.py +3 -7
- {suite_py-1.41.10.dist-info → suite_py-1.42.0.dist-info}/METADATA +4 -4
- {suite_py-1.41.10.dist-info → suite_py-1.42.0.dist-info}/RECORD +16 -26
- suite_py/commands/aggregator.py +0 -159
- suite_py/commands/batch_job.py +0 -215
- suite_py/commands/deploy.py +0 -207
- suite_py/commands/docker.py +0 -91
- suite_py/commands/generator.py +0 -238
- suite_py/commands/id.py +0 -60
- suite_py/commands/ip.py +0 -68
- suite_py/commands/secret.py +0 -204
- suite_py/lib/handler/drone_handler.py +0 -252
- suite_py/lib/handler/vault_handler.py +0 -28
- {suite_py-1.41.10.dist-info → suite_py-1.42.0.dist-info}/WHEEL +0 -0
- {suite_py-1.41.10.dist-info → suite_py-1.42.0.dist-info}/entry_points.txt +0 -0
|
@@ -52,13 +52,6 @@ class CaptainHook:
|
|
|
52
52
|
def get_users_list(self):
|
|
53
53
|
return self.send_get_request("/users/all")
|
|
54
54
|
|
|
55
|
-
def aggregators(self):
|
|
56
|
-
return self.send_get_request("/cloudflare/aggregators/available")
|
|
57
|
-
|
|
58
|
-
def change_aggregator(self, aggregator, qa_address):
|
|
59
|
-
data = {"aggregator": aggregator, "qa_address": qa_address}
|
|
60
|
-
return self.send_put_request("/cloudflare/aggregators", data)
|
|
61
|
-
|
|
62
55
|
def send_metrics(self, metrics):
|
|
63
56
|
self.send_post_request("/suite_py/metrics/", json=metrics).raise_for_status()
|
|
64
57
|
|
|
@@ -8,6 +8,10 @@ from suite_py.lib.tokens import Tokens
|
|
|
8
8
|
_SCOPE = "openid offline_access"
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
class OktaError(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
11
15
|
class Okta:
|
|
12
16
|
def __init__(self, config: Config, tokens: Tokens) -> None:
|
|
13
17
|
self._config = config
|
|
@@ -34,8 +38,8 @@ class Okta:
|
|
|
34
38
|
|
|
35
39
|
refresh_token = self._get_refresh_token()
|
|
36
40
|
if not isinstance(refresh_token, str):
|
|
37
|
-
raise
|
|
38
|
-
"Invalid okta refresh token. Try logging in with `
|
|
41
|
+
raise OktaError(
|
|
42
|
+
"Invalid okta refresh token. Try logging in with `suite-py login`"
|
|
39
43
|
)
|
|
40
44
|
res = oauth.do_refresh_token(
|
|
41
45
|
self._config.okta["client_id"],
|
|
@@ -48,9 +52,9 @@ class Okta:
|
|
|
48
52
|
|
|
49
53
|
def _update_tokens(self, tokens: oauth.OAuthTokenResponse) -> str:
|
|
50
54
|
if not tokens.id_token:
|
|
51
|
-
raise
|
|
55
|
+
raise OktaError("Okta didn't return a new id_token. This shouldn't happen.")
|
|
52
56
|
if not tokens.refresh_token:
|
|
53
|
-
raise
|
|
57
|
+
raise OktaError(
|
|
54
58
|
"Okta didn't return a new refresh_token. This shouldn't happen."
|
|
55
59
|
)
|
|
56
60
|
|
suite_py/lib/metrics.py
CHANGED
suite_py/lib/oauth.py
CHANGED
|
@@ -23,6 +23,10 @@ class OAuthTokenResponse:
|
|
|
23
23
|
expires_in: float
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
class OAuthError(Exception):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
26
30
|
def retrieve_token(base_url, params) -> OAuthTokenResponse:
|
|
27
31
|
url = f"{base_url}/token"
|
|
28
32
|
headers = {
|
|
@@ -34,7 +38,7 @@ def retrieve_token(base_url, params) -> OAuthTokenResponse:
|
|
|
34
38
|
logger.debug(data)
|
|
35
39
|
|
|
36
40
|
if error := data.get("error_description", None):
|
|
37
|
-
raise
|
|
41
|
+
raise OAuthError(f"OAuth error: {error}")
|
|
38
42
|
|
|
39
43
|
return OAuthTokenResponse(
|
|
40
44
|
access_token=data["access_token"],
|
|
@@ -125,12 +129,12 @@ def authorization_code_flow(
|
|
|
125
129
|
server.handle_request()
|
|
126
130
|
|
|
127
131
|
if state != server.received_state:
|
|
128
|
-
raise
|
|
132
|
+
raise OAuthError(
|
|
129
133
|
"Error: session replay or similar attack in progress. Please log out of all connections."
|
|
130
134
|
)
|
|
131
135
|
|
|
132
136
|
if server.error_message:
|
|
133
|
-
raise
|
|
137
|
+
raise OAuthError(server.error_message)
|
|
134
138
|
|
|
135
139
|
# Step4: Request tokens: Exchange your authorization_code and code_verifier for tokens.
|
|
136
140
|
body = {
|
suite_py/lib/tokens.py
CHANGED
|
@@ -10,7 +10,7 @@ from InquirerPy import prompt
|
|
|
10
10
|
|
|
11
11
|
from suite_py.lib import logger
|
|
12
12
|
|
|
13
|
-
TOKENS = ["github", "youtrack"
|
|
13
|
+
TOKENS = ["github", "youtrack"]
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def platform_has_chmod():
|
|
@@ -32,7 +32,7 @@ def should_use_keyring():
|
|
|
32
32
|
pass
|
|
33
33
|
|
|
34
34
|
if not test_success:
|
|
35
|
-
raise
|
|
35
|
+
raise RuntimeError("Loaded keyring didn't match the saved test keyring")
|
|
36
36
|
|
|
37
37
|
return True
|
|
38
38
|
except Exception as error:
|
|
@@ -139,7 +139,7 @@ class Tokens:
|
|
|
139
139
|
if os.path.exists(self._file_name):
|
|
140
140
|
try:
|
|
141
141
|
if not platform_has_chmod():
|
|
142
|
-
raise
|
|
142
|
+
raise RuntimeError("chmod is not supported on this OS")
|
|
143
143
|
|
|
144
144
|
os.chmod(self._file_name, 0o600)
|
|
145
145
|
except Exception as error:
|
|
@@ -162,9 +162,5 @@ class Tokens:
|
|
|
162
162
|
def youtrack(self):
|
|
163
163
|
return self._tokens["youtrack"]
|
|
164
164
|
|
|
165
|
-
@property
|
|
166
|
-
def drone(self):
|
|
167
|
-
return self._tokens["drone"]
|
|
168
|
-
|
|
169
165
|
def okta(self):
|
|
170
166
|
return self._tokens.get("okta", {})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: suite-py
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.42.0
|
|
4
4
|
Summary:
|
|
5
5
|
Author: larrywax, EugenioLaghi, michelangelomo
|
|
6
6
|
Author-email: devops@prima.it
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Requires-Dist: Click (>=7.0)
|
|
15
15
|
Requires-Dist: InquirerPy (>=0.2.0)
|
|
16
|
-
Requires-Dist: Jinja2 (>=
|
|
16
|
+
Requires-Dist: Jinja2 (>=3.1.4,<4.0.0)
|
|
17
17
|
Requires-Dist: PyGithub (>=1.57)
|
|
18
18
|
Requires-Dist: PyYaml (>=5.4)
|
|
19
19
|
Requires-Dist: autoupgrade-prima (>=0.6)
|
|
@@ -21,12 +21,12 @@ Requires-Dist: black (>=22.6,<25.0)
|
|
|
21
21
|
Requires-Dist: boto3 (>=1.17.84)
|
|
22
22
|
Requires-Dist: cement (>=3.0)
|
|
23
23
|
Requires-Dist: colorama (>=0.4.3)
|
|
24
|
-
Requires-Dist: cryptography (==
|
|
24
|
+
Requires-Dist: cryptography (==43.0.0)
|
|
25
25
|
Requires-Dist: halo (>=0.0.28)
|
|
26
26
|
Requires-Dist: inquirer (==3.1.4)
|
|
27
27
|
Requires-Dist: itsdangerous (==2.2.0)
|
|
28
28
|
Requires-Dist: keyring (>=23.9.1,<26.0.0)
|
|
29
|
-
Requires-Dist: kubernetes (==
|
|
29
|
+
Requires-Dist: kubernetes (==30.1.0)
|
|
30
30
|
Requires-Dist: logzero (==1.7.0)
|
|
31
31
|
Requires-Dist: markupsafe (==2.0.1)
|
|
32
32
|
Requires-Dist: pptree (==3.1)
|
|
@@ -1,54 +1,44 @@
|
|
|
1
1
|
suite_py/__init__.py,sha256=REmi3D0X2G1ZWnYpKs8Ffm3NIj-Hw6dMuvz2b9NW344,142
|
|
2
|
-
suite_py/__version__.py,sha256=
|
|
3
|
-
suite_py/cli.py,sha256=
|
|
2
|
+
suite_py/__version__.py,sha256=hJTDtNLTlQS_WH_-iShJEn0PsoEs1NeHPvMJIjNqFfs,49
|
|
3
|
+
suite_py/cli.py,sha256=nQmnOXTLoO12vyH_6d41bd7h8xElRusT_tLGNtYt-Tg,9953
|
|
4
4
|
suite_py/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
suite_py/commands/aggregator.py,sha256=_xyyX7MOMZzDEvzj2AI03OF3sut5PpSIyuRjUiCp5aI,5747
|
|
6
5
|
suite_py/commands/ask_review.py,sha256=yN__Ac-fiZBPShjRDhyCCQZGfVlQE16KozoJk4UtiNw,3788
|
|
7
|
-
suite_py/commands/batch_job.py,sha256=flTSxmNaUv1VcL91Lv-BwEjFpEyF8APTNysQE_2jPYE,7474
|
|
8
6
|
suite_py/commands/bump.py,sha256=oFZU1hPfD11ujFC5G7wFyQOf2alY3xp2SO1h1ldjf3s,5406
|
|
9
|
-
suite_py/commands/check.py,sha256=
|
|
7
|
+
suite_py/commands/check.py,sha256=jCX59g6DgTA55yD_75mgcqJ5zCjRwl_eIRGDeUFjUWY,3316
|
|
10
8
|
suite_py/commands/common.py,sha256=aWCEvO3hqdheuMUmZcHuc9EGZPQTk7VkzkHJk283MxQ,566
|
|
11
9
|
suite_py/commands/context.py,sha256=6dssSqoABPb9gLmL3Y7W2iAGxrY_oma665X8zrSkNNQ,1079
|
|
12
10
|
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
11
|
suite_py/commands/login.py,sha256=A59e1HsbN7Ocv2L_2H0Eb7MZK7AzLkLb72QxBthnIqU,258
|
|
19
|
-
suite_py/commands/merge_pr.py,sha256=
|
|
20
|
-
suite_py/commands/open_pr.py,sha256=
|
|
12
|
+
suite_py/commands/merge_pr.py,sha256=fXIE8mT9MjvvpqE-uVdXGBVFGhn0eQzcBxNr-N8SyAY,5171
|
|
13
|
+
suite_py/commands/open_pr.py,sha256=e6IT6f8L_HKsOEtrGHZQ2HWrFwmvIgXGNCK_TU0WE9k,7009
|
|
21
14
|
suite_py/commands/project_lock.py,sha256=b7OkGysue_Sl13VIT7B5CTBppCvrB_Q6iC0IJRBSHp8,1909
|
|
22
|
-
suite_py/commands/release.py,sha256=
|
|
23
|
-
suite_py/commands/secret.py,sha256=Us7wvsqAPJhe1nUSHOmzO1RSyZriKAGKwi2YEhPNIA4,7976
|
|
15
|
+
suite_py/commands/release.py,sha256=WOBaBGXU5bkxUV12CRGUVHBJChCt1Yo2ySA5HhCDjEY,7723
|
|
24
16
|
suite_py/commands/set_token.py,sha256=fehIqKjKhE-BJGFhgkPTo3Ntr0MvpgLd6EC5yjKuRs8,1508
|
|
25
17
|
suite_py/commands/status.py,sha256=0JUK53_d1-U3WNS742JD2QTiGmCGZONo3jJx8WR7q70,1122
|
|
26
18
|
suite_py/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
-
suite_py/lib/config.py,sha256=
|
|
19
|
+
suite_py/lib/config.py,sha256=52YV0K0GK1WRbeM70IDXkpG8oZD1Zvxipn1b-XekwNA,3907
|
|
28
20
|
suite_py/lib/handler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
21
|
suite_py/lib/handler/aws_handler.py,sha256=dRvRDicikfRbuFtCLPbevaX-yC-fO4LwXFdyqLPJ8OI,8815
|
|
30
|
-
suite_py/lib/handler/captainhook_handler.py,sha256=
|
|
22
|
+
suite_py/lib/handler/captainhook_handler.py,sha256=sp1dxZXO1QJaq304JhtkTrQjn8Z4971e2uFTM7Dh5wg,2983
|
|
31
23
|
suite_py/lib/handler/changelog_handler.py,sha256=-ppnRl3smBA_ys8tPqXmytS4eyntlwfawC2fiXFcwlw,4818
|
|
32
|
-
suite_py/lib/handler/drone_handler.py,sha256=rmtzu30OQyG3vRPlbZKsQhHN9zbguPtXO0RpDjYOTPA,8967
|
|
33
24
|
suite_py/lib/handler/frequent_reviewers_handler.py,sha256=EIJX5FEMWzrxuXS9A17hu1vfxgJSOHSBX_ahCEZ2FVA,2185
|
|
34
25
|
suite_py/lib/handler/git_handler.py,sha256=boxhl9lQz6fjEJ10ib1KrDW-geCVjhA_6nKwv2ll01g,11333
|
|
35
26
|
suite_py/lib/handler/github_handler.py,sha256=AnFL54yOZ5GDIU91wQat4s-d1WTcmg_B_5M7-Rop3wA,2900
|
|
36
27
|
suite_py/lib/handler/metrics_handler.py,sha256=-Tp62pFIiYsBkDga0nQG3lWU-gxH68wEjIIIJeU1jHk,3159
|
|
37
|
-
suite_py/lib/handler/okta_handler.py,sha256=
|
|
28
|
+
suite_py/lib/handler/okta_handler.py,sha256=QOt7fhCy-1Pw_tQ0Psfuy2fXP-gbxvJ1zVNmrGvwRXw,2584
|
|
38
29
|
suite_py/lib/handler/prompt_utils.py,sha256=vgk1O7h-iYEAZv1sXtMh8xIgH1djI398rzxRIgZWZcg,2474
|
|
39
|
-
suite_py/lib/handler/vault_handler.py,sha256=r4osw7qwz3ZFmLg2U1oFPdtRFcXzDXiaWBZC01cYK_w,871
|
|
40
30
|
suite_py/lib/handler/version_handler.py,sha256=DXTx4yCAbFVC6CdMqPJ-LiN5YM-dT2zklG8POyKTP5A,6774
|
|
41
31
|
suite_py/lib/handler/youtrack_handler.py,sha256=eTGBBXjlN_ay1cawtnZ2IG6l78dDyKdMN1x6PxcvtA0,7499
|
|
42
32
|
suite_py/lib/logger.py,sha256=q_qRtpg0Eh7r5tB-Rwz87dnWBwP-a2dIvggkiQikH8M,782
|
|
43
|
-
suite_py/lib/metrics.py,sha256=
|
|
44
|
-
suite_py/lib/oauth.py,sha256=
|
|
33
|
+
suite_py/lib/metrics.py,sha256=CqAIwPIRm0AJR4qmCTj1rw-NCwdSL4Huj2eKhLxgLgU,1629
|
|
34
|
+
suite_py/lib/oauth.py,sha256=NOX96coz_pb10C4pZ2Sk33beJQTTi_N-LXPUjDYuRKM,5057
|
|
45
35
|
suite_py/lib/requests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
36
|
suite_py/lib/requests/auth.py,sha256=wN_WtGFmDUWRFilWzOmUaRBvP2n3EPpPMqex9Zjddko,228
|
|
47
37
|
suite_py/lib/requests/session.py,sha256=P32H3cWnCWunu91WIj2iDM5U3HzaBglg60VN_C9JBL4,267
|
|
48
38
|
suite_py/lib/symbol.py,sha256=z3QYBuNIwD3qQ3zF-cLOomIr_-C3bO_u5UIDAHMiyTo,60
|
|
49
|
-
suite_py/lib/tokens.py,sha256=
|
|
39
|
+
suite_py/lib/tokens.py,sha256=4DbsHDFLIxs40t3mRw_ZyhmejZQ0Bht7iAL8dTCTQd4,5458
|
|
50
40
|
suite_py/templates/login.html,sha256=fJLls2SB84oZTSrxTdA5q1PqfvIHcCD4fhVWfyco7Ig,861
|
|
51
|
-
suite_py-1.
|
|
52
|
-
suite_py-1.
|
|
53
|
-
suite_py-1.
|
|
54
|
-
suite_py-1.
|
|
41
|
+
suite_py-1.42.0.dist-info/METADATA,sha256=BBTBS6aVgG6_2F3a-Rjoi0RPduumq5AN0wpn0RH3gdk,1533
|
|
42
|
+
suite_py-1.42.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
43
|
+
suite_py-1.42.0.dist-info/entry_points.txt,sha256=dVKLC-9Infy-dHJT_MkK6LcDjOgBCJ8lfPkURJhBjxE,46
|
|
44
|
+
suite_py-1.42.0.dist-info/RECORD,,
|
suite_py/commands/aggregator.py
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import re
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
from halo import Halo
|
|
6
|
-
from kubernetes import client, config
|
|
7
|
-
from rich.console import Console
|
|
8
|
-
from rich.table import Table
|
|
9
|
-
|
|
10
|
-
from suite_py.lib import logger, metrics
|
|
11
|
-
from suite_py.lib.handler import prompt_utils
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Aggregator:
|
|
15
|
-
def __init__(self, captainhook, command):
|
|
16
|
-
self._captainhook = captainhook
|
|
17
|
-
self._command = command
|
|
18
|
-
|
|
19
|
-
@metrics.command("aggregator")
|
|
20
|
-
def run(self):
|
|
21
|
-
if self._command == "list":
|
|
22
|
-
self._list_aggregators()
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
if self._command == "change":
|
|
26
|
-
aggregator = self._select_aggregator()
|
|
27
|
-
address = prompt_utils.ask_questions_input(
|
|
28
|
-
"Insert QA url or press enter to set staging URL: ",
|
|
29
|
-
default_text="staging.prima.it",
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
if address.startswith("http"):
|
|
33
|
-
address = re.sub(r"https?://?", "", address)
|
|
34
|
-
|
|
35
|
-
split_addr = re.split(r"([a-z]+)\-([a-z0-9]+)", address)
|
|
36
|
-
|
|
37
|
-
change_request = self._captainhook.change_aggregator(
|
|
38
|
-
aggregator["name"], address
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
self._handle_captainhook_response(
|
|
42
|
-
change_request, aggregator["name"], address
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if "www" in split_addr:
|
|
46
|
-
split_addr[1] = "prima"
|
|
47
|
-
self._update_k8s_ingress_route(aggregator, split_addr[1], split_addr[2])
|
|
48
|
-
elif "evvivass" in split_addr:
|
|
49
|
-
self._update_k8s_ingress_route(aggregator, split_addr[1], split_addr[2])
|
|
50
|
-
else:
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
def _handle_captainhook_response(self, request, aggregator, address):
|
|
54
|
-
if request.status_code == 200:
|
|
55
|
-
change_request = request.json()
|
|
56
|
-
if change_request["success"]:
|
|
57
|
-
logger.info(f"CNAME updated! Now {aggregator} is pointing to {address}")
|
|
58
|
-
else:
|
|
59
|
-
cases = {
|
|
60
|
-
"cloudflare_error": "Error during Cloudflare invocation.",
|
|
61
|
-
"unknown_dns_record": "Impossible to find DNS record associated with aggregator.",
|
|
62
|
-
"unknown_aggregator": "Aggregator not found.",
|
|
63
|
-
"invalid_qa_address": "The QA address does not meet the requirements.",
|
|
64
|
-
}
|
|
65
|
-
logger.error(cases.get(change_request["error"], "unknown error"))
|
|
66
|
-
sys.exit(-1)
|
|
67
|
-
else:
|
|
68
|
-
logger.error("An error has occurred on Captainhook.")
|
|
69
|
-
sys.exit(-1)
|
|
70
|
-
|
|
71
|
-
def _select_aggregator(self):
|
|
72
|
-
with Halo(text="Loading aggregators...", spinner="dots", color="magenta"):
|
|
73
|
-
choices = [
|
|
74
|
-
{"name": agg["name"], "value": agg}
|
|
75
|
-
for agg in self._captainhook.aggregators().json()
|
|
76
|
-
]
|
|
77
|
-
if choices:
|
|
78
|
-
choices.sort(key=lambda x: x["name"])
|
|
79
|
-
return prompt_utils.ask_choices("Select aggregator: ", choices)
|
|
80
|
-
|
|
81
|
-
logger.error("There are no aggregators on Captainhook.")
|
|
82
|
-
sys.exit(-1)
|
|
83
|
-
|
|
84
|
-
def _list_aggregators(self):
|
|
85
|
-
with Halo(text="Loading...", spinner="dots", color="magenta"):
|
|
86
|
-
aggregators = self._captainhook.aggregators()
|
|
87
|
-
|
|
88
|
-
if aggregators.status_code != 200:
|
|
89
|
-
logger.error("Unable to retrieve the list of aggregators.")
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
console = Console()
|
|
93
|
-
|
|
94
|
-
aggregators_table = Table()
|
|
95
|
-
aggregators_table.add_column("Name", style="green")
|
|
96
|
-
aggregators_table.add_column("Address", style="white")
|
|
97
|
-
|
|
98
|
-
for a in aggregators.json():
|
|
99
|
-
aggregators_table.add_row(a["name"], a["content"])
|
|
100
|
-
|
|
101
|
-
logger.info("Available aggregators:")
|
|
102
|
-
console.print(aggregators_table)
|
|
103
|
-
|
|
104
|
-
def _update_k8s_ingress_route(self, aggregator, service_name, namespace):
|
|
105
|
-
try:
|
|
106
|
-
config.load_kube_config()
|
|
107
|
-
except Exception:
|
|
108
|
-
logger.error(
|
|
109
|
-
"\n\nYou need to authenticate, run the following command:\n$ aws eks update-kubeconfig --name main-qa\n\n"
|
|
110
|
-
)
|
|
111
|
-
sys.exit(-1)
|
|
112
|
-
|
|
113
|
-
api = client.CustomObjectsApi()
|
|
114
|
-
res = {
|
|
115
|
-
"apiVersion": "traefik.containo.us/v1alpha1",
|
|
116
|
-
"kind": "IngressRoute",
|
|
117
|
-
"metadata": {
|
|
118
|
-
"name": f"{aggregator['name']}",
|
|
119
|
-
"namespace": f"{namespace}",
|
|
120
|
-
"annotations": {
|
|
121
|
-
"traefik.ingress.kubernetes.io/router.tls": "true",
|
|
122
|
-
},
|
|
123
|
-
"labels": {
|
|
124
|
-
"com.prima.environment": "qa",
|
|
125
|
-
"com.prima.country": "it",
|
|
126
|
-
"app.kubernetes.io/name": f"{aggregator['name']}",
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
"spec": {
|
|
130
|
-
"entryPoints": ["websecure"],
|
|
131
|
-
"routes": [
|
|
132
|
-
{
|
|
133
|
-
"kind": "Rule",
|
|
134
|
-
"match": f"Host(`{aggregator['address']}{aggregator['domain']}`)",
|
|
135
|
-
"services": [{"name": f"{service_name}", "port": 80}],
|
|
136
|
-
}
|
|
137
|
-
],
|
|
138
|
-
},
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
api.create_namespaced_custom_object(
|
|
143
|
-
group="traefik.containo.us",
|
|
144
|
-
version="v1alpha1",
|
|
145
|
-
namespace=namespace,
|
|
146
|
-
plural="ingressroutes",
|
|
147
|
-
body=res,
|
|
148
|
-
)
|
|
149
|
-
logger.info(f"Aggregator {aggregator['name']} created!")
|
|
150
|
-
except Exception:
|
|
151
|
-
api.patch_namespaced_custom_object(
|
|
152
|
-
group="traefik.containo.us",
|
|
153
|
-
version="v1alpha1",
|
|
154
|
-
namespace=namespace,
|
|
155
|
-
plural="ingressroutes",
|
|
156
|
-
name=aggregator["name"],
|
|
157
|
-
body=res,
|
|
158
|
-
)
|
|
159
|
-
logger.info(f"Aggregator {aggregator['name']} updated!")
|
suite_py/commands/batch_job.py
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import re
|
|
3
|
-
import sys
|
|
4
|
-
import textwrap
|
|
5
|
-
|
|
6
|
-
import semver
|
|
7
|
-
|
|
8
|
-
from suite_py.lib import logger, metrics
|
|
9
|
-
from suite_py.lib.handler import prompt_utils
|
|
10
|
-
from suite_py.lib.handler.drone_handler import DroneHandler
|
|
11
|
-
from suite_py.lib.handler.git_handler import GitHandler
|
|
12
|
-
from suite_py.lib.handler.github_handler import GithubHandler
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class BatchJob:
|
|
16
|
-
def __init__(
|
|
17
|
-
self, project, config, tokens, environment, cpu_request, memory_request
|
|
18
|
-
):
|
|
19
|
-
self._project = project
|
|
20
|
-
self._config = config
|
|
21
|
-
self._tokens = tokens
|
|
22
|
-
self._environment = environment
|
|
23
|
-
self._cpu_request = cpu_request
|
|
24
|
-
self._memory_request = memory_request
|
|
25
|
-
self._git = GitHandler(project, config)
|
|
26
|
-
self._github = GithubHandler(tokens)
|
|
27
|
-
self._drone = DroneHandler(config, tokens, repo=project)
|
|
28
|
-
self._countries = _parse_available_countries(self._drone)
|
|
29
|
-
self._repo = self._github.get_repo(project)
|
|
30
|
-
|
|
31
|
-
@metrics.command("batch-job")
|
|
32
|
-
def run(self):
|
|
33
|
-
country = prompt_utils.ask_choices(
|
|
34
|
-
"Which country do you want to run the job on?",
|
|
35
|
-
self._countries,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
if not _has_job_pipeline(self._drone, country, self._environment):
|
|
39
|
-
logger.error(
|
|
40
|
-
"There is no job pipeline for the specified country/environment"
|
|
41
|
-
)
|
|
42
|
-
sys.exit(1)
|
|
43
|
-
|
|
44
|
-
command = prompt_utils.ask_questions_input(
|
|
45
|
-
"What command do you want to execute?", f"/app/bin/{self._project} "
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
self._start_job(country, command)
|
|
49
|
-
|
|
50
|
-
def _start_job(self, country, command):
|
|
51
|
-
promotion = {}
|
|
52
|
-
|
|
53
|
-
if self._environment == "staging":
|
|
54
|
-
build = self._get_latest_master_build()
|
|
55
|
-
|
|
56
|
-
self._ask_confirm(
|
|
57
|
-
country,
|
|
58
|
-
command,
|
|
59
|
-
f'Current master: {build["message"]} (#{build["number"]})',
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
promotion = self._drone.promote_staging(
|
|
63
|
-
build["number"],
|
|
64
|
-
f"job-{country}-{self._environment}",
|
|
65
|
-
f"BATCH_COMMAND={command}&JOB_CPU={self._cpu_request}&JOB_MEMORY={self._memory_request}",
|
|
66
|
-
)
|
|
67
|
-
elif self._environment == "production":
|
|
68
|
-
version = self._get_latest_tag(country)
|
|
69
|
-
|
|
70
|
-
self._ask_confirm(country, command, f"Current tag: {version}")
|
|
71
|
-
|
|
72
|
-
promotion = self._drone.promote_production(
|
|
73
|
-
version,
|
|
74
|
-
f"job-{country}-{self._environment}",
|
|
75
|
-
f"DRONE_TAG={version}&BATCH_COMMAND={command}&JOB_CPU={self._cpu_request}&JOB_MEMORY={self._memory_request}",
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
if "number" not in promotion:
|
|
79
|
-
logger.warning(f"Unable to promote drone build. Response: {promotion}")
|
|
80
|
-
return
|
|
81
|
-
|
|
82
|
-
logger.info("Drone build started successfully!")
|
|
83
|
-
logger.info(
|
|
84
|
-
f"You can follow the build status here: {self._drone.get_build_url(promotion['number'])}"
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
def _get_latest_master_build(self):
|
|
88
|
-
try:
|
|
89
|
-
builds = self._drone.get_builds_from_branch("master")
|
|
90
|
-
|
|
91
|
-
latest_build = None
|
|
92
|
-
non_green_builds = []
|
|
93
|
-
for b in builds:
|
|
94
|
-
if b["status"] == "success":
|
|
95
|
-
if latest_build is None:
|
|
96
|
-
latest_build = b
|
|
97
|
-
break
|
|
98
|
-
|
|
99
|
-
non_green_builds.append(b)
|
|
100
|
-
|
|
101
|
-
if len(non_green_builds) > 0:
|
|
102
|
-
logger.warning(
|
|
103
|
-
"There are recent builds on master still running or failed"
|
|
104
|
-
)
|
|
105
|
-
for b in non_green_builds:
|
|
106
|
-
logger.warning(
|
|
107
|
-
f'* {b["message"]} (Status: {b["status"]}, Number: #{b["number"]})'
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
if not latest_build:
|
|
111
|
-
logger.error("Unable to find latest build on master")
|
|
112
|
-
sys.exit(255)
|
|
113
|
-
|
|
114
|
-
return latest_build
|
|
115
|
-
|
|
116
|
-
except Exception as e:
|
|
117
|
-
print(e)
|
|
118
|
-
logger.error(
|
|
119
|
-
"An error has occurred retrieving current master version during batch job.\nPlease ask #team-platform-operations for help."
|
|
120
|
-
)
|
|
121
|
-
sys.exit(255)
|
|
122
|
-
|
|
123
|
-
def _get_latest_tag(self, country):
|
|
124
|
-
try:
|
|
125
|
-
logger.info("Retrieving latest version, this may take some time...")
|
|
126
|
-
# get latest 10 tags
|
|
127
|
-
tags = self._github.get_tags(self._project)
|
|
128
|
-
# exclude tags that don't match semver notation
|
|
129
|
-
semver_tags = [t for t in tags if semver.VersionInfo.isvalid(t.name)][0:9]
|
|
130
|
-
|
|
131
|
-
builds = []
|
|
132
|
-
for tag in semver_tags:
|
|
133
|
-
for b in self._drone.get_builds_from_tag(tag.name):
|
|
134
|
-
if (
|
|
135
|
-
b["event"] == "promote"
|
|
136
|
-
and b["status"] == "success"
|
|
137
|
-
and b["deploy_to"] == f"deploy-{country}-production"
|
|
138
|
-
and "params" in b
|
|
139
|
-
and "DRONE_TAG" in b["params"]
|
|
140
|
-
):
|
|
141
|
-
builds.append(b)
|
|
142
|
-
|
|
143
|
-
if not builds:
|
|
144
|
-
# may end up here if there isn't any successfull build for latest 10 tags
|
|
145
|
-
logger.error(
|
|
146
|
-
"An error has occurred retrieving current version during rollback.\nPlease ask #team-platform-operations for help."
|
|
147
|
-
)
|
|
148
|
-
sys.exit(255)
|
|
149
|
-
|
|
150
|
-
# get latest build using build number as key
|
|
151
|
-
current_build = max(builds, key=lambda x: x["number"])
|
|
152
|
-
|
|
153
|
-
if not current_build:
|
|
154
|
-
logger.error(
|
|
155
|
-
f"Unable to determine current version for country {country}"
|
|
156
|
-
)
|
|
157
|
-
sys.exit(255)
|
|
158
|
-
|
|
159
|
-
v = semver.VersionInfo.parse(current_build["params"]["DRONE_TAG"])
|
|
160
|
-
logger.info(f"Current version for country {country}: {v}")
|
|
161
|
-
|
|
162
|
-
return v
|
|
163
|
-
except Exception as e:
|
|
164
|
-
print(e)
|
|
165
|
-
logger.error(
|
|
166
|
-
"An error has occurred retrieving current version during batch job.\nPlease ask #team-platform-operations for help."
|
|
167
|
-
)
|
|
168
|
-
sys.exit(255)
|
|
169
|
-
|
|
170
|
-
def _ask_confirm(self, country, command, message):
|
|
171
|
-
logger.info(
|
|
172
|
-
textwrap.dedent(
|
|
173
|
-
f"""
|
|
174
|
-
You're about to run a batch job on project {self._project}, for env {self._environment}, in country {country}.
|
|
175
|
-
{message}
|
|
176
|
-
Command: {command}
|
|
177
|
-
"""
|
|
178
|
-
)
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
if not prompt_utils.ask_confirm("Do you confirm?", default=False):
|
|
182
|
-
sys.exit(0)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def _parse_available_countries(drone):
|
|
186
|
-
pipelines = drone.parse_yaml()
|
|
187
|
-
|
|
188
|
-
if pipelines is None:
|
|
189
|
-
logger.error("The file .drone.yml was not found. Unable to continue.")
|
|
190
|
-
sys.exit(1)
|
|
191
|
-
|
|
192
|
-
countries = []
|
|
193
|
-
REGEX = re.compile(r"deploy-([a-z]+)-.*")
|
|
194
|
-
for pipeline in pipelines:
|
|
195
|
-
if "name" in pipeline:
|
|
196
|
-
c = REGEX.findall(pipeline["name"])
|
|
197
|
-
if len(c) > 0 and c[0] is not None and c[0] not in countries:
|
|
198
|
-
countries.append(c[0])
|
|
199
|
-
|
|
200
|
-
return countries
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def _has_job_pipeline(drone, country, environment):
|
|
204
|
-
pipelines = drone.parse_yaml()
|
|
205
|
-
|
|
206
|
-
if pipelines is None:
|
|
207
|
-
logger.error("The file .drone.yml was not found. Unable to continue.")
|
|
208
|
-
sys.exit(1)
|
|
209
|
-
|
|
210
|
-
job_pipeline = f"job-{country}-{environment}"
|
|
211
|
-
for pipeline in pipelines:
|
|
212
|
-
if "name" in pipeline and pipeline["name"] == job_pipeline:
|
|
213
|
-
return True
|
|
214
|
-
|
|
215
|
-
return False
|