qontract-reconcile 0.10.2.dev43__py3-none-any.whl → 0.10.2.dev45__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.dev43.dist-info → qontract_reconcile-0.10.2.dev45.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev43.dist-info → qontract_reconcile-0.10.2.dev45.dist-info}/RECORD +9 -9
- reconcile/external_resources/aws.py +101 -11
- reconcile/external_resources/factories.py +23 -6
- reconcile/external_resources/manager.py +12 -7
- reconcile/utils/terrascript_aws_client.py +313 -0
- tools/cli_commands/erv2.py +2 -2
- {qontract_reconcile-0.10.2.dev43.dist-info → qontract_reconcile-0.10.2.dev45.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev43.dist-info → qontract_reconcile-0.10.2.dev45.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev43.dist-info → qontract_reconcile-0.10.2.dev45.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.2.
|
3
|
+
Version: 0.10.2.dev45
|
4
4
|
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
5
|
Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
|
6
6
|
Project-URL: repository, https://github.com/app-sre/qontract-reconcile
|
{qontract_reconcile-0.10.2.dev43.dist-info → qontract_reconcile-0.10.2.dev45.dist-info}/RECORD
RENAMED
@@ -195,11 +195,11 @@ reconcile/endpoints_discovery/integration.py,sha256=fzy5tv4c_6WoAZGGBNJ276NVNB1O
|
|
195
195
|
reconcile/endpoints_discovery/merge_request.py,sha256=_yLb4tnvoZMCko8rta2C_CvOInJa9pa3HzSmHNtjgGU,2978
|
196
196
|
reconcile/endpoints_discovery/merge_request_manager.py,sha256=wUMsumxv8RnWaRattax4HfoRlhtVzmgro3GiJJ1C4Vc,6392
|
197
197
|
reconcile/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
198
|
-
reconcile/external_resources/aws.py,sha256=
|
199
|
-
reconcile/external_resources/factories.py,sha256=
|
198
|
+
reconcile/external_resources/aws.py,sha256=KAs656zj4oZYpVbgWsDLXnJWJlLOZdR1JnRhikbF4x0,10712
|
199
|
+
reconcile/external_resources/factories.py,sha256=C0QHT0soEv6z99-ELAAE19S5MaMHhV0t1fSiQn0Coc4,5970
|
200
200
|
reconcile/external_resources/integration.py,sha256=JF38M7R0Z4ADUTx57TZqSZH9k_xpPlbAxQAcGyIISuM,6925
|
201
201
|
reconcile/external_resources/integration_secrets_sync.py,sha256=dX09O3r6KURziUYYfiki10orNjOGVma-XojhVqd0ww4,1667
|
202
|
-
reconcile/external_resources/manager.py,sha256=
|
202
|
+
reconcile/external_resources/manager.py,sha256=ZagwLn6YQ1XmgmMN3qpuDzQsQxa4VOYl-IQPZBwCDqM,17103
|
203
203
|
reconcile/external_resources/meta.py,sha256=noaytFzmShpzLA_ebGh7wuP45mOfHIOnnoUxivjDa1I,672
|
204
204
|
reconcile/external_resources/metrics.py,sha256=KiBjMUaN_z0cSkF_7Ar_a8RiuiwVqjyMcVdISlxhzXE,3898
|
205
205
|
reconcile/external_resources/model.py,sha256=EpgIgVRPUsyfHhgjHv_TLUKjzFiIQt0wUd30K0NJJpI,11826
|
@@ -633,7 +633,7 @@ reconcile/utils/state.py,sha256=az4tBmZ0EdbFcAGiBVUxs3cr2-BVWsuDQiNTvjjQq8s,1637
|
|
633
633
|
reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
|
634
634
|
reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
|
635
635
|
reconcile/utils/terraform_client.py,sha256=H8frsS370y8xfivKLNBD1dwlBLHvfuR6JSN_syBL5Qc,36033
|
636
|
-
reconcile/utils/terrascript_aws_client.py,sha256=
|
636
|
+
reconcile/utils/terrascript_aws_client.py,sha256=UdEM3JeTMiE0VRqtz7gcBWR-c0fouORtPFrniRJ3pao,283505
|
637
637
|
reconcile/utils/three_way_diff_strategy.py,sha256=oQcHXd9LVhirJfoaOBoHUYuZVGfyL2voKr6KVI34zZE,4833
|
638
638
|
reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
|
639
639
|
reconcile/utils/vault.py,sha256=aSA8l9cJlPUHpChFGl27nSY-Mpq9FMjBo7Dcgb1BVfM,15036
|
@@ -751,7 +751,7 @@ tools/sd_app_sre_alert_report.py,sha256=jQpJdXVID68bSNtJNOGDh0-ei1CfEUS4Itr4MAaB
|
|
751
751
|
tools/template_validation.py,sha256=qpKYaTgk0GOPGa2Ct5_5sKdwIHtCAKIBGzsMPuJU5fw,3371
|
752
752
|
tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
753
753
|
tools/cli_commands/container_images_report.py,sha256=8fG9XU-eEhJ7hKCdQzBcdPpvIJR-8WGkHOgFEulpfYQ,5213
|
754
|
-
tools/cli_commands/erv2.py,sha256=
|
754
|
+
tools/cli_commands/erv2.py,sha256=VxUlNXllo947UwmtvS-42IeI9x_t_X3MHrrSI3K_GRo,23274
|
755
755
|
tools/cli_commands/gpg_encrypt.py,sha256=NhzwN49UN7P5_FJgTUN5A4BIwNbFokIE4lwDax2iP5k,4891
|
756
756
|
tools/cli_commands/systems_and_tools.py,sha256=EMHOF1AtUDaoSk0bbjl6oUKYAz4rTZjIBaF-6E6GspM,16816
|
757
757
|
tools/cli_commands/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -773,7 +773,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
773
773
|
tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
|
774
774
|
tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
|
775
775
|
tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
|
776
|
-
qontract_reconcile-0.10.2.
|
777
|
-
qontract_reconcile-0.10.2.
|
778
|
-
qontract_reconcile-0.10.2.
|
779
|
-
qontract_reconcile-0.10.2.
|
776
|
+
qontract_reconcile-0.10.2.dev45.dist-info/METADATA,sha256=weP8FCL2ss8xcapzIrtxBfdAV3gAjUFV5fhHpfGe3nY,24665
|
777
|
+
qontract_reconcile-0.10.2.dev45.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
778
|
+
qontract_reconcile-0.10.2.dev45.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
|
779
|
+
qontract_reconcile-0.10.2.dev45.dist-info/RECORD,,
|
@@ -1,9 +1,11 @@
|
|
1
|
+
import re
|
1
2
|
from abc import ABC, abstractmethod
|
2
3
|
from typing import Any
|
3
4
|
|
4
5
|
from reconcile.external_resources.model import (
|
5
6
|
ExternalResource,
|
6
7
|
ExternalResourceKey,
|
8
|
+
ExternalResourceModuleConfiguration,
|
7
9
|
ExternalResourcesInventory,
|
8
10
|
)
|
9
11
|
from reconcile.utils.external_resource_spec import (
|
@@ -21,10 +23,18 @@ class AWSResourceFactory(ABC):
|
|
21
23
|
self.secret_reader = secret_reader
|
22
24
|
|
23
25
|
@abstractmethod
|
24
|
-
def resolve(
|
26
|
+
def resolve(
|
27
|
+
self,
|
28
|
+
spec: ExternalResourceSpec,
|
29
|
+
module_conf: ExternalResourceModuleConfiguration,
|
30
|
+
) -> dict[str, Any]: ...
|
25
31
|
|
26
32
|
@abstractmethod
|
27
|
-
def validate(
|
33
|
+
def validate(
|
34
|
+
self,
|
35
|
+
resource: ExternalResource,
|
36
|
+
module_conf: ExternalResourceModuleConfiguration,
|
37
|
+
) -> None: ...
|
28
38
|
|
29
39
|
def find_linked_resources(
|
30
40
|
self, spec: ExternalResourceSpec
|
@@ -35,10 +45,18 @@ class AWSResourceFactory(ABC):
|
|
35
45
|
|
36
46
|
|
37
47
|
class AWSDefaultResourceFactory(AWSResourceFactory):
|
38
|
-
def resolve(
|
48
|
+
def resolve(
|
49
|
+
self,
|
50
|
+
spec: ExternalResourceSpec,
|
51
|
+
module_conf: ExternalResourceModuleConfiguration,
|
52
|
+
) -> dict[str, Any]:
|
39
53
|
return ResourceValueResolver(spec=spec, identifier_as_value=True).resolve()
|
40
54
|
|
41
|
-
def validate(
|
55
|
+
def validate(
|
56
|
+
self,
|
57
|
+
resource: ExternalResource,
|
58
|
+
module_conf: ExternalResourceModuleConfiguration,
|
59
|
+
) -> None: ...
|
42
60
|
|
43
61
|
|
44
62
|
class AWSElasticacheFactory(AWSDefaultResourceFactory):
|
@@ -49,7 +67,11 @@ class AWSElasticacheFactory(AWSDefaultResourceFactory):
|
|
49
67
|
"aws", provisioner, "elasticache", identifier
|
50
68
|
)
|
51
69
|
|
52
|
-
def resolve(
|
70
|
+
def resolve(
|
71
|
+
self,
|
72
|
+
spec: ExternalResourceSpec,
|
73
|
+
module_conf: ExternalResourceModuleConfiguration,
|
74
|
+
) -> dict[str, Any]:
|
53
75
|
"""Resolve the elasticache resource specification and translate some attributes to AWS >= 5.60.0 provider format."""
|
54
76
|
rvr = ResourceValueResolver(spec=spec, identifier_as_value=True)
|
55
77
|
data = rvr.resolve()
|
@@ -70,7 +92,11 @@ class AWSElasticacheFactory(AWSDefaultResourceFactory):
|
|
70
92
|
|
71
93
|
return data
|
72
94
|
|
73
|
-
def validate(
|
95
|
+
def validate(
|
96
|
+
self,
|
97
|
+
resource: ExternalResource,
|
98
|
+
module_conf: ExternalResourceModuleConfiguration,
|
99
|
+
) -> None:
|
74
100
|
"""Validate the elasticache resource specification."""
|
75
101
|
data = resource.data
|
76
102
|
if data.get("parameter_group"):
|
@@ -96,6 +122,8 @@ class AWSElasticacheFactory(AWSDefaultResourceFactory):
|
|
96
122
|
|
97
123
|
|
98
124
|
class AWSRdsFactory(AWSDefaultResourceFactory):
|
125
|
+
TIMEOUT_RE = re.compile(r"^\d+m$")
|
126
|
+
|
99
127
|
def _get_source_db_spec(
|
100
128
|
self, provisioner: str, identifier: str
|
101
129
|
) -> ExternalResourceSpec:
|
@@ -110,7 +138,11 @@ class AWSRdsFactory(AWSDefaultResourceFactory):
|
|
110
138
|
"aws", provisioner, "kms", identifier
|
111
139
|
)
|
112
140
|
|
113
|
-
def resolve(
|
141
|
+
def resolve(
|
142
|
+
self,
|
143
|
+
spec: ExternalResourceSpec,
|
144
|
+
module_conf: ExternalResourceModuleConfiguration,
|
145
|
+
) -> dict[str, Any]:
|
114
146
|
rvr = ResourceValueResolver(spec=spec, identifier_as_value=True)
|
115
147
|
data = rvr.resolve()
|
116
148
|
|
@@ -126,7 +158,7 @@ class AWSRdsFactory(AWSDefaultResourceFactory):
|
|
126
158
|
sourcedb_spec = self._get_source_db_spec(
|
127
159
|
spec.provisioner_name, data["replica_source"]
|
128
160
|
)
|
129
|
-
sourcedb = self.resolve(sourcedb_spec)
|
161
|
+
sourcedb = self.resolve(sourcedb_spec, module_conf)
|
130
162
|
sourcedb_region = (
|
131
163
|
sourcedb.get("region", None)
|
132
164
|
or sourcedb_spec.provisioner["resources_default_region"]
|
@@ -142,9 +174,59 @@ class AWSRdsFactory(AWSDefaultResourceFactory):
|
|
142
174
|
spec.provisioner_name, kms_key_id
|
143
175
|
).identifier
|
144
176
|
|
177
|
+
# If not timeouts are set, set default timeouts according to the module reconcile timeout configuration
|
178
|
+
# 5 minutes are substracted to let terraform finish gracefully before the Job is killed.
|
179
|
+
if "timeouts" not in data:
|
180
|
+
data["timeouts"] = {
|
181
|
+
"create": f"{module_conf.reconcile_timeout_minutes - 5}m",
|
182
|
+
"update": f"{module_conf.reconcile_timeout_minutes - 5}m",
|
183
|
+
"delete": f"{module_conf.reconcile_timeout_minutes - 5}m",
|
184
|
+
}
|
145
185
|
return data
|
146
186
|
|
147
|
-
def
|
187
|
+
def _get_timeout_minutes(
|
188
|
+
self,
|
189
|
+
timeout: str,
|
190
|
+
) -> int:
|
191
|
+
if not re.match(AWSRdsFactory.TIMEOUT_RE, timeout):
|
192
|
+
raise ValueError(
|
193
|
+
f"Invalid RDS instance timeout format: {timeout}. Specify timeout in minutes(m)."
|
194
|
+
)
|
195
|
+
return int(timeout[:-1])
|
196
|
+
|
197
|
+
def _validate_timeouts(
|
198
|
+
self,
|
199
|
+
resource: ExternalResource,
|
200
|
+
module_conf: ExternalResourceModuleConfiguration,
|
201
|
+
) -> None:
|
202
|
+
timeouts = resource.data.get("timeouts")
|
203
|
+
if not timeouts:
|
204
|
+
return
|
205
|
+
|
206
|
+
if not isinstance(timeouts, dict):
|
207
|
+
raise ValueError(
|
208
|
+
"Timeouts must be a dictionary with 'create', 'update' and/or 'delete' keys."
|
209
|
+
)
|
210
|
+
|
211
|
+
allowed_keys = {"create", "update", "delete"}
|
212
|
+
if unknown_keys := timeouts.keys() - allowed_keys:
|
213
|
+
raise ValueError(
|
214
|
+
f"Timeouts must be a dictionary with 'create', 'update' and/or 'delete' keys. Offending keys: {unknown_keys}."
|
215
|
+
)
|
216
|
+
|
217
|
+
for option, timeout in timeouts.items():
|
218
|
+
timeout_minutes = self._get_timeout_minutes(timeout)
|
219
|
+
if timeout_minutes >= module_conf.reconcile_timeout_minutes:
|
220
|
+
raise ValueError(
|
221
|
+
f"RDS instance {option} timeout value {timeout_minutes} must be lower than the module reconcile_timeout_minutes value {module_conf.reconcile_timeout_minutes}."
|
222
|
+
)
|
223
|
+
|
224
|
+
def validate(
|
225
|
+
self,
|
226
|
+
resource: ExternalResource,
|
227
|
+
module_conf: ExternalResourceModuleConfiguration,
|
228
|
+
) -> None:
|
229
|
+
self._validate_timeouts(resource, module_conf)
|
148
230
|
|
149
231
|
def find_linked_resources(
|
150
232
|
self, spec: ExternalResourceSpec
|
@@ -166,7 +248,11 @@ class AWSMskFactory(AWSDefaultResourceFactory):
|
|
166
248
|
"aws", provisioner, "msk", identifier
|
167
249
|
)
|
168
250
|
|
169
|
-
def resolve(
|
251
|
+
def resolve(
|
252
|
+
self,
|
253
|
+
spec: ExternalResourceSpec,
|
254
|
+
module_conf: ExternalResourceModuleConfiguration,
|
255
|
+
) -> dict[str, Any]:
|
170
256
|
rvr = ResourceValueResolver(spec=spec, identifier_as_value=True)
|
171
257
|
data = rvr.resolve()
|
172
258
|
data["output_prefix"] = spec.output_prefix
|
@@ -188,7 +274,11 @@ class AWSMskFactory(AWSDefaultResourceFactory):
|
|
188
274
|
del data["users"]
|
189
275
|
return data
|
190
276
|
|
191
|
-
def validate(
|
277
|
+
def validate(
|
278
|
+
self,
|
279
|
+
resource: ExternalResource,
|
280
|
+
module_conf: ExternalResourceModuleConfiguration,
|
281
|
+
) -> None:
|
192
282
|
data = resource.data
|
193
283
|
if (
|
194
284
|
data["number_of_broker_nodes"]
|
@@ -15,6 +15,7 @@ from reconcile.external_resources.meta import QONTRACT_INTEGRATION
|
|
15
15
|
from reconcile.external_resources.model import (
|
16
16
|
ExternalResource,
|
17
17
|
ExternalResourceKey,
|
18
|
+
ExternalResourceModuleConfiguration,
|
18
19
|
ExternalResourceProvision,
|
19
20
|
ExternalResourcesInventory,
|
20
21
|
ModuleInventory,
|
@@ -55,11 +56,19 @@ class ObjectFactory(Generic[T]):
|
|
55
56
|
|
56
57
|
class ExternalResourceFactory(ABC):
|
57
58
|
@abstractmethod
|
58
|
-
def create_external_resource(
|
59
|
+
def create_external_resource(
|
60
|
+
self,
|
61
|
+
spec: ExternalResourceSpec,
|
62
|
+
module_conf: ExternalResourceModuleConfiguration,
|
63
|
+
) -> ExternalResource:
|
59
64
|
pass
|
60
65
|
|
61
66
|
@abstractmethod
|
62
|
-
def validate_external_resource(
|
67
|
+
def validate_external_resource(
|
68
|
+
self,
|
69
|
+
resource: ExternalResource,
|
70
|
+
module_conf: ExternalResourceModuleConfiguration,
|
71
|
+
) -> None:
|
63
72
|
pass
|
64
73
|
|
65
74
|
def find_linked_resources(
|
@@ -121,9 +130,13 @@ class AWSExternalResourceFactory(ExternalResourceFactory):
|
|
121
130
|
self.er_inventory = er_inventory
|
122
131
|
self.secret_reader = secret_reader
|
123
132
|
|
124
|
-
def create_external_resource(
|
133
|
+
def create_external_resource(
|
134
|
+
self,
|
135
|
+
spec: ExternalResourceSpec,
|
136
|
+
module_conf: ExternalResourceModuleConfiguration,
|
137
|
+
) -> ExternalResource:
|
125
138
|
f = self.resource_factories.get_factory(spec.provider)
|
126
|
-
data = f.resolve(spec)
|
139
|
+
data = f.resolve(spec, module_conf)
|
127
140
|
data["tags"] = spec.tags(integration=QONTRACT_INTEGRATION)
|
128
141
|
data["default_tags"] = AWS_DEFAULT_TAGS
|
129
142
|
|
@@ -152,9 +165,13 @@ class AWSExternalResourceFactory(ExternalResourceFactory):
|
|
152
165
|
|
153
166
|
return ExternalResource(data=data, provision=provision)
|
154
167
|
|
155
|
-
def validate_external_resource(
|
168
|
+
def validate_external_resource(
|
169
|
+
self,
|
170
|
+
resource: ExternalResource,
|
171
|
+
module_conf: ExternalResourceModuleConfiguration,
|
172
|
+
) -> None:
|
156
173
|
f = self.resource_factories.get_factory(resource.provision.provider)
|
157
|
-
f.validate(resource)
|
174
|
+
f.validate(resource, module_conf)
|
158
175
|
|
159
176
|
def find_linked_resources(
|
160
177
|
self, spec: ExternalResourceSpec
|
@@ -197,8 +197,11 @@ class ExternalResourcesManager:
|
|
197
197
|
if spec.marked_to_delete:
|
198
198
|
continue
|
199
199
|
module = self.module_inventory.get_from_spec(spec)
|
200
|
+
module_conf = ExternalResourceModuleConfiguration.resolve_configuration(
|
201
|
+
module, spec, self.settings
|
202
|
+
)
|
200
203
|
try:
|
201
|
-
resource = self._build_external_resource(spec)
|
204
|
+
resource = self._build_external_resource(spec, module_conf)
|
202
205
|
except ExternalResourceValidationError as e:
|
203
206
|
self.errors[key] = e
|
204
207
|
continue
|
@@ -208,9 +211,7 @@ class ExternalResourcesManager:
|
|
208
211
|
resource_hash=resource.hash(),
|
209
212
|
input=resource.json(),
|
210
213
|
action=Action.APPLY,
|
211
|
-
module_configuration=
|
212
|
-
module, spec, self.settings
|
213
|
-
),
|
214
|
+
module_configuration=module_conf,
|
214
215
|
linked_resources=self._find_linked_resources(spec),
|
215
216
|
)
|
216
217
|
r.add(reconciliation)
|
@@ -362,10 +363,14 @@ class ExternalResourcesManager:
|
|
362
363
|
)
|
363
364
|
self.state_mgr.update_resource_status(key, ResourceStatus.CREATED)
|
364
365
|
|
365
|
-
def _build_external_resource(
|
366
|
+
def _build_external_resource(
|
367
|
+
self,
|
368
|
+
spec: ExternalResourceSpec,
|
369
|
+
module_conf: ExternalResourceModuleConfiguration,
|
370
|
+
) -> ExternalResource:
|
366
371
|
f = self.factories.get_factory(spec.provision_provider)
|
367
|
-
resource = f.create_external_resource(spec)
|
368
|
-
f.validate_external_resource(resource)
|
372
|
+
resource = f.create_external_resource(spec, module_conf)
|
373
|
+
f.validate_external_resource(resource, module_conf)
|
369
374
|
return resource
|
370
375
|
|
371
376
|
def _find_linked_resources(
|
@@ -77,6 +77,8 @@ from terrascript.resource import (
|
|
77
77
|
aws_ec2_transit_gateway_vpc_attachment,
|
78
78
|
aws_ec2_transit_gateway_vpc_attachment_accepter,
|
79
79
|
aws_ecr_repository,
|
80
|
+
aws_elasticache_parameter_group,
|
81
|
+
aws_elasticache_replication_group,
|
80
82
|
aws_elasticsearch_domain,
|
81
83
|
aws_iam_access_key,
|
82
84
|
aws_iam_group,
|
@@ -104,6 +106,8 @@ from terrascript.resource import (
|
|
104
106
|
aws_lb_listener_rule,
|
105
107
|
aws_lb_target_group,
|
106
108
|
aws_lb_target_group_attachment,
|
109
|
+
aws_msk_cluster,
|
110
|
+
aws_msk_configuration,
|
107
111
|
aws_ram_principal_association,
|
108
112
|
aws_ram_resource_association,
|
109
113
|
aws_ram_resource_share,
|
@@ -1641,6 +1645,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
|
|
1641
1645
|
self.populate_tf_resource_rds(spec)
|
1642
1646
|
elif provider == "s3":
|
1643
1647
|
self.populate_tf_resource_s3(spec)
|
1648
|
+
elif provider == "elasticache":
|
1649
|
+
self.populate_tf_resource_elasticache(spec)
|
1644
1650
|
elif provider == "aws-iam-service-account":
|
1645
1651
|
self.populate_tf_resource_service_account(spec, ocm_map=ocm_map)
|
1646
1652
|
elif provider == "secrets-manager-service-account":
|
@@ -1683,6 +1689,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
|
|
1683
1689
|
self.populate_tf_resource_rosa_authenticator(spec)
|
1684
1690
|
elif provider == "rosa-authenticator-vpce":
|
1685
1691
|
self.populate_tf_resource_rosa_authenticator_vpce(spec)
|
1692
|
+
elif provider == "msk":
|
1693
|
+
self.populate_tf_resource_msk(spec)
|
1686
1694
|
else:
|
1687
1695
|
raise UnknownProviderError(provider)
|
1688
1696
|
|
@@ -2483,6 +2491,94 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
|
|
2483
2491
|
|
2484
2492
|
return bucket_tf_resource
|
2485
2493
|
|
2494
|
+
def populate_tf_resource_elasticache(self, spec):
|
2495
|
+
account = spec.provisioner_name
|
2496
|
+
identifier = spec.identifier
|
2497
|
+
values = self.init_values(spec)
|
2498
|
+
output_prefix = spec.output_prefix
|
2499
|
+
values.setdefault("replication_group_id", values["identifier"])
|
2500
|
+
values.pop("identifier", None)
|
2501
|
+
|
2502
|
+
tf_resources = []
|
2503
|
+
self.init_common_outputs(tf_resources, spec)
|
2504
|
+
|
2505
|
+
default_region = self.default_regions.get(account)
|
2506
|
+
desired_region = values.pop("region", default_region)
|
2507
|
+
|
2508
|
+
provider = ""
|
2509
|
+
if desired_region is not None and self._multiregion_account(account):
|
2510
|
+
provider = "aws." + desired_region
|
2511
|
+
values["provider"] = provider
|
2512
|
+
|
2513
|
+
if not values.get("apply_immediately"):
|
2514
|
+
values["apply_immediately"] = False
|
2515
|
+
|
2516
|
+
parameter_group = values.get("parameter_group")
|
2517
|
+
# Assume that cluster enabled is false if parameter group unset
|
2518
|
+
pg_cluster_enabled = False
|
2519
|
+
|
2520
|
+
if parameter_group:
|
2521
|
+
pg_values = self.get_values(parameter_group)
|
2522
|
+
pg_name = pg_values["name"]
|
2523
|
+
pg_identifier = pg_name
|
2524
|
+
|
2525
|
+
# If the desired region is not the same as the default region
|
2526
|
+
# we append the region to the identifier to make it unique
|
2527
|
+
# in the terraform config
|
2528
|
+
if desired_region is not None and desired_region != default_region:
|
2529
|
+
pg_identifier = f"{pg_name}-{desired_region}"
|
2530
|
+
|
2531
|
+
pg_values["parameter"] = pg_values.pop("parameters")
|
2532
|
+
for param in pg_values["parameter"]:
|
2533
|
+
if param["name"] == "cluster-enabled" and param["value"] == "yes":
|
2534
|
+
pg_cluster_enabled = True
|
2535
|
+
|
2536
|
+
if self._multiregion_account(account) and len(provider) > 0:
|
2537
|
+
pg_values["provider"] = provider
|
2538
|
+
pg_tf_resource = aws_elasticache_parameter_group(pg_identifier, **pg_values)
|
2539
|
+
tf_resources.append(pg_tf_resource)
|
2540
|
+
values["depends_on"] = [
|
2541
|
+
f"aws_elasticache_parameter_group.{pg_identifier}",
|
2542
|
+
]
|
2543
|
+
values["parameter_group_name"] = pg_name
|
2544
|
+
values.pop("parameter_group", None)
|
2545
|
+
|
2546
|
+
auth_token = spec.get_secret_field("db.auth_token")
|
2547
|
+
if not auth_token:
|
2548
|
+
auth_token = self.generate_random_password()
|
2549
|
+
|
2550
|
+
if values.get("transit_encryption_enabled", False):
|
2551
|
+
values["auth_token"] = auth_token
|
2552
|
+
|
2553
|
+
# elasticache replication group
|
2554
|
+
# Ref: https://www.terraform.io/docs/providers/aws/r/
|
2555
|
+
# elasticache_replication_group.html
|
2556
|
+
tf_resource = aws_elasticache_replication_group(identifier, **values)
|
2557
|
+
tf_resources.append(tf_resource)
|
2558
|
+
# elasticache outputs
|
2559
|
+
# we want the outputs to be formed into an OpenShift Secret
|
2560
|
+
# with the following fields
|
2561
|
+
# db.endpoint
|
2562
|
+
output_name = output_prefix + "__db_endpoint"
|
2563
|
+
# https://docs.aws.amazon.com/AmazonElastiCache/
|
2564
|
+
# latest/red-ug/Endpoints.html
|
2565
|
+
if pg_cluster_enabled:
|
2566
|
+
output_value = "${" + tf_resource.configuration_endpoint_address + "}"
|
2567
|
+
else:
|
2568
|
+
output_value = "${" + tf_resource.primary_endpoint_address + "}"
|
2569
|
+
tf_resources.append(Output(output_name, value=output_value))
|
2570
|
+
# db.port
|
2571
|
+
output_name = output_prefix + "__db_port"
|
2572
|
+
output_value = "${" + str(tf_resource.port) + "}"
|
2573
|
+
tf_resources.append(Output(output_name, value=output_value))
|
2574
|
+
# db.auth_token
|
2575
|
+
if values.get("transit_encryption_enabled", False):
|
2576
|
+
output_name = output_prefix + "__db_auth_token"
|
2577
|
+
output_value = values["auth_token"]
|
2578
|
+
tf_resources.append(Output(output_name, value=output_value, sensitive=True))
|
2579
|
+
|
2580
|
+
self.add_resources(account, tf_resources)
|
2581
|
+
|
2486
2582
|
def populate_tf_resource_service_account(self, spec, ocm_map=None):
|
2487
2583
|
account = spec.provisioner_name
|
2488
2584
|
identifier = spec.identifier
|
@@ -4191,6 +4287,24 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
|
|
4191
4287
|
values["db_name"] = db_name
|
4192
4288
|
if values.get("replica_source"):
|
4193
4289
|
values.pop("db_name", None)
|
4290
|
+
elif spec.provider == "elasticache":
|
4291
|
+
if description := values.pop("replication_group_description", None):
|
4292
|
+
values["description"] = description
|
4293
|
+
if num_cache_clusters := values.pop("number_cache_clusters", None):
|
4294
|
+
values["num_cache_clusters"] = num_cache_clusters
|
4295
|
+
if cluster_mode := values.pop("cluster_mode", {}):
|
4296
|
+
for k, v in cluster_mode.items():
|
4297
|
+
values[k] = v
|
4298
|
+
values.pop("availability_zones", None)
|
4299
|
+
elif spec.provider == "msk":
|
4300
|
+
if ebs_volume_size := values.get("broker_node_group_info", {}).pop(
|
4301
|
+
"ebs_volume_size", None
|
4302
|
+
):
|
4303
|
+
values["broker_node_group_info"].setdefault(
|
4304
|
+
"storage_info", {}
|
4305
|
+
).setdefault("ebs_storage_info", {})[
|
4306
|
+
"volume_size"
|
4307
|
+
] = ebs_volume_size
|
4194
4308
|
|
4195
4309
|
return values
|
4196
4310
|
|
@@ -6542,6 +6656,205 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
|
|
6542
6656
|
|
6543
6657
|
self.add_resources(account, tf_resources)
|
6544
6658
|
|
6659
|
+
def populate_tf_resource_msk(self, spec):
|
6660
|
+
account = spec.provisioner_name
|
6661
|
+
values = self.init_values(spec)
|
6662
|
+
output_prefix = spec.output_prefix
|
6663
|
+
tf_resources = []
|
6664
|
+
resource_id = spec.identifier
|
6665
|
+
|
6666
|
+
del values["identifier"]
|
6667
|
+
values.setdefault("cluster_name", spec.identifier)
|
6668
|
+
|
6669
|
+
# common
|
6670
|
+
self.init_common_outputs(tf_resources, spec)
|
6671
|
+
|
6672
|
+
# validations
|
6673
|
+
if (
|
6674
|
+
values["number_of_broker_nodes"]
|
6675
|
+
% len(values["broker_node_group_info"]["client_subnets"])
|
6676
|
+
!= 0
|
6677
|
+
):
|
6678
|
+
raise ValueError(
|
6679
|
+
"number_of_broker_nodes must be a multiple of the number of specified client subnets."
|
6680
|
+
)
|
6681
|
+
|
6682
|
+
scram_enabled = (
|
6683
|
+
values.get("client_authentication", {}).get("sasl", {}).get("scram", False)
|
6684
|
+
)
|
6685
|
+
scram_users = {}
|
6686
|
+
if scram_enabled:
|
6687
|
+
if not spec.resource.get("users", []):
|
6688
|
+
raise ValueError(
|
6689
|
+
"users attribute must be given when client_authentication.sasl.scram is enabled."
|
6690
|
+
)
|
6691
|
+
scram_users = {
|
6692
|
+
user["name"]: self.secret_reader.read_all(user["secret"])
|
6693
|
+
for user in spec.resource["users"]
|
6694
|
+
}
|
6695
|
+
# validate user objects
|
6696
|
+
for user, secret in scram_users.items():
|
6697
|
+
if secret.keys() != {"password", "username"}:
|
6698
|
+
raise ValueError(
|
6699
|
+
f"MSK user '{user}' secret must contain only 'username' and 'password' keys!"
|
6700
|
+
)
|
6701
|
+
|
6702
|
+
# resource - msk config
|
6703
|
+
# unique msk config resource name enables "create_before_destroy" lifecycle
|
6704
|
+
# which is required when changing version which requires a resource replacement
|
6705
|
+
msk_version_str = values["kafka_version"].replace(".", "-")
|
6706
|
+
msk_config_name = f"{resource_id}-{msk_version_str}"
|
6707
|
+
msk_config = aws_msk_configuration(
|
6708
|
+
msk_config_name,
|
6709
|
+
name=msk_config_name,
|
6710
|
+
kafka_versions=[values["kafka_version"]],
|
6711
|
+
server_properties=values["server_properties"],
|
6712
|
+
# lifecycle create_before_destroy is required to ensure that the config is created
|
6713
|
+
# before it is assigned to the cluster
|
6714
|
+
lifecycle={
|
6715
|
+
"create_before_destroy": True,
|
6716
|
+
},
|
6717
|
+
)
|
6718
|
+
tf_resources.append(msk_config)
|
6719
|
+
values.pop("server_properties", None)
|
6720
|
+
|
6721
|
+
# resource - cluster
|
6722
|
+
values["configuration_info"] = {
|
6723
|
+
"arn": "${" + msk_config.arn + "}",
|
6724
|
+
"revision": "${" + msk_config.latest_revision + "}",
|
6725
|
+
}
|
6726
|
+
msk_cluster = aws_msk_cluster(resource_id, **values)
|
6727
|
+
tf_resources.append(msk_cluster)
|
6728
|
+
|
6729
|
+
# resource - cloudwatch
|
6730
|
+
if (
|
6731
|
+
values.get("logging_info", {})
|
6732
|
+
.get("broker_logs", {})
|
6733
|
+
.get("cloudwatch_logs", {})
|
6734
|
+
.get("enabled", False)
|
6735
|
+
):
|
6736
|
+
log_group_values = {
|
6737
|
+
"name": f"{resource_id}-msk-broker-logs",
|
6738
|
+
"tags": values["tags"],
|
6739
|
+
"retention_in_days": values["logging_info"]["broker_logs"][
|
6740
|
+
"cloudwatch_logs"
|
6741
|
+
]["retention_in_days"],
|
6742
|
+
}
|
6743
|
+
log_group_tf_resource = aws_cloudwatch_log_group(
|
6744
|
+
resource_id, **log_group_values
|
6745
|
+
)
|
6746
|
+
tf_resources.append(log_group_tf_resource)
|
6747
|
+
del values["logging_info"]["broker_logs"]["cloudwatch_logs"][
|
6748
|
+
"retention_in_days"
|
6749
|
+
]
|
6750
|
+
values["logging_info"]["broker_logs"]["cloudwatch_logs"]["log_group"] = (
|
6751
|
+
log_group_tf_resource.name
|
6752
|
+
)
|
6753
|
+
|
6754
|
+
# resource - secret manager for SCRAM client credentials
|
6755
|
+
if scram_enabled and scram_users:
|
6756
|
+
scram_secrets: list[
|
6757
|
+
tuple[aws_secretsmanager_secret, aws_secretsmanager_secret_version]
|
6758
|
+
] = []
|
6759
|
+
|
6760
|
+
# kms
|
6761
|
+
kms_values = {
|
6762
|
+
"description": "KMS key for MSK SCRAM credentials",
|
6763
|
+
"tags": values["tags"],
|
6764
|
+
}
|
6765
|
+
kms_key = aws_kms_key(resource_id, **kms_values)
|
6766
|
+
tf_resources.append(kms_key)
|
6767
|
+
|
6768
|
+
kms_key_alias = aws_kms_alias(
|
6769
|
+
resource_id,
|
6770
|
+
name=f"alias/{resource_id}-msk-scram",
|
6771
|
+
target_key_id="${" + kms_key.arn + "}",
|
6772
|
+
)
|
6773
|
+
tf_resources.append(kms_key_alias)
|
6774
|
+
|
6775
|
+
for user, secret in scram_users.items():
|
6776
|
+
secret_identifier = f"AmazonMSK_{resource_id}-{user}"
|
6777
|
+
|
6778
|
+
secret_values = {
|
6779
|
+
"name": secret_identifier,
|
6780
|
+
"tags": values["tags"],
|
6781
|
+
"kms_key_id": "${" + kms_key.arn + "}",
|
6782
|
+
}
|
6783
|
+
secret_resource = aws_secretsmanager_secret(
|
6784
|
+
secret_identifier, **secret_values
|
6785
|
+
)
|
6786
|
+
tf_resources.append(secret_resource)
|
6787
|
+
|
6788
|
+
version_values = {
|
6789
|
+
"secret_id": "${" + secret_resource.arn + "}",
|
6790
|
+
"secret_string": json.dumps(secret, sort_keys=True),
|
6791
|
+
}
|
6792
|
+
version_resource = aws_secretsmanager_secret_version(
|
6793
|
+
secret_identifier, **version_values
|
6794
|
+
)
|
6795
|
+
tf_resources.append(version_resource)
|
6796
|
+
|
6797
|
+
secret_policy_values = {
|
6798
|
+
"secret_arn": "${" + secret_resource.arn + "}",
|
6799
|
+
"policy": json.dumps({
|
6800
|
+
"Version": "2012-10-17",
|
6801
|
+
"Statement": [
|
6802
|
+
{
|
6803
|
+
"Sid": "AWSKafkaResourcePolicy",
|
6804
|
+
"Effect": "Allow",
|
6805
|
+
"Principal": {"Service": "kafka.amazonaws.com"},
|
6806
|
+
"Action": "secretsmanager:getSecretValue",
|
6807
|
+
"Resource": "${" + secret_resource.arn + "}",
|
6808
|
+
}
|
6809
|
+
],
|
6810
|
+
}),
|
6811
|
+
}
|
6812
|
+
secret_policy = aws_secretsmanager_secret_policy(
|
6813
|
+
secret_identifier, **secret_policy_values
|
6814
|
+
)
|
6815
|
+
tf_resources.append(secret_policy)
|
6816
|
+
scram_secrets.append((secret_resource, version_resource))
|
6817
|
+
|
6818
|
+
# create ONE scram secret association for each secret created above
|
6819
|
+
scram_secret_association_values = {
|
6820
|
+
"cluster_arn": "${" + msk_cluster.arn + "}",
|
6821
|
+
"secret_arn_list": ["${" + s.arn + "}" for s, _ in scram_secrets],
|
6822
|
+
"depends_on": self.get_dependencies([v for _, v in scram_secrets]),
|
6823
|
+
}
|
6824
|
+
scram_secret_association = aws_msk_scram_secret_association(
|
6825
|
+
resource_id, **scram_secret_association_values
|
6826
|
+
)
|
6827
|
+
tf_resources.append(scram_secret_association)
|
6828
|
+
|
6829
|
+
# outputs
|
6830
|
+
tf_resources += [
|
6831
|
+
Output(
|
6832
|
+
output_prefix + "__zookeeper_connect_string",
|
6833
|
+
value="${" + msk_cluster.zookeeper_connect_string + "}",
|
6834
|
+
),
|
6835
|
+
Output(
|
6836
|
+
output_prefix + "__zookeeper_connect_string_tls",
|
6837
|
+
value="${" + msk_cluster.zookeeper_connect_string_tls + "}",
|
6838
|
+
),
|
6839
|
+
Output(
|
6840
|
+
output_prefix + "__bootstrap_brokers",
|
6841
|
+
value="${" + msk_cluster.bootstrap_brokers + "}",
|
6842
|
+
),
|
6843
|
+
Output(
|
6844
|
+
output_prefix + "__bootstrap_brokers_tls",
|
6845
|
+
value="${" + msk_cluster.bootstrap_brokers_tls + "}",
|
6846
|
+
),
|
6847
|
+
Output(
|
6848
|
+
output_prefix + "__bootstrap_brokers_sasl_iam",
|
6849
|
+
value="${" + msk_cluster.bootstrap_brokers_sasl_iam + "}",
|
6850
|
+
),
|
6851
|
+
Output(
|
6852
|
+
output_prefix + "__bootstrap_brokers_sasl_scram",
|
6853
|
+
value="${" + msk_cluster.bootstrap_brokers_sasl_scram + "}",
|
6854
|
+
),
|
6855
|
+
]
|
6856
|
+
self.add_resources(account, tf_resources)
|
6857
|
+
|
6545
6858
|
def populate_saml_idp(self, account_name: str, name: str, metadata: str) -> None:
|
6546
6859
|
saml_idp = aws_iam_saml_provider(
|
6547
6860
|
f"{account_name}-{name}", name=name, saml_metadata_document=metadata
|
tools/cli_commands/erv2.py
CHANGED
@@ -118,13 +118,13 @@ class Erv2Cli:
|
|
118
118
|
self._er_settings, m_inventory, er_inventory, self._secret_reader
|
119
119
|
)
|
120
120
|
f = factories.get_factory(spec.provision_provider)
|
121
|
-
self._resource = f.create_external_resource(spec)
|
122
|
-
f.validate_external_resource(self._resource)
|
123
121
|
self._module_configuration = (
|
124
122
|
ExternalResourceModuleConfiguration.resolve_configuration(
|
125
123
|
m_inventory.get_from_spec(spec), spec, self._er_settings
|
126
124
|
)
|
127
125
|
)
|
126
|
+
self._resource = f.create_external_resource(spec, self._module_configuration)
|
127
|
+
f.validate_external_resource(self._resource, self._module_configuration)
|
128
128
|
|
129
129
|
@property
|
130
130
|
def input_data(self) -> str:
|
{qontract_reconcile-0.10.2.dev43.dist-info → qontract_reconcile-0.10.2.dev45.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|