qontract-reconcile 0.10.1rc1052__py3-none-any.whl → 0.10.1rc1053__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.1rc1052.dist-info → qontract_reconcile-0.10.1rc1053.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc1052.dist-info → qontract_reconcile-0.10.1rc1053.dist-info}/RECORD +8 -8
- reconcile/aws_ami_cleanup/integration.py +140 -188
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +161 -0
- reconcile/test/test_aws_ami_cleanup.py +59 -129
- reconcile/gql_definitions/aws_ami_cleanup/asg_namespaces.py +0 -124
- {qontract_reconcile-0.10.1rc1052.dist-info → qontract_reconcile-0.10.1rc1053.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc1052.dist-info → qontract_reconcile-0.10.1rc1053.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc1052.dist-info → qontract_reconcile-0.10.1rc1053.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc1052.dist-info → qontract_reconcile-0.10.1rc1053.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc1053
|
4
4
|
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
5
|
Home-page: https://github.com/app-sre/qontract-reconcile
|
6
6
|
Author: Red Hat App-SRE Team
|
{qontract_reconcile-0.10.1rc1052.dist-info → qontract_reconcile-0.10.1rc1053.dist-info}/RECORD
RENAMED
@@ -145,7 +145,7 @@ reconcile/aws_account_manager/metrics.py,sha256=YB10ea4kIGwJfs5N14RF-RoXPb-QQWaD
|
|
145
145
|
reconcile/aws_account_manager/reconciler.py,sha256=8mwwcWVVNoUzmttzxgnLyePqN823v1t_dQCNCrx1mG0,15035
|
146
146
|
reconcile/aws_account_manager/utils.py,sha256=iYPPOtbZ7FiKkz9v5f1YXRIHw5YFOtSavUkF8oMwfJY,1439
|
147
147
|
reconcile/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
148
|
-
reconcile/aws_ami_cleanup/integration.py,sha256=
|
148
|
+
reconcile/aws_ami_cleanup/integration.py,sha256=KG7g9NpbKmoaveDD3oi9SinqUE29NaM-4lGo-6YuHlM,9302
|
149
149
|
reconcile/aws_cloudwatch_log_retention/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
150
150
|
reconcile/aws_cloudwatch_log_retention/integration.py,sha256=0UcSZIrGvnGY4m9fj87oejIolIP_qTxtJInpmW9jrQ0,7772
|
151
151
|
reconcile/aws_saml_idp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -222,7 +222,7 @@ reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py,sh
|
|
222
222
|
reconcile/gql_definitions/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
223
223
|
reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=JdqtE3gMpeodymPJST-aFVkYP_MO--_CcwjF070R5Cs,4883
|
224
224
|
reconcile/gql_definitions/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
225
|
-
reconcile/gql_definitions/aws_ami_cleanup/
|
225
|
+
reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py,sha256=jIgOa888MYLLvVsn1ir3nbkhWLG5T6dBg7oDnp1q8BI,4108
|
226
226
|
reconcile/gql_definitions/aws_saml_idp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
227
227
|
reconcile/gql_definitions/aws_saml_idp/aws_accounts.py,sha256=pR9Qm6P9Roe4OJaDXvfm8AcfkSSAtriQdlwLwW7UdUU,2666
|
228
228
|
reconcile/gql_definitions/aws_saml_roles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -500,7 +500,7 @@ reconcile/test/test_acs_policies.py,sha256=8pwnXpAO-0OI-6oubjf_oPPlpZjVldeZfJJ9u
|
|
500
500
|
reconcile/test/test_acs_rbac.py,sha256=lvNd8GY0-GHzcOdOn13QWdrqbBXXKzNT7EEDHNH7cjM,28272
|
501
501
|
reconcile/test/test_aggregated_list.py,sha256=iiWitQuNYC58aimWaiBoE4NROHjr1NCgQ91MnHEG_Ro,6412
|
502
502
|
reconcile/test/test_amtool.py,sha256=vxRhGieeydMBOb9UI2ziMHjJa8puMeGNsUhGhy-yMnk,1032
|
503
|
-
reconcile/test/test_aws_ami_cleanup.py,sha256=
|
503
|
+
reconcile/test/test_aws_ami_cleanup.py,sha256=s9E82ENpKrzsy9_KTj0tbQN2MVXeacq-_Ztw7FWbq7Q,6603
|
504
504
|
reconcile/test/test_aws_ami_share.py,sha256=eSITdDoXs8mMY7P2lFxAX2DA0sJ9RW6D1tG8Rek0gLE,1981
|
505
505
|
reconcile/test/test_aws_cloudwatch_log_retention.py,sha256=fOUq_vJldGPjXKBdRQsSv4zFz_qenVtRKJFKpbz8BlA,13712
|
506
506
|
reconcile/test/test_aws_iam_keys.py,sha256=MfE9EvItyPNPAl5QaLlJFUvvrZFiar518TM2wWNjJn4,1829
|
@@ -867,8 +867,8 @@ tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jr
|
|
867
867
|
tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
|
868
868
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
869
869
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
870
|
-
qontract_reconcile-0.10.
|
871
|
-
qontract_reconcile-0.10.
|
872
|
-
qontract_reconcile-0.10.
|
873
|
-
qontract_reconcile-0.10.
|
874
|
-
qontract_reconcile-0.10.
|
870
|
+
qontract_reconcile-0.10.1rc1053.dist-info/METADATA,sha256=DIX4cB4NDK1taSiJkGbWr1jmAZIQpsQHp_6xGaNjKQI,2213
|
871
|
+
qontract_reconcile-0.10.1rc1053.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
|
872
|
+
qontract_reconcile-0.10.1rc1053.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
873
|
+
qontract_reconcile-0.10.1rc1053.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
874
|
+
qontract_reconcile-0.10.1rc1053.dist-info/RECORD,,
|
@@ -1,9 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
import re
|
3
|
-
import
|
3
|
+
from collections import defaultdict
|
4
4
|
from collections.abc import (
|
5
5
|
Callable,
|
6
|
-
Mapping,
|
7
6
|
)
|
8
7
|
from datetime import (
|
9
8
|
datetime,
|
@@ -11,27 +10,20 @@ from datetime import (
|
|
11
10
|
)
|
12
11
|
from typing import (
|
13
12
|
TYPE_CHECKING,
|
14
|
-
Any,
|
15
13
|
)
|
16
14
|
|
17
15
|
from botocore.exceptions import ClientError
|
18
16
|
from pydantic import (
|
19
17
|
BaseModel,
|
20
|
-
Field,
|
21
18
|
)
|
22
19
|
|
23
|
-
from reconcile import
|
24
|
-
|
25
|
-
|
26
|
-
ASGImageStaticV1,
|
27
|
-
NamespaceTerraformProviderResourceAWSV1,
|
28
|
-
NamespaceTerraformResourceASGV1,
|
29
|
-
NamespaceV1,
|
20
|
+
from reconcile.gql_definitions.aws_ami_cleanup.aws_accounts import (
|
21
|
+
AWSAccountCleanupOptionAMIV1,
|
22
|
+
AWSAccountSharingOptionAMIV1,
|
30
23
|
)
|
31
|
-
from reconcile.gql_definitions.aws_ami_cleanup.
|
32
|
-
query as
|
24
|
+
from reconcile.gql_definitions.aws_ami_cleanup.aws_accounts import (
|
25
|
+
query as aws_accounts_query,
|
33
26
|
)
|
34
|
-
from reconcile.status import ExitCodes
|
35
27
|
from reconcile.typed_queries.app_interface_vault_settings import (
|
36
28
|
get_app_interface_vault_settings,
|
37
29
|
)
|
@@ -40,7 +32,6 @@ from reconcile.utils.aws_api import AWSApi
|
|
40
32
|
from reconcile.utils.defer import defer
|
41
33
|
from reconcile.utils.parse_dhms_duration import dhms_to_seconds
|
42
34
|
from reconcile.utils.secret_reader import create_secret_reader
|
43
|
-
from reconcile.utils.terrascript_aws_client import TerrascriptClient as Terrascript
|
44
35
|
|
45
36
|
if TYPE_CHECKING:
|
46
37
|
from mypy_boto3_ec2 import EC2Client
|
@@ -51,23 +42,9 @@ QONTRACT_INTEGRATION = "aws_ami_cleanup"
|
|
51
42
|
MANAGED_TAG = {"Key": "managed_by_integration", "Value": QONTRACT_INTEGRATION}
|
52
43
|
|
53
44
|
|
54
|
-
class CannotCompareTagsError(Exception):
|
55
|
-
pass
|
56
|
-
|
57
|
-
|
58
|
-
class AmiTag(BaseModel):
|
59
|
-
key: str = Field(alias="Key")
|
60
|
-
value: str = Field(alias="Value")
|
61
|
-
|
62
|
-
class Config:
|
63
|
-
allow_population_by_field_name = True
|
64
|
-
frozen = True
|
65
|
-
|
66
|
-
|
67
45
|
class AWSAmi(BaseModel):
|
68
46
|
name: str
|
69
47
|
image_id: str
|
70
|
-
tags: set[AmiTag]
|
71
48
|
creation_date: datetime
|
72
49
|
snapshot_ids: list[str]
|
73
50
|
|
@@ -75,237 +52,214 @@ class AWSAmi(BaseModel):
|
|
75
52
|
frozen = True
|
76
53
|
|
77
54
|
|
78
|
-
|
79
|
-
|
80
|
-
tags: set[AmiTag]
|
55
|
+
def get_aws_amis_from_launch_templates(ec2_client: EC2Client) -> set[str]:
|
56
|
+
amis = set()
|
81
57
|
|
82
|
-
|
83
|
-
|
58
|
+
paginator = ec2_client.get_paginator("describe_launch_templates")
|
59
|
+
for page in paginator.paginate():
|
60
|
+
for launch_template in page.get("LaunchTemplates", []):
|
61
|
+
if launch_template_id := launch_template.get("LaunchTemplateId"):
|
62
|
+
launch_template_versions = ec2_client.describe_launch_template_versions(
|
63
|
+
LaunchTemplateId=launch_template_id,
|
64
|
+
Versions=[str(launch_template.get("LatestVersionNumber"))],
|
65
|
+
).get("LaunchTemplateVersions", [])
|
66
|
+
|
67
|
+
if launch_template_versions:
|
68
|
+
if (
|
69
|
+
ami_id := launch_template_versions[0]
|
70
|
+
.get("LaunchTemplateData", {})
|
71
|
+
.get("ImageId")
|
72
|
+
):
|
73
|
+
amis.add(ami_id)
|
74
|
+
|
75
|
+
return amis
|
84
76
|
|
85
77
|
|
86
78
|
def get_aws_amis(
|
87
|
-
aws_api: AWSApi,
|
88
79
|
ec2_client: EC2Client,
|
89
80
|
owner: str,
|
90
81
|
regex: str,
|
91
82
|
age_in_seconds: int,
|
92
83
|
utc_now: datetime,
|
93
|
-
region: str,
|
94
84
|
) -> list[AWSAmi]:
|
95
85
|
"""Get amis that match regex older than given age"""
|
96
86
|
|
97
|
-
images = aws_api.paginate(
|
98
|
-
ec2_client, "describe_images", "Images", {"Owners": [owner]}
|
99
|
-
)
|
100
|
-
|
101
87
|
pattern = re.compile(regex)
|
88
|
+
paginator = ec2_client.get_paginator("describe_images")
|
102
89
|
results = []
|
103
|
-
for
|
104
|
-
|
105
|
-
|
90
|
+
for page in paginator.paginate(Owners=[owner]):
|
91
|
+
for image in page.get("Images", []):
|
92
|
+
if not re.search(pattern, image["Name"]):
|
93
|
+
continue
|
106
94
|
|
107
|
-
|
108
|
-
|
109
|
-
|
95
|
+
creation_date = datetime.strptime(
|
96
|
+
image["CreationDate"], "%Y-%m-%dT%H:%M:%S.%fZ"
|
97
|
+
)
|
98
|
+
current_delta = utc_now - creation_date
|
99
|
+
delete_delta = timedelta(seconds=age_in_seconds)
|
110
100
|
|
111
|
-
|
112
|
-
|
101
|
+
if current_delta < delete_delta:
|
102
|
+
continue
|
113
103
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
104
|
+
snapshot_ids = []
|
105
|
+
for bdv in image["BlockDeviceMappings"]:
|
106
|
+
ebs = bdv.get("Ebs")
|
107
|
+
if not ebs:
|
108
|
+
continue
|
118
109
|
|
119
|
-
|
120
|
-
|
121
|
-
ebs = bdv.get("Ebs")
|
122
|
-
if not ebs:
|
123
|
-
continue
|
110
|
+
if sid := ebs.get("SnapshotId"):
|
111
|
+
snapshot_ids.append(sid)
|
124
112
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
image_id=i["ImageId"],
|
133
|
-
tags=tags,
|
134
|
-
creation_date=creation_date,
|
135
|
-
snapshot_ids=snapshot_ids,
|
113
|
+
results.append(
|
114
|
+
AWSAmi(
|
115
|
+
name=image["Name"],
|
116
|
+
image_id=image["ImageId"],
|
117
|
+
creation_date=creation_date,
|
118
|
+
snapshot_ids=snapshot_ids,
|
119
|
+
)
|
136
120
|
)
|
137
|
-
)
|
138
121
|
|
139
122
|
return results
|
140
123
|
|
141
124
|
|
142
125
|
def get_region(
|
143
|
-
|
144
|
-
|
126
|
+
config_region: str | None,
|
127
|
+
account_name: str,
|
128
|
+
resources_default_region: str,
|
129
|
+
supported_deployment_regions: list[str] | None,
|
145
130
|
) -> str:
|
146
131
|
"""Defines the region to search for AMIs."""
|
147
|
-
region =
|
148
|
-
if region not in
|
149
|
-
raise ValueError(f"region {region} is not supported in {
|
132
|
+
region = config_region or resources_default_region
|
133
|
+
if region not in (supported_deployment_regions or []):
|
134
|
+
raise ValueError(f"region {region} is not supported in {account_name}")
|
150
135
|
|
151
136
|
return region
|
152
137
|
|
153
138
|
|
154
|
-
def get_app_interface_amis(
|
155
|
-
namespaces: list[NamespaceV1] | None, ts: Terrascript
|
156
|
-
) -> list[AIAmi]:
|
157
|
-
"""Returns all the ami referenced in ASGs in app-interface."""
|
158
|
-
app_interface_amis = []
|
159
|
-
for n in namespaces or []:
|
160
|
-
for er in n.external_resources or []:
|
161
|
-
if not isinstance(er, NamespaceTerraformProviderResourceAWSV1):
|
162
|
-
continue
|
163
|
-
|
164
|
-
for r in er.resources:
|
165
|
-
if not isinstance(r, NamespaceTerraformResourceASGV1):
|
166
|
-
continue
|
167
|
-
|
168
|
-
tags = set()
|
169
|
-
for i in r.image:
|
170
|
-
if isinstance(i, ASGImageGitV1):
|
171
|
-
tags.add(
|
172
|
-
AmiTag(
|
173
|
-
key=i.tag_name,
|
174
|
-
value=ts.get_commit_sha(i.dict(by_alias=True)),
|
175
|
-
)
|
176
|
-
)
|
177
|
-
elif isinstance(i, ASGImageStaticV1):
|
178
|
-
tags.add(AmiTag(key=i.tag_name, value=i.value))
|
179
|
-
|
180
|
-
app_interface_amis.append(AIAmi(identifier=r.identifier, tags=tags))
|
181
|
-
|
182
|
-
return app_interface_amis
|
183
|
-
|
184
|
-
|
185
|
-
def check_aws_ami_in_use(
|
186
|
-
aws_ami: AWSAmi, app_interface_amis: list[AIAmi]
|
187
|
-
) -> str | None:
|
188
|
-
"""Verifies if the given AWS ami is in use in a defined app-interface ASG."""
|
189
|
-
for ai_ami in app_interface_amis:
|
190
|
-
# This can happen if the ASG init template has changed over the time. We don't have a way
|
191
|
-
# to properly delete these automatically since we cannot assure they are not in use.
|
192
|
-
# The integration will fail in this case and these amis will need to be handled manually.
|
193
|
-
if len(ai_ami.tags) > len(aws_ami.tags):
|
194
|
-
raise CannotCompareTagsError(
|
195
|
-
f"{ai_ami.identifier} AI AMI has more tags than {aws_ami.image_id} AWS AMI"
|
196
|
-
)
|
197
|
-
|
198
|
-
if ai_ami.tags.issubset(aws_ami.tags):
|
199
|
-
return ai_ami.identifier
|
200
|
-
|
201
|
-
return None
|
202
|
-
|
203
|
-
|
204
139
|
@defer
|
205
140
|
def run(dry_run: bool, thread_pool_size: int, defer: Callable | None = None) -> None:
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
#
|
211
|
-
# it's easy that it breaks without being noticed. Once it is properly monitored, this should
|
212
|
-
# be moved to a typed query.
|
213
|
-
query_data = queries.get_aws_accounts(terraform_state=True, cleanup=True)
|
141
|
+
utc_now = datetime.utcnow()
|
142
|
+
gqlapi = gql.get_api()
|
143
|
+
aws_accounts = aws_accounts_query(gqlapi.query).accounts
|
144
|
+
|
145
|
+
# Get accounts that have cleanup configured
|
214
146
|
cleanup_accounts = []
|
215
|
-
for
|
216
|
-
|
217
|
-
if not cleanups:
|
147
|
+
for account in aws_accounts or []:
|
148
|
+
if not account.cleanup:
|
218
149
|
continue
|
219
150
|
is_ami_related = False
|
220
|
-
for cleanup in
|
221
|
-
is_ami_related |= cleanup.
|
151
|
+
for cleanup in account.cleanup:
|
152
|
+
is_ami_related |= cleanup.provider == "ami"
|
222
153
|
if not is_ami_related:
|
223
154
|
continue
|
224
|
-
cleanup_accounts.append(
|
155
|
+
cleanup_accounts.append(account)
|
225
156
|
|
226
|
-
|
157
|
+
# Build a dict with all accounts that are used to share amis with. Together with
|
158
|
+
# the cleanup account we will look into the account's launch templates in order to
|
159
|
+
# find the AMIs that are currently being used. We will make sure that those are not
|
160
|
+
# deleted even if they have expired.
|
161
|
+
ami_accounts = defaultdict(set)
|
162
|
+
for account in cleanup_accounts:
|
163
|
+
ami_accounts[account.name].add(account.resources_default_region)
|
227
164
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
thread_pool_size,
|
232
|
-
cleanup_accounts,
|
233
|
-
settings=vault_settings.dict(by_alias=True),
|
234
|
-
)
|
165
|
+
for sharing_config in account.sharing or []:
|
166
|
+
if not isinstance(sharing_config, AWSAccountSharingOptionAMIV1):
|
167
|
+
continue
|
235
168
|
|
236
|
-
|
237
|
-
|
238
|
-
|
169
|
+
region = get_region(
|
170
|
+
config_region=sharing_config.region,
|
171
|
+
account_name=sharing_config.account.name,
|
172
|
+
resources_default_region=sharing_config.account.resources_default_region,
|
173
|
+
supported_deployment_regions=sharing_config.account.supported_deployment_regions,
|
174
|
+
)
|
239
175
|
|
240
|
-
|
241
|
-
|
176
|
+
ami_accounts[sharing_config.account.name].add(
|
177
|
+
sharing_config.account.resources_default_region
|
178
|
+
)
|
179
|
+
|
180
|
+
# Build AWSApi object. We will use all those accounts listed in ami_accounts since
|
181
|
+
# we will also need to look for used AMIs.
|
182
|
+
accounts_dicted = [
|
183
|
+
account.dict(by_alias=True)
|
184
|
+
for account in aws_accounts or []
|
185
|
+
if account.name in ami_accounts
|
186
|
+
]
|
187
|
+
secret_reader = create_secret_reader(
|
188
|
+
use_vault=get_app_interface_vault_settings().vault
|
189
|
+
)
|
190
|
+
aws_api = AWSApi(1, accounts_dicted, secret_reader=secret_reader, init_users=False)
|
242
191
|
if defer: # defer is provided by the method decorator; this makes just mypy happy.
|
243
192
|
defer(aws_api.cleanup)
|
244
193
|
|
245
|
-
|
194
|
+
# Get all AMIs used
|
195
|
+
amis_used_in_launch_templates = set()
|
196
|
+
for account_name, regions in ami_accounts.items():
|
197
|
+
for region in regions:
|
198
|
+
session = aws_api.get_session(account_name)
|
199
|
+
ec2_client = aws_api.get_session_client(session, "ec2", region)
|
200
|
+
launch_template_amis = get_aws_amis_from_launch_templates(
|
201
|
+
ec2_client=ec2_client
|
202
|
+
)
|
203
|
+
if launch_template_amis:
|
204
|
+
amis_used_in_launch_templates.update(launch_template_amis)
|
205
|
+
|
206
|
+
# The action
|
246
207
|
for account in cleanup_accounts:
|
247
|
-
for
|
248
|
-
if
|
208
|
+
for cleanup_config in account.cleanup or []:
|
209
|
+
if not isinstance(cleanup_config, AWSAccountCleanupOptionAMIV1):
|
249
210
|
continue
|
250
211
|
|
251
|
-
region = get_region(
|
252
|
-
|
253
|
-
|
212
|
+
region = get_region(
|
213
|
+
config_region=cleanup_config.region,
|
214
|
+
account_name=account.name,
|
215
|
+
resources_default_region=account.resources_default_region,
|
216
|
+
supported_deployment_regions=account.supported_deployment_regions,
|
217
|
+
)
|
218
|
+
age_in_seconds = dhms_to_seconds(cleanup_config.age)
|
254
219
|
|
255
|
-
session = aws_api.get_session(account
|
220
|
+
session = aws_api.get_session(account.name)
|
256
221
|
ec2_client = aws_api.get_session_client(session, "ec2", region)
|
257
222
|
|
258
|
-
|
259
|
-
aws_api=aws_api,
|
223
|
+
aws_amis = get_aws_amis(
|
260
224
|
ec2_client=ec2_client,
|
261
|
-
owner=account
|
262
|
-
regex=regex,
|
225
|
+
owner=account.uid,
|
226
|
+
regex=cleanup_config.regex,
|
263
227
|
age_in_seconds=age_in_seconds,
|
264
228
|
utc_now=utc_now,
|
265
|
-
region=region,
|
266
229
|
)
|
267
230
|
|
268
|
-
for
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
identifier,
|
276
|
-
)
|
277
|
-
continue
|
278
|
-
except CannotCompareTagsError as e:
|
279
|
-
logging.error(e)
|
280
|
-
if not dry_run:
|
281
|
-
exit_code = ExitCodes.ERROR
|
231
|
+
for ami in aws_amis:
|
232
|
+
if ami.image_id in amis_used_in_launch_templates:
|
233
|
+
logging.info(
|
234
|
+
"Discarding AMI %s with id %s as it is still in use.",
|
235
|
+
ami.name,
|
236
|
+
ami.image_id,
|
237
|
+
)
|
282
238
|
continue
|
283
239
|
|
284
240
|
logging.info(
|
285
241
|
"Deregistering image %s with id %s created in %s",
|
286
|
-
|
287
|
-
|
288
|
-
|
242
|
+
ami.name,
|
243
|
+
ami.image_id,
|
244
|
+
ami.creation_date,
|
289
245
|
)
|
290
246
|
|
291
247
|
try:
|
292
|
-
ec2_client.deregister_image(
|
293
|
-
ImageId=aws_ami.image_id, DryRun=dry_run
|
294
|
-
)
|
248
|
+
ec2_client.deregister_image(ImageId=ami.image_id, DryRun=dry_run)
|
295
249
|
except ClientError as e:
|
296
250
|
if "DryRunOperation" in str(e):
|
297
251
|
logging.info(e)
|
298
252
|
else:
|
299
253
|
raise
|
300
254
|
|
301
|
-
if not
|
255
|
+
if not ami.snapshot_ids:
|
302
256
|
continue
|
303
257
|
|
304
|
-
for snapshot_id in
|
258
|
+
for snapshot_id in ami.snapshot_ids:
|
305
259
|
logging.info(
|
306
260
|
"Deleting associated snapshot %s from image %s",
|
307
261
|
snapshot_id,
|
308
|
-
|
262
|
+
ami.image_id,
|
309
263
|
)
|
310
264
|
|
311
265
|
try:
|
@@ -317,5 +271,3 @@ def run(dry_run: bool, thread_pool_size: int, defer: Callable | None = None) ->
|
|
317
271
|
logging.info(e)
|
318
272
|
else:
|
319
273
|
raise
|
320
|
-
|
321
|
-
sys.exit(exit_code)
|
@@ -0,0 +1,161 @@
|
|
1
|
+
"""
|
2
|
+
Generated by qenerate plugin=pydantic_v1. DO NOT MODIFY MANUALLY!
|
3
|
+
"""
|
4
|
+
from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
|
5
|
+
from datetime import datetime # noqa: F401 # pylint: disable=W0611
|
6
|
+
from enum import Enum # noqa: F401 # pylint: disable=W0611
|
7
|
+
from typing import ( # noqa: F401 # pylint: disable=W0611
|
8
|
+
Any,
|
9
|
+
Optional,
|
10
|
+
Union,
|
11
|
+
)
|
12
|
+
|
13
|
+
from pydantic import ( # noqa: F401 # pylint: disable=W0611
|
14
|
+
BaseModel,
|
15
|
+
Extra,
|
16
|
+
Field,
|
17
|
+
Json,
|
18
|
+
)
|
19
|
+
|
20
|
+
from reconcile.gql_definitions.fragments.aws_account_common import AWSAccountCommon
|
21
|
+
from reconcile.gql_definitions.fragments.terraform_state import TerraformState
|
22
|
+
|
23
|
+
|
24
|
+
DEFINITION = """
|
25
|
+
fragment AWSAccountCommon on AWSAccount_v1 {
|
26
|
+
path
|
27
|
+
name
|
28
|
+
description
|
29
|
+
uid
|
30
|
+
terraformUsername
|
31
|
+
consoleUrl
|
32
|
+
resourcesDefaultRegion
|
33
|
+
supportedDeploymentRegions
|
34
|
+
providerVersion
|
35
|
+
accountOwners {
|
36
|
+
name
|
37
|
+
email
|
38
|
+
}
|
39
|
+
automationToken {
|
40
|
+
... VaultSecret
|
41
|
+
}
|
42
|
+
garbageCollection
|
43
|
+
enableDeletion
|
44
|
+
deletionApprovals {
|
45
|
+
type
|
46
|
+
name
|
47
|
+
expiration
|
48
|
+
}
|
49
|
+
disable {
|
50
|
+
integrations
|
51
|
+
}
|
52
|
+
deleteKeys
|
53
|
+
premiumSupport
|
54
|
+
partition
|
55
|
+
}
|
56
|
+
|
57
|
+
fragment TerraformState on TerraformStateAWS_v1 {
|
58
|
+
provider
|
59
|
+
bucket
|
60
|
+
region
|
61
|
+
integrations {
|
62
|
+
key
|
63
|
+
integration
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
fragment VaultSecret on VaultSecret_v1 {
|
68
|
+
path
|
69
|
+
field
|
70
|
+
version
|
71
|
+
format
|
72
|
+
}
|
73
|
+
|
74
|
+
query AWSAccountsAmiCleanup {
|
75
|
+
accounts: awsaccounts_v1 {
|
76
|
+
...AWSAccountCommon
|
77
|
+
terraformState {
|
78
|
+
...TerraformState
|
79
|
+
}
|
80
|
+
cleanup {
|
81
|
+
provider
|
82
|
+
... on AWSAccountCleanupOptionAMI_v1 {
|
83
|
+
regex
|
84
|
+
age
|
85
|
+
region
|
86
|
+
}
|
87
|
+
}
|
88
|
+
sharing {
|
89
|
+
provider
|
90
|
+
account {
|
91
|
+
name
|
92
|
+
supportedDeploymentRegions
|
93
|
+
resourcesDefaultRegion
|
94
|
+
}
|
95
|
+
... on AWSAccountSharingOptionAMI_v1 {
|
96
|
+
region
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
"""
|
102
|
+
|
103
|
+
|
104
|
+
class ConfiguredBaseModel(BaseModel):
|
105
|
+
class Config:
|
106
|
+
smart_union=True
|
107
|
+
extra=Extra.forbid
|
108
|
+
|
109
|
+
|
110
|
+
class AWSAccountCleanupOptionV1(ConfiguredBaseModel):
|
111
|
+
provider: str = Field(..., alias="provider")
|
112
|
+
|
113
|
+
|
114
|
+
class AWSAccountCleanupOptionAMIV1(AWSAccountCleanupOptionV1):
|
115
|
+
regex: str = Field(..., alias="regex")
|
116
|
+
age: str = Field(..., alias="age")
|
117
|
+
region: Optional[str] = Field(..., alias="region")
|
118
|
+
|
119
|
+
|
120
|
+
class AWSAccountSharingOptionV1_AWSAccountV1(ConfiguredBaseModel):
|
121
|
+
name: str = Field(..., alias="name")
|
122
|
+
supported_deployment_regions: Optional[list[str]] = Field(..., alias="supportedDeploymentRegions")
|
123
|
+
resources_default_region: str = Field(..., alias="resourcesDefaultRegion")
|
124
|
+
|
125
|
+
|
126
|
+
class AWSAccountSharingOptionV1(ConfiguredBaseModel):
|
127
|
+
provider: str = Field(..., alias="provider")
|
128
|
+
account: AWSAccountSharingOptionV1_AWSAccountV1 = Field(..., alias="account")
|
129
|
+
|
130
|
+
|
131
|
+
class AWSAccountSharingOptionAMIV1(AWSAccountSharingOptionV1):
|
132
|
+
region: Optional[str] = Field(..., alias="region")
|
133
|
+
|
134
|
+
|
135
|
+
class AWSAccountV1(AWSAccountCommon):
|
136
|
+
terraform_state: Optional[TerraformState] = Field(..., alias="terraformState")
|
137
|
+
cleanup: Optional[list[Union[AWSAccountCleanupOptionAMIV1, AWSAccountCleanupOptionV1]]] = Field(..., alias="cleanup")
|
138
|
+
sharing: Optional[list[Union[AWSAccountSharingOptionAMIV1, AWSAccountSharingOptionV1]]] = Field(..., alias="sharing")
|
139
|
+
|
140
|
+
|
141
|
+
class AWSAccountsAmiCleanupQueryData(ConfiguredBaseModel):
|
142
|
+
accounts: Optional[list[AWSAccountV1]] = Field(..., alias="accounts")
|
143
|
+
|
144
|
+
|
145
|
+
def query(query_func: Callable, **kwargs: Any) -> AWSAccountsAmiCleanupQueryData:
|
146
|
+
"""
|
147
|
+
This is a convenience function which queries and parses the data into
|
148
|
+
concrete types. It should be compatible with most GQL clients.
|
149
|
+
You do not have to use it to consume the generated data classes.
|
150
|
+
Alternatively, you can also mime and alternate the behavior
|
151
|
+
of this function in the caller.
|
152
|
+
|
153
|
+
Parameters:
|
154
|
+
query_func (Callable): Function which queries your GQL Server
|
155
|
+
kwargs: optional arguments that will be passed to the query function
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
AWSAccountsAmiCleanupQueryData: queried data parsed into generated classes
|
159
|
+
"""
|
160
|
+
raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
|
161
|
+
return AWSAccountsAmiCleanupQueryData(**raw_data)
|
@@ -11,35 +11,26 @@ from typing import (
|
|
11
11
|
TYPE_CHECKING,
|
12
12
|
Any,
|
13
13
|
)
|
14
|
-
from unittest.mock import MagicMock
|
15
14
|
|
16
15
|
import boto3
|
17
16
|
import pytest
|
18
17
|
from moto import mock_ec2
|
19
|
-
from pytest_mock import MockerFixture
|
20
18
|
|
21
19
|
from reconcile.aws_ami_cleanup.integration import (
|
22
|
-
AIAmi,
|
23
|
-
AmiTag,
|
24
|
-
AWSAmi,
|
25
|
-
CannotCompareTagsError,
|
26
|
-
check_aws_ami_in_use,
|
27
|
-
get_app_interface_amis,
|
28
20
|
get_aws_amis,
|
21
|
+
get_aws_amis_from_launch_templates,
|
29
22
|
)
|
30
|
-
from reconcile.gql_definitions.aws_ami_cleanup.asg_namespaces import (
|
31
|
-
ASGNamespacesQueryData,
|
32
|
-
)
|
33
|
-
from reconcile.test.fixtures import Fixtures
|
34
|
-
from reconcile.utils.aws_api import AWSApi
|
35
|
-
from reconcile.utils.terrascript_aws_client import TerrascriptClient as Terrascript
|
36
23
|
|
37
24
|
if TYPE_CHECKING:
|
38
25
|
from mypy_boto3_ec2 import EC2Client
|
39
|
-
from mypy_boto3_ec2.type_defs import
|
26
|
+
from mypy_boto3_ec2.type_defs import (
|
27
|
+
CreateImageResultTypeDef,
|
28
|
+
LaunchTemplateVersionTypeDef,
|
29
|
+
)
|
40
30
|
else:
|
41
31
|
EC2Client = object
|
42
32
|
CreateImageResultTypeDef = dict
|
33
|
+
LaunchTemplateVersionTypeDef = dict
|
43
34
|
|
44
35
|
MOTO_DEFAULT_ACCOUNT = "123456789012"
|
45
36
|
|
@@ -57,19 +48,6 @@ def accounts() -> list[dict[str, Any]]:
|
|
57
48
|
]
|
58
49
|
|
59
50
|
|
60
|
-
@pytest.fixture
|
61
|
-
def aws_api(accounts: list[dict[str, Any]], mocker: MockerFixture) -> AWSApi:
|
62
|
-
mock_secret_reader = mocker.patch(
|
63
|
-
"reconcile.utils.aws_api.SecretReader", autospec=True
|
64
|
-
)
|
65
|
-
mock_secret_reader.return_value.read_all.return_value = {
|
66
|
-
"aws_access_key_id": "key_id",
|
67
|
-
"aws_secret_access_key": "access_key",
|
68
|
-
"region": "tf_state_bucket_region",
|
69
|
-
}
|
70
|
-
return AWSApi(1, accounts, init_users=False)
|
71
|
-
|
72
|
-
|
73
51
|
@pytest.fixture
|
74
52
|
def ec2_client() -> Generator[EC2Client, None, None]:
|
75
53
|
with mock_ec2():
|
@@ -123,46 +101,53 @@ def suse_image(ec2_client: EC2Client) -> CreateImageResultTypeDef:
|
|
123
101
|
|
124
102
|
|
125
103
|
@pytest.fixture
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
104
|
+
def ubuntu_launch_template(ec2_client: EC2Client) -> LaunchTemplateVersionTypeDef:
|
105
|
+
# Ubuntu AMI from moto/ec2/resources/amis.json
|
106
|
+
response = ec2_client.create_launch_template(
|
107
|
+
LaunchTemplateName="Ubuntu",
|
108
|
+
LaunchTemplateData={
|
109
|
+
"ImageId": "ami-1e749f67", # Canonical, Ubuntu, 14.04 LTS
|
110
|
+
"InstanceType": "t3.micro",
|
111
|
+
"SecurityGroupIds": ["sg-12345678"],
|
112
|
+
},
|
113
|
+
)
|
114
|
+
|
115
|
+
return ec2_client.describe_launch_template_versions(
|
116
|
+
LaunchTemplateId=response["LaunchTemplate"]["LaunchTemplateId"],
|
117
|
+
Versions=["1"],
|
118
|
+
)["LaunchTemplateVersions"][0]
|
119
|
+
|
120
|
+
|
121
|
+
@pytest.fixture
|
122
|
+
def suse_launch_template(ec2_client: EC2Client) -> LaunchTemplateVersionTypeDef:
|
123
|
+
# SUSE AMI from moto/ec2/resources/amis.json
|
124
|
+
response = ec2_client.create_launch_template(
|
125
|
+
LaunchTemplateName="SUSE",
|
126
|
+
LaunchTemplateData={
|
127
|
+
"ImageId": "ami-35e92e4c", # SUSE Linux Enterprise Server 12 SP3
|
128
|
+
"InstanceType": "t3.micro",
|
129
|
+
"SecurityGroupIds": ["sg-12345678"],
|
130
|
+
},
|
131
|
+
)
|
132
|
+
|
133
|
+
return ec2_client.describe_launch_template_versions(
|
134
|
+
LaunchTemplateId=response["LaunchTemplate"]["LaunchTemplateId"],
|
135
|
+
Versions=["1"],
|
136
|
+
)["LaunchTemplateVersions"][0]
|
149
137
|
|
150
138
|
|
151
139
|
def test_get_aws_amis_success(
|
152
140
|
ec2_client: EC2Client,
|
153
|
-
aws_api: AWSApi,
|
154
141
|
rhel_image: CreateImageResultTypeDef,
|
155
142
|
suse_image: CreateImageResultTypeDef,
|
156
143
|
) -> None:
|
157
144
|
utc_now = datetime.utcnow() + timedelta(seconds=60)
|
158
145
|
amis = get_aws_amis(
|
159
|
-
aws_api=aws_api,
|
160
146
|
ec2_client=ec2_client,
|
161
147
|
owner=MOTO_DEFAULT_ACCOUNT,
|
162
148
|
regex="ci-int-jenkins-worker-rhel7.*",
|
163
149
|
age_in_seconds=30,
|
164
150
|
utc_now=utc_now,
|
165
|
-
region="us-east-1",
|
166
151
|
)
|
167
152
|
|
168
153
|
assert len(amis) == 1
|
@@ -171,19 +156,16 @@ def test_get_aws_amis_success(
|
|
171
156
|
|
172
157
|
def test_get_aws_amis_unmatched_regex(
|
173
158
|
ec2_client: EC2Client,
|
174
|
-
aws_api: AWSApi,
|
175
159
|
rhel_image: CreateImageResultTypeDef,
|
176
160
|
suse_image: CreateImageResultTypeDef,
|
177
161
|
) -> None:
|
178
162
|
utc_now = datetime.utcnow() + timedelta(seconds=60)
|
179
163
|
amis = get_aws_amis(
|
180
|
-
aws_api=aws_api,
|
181
164
|
ec2_client=ec2_client,
|
182
165
|
owner=MOTO_DEFAULT_ACCOUNT,
|
183
166
|
regex="ci-int-jenkins-worker-centos7.*",
|
184
167
|
age_in_seconds=30,
|
185
168
|
utc_now=utc_now,
|
186
|
-
region="us-east-1",
|
187
169
|
)
|
188
170
|
|
189
171
|
assert len(amis) == 0
|
@@ -191,19 +173,16 @@ def test_get_aws_amis_unmatched_regex(
|
|
191
173
|
|
192
174
|
def test_get_aws_amis_different_account(
|
193
175
|
ec2_client: EC2Client,
|
194
|
-
aws_api: AWSApi,
|
195
176
|
rhel_image: CreateImageResultTypeDef,
|
196
177
|
suse_image: CreateImageResultTypeDef,
|
197
178
|
) -> None:
|
198
179
|
utc_now = datetime.utcnow() + timedelta(seconds=60)
|
199
180
|
amis = get_aws_amis(
|
200
|
-
aws_api=aws_api,
|
201
181
|
ec2_client=ec2_client,
|
202
182
|
owner="789123456789",
|
203
183
|
regex="ci-int-jenkins-worker-rhel7.*",
|
204
184
|
age_in_seconds=30,
|
205
185
|
utc_now=utc_now,
|
206
|
-
region="us-east-1",
|
207
186
|
)
|
208
187
|
|
209
188
|
assert len(amis) == 0
|
@@ -211,90 +190,41 @@ def test_get_aws_amis_different_account(
|
|
211
190
|
|
212
191
|
def test_get_aws_amis_too_young(
|
213
192
|
ec2_client: EC2Client,
|
214
|
-
aws_api: AWSApi,
|
215
193
|
rhel_image: CreateImageResultTypeDef,
|
216
194
|
suse_image: CreateImageResultTypeDef,
|
217
195
|
) -> None:
|
218
196
|
utc_now = datetime.utcnow() + timedelta(seconds=60)
|
219
197
|
amis = get_aws_amis(
|
220
|
-
aws_api=aws_api,
|
221
198
|
ec2_client=ec2_client,
|
222
199
|
owner=MOTO_DEFAULT_ACCOUNT,
|
223
200
|
regex="ci-int-jenkins-worker-rhel7.*",
|
224
201
|
age_in_seconds=90,
|
225
202
|
utc_now=utc_now,
|
226
|
-
region="us-east-1",
|
227
203
|
)
|
228
204
|
|
229
205
|
assert len(amis) == 0
|
230
206
|
|
231
207
|
|
232
|
-
def
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
assert app_interface_amis[1].tags == ai_amis_fxt[1].tags
|
243
|
-
|
244
|
-
|
245
|
-
def test_check_aws_ami_in_use(ai_amis_fxt: list[AIAmi]) -> None:
|
246
|
-
utc_now = datetime.utcnow()
|
247
|
-
aws_ami = AWSAmi(
|
248
|
-
name="ci-int-jenkins-worker-app-sre-sha-0123",
|
249
|
-
image_id="ami-123456",
|
250
|
-
creation_date=utc_now,
|
251
|
-
tags={
|
252
|
-
AmiTag(key="infra_commit", value="sha-0123"),
|
253
|
-
AmiTag(key="type", value="ci-int-jenkins-worker-app-sre"),
|
254
|
-
},
|
255
|
-
snapshot_ids=[],
|
256
|
-
)
|
257
|
-
assert check_aws_ami_in_use(aws_ami, ai_amis_fxt) == "ci-int-jenkins-worker-app-sre"
|
258
|
-
|
259
|
-
aws_ami = AWSAmi(
|
260
|
-
name="ci-int-jenkins-worker-app-interface-sha-4567",
|
261
|
-
image_id="ami-823445",
|
262
|
-
creation_date=utc_now,
|
263
|
-
tags={
|
264
|
-
AmiTag(key="type", value="ci-int-jenkins-worker-app-interface"),
|
265
|
-
AmiTag(key="infra_commit", value="sha-4567"),
|
266
|
-
},
|
267
|
-
snapshot_ids=[],
|
268
|
-
)
|
269
|
-
assert (
|
270
|
-
check_aws_ami_in_use(aws_ami, ai_amis_fxt)
|
271
|
-
== "ci-int-jenkins-worker-app-interface"
|
272
|
-
)
|
273
|
-
|
274
|
-
aws_ami = AWSAmi(
|
275
|
-
name="ci-int-jenkins-worker-app-interface-a-different-sha",
|
276
|
-
image_id="ami-823445",
|
277
|
-
creation_date=utc_now,
|
278
|
-
tags={
|
279
|
-
AmiTag(key="type", value="ci-int-jenkins-worker-app-interface"),
|
280
|
-
AmiTag(key="infra_commit", value="a-different-sha"),
|
281
|
-
},
|
282
|
-
snapshot_ids=[],
|
283
|
-
)
|
284
|
-
|
285
|
-
assert not check_aws_ami_in_use(aws_ami, ai_amis_fxt)
|
208
|
+
def test_get_aws_amis_from_launch_templates(
|
209
|
+
ec2_client: EC2Client,
|
210
|
+
ubuntu_launch_template: LaunchTemplateVersionTypeDef,
|
211
|
+
suse_launch_template: LaunchTemplateVersionTypeDef,
|
212
|
+
) -> None:
|
213
|
+
amis = get_aws_amis_from_launch_templates(ec2_client)
|
214
|
+
assert amis == {
|
215
|
+
ubuntu_launch_template["LaunchTemplateData"]["ImageId"],
|
216
|
+
suse_launch_template["LaunchTemplateData"]["ImageId"],
|
217
|
+
}
|
286
218
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
AmiTag(key="type", value="ci-int-jenkins-worker-app-interface"),
|
293
|
-
},
|
294
|
-
snapshot_ids=[],
|
219
|
+
# create a new ubuntu version
|
220
|
+
new_ami_id = "ami-785db401" # "Canonical, Ubuntu, 16.04 LTS
|
221
|
+
ec2_client.create_launch_template_version(
|
222
|
+
LaunchTemplateId=ubuntu_launch_template["LaunchTemplateId"],
|
223
|
+
LaunchTemplateData={"ImageId": new_ami_id},
|
295
224
|
)
|
296
225
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
226
|
+
amis = get_aws_amis_from_launch_templates(ec2_client)
|
227
|
+
assert amis == {
|
228
|
+
new_ami_id,
|
229
|
+
suse_launch_template["LaunchTemplateData"]["ImageId"],
|
230
|
+
}
|
@@ -1,124 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Generated by qenerate plugin=pydantic_v1. DO NOT MODIFY MANUALLY!
|
3
|
-
"""
|
4
|
-
from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
|
5
|
-
from datetime import datetime # noqa: F401 # pylint: disable=W0611
|
6
|
-
from enum import Enum # noqa: F401 # pylint: disable=W0611
|
7
|
-
from typing import ( # noqa: F401 # pylint: disable=W0611
|
8
|
-
Any,
|
9
|
-
Optional,
|
10
|
-
Union,
|
11
|
-
)
|
12
|
-
|
13
|
-
from pydantic import ( # noqa: F401 # pylint: disable=W0611
|
14
|
-
BaseModel,
|
15
|
-
Extra,
|
16
|
-
Field,
|
17
|
-
Json,
|
18
|
-
)
|
19
|
-
|
20
|
-
|
21
|
-
DEFINITION = """
|
22
|
-
query ASGNamespaces {
|
23
|
-
namespaces: namespaces_v1 {
|
24
|
-
name
|
25
|
-
externalResources {
|
26
|
-
provider
|
27
|
-
provisioner {
|
28
|
-
name
|
29
|
-
}
|
30
|
-
... on NamespaceTerraformProviderResourceAWS_v1 {
|
31
|
-
resources {
|
32
|
-
provider
|
33
|
-
... on NamespaceTerraformResourceASG_v1 {
|
34
|
-
identifier
|
35
|
-
image {
|
36
|
-
provider
|
37
|
-
... on ASGImageGit_v1 {
|
38
|
-
tag_name
|
39
|
-
url
|
40
|
-
ref
|
41
|
-
}
|
42
|
-
... on ASGImageStatic_v1 {
|
43
|
-
tag_name
|
44
|
-
value
|
45
|
-
}
|
46
|
-
}
|
47
|
-
}
|
48
|
-
}
|
49
|
-
}
|
50
|
-
}
|
51
|
-
}
|
52
|
-
}
|
53
|
-
"""
|
54
|
-
|
55
|
-
|
56
|
-
class ConfiguredBaseModel(BaseModel):
|
57
|
-
class Config:
|
58
|
-
smart_union=True
|
59
|
-
extra=Extra.forbid
|
60
|
-
|
61
|
-
|
62
|
-
class ExternalResourcesProvisionerV1(ConfiguredBaseModel):
|
63
|
-
name: str = Field(..., alias="name")
|
64
|
-
|
65
|
-
|
66
|
-
class NamespaceExternalResourceV1(ConfiguredBaseModel):
|
67
|
-
provider: str = Field(..., alias="provider")
|
68
|
-
provisioner: ExternalResourcesProvisionerV1 = Field(..., alias="provisioner")
|
69
|
-
|
70
|
-
|
71
|
-
class NamespaceTerraformResourceAWSV1(ConfiguredBaseModel):
|
72
|
-
provider: str = Field(..., alias="provider")
|
73
|
-
|
74
|
-
|
75
|
-
class ASGImageV1(ConfiguredBaseModel):
|
76
|
-
provider: str = Field(..., alias="provider")
|
77
|
-
|
78
|
-
|
79
|
-
class ASGImageGitV1(ASGImageV1):
|
80
|
-
tag_name: str = Field(..., alias="tag_name")
|
81
|
-
url: str = Field(..., alias="url")
|
82
|
-
ref: str = Field(..., alias="ref")
|
83
|
-
|
84
|
-
|
85
|
-
class ASGImageStaticV1(ASGImageV1):
|
86
|
-
tag_name: str = Field(..., alias="tag_name")
|
87
|
-
value: str = Field(..., alias="value")
|
88
|
-
|
89
|
-
|
90
|
-
class NamespaceTerraformResourceASGV1(NamespaceTerraformResourceAWSV1):
|
91
|
-
identifier: str = Field(..., alias="identifier")
|
92
|
-
image: list[Union[ASGImageGitV1, ASGImageStaticV1, ASGImageV1]] = Field(..., alias="image")
|
93
|
-
|
94
|
-
|
95
|
-
class NamespaceTerraformProviderResourceAWSV1(NamespaceExternalResourceV1):
|
96
|
-
resources: list[Union[NamespaceTerraformResourceASGV1, NamespaceTerraformResourceAWSV1]] = Field(..., alias="resources")
|
97
|
-
|
98
|
-
|
99
|
-
class NamespaceV1(ConfiguredBaseModel):
|
100
|
-
name: str = Field(..., alias="name")
|
101
|
-
external_resources: Optional[list[Union[NamespaceTerraformProviderResourceAWSV1, NamespaceExternalResourceV1]]] = Field(..., alias="externalResources")
|
102
|
-
|
103
|
-
|
104
|
-
class ASGNamespacesQueryData(ConfiguredBaseModel):
|
105
|
-
namespaces: Optional[list[NamespaceV1]] = Field(..., alias="namespaces")
|
106
|
-
|
107
|
-
|
108
|
-
def query(query_func: Callable, **kwargs: Any) -> ASGNamespacesQueryData:
|
109
|
-
"""
|
110
|
-
This is a convenience function which queries and parses the data into
|
111
|
-
concrete types. It should be compatible with most GQL clients.
|
112
|
-
You do not have to use it to consume the generated data classes.
|
113
|
-
Alternatively, you can also mime and alternate the behavior
|
114
|
-
of this function in the caller.
|
115
|
-
|
116
|
-
Parameters:
|
117
|
-
query_func (Callable): Function which queries your GQL Server
|
118
|
-
kwargs: optional arguments that will be passed to the query function
|
119
|
-
|
120
|
-
Returns:
|
121
|
-
ASGNamespacesQueryData: queried data parsed into generated classes
|
122
|
-
"""
|
123
|
-
raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
|
124
|
-
return ASGNamespacesQueryData(**raw_data)
|
{qontract_reconcile-0.10.1rc1052.dist-info → qontract_reconcile-0.10.1rc1053.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|