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.
@@ -1,204 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import json
3
- import os
4
- import sys
5
-
6
- import yaml
7
-
8
- from suite_py.lib import logger, metrics
9
- from suite_py.lib.handler import prompt_utils
10
- from suite_py.lib.handler.vault_handler import VaultHandler
11
-
12
- profiles_mapping = {
13
- "common": {
14
- "stack_name_pattern": "{service}-platform-cluster-{env}-serviceaccount-iamrole",
15
- "logical_id": "ServiceAccountIamRole",
16
- "developer_role": "arn:aws:iam::595659439703:role/aws-reserved/sso.amazonaws.com/eu-west-1/AWSReservedSSO_ItalyDeveloperPlatform_cb5de4d001016cc4",
17
- },
18
- "it": {
19
- "stack_name_pattern": "ecs-roles-{service}-{env}",
20
- "logical_id": "ECSTaskRole",
21
- "developer_role": "arn:aws:iam::001575623345:role/AllowNonProductionBiscuitDecryption",
22
- },
23
- "uk": {
24
- "stack_name_pattern": "{service}-main-{env}-serviceaccount-iamrole",
25
- "logical_id": "ServiceAccountIamRole",
26
- "developer_role": "arn:aws:iam::815911007681:role/aws-reserved/sso.amazonaws.com/eu-west-1/AWSReservedSSO_UnitedKingdomDeveloper_f578b0b2b98ea593",
27
- },
28
- "es": {
29
- "stack_name_pattern": "{service}-main-{env}-serviceaccount-iamrole",
30
- "logical_id": "ServiceAccountIamRole",
31
- "developer_role": "arn:aws:iam::199959896441:role/aws-reserved/sso.amazonaws.com/eu-west-1/AWSReservedSSO_SpainDeveloper_acf53aa98cfc0ac4",
32
- },
33
- }
34
-
35
-
36
- class Secret:
37
- def __init__(self, project, config, action, base_profile, secret_file):
38
- self._project = project
39
- self._config = config
40
- self._path = os.path.join(config.user["projects_home"], project)
41
- self._vault = VaultHandler(project, config)
42
- self._action = action
43
- self._base_profile = base_profile
44
- self._secret_file = secret_file
45
-
46
- @metrics.command("secret")
47
- def run(self):
48
- if self._base_profile is not None:
49
- self._config.vault["base_secret_profile"] = self._base_profile
50
-
51
- if not self._secret_file:
52
- self._secret_file = "config/secrets.yml"
53
-
54
- if self._action == "create":
55
- self._create_new_secret()
56
- return
57
-
58
- if self._action == "grant":
59
- secrets = self._get_available_secrets()
60
- secret = prompt_utils.ask_choices("Select secret: ", secrets)
61
- self._set_grant_on_secret(secret)
62
- return
63
-
64
- logger.error(
65
- "You have to specify what to do! See available flags with suite-py secret --help"
66
- )
67
-
68
- def _create_new_secret(self):
69
- secret = prompt_utils.ask_questions_input("Enter secret name: ")
70
- value = prompt_utils.ask_questions_input("Enter secret value: ")
71
- e = self._vault.exec(
72
- self._config.vault["base_secret_profile"],
73
- f"biscuit put -f {self._secret_file} -- {secret} {value}",
74
- )
75
- if e.returncode != 0:
76
- logger.error("An error occurred.")
77
- sys.exit(1)
78
-
79
- if prompt_utils.ask_confirm(f"Do you want to grant permissions for {secret}?"):
80
- self._set_grant_on_secret(secret)
81
-
82
- def _set_grant_on_secret(self, secret_name):
83
- environment = self._ask_environment()
84
- profiles = self._ask_profile()
85
- for profile in profiles:
86
- mapping = profiles_mapping[profile]
87
- stack_name = (
88
- mapping["stack_name_pattern"]
89
- .replace("{service}", self._project)
90
- .replace("{env}", environment)
91
- )
92
- # Get stack resource info
93
- logger.info(
94
- f"Obtaining ARN for {mapping['logical_id']} from stack {stack_name}..."
95
- )
96
- stack_resource = self._get_stack_resource(
97
- profile, stack_name, mapping["logical_id"]
98
- )
99
- if stack_resource is None:
100
- iam_role_arn = self._manual_iam_arn()
101
- else:
102
- iam_role_name = stack_resource["StackResourceDetail"][
103
- "PhysicalResourceId"
104
- ]
105
- # Get IAM Role ARN
106
- iam_role = self._get_iam_role(profile, iam_role_name)
107
- iam_role_arn = iam_role["Role"]["Arn"]
108
- if iam_role_arn is None:
109
- iam_role_arn = self._manual_iam_arn()
110
- logger.info(f"ARN found for profile {profile}: {iam_role_arn}")
111
-
112
- if iam_role_arn is None:
113
- continue
114
-
115
- logger.debug(
116
- f"---\nGranting permissions on {self._project}/{secret_name}\nEnvironment: {environment}\nUsing vault profile: {profile}\nIAM Role ARN: {iam_role_arn}\n---"
117
- )
118
- if not prompt_utils.ask_confirm("Do you want to continue?", default=True):
119
- continue
120
-
121
- # Grant decrypt permissions to task
122
- logger.info("Granting permissions to task...")
123
- e = self._vault.exec(
124
- self._config.vault["base_secret_profile"],
125
- f"biscuit kms grants create --grantee-principal {iam_role_arn} -f {self._secret_file} {secret_name}",
126
- )
127
- if e.returncode != 0:
128
- logger.error("An error occurred.")
129
- continue
130
-
131
- # Grant decrypt permissions to developers group
132
- if environment in ["staging", "qa"]:
133
- logger.info("Granting permissions to devs...")
134
- e = self._vault.exec(
135
- self._config.vault["base_secret_profile"],
136
- f"biscuit kms grants create --grantee-principal {mapping['developer_role']} -f {self._secret_file} {secret_name}",
137
- )
138
- if e.returncode != 0:
139
- logger.error("An error occurred.")
140
- continue
141
-
142
- logger.info("Ta-da! Permissions granted.")
143
-
144
- def _get_available_secrets(self):
145
- try:
146
- secrets = []
147
- with open(f"{self._path}/{self._secret_file}", encoding="utf-8") as s:
148
- yaml_secrets = yaml.load(s, Loader=yaml.FullLoader)
149
- secrets = [
150
- key for key, values in yaml_secrets.items() if key != "_keys"
151
- ]
152
-
153
- return secrets
154
- except FileNotFoundError:
155
- logger.error(f"Can't load secrets from {self._secret_file}.")
156
- sys.exit(1)
157
-
158
- def _get_stack_resource(self, profile, stack_name, logical_id):
159
- e = self._vault.exec(
160
- profile,
161
- f"aws cloudformation describe-stack-resource --stack-name {stack_name} --logical-resource-id {logical_id}",
162
- )
163
- command_result = e.stdout.read()
164
- if e.returncode != 0:
165
- logger.warning("An error occurred trying to obtain stack. Skipping...")
166
- return None
167
- stack_info = command_result.decode("utf8").replace("'", '"')
168
- data = json.loads(stack_info)
169
- return data
170
-
171
- def _get_iam_role(self, profile, iam_role_name):
172
- e = self._vault.exec(
173
- profile,
174
- f"aws iam get-role --role-name {iam_role_name}",
175
- additional_args="--no-session",
176
- )
177
- command_result = e.stdout.read()
178
- if e.returncode != 0:
179
- logger.warning(
180
- "An error occurred trying to obtain IAM Role details. Skipping..."
181
- )
182
- return None
183
- stack_info = command_result.decode("utf8").replace("'", '"')
184
- data = json.loads(stack_info)
185
- return data
186
-
187
- def _manual_iam_arn(self):
188
- arn = prompt_utils.ask_questions_input(
189
- "No associated IAM Role ARN found. Insert it manually or leave blank to skip",
190
- default_text="",
191
- )
192
- if arn == "":
193
- return None
194
- return arn
195
-
196
- def _ask_environment(self):
197
- return prompt_utils.ask_choices(
198
- "Select an environment:", ["production", "staging", "qa"]
199
- )
200
-
201
- def _ask_profile(self):
202
- return prompt_utils.ask_multiple_choices(
203
- "Select vault profile(s):", self._config.vault["profiles"]
204
- )
@@ -1,252 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- import os
3
- import subprocess
4
- import sys
5
- import time
6
- from distutils.version import StrictVersion # pylint: disable=W0402
7
- from subprocess import CalledProcessError
8
-
9
- import requests
10
- import yaml
11
- from halo import Halo
12
-
13
- from suite_py.lib import logger
14
-
15
-
16
- class DroneHandler:
17
- def __init__(self, config, tokens, repo=None):
18
- self._token = tokens.drone
19
- self._config = config
20
- os.environ["DRONE_TOKEN"] = self._token
21
- self._baseurl = os.environ.get("DRONE_SERVER", "https://drone-1.prima.it")
22
- self._repo = repo
23
- if repo:
24
- self._path = os.path.join(config.user["projects_home"], repo)
25
-
26
- def get_last_build_url(self, prefix=None):
27
- with Halo(text="Contacting drone...", spinner="dots", color="magenta"):
28
- # necessario per far comparire la build che abbiamo appena pushato
29
- time.sleep(2)
30
- try:
31
- # pylint: disable-next=missing-timeout
32
- builds = requests.get(
33
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds",
34
- headers={"Authorization": f"Bearer {self._token}"},
35
- ).json()
36
-
37
- if prefix:
38
- builds = [b for b in builds if b["target"].startswith(prefix)]
39
-
40
- return f"{self._baseurl}/primait/{self._repo}/{builds[0]['number']}"
41
- except Exception:
42
- return ""
43
-
44
- def get_pr_build_number(self, commit_sha):
45
- with Halo(text="Contacting drone...", spinner="dots", color="magenta"):
46
- tries = 10
47
- while tries > 0:
48
- tries -= 1
49
- try:
50
- # pylint: disable-next=missing-timeout
51
- builds = requests.get(
52
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds?per_page=100",
53
- headers={"Authorization": f"Bearer {self._token}"},
54
- ).json()
55
- builds = [b for b in builds if b["after"] == commit_sha]
56
- return builds[0]["number"]
57
- except Exception:
58
- time.sleep(1)
59
-
60
- return None
61
-
62
- def get_user(self):
63
- try:
64
- # pylint: disable-next=missing-timeout
65
- user = requests.get(
66
- f"{self._baseurl}/api/user",
67
- headers={"Authorization": f"Bearer {self._token}"},
68
- ).json()
69
- return user
70
- except Exception:
71
- return None
72
-
73
- def get_repo_build(self, repo, build_number):
74
- # pylint: disable-next=missing-timeout
75
- build = requests.get(
76
- f"{self._baseurl}/api/repos/primait/{repo}/builds/{build_number}",
77
- headers={"Authorization": f"Bearer {self._token}"},
78
- ).json()
79
-
80
- return build
81
-
82
- def get_build_url(self, build_number):
83
- if build_number:
84
- return f"{self._baseurl}/primait/{self._repo}/{build_number}"
85
- return None
86
-
87
- def get_repo_build_url(self, repo, build_number):
88
- if build_number:
89
- return f"{self._baseurl}/primait/{repo}/{build_number}"
90
- return None
91
-
92
- def get_build_and_pipeline_url(self, repo, build_number, pipeline_number):
93
- if build_number:
94
- return f"{self._baseurl}/primait/{repo}/{build_number}/{pipeline_number}"
95
- return None
96
-
97
- def get_tags_from_builds(self):
98
- tags = []
99
- # pylint: disable-next=missing-timeout
100
- builds = requests.get(
101
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds?per_page=100",
102
- headers={"Authorization": f"Bearer {self._token}"},
103
- ).json()
104
-
105
- for build in builds:
106
- if build["event"] == "tag":
107
- tags.append(build["ref"].replace("refs/tags/", ""))
108
-
109
- tags = list(dict.fromkeys(tags))
110
- tags.sort(key=StrictVersion, reverse=True)
111
- return tags
112
-
113
- def get_builds_from_tag(self, tag):
114
- try:
115
- # pylint: disable-next=missing-timeout
116
- builds = requests.get(
117
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds?per_page=100&tag={tag}",
118
- headers={"Authorization": f"Bearer {self._token}"},
119
- ).json()
120
-
121
- except Exception:
122
- builds = []
123
-
124
- if isinstance(builds, dict) and builds.get("message") == "Unauthorized":
125
- raise Exception("Drone API Error: Unauthorized - run suite-py check")
126
-
127
- return builds
128
-
129
- def get_builds_from_branch(self, branch):
130
- try:
131
- # pylint: disable-next=missing-timeout
132
- builds = requests.get(
133
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds?branch={branch}&per_page=100",
134
- headers={"Authorization": f"Bearer {self._token}"},
135
- ).json()
136
-
137
- return [b for b in builds if b["event"] == "push"]
138
- except Exception:
139
- return []
140
-
141
- def get_build_number_from_tag(self, tag):
142
- attempts = 0
143
- while attempts < 3:
144
-
145
- try:
146
- # pylint: disable-next=missing-timeout
147
- builds = requests.get(
148
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds?per_page=100&tag={tag}",
149
- headers={"Authorization": f"Bearer {self._token}"},
150
- ).json()
151
-
152
- for build in builds:
153
- if build["event"] == "tag":
154
- return build["number"]
155
- except Exception:
156
- pass
157
-
158
- time.sleep(2)
159
- attempts += 1
160
-
161
- return None
162
-
163
- def promote(self, build, target, querystring):
164
- try:
165
- # pylint: disable-next=missing-timeout
166
- return requests.post(
167
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds/{build}/promote?target={target}&{querystring}",
168
- headers={"Authorization": f"Bearer {self._token}"},
169
- ).json()
170
- except Exception as e:
171
- logger.error(f"Unable to trigger drone build, exiting with error: {e}")
172
- sys.exit(-1)
173
-
174
- def promote_staging(self, build, target, querystring):
175
- return self.promote(build, target, querystring)
176
-
177
- def promote_production(self, tag, target, querystring):
178
- build = self.get_build_number_from_tag(tag)
179
- if build:
180
- return self.promote(build, target, querystring)
181
-
182
- logger.error(
183
- f"Unable to retrieve drone build for repo {self._repo} with tag {tag}"
184
- )
185
- sys.exit(-1)
186
-
187
- def launch_build(self, build):
188
- # pylint: disable-next=missing-timeout
189
- return requests.post(
190
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds/{build}",
191
- headers={"Authorization": f"Bearer {self._token}"},
192
- ).json()
193
-
194
- def prestart_success(self, build_number):
195
- with Halo(text="Contacting drone...", spinner="dots", color="magenta"):
196
- tries = 10
197
- while build_number and tries > 0:
198
- tries -= 1
199
- # pylint: disable-next=missing-timeout
200
- build_status = requests.get(
201
- f"{self._baseurl}/api/repos/primait/{self._repo}/builds/{build_number}",
202
- headers={"Authorization": f"Bearer {self._token}"},
203
- ).json()
204
-
205
- steps = build_status["stages"][0]["steps"]
206
-
207
- for step in steps:
208
- if step["name"] == "pre-start":
209
- if step["status"] == "success":
210
- return True
211
- break
212
- time.sleep(3)
213
- return False
214
-
215
- def fmt(self):
216
- try:
217
- subprocess.run(
218
- ["drone", "fmt", "--save", ".drone.yml"], cwd=self._path, check=True
219
- )
220
- except CalledProcessError as e:
221
- logger.error(f"{self._repo}: unable to format the .drone.yml {e}")
222
- sys.exit(-1)
223
-
224
- def validate(self):
225
- try:
226
- subprocess.run(
227
- ["drone", "lint", "--trusted", ".drone.yml"], cwd=self._path, check=True
228
- )
229
- except CalledProcessError as e:
230
- logger.error(f"{self._path}: the .drone.yml is not valid {e}")
231
- sys.exit(-1)
232
-
233
- def sign(self):
234
- try:
235
- subprocess.run(
236
- ["drone", "sign", f"primait/{self._repo}", "--save"],
237
- cwd=self._path,
238
- check=True,
239
- )
240
- except CalledProcessError as e:
241
- logger.error(f"{self._repo}: unable to sign the .drone.yml {e}")
242
- sys.exit(-1)
243
-
244
- def parse_yaml(self):
245
- repo_path = os.path.join(self._config.user["projects_home"], self._repo)
246
- yaml_path = os.path.join(repo_path, ".drone.yml")
247
- try:
248
- return yaml.load_all(
249
- open(yaml_path, "r", encoding="utf-8"), Loader=yaml.FullLoader
250
- )
251
- except FileNotFoundError:
252
- return None
@@ -1,28 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- import os
3
- import subprocess
4
- import sys
5
- from subprocess import CalledProcessError
6
-
7
- from suite_py.lib import logger
8
-
9
-
10
- class VaultHandler:
11
- def __init__(self, repo, config):
12
- self._repo = repo
13
- self._projects_home = config.user["projects_home"]
14
- self._path = os.path.join(self._projects_home, repo)
15
-
16
- def exec(self, profile, command, additional_args=""):
17
- try:
18
- c = subprocess.Popen( # pylint: disable=consider-using-with
19
- f"aws-vault exec {profile} {additional_args} -- {command}",
20
- stdout=subprocess.PIPE,
21
- shell=True,
22
- cwd=self._path,
23
- ) # .stdout.read()
24
- c.wait()
25
- return c
26
- except CalledProcessError as e:
27
- logger.error(f"Error during command execution: {e}")
28
- sys.exit(-1)