qontract-reconcile 0.10.2.dev303__py3-none-any.whl → 0.10.2.dev314__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.
- {qontract_reconcile-0.10.2.dev303.dist-info → qontract_reconcile-0.10.2.dev314.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev303.dist-info → qontract_reconcile-0.10.2.dev314.dist-info}/RECORD +30 -30
- reconcile/gql_definitions/common/aws_vpc_requests.py +4 -1
- reconcile/gql_definitions/fragments/aws_vpc_request.py +3 -0
- reconcile/gql_definitions/introspection.json +36 -12
- reconcile/gql_definitions/status_board/status_board.py +0 -20
- reconcile/prometheus_rules_tester/integration.py +1 -1
- reconcile/rhidp/common.py +2 -0
- reconcile/status_board.py +9 -133
- reconcile/terraform_tgw_attachments.py +1 -1
- reconcile/terraform_users.py +3 -1
- reconcile/terraform_vpc_peerings.py +1 -1
- reconcile/terraform_vpc_resources/integration.py +19 -1
- reconcile/typed_queries/status_board.py +8 -43
- reconcile/utils/ocm/base.py +10 -0
- reconcile/utils/ocm/status_board.py +0 -13
- reconcile/utils/openssl.py +2 -2
- reconcile/utils/repo_owners.py +21 -29
- reconcile/utils/runtime/meta.py +2 -1
- reconcile/utils/sharding.py +1 -1
- reconcile/utils/sqs_gateway.py +14 -10
- reconcile/utils/structs.py +3 -3
- reconcile/utils/terraform_client.py +27 -25
- reconcile/utils/terrascript_aws_client.py +487 -372
- reconcile/utils/throughput.py +1 -1
- reconcile/vpc_peerings_validator.py +2 -2
- tools/qontract_cli.py +1 -1
- tools/sre_checkpoints/util.py +5 -3
- {qontract_reconcile-0.10.2.dev303.dist-info → qontract_reconcile-0.10.2.dev314.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev303.dist-info → qontract_reconcile-0.10.2.dev314.dist-info}/entry_points.txt +0 -0
@@ -31,7 +31,11 @@ from reconcile.utils.runtime.integration import (
|
|
31
31
|
from reconcile.utils.secret_reader import create_secret_reader
|
32
32
|
from reconcile.utils.semver_helper import make_semver
|
33
33
|
from reconcile.utils.terraform_client import TerraformClient
|
34
|
-
from reconcile.utils.terrascript_aws_client import
|
34
|
+
from reconcile.utils.terrascript_aws_client import (
|
35
|
+
VPC_REQUEST_DEFAULT_PRIVATE_SUBNET_TAGS,
|
36
|
+
VPC_REQUEST_DEFAULT_PUBLIC_SUBNET_TAGS,
|
37
|
+
TerrascriptClient,
|
38
|
+
)
|
35
39
|
from reconcile.utils.vcs import VCS
|
36
40
|
|
37
41
|
QONTRACT_INTEGRATION = "terraform_vpc_resources"
|
@@ -90,14 +94,28 @@ class TerraformVpcResources(QontractReconcileIntegration[TerraformVpcResourcesPa
|
|
90
94
|
f"{request.identifier}-public_subnets", {}
|
91
95
|
).get("value", [])
|
92
96
|
|
97
|
+
if request.subnets:
|
98
|
+
private_subnet_tags = VPC_REQUEST_DEFAULT_PRIVATE_SUBNET_TAGS | (
|
99
|
+
request.subnets.private_subnet_tags or {}
|
100
|
+
)
|
101
|
+
public_subnet_tags = VPC_REQUEST_DEFAULT_PUBLIC_SUBNET_TAGS | (
|
102
|
+
request.subnets.public_subnet_tags or {}
|
103
|
+
)
|
104
|
+
else:
|
105
|
+
private_subnet_tags = VPC_REQUEST_DEFAULT_PRIVATE_SUBNET_TAGS
|
106
|
+
public_subnet_tags = VPC_REQUEST_DEFAULT_PUBLIC_SUBNET_TAGS
|
107
|
+
|
93
108
|
values = {
|
94
109
|
"static": {
|
95
110
|
"vpc_id": outputs_per_account.get(
|
96
111
|
f"{request.identifier}-vpc_id", {}
|
97
112
|
).get("value"),
|
113
|
+
"vpc_tags": request.vpc_tags or {},
|
98
114
|
"subnets": {
|
99
115
|
"private": private_subnets,
|
100
116
|
"public": public_subnets,
|
117
|
+
"private_subnet_tags": private_subnet_tags,
|
118
|
+
"public_subnet_tags": public_subnet_tags,
|
101
119
|
},
|
102
120
|
"account_name": request.account.name,
|
103
121
|
"region": request.region,
|
@@ -9,10 +9,6 @@ from reconcile.gql_definitions.status_board.status_board import (
|
|
9
9
|
query,
|
10
10
|
)
|
11
11
|
from reconcile.utils import gql
|
12
|
-
from reconcile.utils.ocm.status_board import (
|
13
|
-
METADATA_MANAGED_BY_KEY,
|
14
|
-
METADATA_MANAGED_BY_VALUE,
|
15
|
-
)
|
16
12
|
|
17
13
|
|
18
14
|
def get_status_board(
|
@@ -23,11 +19,11 @@ def get_status_board(
|
|
23
19
|
return query(query_func).status_board_v1 or []
|
24
20
|
|
25
21
|
|
26
|
-
def
|
22
|
+
def get_selected_app_names(
|
27
23
|
global_selectors: Iterable[str],
|
28
24
|
product: StatusBoardProductV1,
|
29
|
-
) ->
|
30
|
-
|
25
|
+
) -> set[str]:
|
26
|
+
selected_app_names: set[str] = set()
|
31
27
|
|
32
28
|
apps: dict[str, Any] = {"apps": []}
|
33
29
|
for namespace in product.product_environment.namespaces or []:
|
@@ -35,45 +31,15 @@ def get_selected_app_data(
|
|
35
31
|
if namespace.app.parent_app:
|
36
32
|
prefix = f"{namespace.app.parent_app.name}-"
|
37
33
|
name = f"{prefix}{namespace.app.name}"
|
38
|
-
|
39
|
-
deployment_saas_files = set()
|
40
|
-
if namespace.app.saas_files:
|
41
|
-
deployment_saas_files = {
|
42
|
-
saas_file.name
|
43
|
-
for saas_file in namespace.app.saas_files
|
44
|
-
if "Deployment" in saas_file.managed_resource_types
|
45
|
-
or "ClowdApp" in saas_file.managed_resource_types
|
46
|
-
}
|
47
|
-
|
48
|
-
selected_app_data[name] = {
|
49
|
-
"metadata": {
|
50
|
-
METADATA_MANAGED_BY_KEY: METADATA_MANAGED_BY_VALUE,
|
51
|
-
"deploymentSaasFiles": set(deployment_saas_files),
|
52
|
-
},
|
53
|
-
}
|
54
|
-
|
34
|
+
selected_app_names.add(name)
|
55
35
|
app = namespace.app.dict(by_alias=True)
|
56
36
|
app["name"] = name
|
57
37
|
apps["apps"].append(app)
|
58
38
|
|
59
39
|
for child in namespace.app.children_apps or []:
|
60
40
|
name = f"{namespace.app.name}-{child.name}"
|
61
|
-
if name not in
|
62
|
-
|
63
|
-
if child.saas_files:
|
64
|
-
deployment_saas_files = {
|
65
|
-
saas_file.name
|
66
|
-
for saas_file in child.saas_files
|
67
|
-
if "Deployment" in saas_file.managed_resource_types
|
68
|
-
}
|
69
|
-
|
70
|
-
selected_app_data[name] = {
|
71
|
-
"metadata": {
|
72
|
-
METADATA_MANAGED_BY_KEY: METADATA_MANAGED_BY_VALUE,
|
73
|
-
"deploymentSaasFiles": set(deployment_saas_files),
|
74
|
-
},
|
75
|
-
}
|
76
|
-
|
41
|
+
if name not in selected_app_names:
|
42
|
+
selected_app_names.add(f"{namespace.app.name}-{child.name}")
|
77
43
|
child_dict = child.dict(by_alias=True)
|
78
44
|
child_dict["name"] = name
|
79
45
|
apps["apps"].append(child_dict)
|
@@ -86,7 +52,6 @@ def get_selected_app_data(
|
|
86
52
|
apps_to_remove: set[str] = set()
|
87
53
|
results = parser.parse(selector).find(apps)
|
88
54
|
apps_to_remove.update(match.value["name"] for match in results)
|
89
|
-
|
90
|
-
selected_app_data.pop(app_name, None)
|
55
|
+
selected_app_names -= apps_to_remove
|
91
56
|
|
92
|
-
return
|
57
|
+
return selected_app_names
|
reconcile/utils/ocm/base.py
CHANGED
@@ -215,6 +215,10 @@ class OCMExternalConfiguration(BaseModel):
|
|
215
215
|
syncsets: dict
|
216
216
|
|
217
217
|
|
218
|
+
class OCMExternalAuthConfig(BaseModel):
|
219
|
+
enabled: bool
|
220
|
+
|
221
|
+
|
218
222
|
PRODUCT_ID_OSD = "osd"
|
219
223
|
PRODUCT_ID_ROSA = "rosa"
|
220
224
|
|
@@ -274,6 +278,8 @@ class OCMCluster(BaseModel):
|
|
274
278
|
|
275
279
|
external_configuration: OCMExternalConfiguration | None
|
276
280
|
|
281
|
+
external_auth_config: OCMExternalAuthConfig | None
|
282
|
+
|
277
283
|
def minor_version(self) -> str:
|
278
284
|
version_info = parse_semver(self.version.raw_id)
|
279
285
|
return f"{version_info.major}.{version_info.minor}"
|
@@ -315,6 +321,10 @@ class OCMCluster(BaseModel):
|
|
315
321
|
def base_domain(self) -> str | None:
|
316
322
|
return self.dns.base_domain if self.dns else None
|
317
323
|
|
324
|
+
@property
|
325
|
+
def external_auth_enabled(self) -> bool:
|
326
|
+
return self.external_auth_config.enabled if self.external_auth_config else False
|
327
|
+
|
318
328
|
|
319
329
|
class OCMLabel(BaseModel):
|
320
330
|
"""
|
@@ -21,7 +21,6 @@ class IDSpec(TypedDict):
|
|
21
21
|
|
22
22
|
|
23
23
|
class ApplicationOCMSpec(BaseOCMSpec):
|
24
|
-
metadata: dict[str, Any]
|
25
24
|
product: IDSpec
|
26
25
|
|
27
26
|
|
@@ -118,18 +117,6 @@ def create_service(ocm_api: OCMBaseClient, spec: ServiceOCMSpec) -> str:
|
|
118
117
|
return resp["id"]
|
119
118
|
|
120
119
|
|
121
|
-
def update_application(
|
122
|
-
ocm_api: OCMBaseClient,
|
123
|
-
application_id: str,
|
124
|
-
spec: ApplicationOCMSpec,
|
125
|
-
) -> None:
|
126
|
-
data = spec | {
|
127
|
-
"metadata": spec.get("metadata", {})
|
128
|
-
| {METADATA_MANAGED_BY_KEY: METADATA_MANAGED_BY_VALUE}
|
129
|
-
}
|
130
|
-
ocm_api.patch(f"/api/status-board/v1/applications/{application_id}", data=data)
|
131
|
-
|
132
|
-
|
133
120
|
def update_service(
|
134
121
|
ocm_api: OCMBaseClient,
|
135
122
|
service_id: str,
|
reconcile/utils/openssl.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
from OpenSSL import crypto
|
2
2
|
|
3
3
|
|
4
|
-
def certificate_matches_host(certificate, host):
|
4
|
+
def certificate_matches_host(certificate: bytes, host: str) -> bool:
|
5
5
|
common_name = get_certificate_common_name(certificate)
|
6
6
|
return host.endswith(common_name.replace("*.", ""))
|
7
7
|
|
8
8
|
|
9
|
-
def get_certificate_common_name(certificate):
|
9
|
+
def get_certificate_common_name(certificate: bytes) -> str:
|
10
10
|
cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
|
11
11
|
subject = cert.get_subject()
|
12
12
|
return subject.CN
|
reconcile/utils/repo_owners.py
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
import pathlib
|
4
|
+
from collections.abc import Iterable, Mapping
|
4
5
|
|
5
6
|
from ruamel import yaml
|
6
7
|
|
8
|
+
from reconcile.utils.github_api import GithubRepositoryApi
|
9
|
+
from reconcile.utils.gitlab_api import GitLabApi
|
10
|
+
|
7
11
|
_LOG = logging.getLogger(__name__)
|
8
12
|
|
9
13
|
|
@@ -12,38 +16,24 @@ class RepoOwners:
|
|
12
16
|
Abstracts the owners of a repository with per-path granularity.
|
13
17
|
"""
|
14
18
|
|
15
|
-
def __init__(
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
git_cli: GitLabApi | GithubRepositoryApi,
|
22
|
+
ref: str = "master",
|
23
|
+
recursive: bool = True,
|
24
|
+
) -> None:
|
16
25
|
self._git_cli = git_cli
|
17
26
|
self._ref = ref
|
18
|
-
self._owners_map = None
|
27
|
+
self._owners_map: dict[str, dict[str, set[str]]] | None = None
|
19
28
|
self._recursive = recursive
|
20
29
|
|
21
30
|
@property
|
22
|
-
def owners_map(self):
|
31
|
+
def owners_map(self) -> dict[str, dict[str, set[str]]]:
|
23
32
|
if self._owners_map is None:
|
24
33
|
self._owners_map = self._get_owners_map()
|
25
34
|
return self._owners_map
|
26
35
|
|
27
|
-
def
|
28
|
-
"""
|
29
|
-
Gets all the owners of the repository.
|
30
|
-
|
31
|
-
:return: the repository owners
|
32
|
-
:rtype: dict
|
33
|
-
"""
|
34
|
-
repo_owners = {"approvers": set(), "reviewers": set()}
|
35
|
-
|
36
|
-
if "." in self.owners_map:
|
37
|
-
repo_owners["approvers"].update(self.owners_map["."]["approvers"])
|
38
|
-
repo_owners["reviewers"].update(self.owners_map["."]["reviewers"])
|
39
|
-
|
40
|
-
for owners in self.owners_map.values():
|
41
|
-
repo_owners["approvers"].update(owners["approvers"])
|
42
|
-
repo_owners["reviewers"].update(owners["reviewers"])
|
43
|
-
|
44
|
-
return repo_owners
|
45
|
-
|
46
|
-
def get_root_owners(self):
|
36
|
+
def get_root_owners(self) -> dict[str, list[str]]:
|
47
37
|
"""
|
48
38
|
Gets all the owners defined in the repository root.
|
49
39
|
|
@@ -56,7 +46,7 @@ class RepoOwners:
|
|
56
46
|
|
57
47
|
return {"approvers": [], "reviewers": []}
|
58
48
|
|
59
|
-
def get_path_owners(self, path):
|
49
|
+
def get_path_owners(self, path: str) -> dict[str, list[str]]:
|
60
50
|
"""
|
61
51
|
Gets all the owners of a given path, no matter in which
|
62
52
|
level of the filesystem tree the owner was specified.
|
@@ -67,7 +57,7 @@ class RepoOwners:
|
|
67
57
|
:return: the path owners
|
68
58
|
:rtype: dict
|
69
59
|
"""
|
70
|
-
path_owners = {"approvers": set(), "reviewers": set()}
|
60
|
+
path_owners: dict[str, set[str]] = {"approvers": set(), "reviewers": set()}
|
71
61
|
|
72
62
|
if "." in self.owners_map:
|
73
63
|
path_owners["approvers"].update(self.owners_map["."]["approvers"])
|
@@ -80,7 +70,7 @@ class RepoOwners:
|
|
80
70
|
|
81
71
|
return self._set_to_sorted_list(path_owners)
|
82
72
|
|
83
|
-
def get_path_closest_owners(self, path):
|
73
|
+
def get_path_closest_owners(self, path: str) -> dict[str, list[str]]:
|
84
74
|
"""
|
85
75
|
Gets all closest owners of a given path, no matter in which
|
86
76
|
level of the filesystem tree the owner was specified.
|
@@ -108,7 +98,7 @@ class RepoOwners:
|
|
108
98
|
|
109
99
|
return {"approvers": [], "reviewers": []}
|
110
100
|
|
111
|
-
def _get_owners_map(self):
|
101
|
+
def _get_owners_map(self) -> dict[str, dict[str, set[str]]]:
|
112
102
|
"""
|
113
103
|
Maps all the OWNERS files content to their respective
|
114
104
|
owned directory.
|
@@ -173,7 +163,7 @@ class RepoOwners:
|
|
173
163
|
}
|
174
164
|
return owners_map
|
175
165
|
|
176
|
-
def _get_aliases(self):
|
166
|
+
def _get_aliases(self) -> dict[str, list[str]] | None:
|
177
167
|
"""
|
178
168
|
Retrieves the approvers aliases from the OWNERS_ALIASES file.
|
179
169
|
|
@@ -191,7 +181,9 @@ class RepoOwners:
|
|
191
181
|
return aliases["aliases"]
|
192
182
|
|
193
183
|
@staticmethod
|
194
|
-
def _set_to_sorted_list(
|
184
|
+
def _set_to_sorted_list(
|
185
|
+
owners: Mapping[str, Iterable[str]],
|
186
|
+
) -> dict[str, list[str]]:
|
195
187
|
approvers = owners["approvers"]
|
196
188
|
sorted_approvers = sorted(approvers) if approvers else []
|
197
189
|
|
reconcile/utils/runtime/meta.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import dataclasses
|
2
2
|
from dataclasses import dataclass
|
3
|
+
from typing import Any
|
3
4
|
|
4
5
|
|
5
6
|
@dataclass
|
@@ -8,5 +9,5 @@ class IntegrationMeta:
|
|
8
9
|
args: list[str]
|
9
10
|
short_help: str | None
|
10
11
|
|
11
|
-
def to_dict(self):
|
12
|
+
def to_dict(self) -> dict[str, Any]:
|
12
13
|
return dataclasses.asdict(self)
|
reconcile/utils/sharding.py
CHANGED
reconcile/utils/sqs_gateway.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
|
+
from collections.abc import Iterable, Mapping
|
4
|
+
from typing import Any, Self
|
3
5
|
|
4
6
|
from reconcile.utils.aws_api import AWSApi
|
5
7
|
from reconcile.utils.secret_reader import SecretReader
|
@@ -12,7 +14,9 @@ class SQSGatewayInitError(Exception):
|
|
12
14
|
class SQSGateway:
|
13
15
|
"""Wrapper around SQS AWS SDK"""
|
14
16
|
|
15
|
-
def __init__(
|
17
|
+
def __init__(
|
18
|
+
self, accounts: Iterable[Mapping[str, Any]], secret_reader: SecretReader
|
19
|
+
) -> None:
|
16
20
|
queue_url = os.environ.get("gitlab_pr_submitter_queue_url") # noqa: SIM112
|
17
21
|
if not queue_url:
|
18
22
|
raise SQSGatewayInitError(
|
@@ -30,17 +34,17 @@ class SQSGateway:
|
|
30
34
|
self.sqs = self._aws_api.get_session_client(session, "sqs")
|
31
35
|
self.queue_url = queue_url
|
32
36
|
|
33
|
-
def __enter__(self):
|
37
|
+
def __enter__(self) -> Self:
|
34
38
|
return self
|
35
39
|
|
36
|
-
def __exit__(self, *ext):
|
40
|
+
def __exit__(self, *ext: Any) -> None:
|
37
41
|
self.cleanup()
|
38
42
|
|
39
|
-
def cleanup(self):
|
43
|
+
def cleanup(self) -> None:
|
40
44
|
self._aws_api.cleanup()
|
41
45
|
|
42
46
|
@staticmethod
|
43
|
-
def get_queue_account(accounts, queue_url):
|
47
|
+
def get_queue_account(accounts: Iterable[Mapping[str, Any]], queue_url: str) -> str:
|
44
48
|
queue_account_uid = queue_url.split("/")[3]
|
45
49
|
queue_account_name = [
|
46
50
|
a["name"] for a in accounts if a["uid"] == queue_account_uid
|
@@ -49,14 +53,14 @@ class SQSGateway:
|
|
49
53
|
raise SQSGatewayInitError(f"account uid not found: {queue_account_uid}")
|
50
54
|
return queue_account_name[0]
|
51
55
|
|
52
|
-
def send_message(self, body):
|
56
|
+
def send_message(self, body: Mapping[str, Any]) -> None:
|
53
57
|
self.sqs.send_message(QueueUrl=self.queue_url, MessageBody=json.dumps(body))
|
54
58
|
|
55
59
|
def receive_messages(
|
56
60
|
self,
|
57
|
-
visibility_timeout=30,
|
58
|
-
wait_time_seconds=20,
|
59
|
-
):
|
61
|
+
visibility_timeout: int = 30,
|
62
|
+
wait_time_seconds: int = 20,
|
63
|
+
) -> list[tuple[str, dict[str, Any]]]:
|
60
64
|
messages = self.sqs.receive_message(
|
61
65
|
QueueUrl=self.queue_url,
|
62
66
|
VisibilityTimeout=visibility_timeout,
|
@@ -64,5 +68,5 @@ class SQSGateway:
|
|
64
68
|
).get("Messages", [])
|
65
69
|
return [(m["ReceiptHandle"], json.loads(m["Body"])) for m in messages]
|
66
70
|
|
67
|
-
def delete_message(self, receipt_handle):
|
71
|
+
def delete_message(self, receipt_handle: str) -> None:
|
68
72
|
self.sqs.delete_message(QueueUrl=self.queue_url, ReceiptHandle=receipt_handle)
|
reconcile/utils/structs.py
CHANGED
@@ -5,12 +5,12 @@ from pydantic.dataclasses import dataclass
|
|
5
5
|
class CommandExecutionResult:
|
6
6
|
"""This class represents a command execution result"""
|
7
7
|
|
8
|
-
def __init__(self, is_ok, message):
|
8
|
+
def __init__(self, is_ok: bool, message: str) -> None:
|
9
9
|
self.is_ok = is_ok
|
10
10
|
self.message = message
|
11
11
|
|
12
|
-
def __str__(self):
|
12
|
+
def __str__(self) -> str:
|
13
13
|
return str(self.message)
|
14
14
|
|
15
|
-
def __bool__(self):
|
15
|
+
def __bool__(self) -> bool:
|
16
16
|
return self.is_ok
|
@@ -5,8 +5,8 @@ import shutil
|
|
5
5
|
import tempfile
|
6
6
|
from collections import defaultdict
|
7
7
|
from collections.abc import (
|
8
|
-
Generator,
|
9
8
|
Iterable,
|
9
|
+
Iterator,
|
10
10
|
Mapping,
|
11
11
|
)
|
12
12
|
from contextlib import contextmanager
|
@@ -86,8 +86,8 @@ class TerraformClient:
|
|
86
86
|
working_dirs: Mapping[str, str],
|
87
87
|
thread_pool_size: int,
|
88
88
|
aws_api: AWSApi | None = None,
|
89
|
-
init_users=False,
|
90
|
-
):
|
89
|
+
init_users: bool = False,
|
90
|
+
) -> None:
|
91
91
|
self.integration = integration
|
92
92
|
self.integration_version = integration_version
|
93
93
|
self.integration_prefix = integration_prefix
|
@@ -101,7 +101,7 @@ class TerraformClient:
|
|
101
101
|
|
102
102
|
self.specs: list[TerraformSpec] = []
|
103
103
|
self.init_specs()
|
104
|
-
self.outputs: dict = {}
|
104
|
+
self.outputs: dict[str, Any] = {}
|
105
105
|
self.init_outputs()
|
106
106
|
|
107
107
|
self.OUTPUT_TYPE_SECRETS = "Secrets"
|
@@ -112,19 +112,19 @@ class TerraformClient:
|
|
112
112
|
if init_users:
|
113
113
|
self.init_existing_users()
|
114
114
|
|
115
|
-
def init_existing_users(self):
|
115
|
+
def init_existing_users(self) -> None:
|
116
116
|
self.users = {
|
117
117
|
account: list(self.format_output(output, self.OUTPUT_TYPE_PASSWORDS).keys())
|
118
118
|
for account, output in self.outputs.items()
|
119
119
|
}
|
120
120
|
|
121
|
-
def increment_apply_count(self):
|
121
|
+
def increment_apply_count(self) -> None:
|
122
122
|
self.apply_count += 1
|
123
123
|
|
124
124
|
def should_apply(self) -> bool:
|
125
125
|
return self.apply_count > 0
|
126
126
|
|
127
|
-
def get_new_users(self):
|
127
|
+
def get_new_users(self) -> list[tuple[str, Any, str, Any]]:
|
128
128
|
new_users = []
|
129
129
|
self.init_outputs() # get updated output
|
130
130
|
for account, output in self.outputs.items():
|
@@ -141,7 +141,7 @@ class TerraformClient:
|
|
141
141
|
))
|
142
142
|
return new_users
|
143
143
|
|
144
|
-
def init_specs(self):
|
144
|
+
def init_specs(self) -> None:
|
145
145
|
self.specs = [
|
146
146
|
TerraformSpec(name=name, working_dir=wd)
|
147
147
|
for name, wd in self.working_dirs.items()
|
@@ -152,7 +152,7 @@ class TerraformClient:
|
|
152
152
|
@contextmanager
|
153
153
|
def _terraform_log_file(
|
154
154
|
self, working_dir: str
|
155
|
-
) ->
|
155
|
+
) -> Iterator[tuple[IO[bytes], dict[str, str]]]:
|
156
156
|
with tempfile.NamedTemporaryFile(dir=working_dir) as f:
|
157
157
|
env = {
|
158
158
|
"TF_LOG": TERRAFORM_LOG_LEVEL,
|
@@ -161,7 +161,7 @@ class TerraformClient:
|
|
161
161
|
yield f, env
|
162
162
|
|
163
163
|
@retry(exceptions=TerraformCommandError)
|
164
|
-
def terraform_init(self, spec: TerraformSpec):
|
164
|
+
def terraform_init(self, spec: TerraformSpec) -> None:
|
165
165
|
with self._terraform_log_file(spec.working_dir) as (f, env):
|
166
166
|
return_code, stdout, stderr = lean_tf.init(spec.working_dir, env=env)
|
167
167
|
log = f.read().decode("utf-8")
|
@@ -171,12 +171,12 @@ class TerraformClient:
|
|
171
171
|
return_code, "init", output=stdout, stderr=stderr
|
172
172
|
)
|
173
173
|
|
174
|
-
def init_outputs(self):
|
174
|
+
def init_outputs(self) -> None:
|
175
175
|
results = threaded.run(self.terraform_output, self.specs, self.thread_pool_size)
|
176
176
|
self.outputs = dict(results)
|
177
177
|
|
178
178
|
@retry(exceptions=TerraformCommandError)
|
179
|
-
def terraform_output(self, spec: TerraformSpec):
|
179
|
+
def terraform_output(self, spec: TerraformSpec) -> tuple[str, Any]:
|
180
180
|
with self._terraform_log_file(spec.working_dir) as (f, env):
|
181
181
|
return_code, stdout, stderr = lean_tf.output(spec.working_dir, env=env)
|
182
182
|
log = f.read().decode("utf-8")
|
@@ -194,17 +194,17 @@ class TerraformClient:
|
|
194
194
|
return spec.name, json.loads(stdout)
|
195
195
|
|
196
196
|
# terraform plan
|
197
|
-
def plan(self, enable_deletion):
|
197
|
+
def plan(self, enable_deletion: bool) -> tuple[bool, bool]:
|
198
198
|
errors = False
|
199
199
|
disabled_deletions_detected = False
|
200
|
-
results = threaded.run(
|
200
|
+
results: list[tuple[bool, list[AccountUser], bool]] = threaded.run(
|
201
201
|
self.terraform_plan,
|
202
202
|
self.specs,
|
203
203
|
self.thread_pool_size,
|
204
204
|
enable_deletion=enable_deletion,
|
205
205
|
)
|
206
206
|
|
207
|
-
self.created_users = []
|
207
|
+
self.created_users: list[AccountUser] = []
|
208
208
|
for disabled_deletion_detected, created_users, error in results:
|
209
209
|
if error:
|
210
210
|
errors = True
|
@@ -278,7 +278,7 @@ class TerraformClient:
|
|
278
278
|
self,
|
279
279
|
spec: TerraformSpec,
|
280
280
|
enable_deletion: bool,
|
281
|
-
) -> tuple[bool, list]:
|
281
|
+
) -> tuple[bool, list[AccountUser]]:
|
282
282
|
disabled_deletion_detected = False
|
283
283
|
name = spec.name
|
284
284
|
account_enable_deletion = self.accounts[name].get("enableDeletion") or False
|
@@ -412,7 +412,9 @@ class TerraformClient:
|
|
412
412
|
)
|
413
413
|
return disabled_deletion_detected, created_users
|
414
414
|
|
415
|
-
def deletion_approved(
|
415
|
+
def deletion_approved(
|
416
|
+
self, account_name: str, resource_type: str, resource_name: str
|
417
|
+
) -> bool:
|
416
418
|
account = self.accounts[account_name]
|
417
419
|
deletion_approvals = account.get("deletionApprovals")
|
418
420
|
if not deletion_approvals:
|
@@ -439,11 +441,11 @@ class TerraformClient:
|
|
439
441
|
return False
|
440
442
|
|
441
443
|
# terraform apply
|
442
|
-
def apply(self):
|
444
|
+
def apply(self) -> bool:
|
443
445
|
errors = threaded.run(self.terraform_apply, self.specs, self.thread_pool_size)
|
444
446
|
return any(errors)
|
445
447
|
|
446
|
-
def terraform_apply(self, spec: TerraformSpec):
|
448
|
+
def terraform_apply(self, spec: TerraformSpec) -> bool:
|
447
449
|
with self._terraform_log_file(spec.working_dir) as (f, env):
|
448
450
|
return_code, stdout, stderr = lean_tf.apply(
|
449
451
|
spec.working_dir,
|
@@ -486,9 +488,9 @@ class TerraformClient:
|
|
486
488
|
|
487
489
|
return replicas_info
|
488
490
|
|
489
|
-
def format_output(self, output, type):
|
491
|
+
def format_output(self, output: Any, type: str) -> dict[str, dict[str, Any]]:
|
490
492
|
# data is a dictionary of dictionaries
|
491
|
-
data = {}
|
493
|
+
data: dict[str, dict[str, Any]] = {}
|
492
494
|
if output is None:
|
493
495
|
return data
|
494
496
|
|
@@ -643,7 +645,7 @@ class TerraformClient:
|
|
643
645
|
return error_occured
|
644
646
|
|
645
647
|
@staticmethod
|
646
|
-
def split_to_lines(*outputs):
|
648
|
+
def split_to_lines(*outputs: str) -> Any:
|
647
649
|
split_outputs = []
|
648
650
|
try:
|
649
651
|
for output in outputs:
|
@@ -656,7 +658,7 @@ class TerraformClient:
|
|
656
658
|
return split_outputs[0]
|
657
659
|
return split_outputs
|
658
660
|
|
659
|
-
def cleanup(self):
|
661
|
+
def cleanup(self) -> None:
|
660
662
|
if self._aws_api is not None:
|
661
663
|
self._aws_api.cleanup()
|
662
664
|
for wd in self.working_dirs.values():
|
@@ -757,7 +759,7 @@ class TerraformClient:
|
|
757
759
|
|
758
760
|
def validate_db_upgrade(
|
759
761
|
self, account_name: str, resource_name: str, resource_change: Mapping[str, Any]
|
760
|
-
):
|
762
|
+
) -> None:
|
761
763
|
"""
|
762
764
|
Determine whether the RDS engine version upgrade is valid.
|
763
765
|
|
@@ -862,7 +864,7 @@ class TerraformClient:
|
|
862
864
|
],
|
863
865
|
}
|
864
866
|
|
865
|
-
def is_supported(engine, version):
|
867
|
+
def is_supported(engine: str, version: str) -> bool:
|
866
868
|
parsed_version = pkg_version.parse(version)
|
867
869
|
if engine == "mysql":
|
868
870
|
return any(
|