suite-py 1.41.10__py3-none-any.whl → 1.42.1__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.
@@ -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 Exception(
38
- "Invalid okta refresh token. Try logging in with `suite_py login`"
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 Exception("Okta didn't return a new id_token. This shouldn't happen.")
55
+ raise OktaError("Okta didn't return a new id_token. This shouldn't happen.")
52
56
  if not tokens.refresh_token:
53
- raise Exception(
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
@@ -12,7 +12,7 @@ def _metrics() -> Metrics:
12
12
  if _metrics_handler:
13
13
  return _metrics_handler
14
14
 
15
- raise Exception(
15
+ raise RuntimeError(
16
16
  "command_executed called before logger.setup(). This is a bug, please report it"
17
17
  )
18
18
 
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 Exception(f"OAuth error: {error}")
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 Exception(
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 Exception(server.error_message)
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", "drone"]
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 Exception("Loaded keyring didn't match the saved test keyring")
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 Exception("chmod is not supported on this OS")
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.41.10
3
+ Version: 1.42.1
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 (>=2.11,<3.0.0)
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 (==42.0.7)
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.0.0)
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=i6HmzASsryZBCC4p1Cf_qbUBilep7P848kIqMZUnTNs,50
3
- suite_py/cli.py,sha256=oGKcY9TUvGgXJl1tYBlkvSCcz6A6Aa7Fye5yEPqB4F8,15159
2
+ suite_py/__version__.py,sha256=2qe7wju_9Za3gMD66khHaby5fLHqRazj7tWdFCm5iWk,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=0e2NsPi3cqvCwtNYzhR1UroT59bCojLlGo69vv3FiOA,4074
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=gUpoDx3q23X6gF9PLXCZnIL9DfRw_3D0LlqBlGVt7rA,5676
20
- suite_py/commands/open_pr.py,sha256=U7MVl-JFKu1mdfxC_UvxUHtPLEln0g4kKl-SvP-6zd8,7159
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=clKgmsNgR9DdgBkYjXI3NqVaw8_mCe2TZHegby3ESn4,16634
23
- suite_py/commands/secret.py,sha256=Us7wvsqAPJhe1nUSHOmzO1RSyZriKAGKwi2YEhPNIA4,7976
15
+ suite_py/commands/release.py,sha256=Mh6esivJ234cRE5mYWPOZSgpujO47RprySh0U_9BD9s,7857
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=4uKXAav8E-VXlXGPnkYQ_fZYyi6058S0FM2JmyV8L2k,3970
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=xZ9NueGfh14L46S8AWSCZAGJdOFUCa2cFbbguq5SVv8,3281
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=3GEnJxbLXlu2zjFWniYwG0m1mFKJGfske1t-uUUdpZU,2545
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=hGGrWg_c3uTz4xhpb7POGZh_xhIAYU3drRo-KgwpBvM,1626
44
- suite_py/lib/oauth.py,sha256=Y_I8a9wDKOVQ75xJH5ECDRH4xrJHSPVPUvVVM2fTQdA,5014
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=kK4vatd5iKEFaue0BZwK1f66YOh9zLdLzP41ZOhjMCU,5534
39
+ suite_py/lib/tokens.py,sha256=4DbsHDFLIxs40t3mRw_ZyhmejZQ0Bht7iAL8dTCTQd4,5458
50
40
  suite_py/templates/login.html,sha256=fJLls2SB84oZTSrxTdA5q1PqfvIHcCD4fhVWfyco7Ig,861
51
- suite_py-1.41.10.dist-info/METADATA,sha256=A_GGYDs7Uq5qzCIiUW-PKqxk0QdgS4su2FMwpKWc4A4,1533
52
- suite_py-1.41.10.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
53
- suite_py-1.41.10.dist-info/entry_points.txt,sha256=dVKLC-9Infy-dHJT_MkK6LcDjOgBCJ8lfPkURJhBjxE,46
54
- suite_py-1.41.10.dist-info/RECORD,,
41
+ suite_py-1.42.1.dist-info/METADATA,sha256=eotAZFpnIsQMp5sbP7CK4YwP-m5DjWrzpkDA75KdYMs,1533
42
+ suite_py-1.42.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
43
+ suite_py-1.42.1.dist-info/entry_points.txt,sha256=dVKLC-9Infy-dHJT_MkK6LcDjOgBCJ8lfPkURJhBjxE,46
44
+ suite_py-1.42.1.dist-info/RECORD,,
@@ -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!")
@@ -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