qontract-reconcile 0.10.1rc1173__py3-none-any.whl → 0.10.1rc1175__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.1rc1173.dist-info → qontract_reconcile-0.10.1rc1175.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc1173.dist-info → qontract_reconcile-0.10.1rc1175.dist-info}/RECORD +9 -9
- reconcile/external_resources/aws.py +5 -5
- reconcile/external_resources/reconciler.py +72 -66
- tools/cli_commands/erv2.py +146 -37
- tools/qontract_cli.py +5 -1
- {qontract_reconcile-0.10.1rc1173.dist-info → qontract_reconcile-0.10.1rc1175.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc1173.dist-info → qontract_reconcile-0.10.1rc1175.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc1173.dist-info → qontract_reconcile-0.10.1rc1175.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc1173.dist-info → qontract_reconcile-0.10.1rc1175.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.1rc1175
|
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.1rc1173.dist-info → qontract_reconcile-0.10.1rc1175.dist-info}/RECORD
RENAMED
@@ -192,7 +192,7 @@ reconcile/endpoints_discovery/integration.py,sha256=znfnlm8bZesfcNbQnaR2aaVM-DTB
|
|
192
192
|
reconcile/endpoints_discovery/merge_request.py,sha256=_yLb4tnvoZMCko8rta2C_CvOInJa9pa3HzSmHNtjgGU,2978
|
193
193
|
reconcile/endpoints_discovery/merge_request_manager.py,sha256=wUMsumxv8RnWaRattax4HfoRlhtVzmgro3GiJJ1C4Vc,6392
|
194
194
|
reconcile/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
195
|
-
reconcile/external_resources/aws.py,sha256=
|
195
|
+
reconcile/external_resources/aws.py,sha256=EN2Vz6IRp6TNWX5vwGAGESrD09_8sTFzKgZjdDR6cmg,7072
|
196
196
|
reconcile/external_resources/factories.py,sha256=KrJDh52_8PeCEVjwfeVr1jwAJDdhMXRQ_XcBETfnKY4,4988
|
197
197
|
reconcile/external_resources/integration.py,sha256=gBVO5dE8JyZ3xYcYik-MTIp_18oU7_hpYc_oztyfElQ,6753
|
198
198
|
reconcile/external_resources/integration_secrets_sync.py,sha256=dX09O3r6KURziUYYfiki10orNjOGVma-XojhVqd0ww4,1667
|
@@ -200,7 +200,7 @@ reconcile/external_resources/manager.py,sha256=fXUm09w-9FRWYfJAwtV4_td1UHNLzdoGh
|
|
200
200
|
reconcile/external_resources/meta.py,sha256=noaytFzmShpzLA_ebGh7wuP45mOfHIOnnoUxivjDa1I,672
|
201
201
|
reconcile/external_resources/metrics.py,sha256=nMbyonGZEJDD1lYzpQY2eR9TNwvxYC4ZCcpi6wrExcM,1037
|
202
202
|
reconcile/external_resources/model.py,sha256=H3elpiqehg_jACy28fGV5_77n8gKclVO77-7cfbaMNA,9178
|
203
|
-
reconcile/external_resources/reconciler.py,sha256=
|
203
|
+
reconcile/external_resources/reconciler.py,sha256=K9QvbQCIOCuOHnPIxQE_P_jFtrkF3dGo8d_cCCh08Ys,8973
|
204
204
|
reconcile/external_resources/secrets_sync.py,sha256=6n0oDPLjd9Ql0lf6zsr1AZw8A6EEe3yCzl20XodtgkE,16229
|
205
205
|
reconcile/external_resources/state.py,sha256=z086bnIUTOkzFmQvS9rSAhFsM3Aw_9PLKHBACJ-0tQc,9690
|
206
206
|
reconcile/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -838,11 +838,11 @@ tools/app_interface_metrics_exporter.py,sha256=zkwkxdAUAxjdc-pzx2_oJXG25fo0Fnyd5
|
|
838
838
|
tools/app_interface_reporter.py,sha256=oZPib4HPq0aZ2Zui1QGJGk6qQdfpeihujGDBnSdKyGE,17627
|
839
839
|
tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
|
840
840
|
tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
|
841
|
-
tools/qontract_cli.py,sha256=
|
841
|
+
tools/qontract_cli.py,sha256=YMOYkmP9WZFMX8wPxoJZ5ddstK3YyXMMx6ReXnCiH0w,140707
|
842
842
|
tools/sd_app_sre_alert_report.py,sha256=e9vAdyenUz2f5c8-z-5WY0wv-SJ9aePKDH2r4IwB6pc,5063
|
843
843
|
tools/template_validation.py,sha256=qpKYaTgk0GOPGa2Ct5_5sKdwIHtCAKIBGzsMPuJU5fw,3371
|
844
844
|
tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
845
|
-
tools/cli_commands/erv2.py,sha256=
|
845
|
+
tools/cli_commands/erv2.py,sha256=469qdhyaf7thpPQ4hJSurvmxBqYDJsoI8H4AigQIF7U,20737
|
846
846
|
tools/cli_commands/gpg_encrypt.py,sha256=x02JOMn834z89YSNvr5B-oJky7rR1C0begCkPh45eHk,4958
|
847
847
|
tools/cli_commands/systems_and_tools.py,sha256=EMHOF1AtUDaoSk0bbjl6oUKYAz4rTZjIBaF-6E6GspM,16816
|
848
848
|
tools/cli_commands/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -880,8 +880,8 @@ tools/test/test_qontract_cli.py,sha256=iuzKbQ6ahinvjoQmQLBrG4shey0z-1rB6qCgS8T6d
|
|
880
880
|
tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
|
881
881
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
882
882
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
883
|
-
qontract_reconcile-0.10.
|
884
|
-
qontract_reconcile-0.10.
|
885
|
-
qontract_reconcile-0.10.
|
886
|
-
qontract_reconcile-0.10.
|
887
|
-
qontract_reconcile-0.10.
|
883
|
+
qontract_reconcile-0.10.1rc1175.dist-info/METADATA,sha256=W7cOYjf2juRnK6QmkGQqjd_MG3Sft1hMo5imMLED1qo,2213
|
884
|
+
qontract_reconcile-0.10.1rc1175.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
|
885
|
+
qontract_reconcile-0.10.1rc1175.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
886
|
+
qontract_reconcile-0.10.1rc1175.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
887
|
+
qontract_reconcile-0.10.1rc1175.dist-info/RECORD,,
|
@@ -9,7 +9,6 @@ from reconcile.utils.external_resource_spec import (
|
|
9
9
|
ExternalResourceSpec,
|
10
10
|
)
|
11
11
|
from reconcile.utils.external_resources import ResourceValueResolver
|
12
|
-
from reconcile.utils.helpers import generate_random_password
|
13
12
|
from reconcile.utils.secret_reader import SecretReaderBase
|
14
13
|
|
15
14
|
|
@@ -59,10 +58,6 @@ class AWSElasticacheFactory(AWSDefaultResourceFactory):
|
|
59
58
|
pg_data = rvr._get_values(data["parameter_group"])
|
60
59
|
data["parameter_group"] = pg_data
|
61
60
|
|
62
|
-
if data.get("transit_encryption_enabled", False):
|
63
|
-
data["auth_token"] = (
|
64
|
-
spec.get_secret_field("db.auth_token") or generate_random_password()
|
65
|
-
)
|
66
61
|
return data
|
67
62
|
|
68
63
|
def validate(self, resource: ExternalResource) -> None:
|
@@ -71,6 +66,11 @@ class AWSElasticacheFactory(AWSDefaultResourceFactory):
|
|
71
66
|
if data.get("parameter_group"):
|
72
67
|
if not data["parameter_group"].get("name"):
|
73
68
|
data["parameter_group"]["name"] = f"{data['replication_group_id']}-pg"
|
69
|
+
else:
|
70
|
+
# prefix the parameter_group name with the replication_group_id
|
71
|
+
data["parameter_group"]["name"] = (
|
72
|
+
f"{data['replication_group_id']}-{data['parameter_group']['name']}"
|
73
|
+
)
|
74
74
|
|
75
75
|
if (
|
76
76
|
data.get("parameter_group_name")
|
@@ -21,6 +21,7 @@ from kubernetes.client import (
|
|
21
21
|
from pydantic import BaseModel
|
22
22
|
|
23
23
|
from reconcile.external_resources.model import (
|
24
|
+
Action,
|
24
25
|
Reconciliation,
|
25
26
|
)
|
26
27
|
from reconcile.external_resources.state import ReconcileStatus
|
@@ -88,6 +89,75 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
|
|
88
89
|
}
|
89
90
|
|
90
91
|
def job_spec(self) -> V1JobSpec:
|
92
|
+
job_container = V1Container(
|
93
|
+
name="job",
|
94
|
+
image=self.reconciliation.module_configuration.image_version,
|
95
|
+
image_pull_policy="Always",
|
96
|
+
env=[
|
97
|
+
V1EnvVar(
|
98
|
+
name="DRY_RUN",
|
99
|
+
value=str(self.is_dry_run),
|
100
|
+
),
|
101
|
+
V1EnvVar(
|
102
|
+
name="ACTION",
|
103
|
+
value=self.reconciliation.action.value,
|
104
|
+
),
|
105
|
+
],
|
106
|
+
volume_mounts=[
|
107
|
+
V1VolumeMount(
|
108
|
+
name="credentials",
|
109
|
+
mount_path="/credentials",
|
110
|
+
sub_path="credentials",
|
111
|
+
),
|
112
|
+
V1VolumeMount(
|
113
|
+
name="workdir",
|
114
|
+
mount_path="/work",
|
115
|
+
),
|
116
|
+
self.scripts_volume_mount("/inputs"),
|
117
|
+
],
|
118
|
+
)
|
119
|
+
outputs_secret_container = V1Container(
|
120
|
+
name="outputs",
|
121
|
+
image=self.reconciliation.module_configuration.outputs_secret_image_version,
|
122
|
+
image_pull_policy="Always",
|
123
|
+
env=[
|
124
|
+
V1EnvVar(
|
125
|
+
name="NAMESPACE",
|
126
|
+
value_from=V1EnvVarSource(
|
127
|
+
field_ref=V1ObjectFieldSelector(field_path="metadata.namespace")
|
128
|
+
),
|
129
|
+
),
|
130
|
+
V1EnvVar(
|
131
|
+
name="ACTION",
|
132
|
+
value=self.reconciliation.action,
|
133
|
+
),
|
134
|
+
V1EnvVar(
|
135
|
+
name="DRY_RUN",
|
136
|
+
value=str(self.is_dry_run),
|
137
|
+
),
|
138
|
+
],
|
139
|
+
volume_mounts=[
|
140
|
+
V1VolumeMount(
|
141
|
+
name="credentials",
|
142
|
+
mount_path="/.aws/credentials",
|
143
|
+
sub_path="credentials",
|
144
|
+
),
|
145
|
+
V1VolumeMount(
|
146
|
+
name="workdir",
|
147
|
+
mount_path="/work",
|
148
|
+
),
|
149
|
+
self.scripts_volume_mount("/inputs"),
|
150
|
+
],
|
151
|
+
)
|
152
|
+
|
153
|
+
# For delete actions, we don't need to run the outputs_secrets container
|
154
|
+
if self.reconciliation.action == Action.APPLY:
|
155
|
+
init_containers = [job_container]
|
156
|
+
containers = [outputs_secret_container]
|
157
|
+
else:
|
158
|
+
init_containers = []
|
159
|
+
containers = [job_container]
|
160
|
+
|
91
161
|
return V1JobSpec(
|
92
162
|
backoff_limit=0,
|
93
163
|
active_deadline_seconds=self.reconciliation.module_configuration.reconcile_timeout_minutes
|
@@ -98,72 +168,8 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
|
|
98
168
|
annotations=self.annotations(), labels=self.labels()
|
99
169
|
),
|
100
170
|
spec=V1PodSpec(
|
101
|
-
init_containers=
|
102
|
-
|
103
|
-
name="job",
|
104
|
-
image=self.reconciliation.module_configuration.image_version,
|
105
|
-
image_pull_policy="Always",
|
106
|
-
env=[
|
107
|
-
V1EnvVar(
|
108
|
-
name="DRY_RUN",
|
109
|
-
value=str(self.is_dry_run),
|
110
|
-
),
|
111
|
-
V1EnvVar(
|
112
|
-
name="ACTION",
|
113
|
-
value=self.reconciliation.action.value,
|
114
|
-
),
|
115
|
-
],
|
116
|
-
volume_mounts=[
|
117
|
-
V1VolumeMount(
|
118
|
-
name="credentials",
|
119
|
-
mount_path="/credentials",
|
120
|
-
sub_path="credentials",
|
121
|
-
),
|
122
|
-
V1VolumeMount(
|
123
|
-
name="workdir",
|
124
|
-
mount_path="/work",
|
125
|
-
),
|
126
|
-
self.scripts_volume_mount("/inputs"),
|
127
|
-
],
|
128
|
-
)
|
129
|
-
],
|
130
|
-
containers=[
|
131
|
-
V1Container(
|
132
|
-
name="outputs",
|
133
|
-
image=self.reconciliation.module_configuration.outputs_secret_image_version,
|
134
|
-
image_pull_policy="Always",
|
135
|
-
env=[
|
136
|
-
V1EnvVar(
|
137
|
-
name="NAMESPACE",
|
138
|
-
value_from=V1EnvVarSource(
|
139
|
-
field_ref=V1ObjectFieldSelector(
|
140
|
-
field_path="metadata.namespace"
|
141
|
-
)
|
142
|
-
),
|
143
|
-
),
|
144
|
-
V1EnvVar(
|
145
|
-
name="ACTION",
|
146
|
-
value=self.reconciliation.action,
|
147
|
-
),
|
148
|
-
V1EnvVar(
|
149
|
-
name="DRY_RUN",
|
150
|
-
value=str(self.is_dry_run),
|
151
|
-
),
|
152
|
-
],
|
153
|
-
volume_mounts=[
|
154
|
-
V1VolumeMount(
|
155
|
-
name="credentials",
|
156
|
-
mount_path="/.aws/credentials",
|
157
|
-
sub_path="credentials",
|
158
|
-
),
|
159
|
-
V1VolumeMount(
|
160
|
-
name="workdir",
|
161
|
-
mount_path="/work",
|
162
|
-
),
|
163
|
-
self.scripts_volume_mount("/inputs"),
|
164
|
-
],
|
165
|
-
)
|
166
|
-
],
|
171
|
+
init_containers=init_containers,
|
172
|
+
containers=containers,
|
167
173
|
image_pull_secrets=[V1LocalObjectReference(name="quay.io")],
|
168
174
|
volumes=[
|
169
175
|
V1Volume(
|
tools/cli_commands/erv2.py
CHANGED
@@ -4,12 +4,12 @@ import json
|
|
4
4
|
import os
|
5
5
|
import sys
|
6
6
|
from collections.abc import Iterator
|
7
|
-
from contextlib import contextmanager
|
7
|
+
from contextlib import contextmanager, suppress
|
8
8
|
from difflib import get_close_matches
|
9
9
|
from enum import Enum
|
10
10
|
from pathlib import Path
|
11
11
|
from subprocess import CalledProcessError, run
|
12
|
-
from typing import Protocol
|
12
|
+
from typing import Any, Protocol
|
13
13
|
|
14
14
|
from pydantic import BaseModel
|
15
15
|
from rich import print as rich_print
|
@@ -178,6 +178,7 @@ class Erv2Cli:
|
|
178
178
|
|
179
179
|
# run cdktf synth
|
180
180
|
with task(self.progress_spinner, "-- Running CDKTF synth"):
|
181
|
+
run(["docker", "pull", self.image], check=True, capture_output=True)
|
181
182
|
run(
|
182
183
|
[
|
183
184
|
"docker",
|
@@ -245,8 +246,14 @@ class TfAction(Enum):
|
|
245
246
|
DESTROY = "delete"
|
246
247
|
|
247
248
|
|
249
|
+
class Change(BaseModel):
|
250
|
+
before: Any | None = None
|
251
|
+
after: Any | None = None
|
252
|
+
|
253
|
+
|
248
254
|
class TfResource(BaseModel):
|
249
255
|
address: str
|
256
|
+
change: Change | None = None
|
250
257
|
|
251
258
|
@property
|
252
259
|
def id(self) -> str:
|
@@ -269,13 +276,19 @@ class TfResourceList(BaseModel):
|
|
269
276
|
def __iter__(self) -> Iterator[TfResource]: # type: ignore
|
270
277
|
return iter(self.resources)
|
271
278
|
|
272
|
-
def
|
279
|
+
def get_resource_by_address(self, address: str) -> TfResource | None:
|
273
280
|
for resource in self.resources:
|
274
281
|
if resource.address == address:
|
275
282
|
return resource
|
276
283
|
return None
|
277
284
|
|
278
|
-
def
|
285
|
+
def get_resource_by_type(self, type: str) -> TfResource:
|
286
|
+
results = self.get_resources_by_type(type)
|
287
|
+
if len(results) > 1:
|
288
|
+
raise ValueError(f"More than one resource found for type '{type}'!")
|
289
|
+
return results[0]
|
290
|
+
|
291
|
+
def get_resources_by_type(self, type: str) -> list[TfResource]:
|
279
292
|
results = [resource for resource in self.resources if resource.type == type]
|
280
293
|
if not results:
|
281
294
|
raise KeyError(f"Resource type '{type}' not found!")
|
@@ -287,13 +300,13 @@ class TfResourceList(BaseModel):
|
|
287
300
|
self holds the source resources (terraform-resources).
|
288
301
|
The tf_resource is the destination resource (ERv2).
|
289
302
|
"""
|
290
|
-
if resource := self.
|
303
|
+
if resource := self.get_resource_by_address(tf_resource.address):
|
291
304
|
# exact match by AWS address
|
292
305
|
return [resource]
|
293
306
|
|
294
307
|
# a resource with the same ID does not exist
|
295
308
|
# let's try to find the resource by the AWS type
|
296
|
-
results = self.
|
309
|
+
results = self.get_resources_by_type(tf_resource.type)
|
297
310
|
if len(results) == 1:
|
298
311
|
# there is just one resource with the same type
|
299
312
|
# this must be the searched resource.
|
@@ -354,6 +367,25 @@ class TerraformCli:
|
|
354
367
|
if not self._dry_run:
|
355
368
|
self._tf_run(self._path, ["state", "push", str(self.state_file)])
|
356
369
|
|
370
|
+
def _tf_import(self, address: str, value: str) -> None:
|
371
|
+
"""Import a resource.
|
372
|
+
|
373
|
+
!!! Attention !!!
|
374
|
+
|
375
|
+
Because terraform import doesn't use the local state file and always imports the resource to the remote state,
|
376
|
+
we need to push and pull the state file again.
|
377
|
+
"""
|
378
|
+
# push local changes
|
379
|
+
self._tf_state_push()
|
380
|
+
with task(
|
381
|
+
self.progress_spinner,
|
382
|
+
f"-- Importing resource {address} {'[b red](DRY-RUN)' if self._dry_run else ''}",
|
383
|
+
):
|
384
|
+
if not self._dry_run:
|
385
|
+
self._tf_run(self._path, ["import", address, value])
|
386
|
+
# and pull the state file again
|
387
|
+
self._tf_state_pull()
|
388
|
+
|
357
389
|
def upload_state(self) -> None:
|
358
390
|
self._tf_state_push()
|
359
391
|
|
@@ -362,7 +394,10 @@ class TerraformCli:
|
|
362
394
|
plan = json.loads(self._tf_run(self._path, ["show", "-json", "plan.out"]))
|
363
395
|
return TfResourceList(
|
364
396
|
resources=[
|
365
|
-
TfResource(
|
397
|
+
TfResource(
|
398
|
+
address=r["address"],
|
399
|
+
change=Change(**r["change"]),
|
400
|
+
)
|
366
401
|
for r in plan["resource_changes"]
|
367
402
|
if action.value.lower() in r["change"]["actions"]
|
368
403
|
]
|
@@ -372,24 +407,112 @@ class TerraformCli:
|
|
372
407
|
self, source_state_file: Path, source: TfResource, destination: TfResource
|
373
408
|
) -> None:
|
374
409
|
"""Move the resource from source state file to destination state file."""
|
375
|
-
|
376
|
-
self.progress_spinner
|
377
|
-
|
378
|
-
|
410
|
+
with task(
|
411
|
+
self.progress_spinner,
|
412
|
+
f"-- Moving {destination} {'[b red](DRY-RUN)' if self._dry_run else ''}",
|
413
|
+
):
|
414
|
+
if not self._dry_run:
|
415
|
+
self._tf_run(
|
416
|
+
self._path,
|
417
|
+
[
|
418
|
+
"state",
|
419
|
+
"mv",
|
420
|
+
"-lock=false",
|
421
|
+
f"-state={source_state_file!s}",
|
422
|
+
f"-state-out={self.state_file!s}",
|
423
|
+
f"{source.address}",
|
424
|
+
f"{destination.address}",
|
425
|
+
],
|
426
|
+
)
|
427
|
+
|
428
|
+
def commit(self, source: TerraformCli) -> None:
|
429
|
+
"""Commit the changes."""
|
379
430
|
if not self._dry_run:
|
380
|
-
self.
|
381
|
-
self.
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
431
|
+
if self.progress_spinner:
|
432
|
+
self.progress_spinner.stop()
|
433
|
+
if not Confirm.ask(
|
434
|
+
"\nEverything ok? Would you like to upload the modified terraform states",
|
435
|
+
default=False,
|
436
|
+
):
|
437
|
+
return
|
438
|
+
|
439
|
+
if self.progress_spinner:
|
440
|
+
self.progress_spinner.start()
|
441
|
+
|
442
|
+
# finally push the terraform states
|
443
|
+
self.upload_state()
|
444
|
+
source.upload_state()
|
445
|
+
|
446
|
+
def _elasticache_import_password(
|
447
|
+
self, destination: TfResource, password: str
|
448
|
+
) -> None:
|
449
|
+
"""Import the elasticache auth_token random_password."""
|
450
|
+
self._tf_import(address=destination.address, value=password)
|
451
|
+
|
452
|
+
if (
|
453
|
+
not destination.change
|
454
|
+
or not destination.change.after
|
455
|
+
or not destination.change.after.get("override_special")
|
456
|
+
):
|
457
|
+
# nothing to change, nothing to do
|
458
|
+
return
|
459
|
+
|
460
|
+
state_data = json.loads(self.state_file.read_text())
|
461
|
+
state_data["serial"] += 1
|
462
|
+
if self._dry_run:
|
463
|
+
# in dry-run mode, tf_import is a no-op, therefore, the password is not in the state yet.
|
464
|
+
return
|
465
|
+
state_password_obj = next(
|
466
|
+
r for r in state_data["resources"] if r["name"] == destination.id
|
467
|
+
)
|
468
|
+
|
469
|
+
# Set the "override_special" to disable the password reset
|
470
|
+
state_password_obj["instances"][0]["attributes"]["override_special"] = (
|
471
|
+
destination.change.after["override_special"]
|
472
|
+
)
|
473
|
+
# Write the state,
|
474
|
+
self.state_file.write_text(json.dumps(state_data, indent=2))
|
475
|
+
|
476
|
+
def migrate_elasticache_resources(self, source: TerraformCli) -> None:
|
477
|
+
source_resources = source.resource_changes(TfAction.DESTROY)
|
478
|
+
destination_resources = self.resource_changes(TfAction.CREATE)
|
479
|
+
if not source_resources or not destination_resources:
|
480
|
+
raise ValueError("No resource changes found!")
|
481
|
+
|
482
|
+
source_ec = source_resources.get_resource_by_type(
|
483
|
+
"aws_elasticache_replication_group"
|
484
|
+
)
|
485
|
+
if not source_ec.change or not source_ec.change.before:
|
486
|
+
raise ValueError(
|
487
|
+
"Something went wrong with the source elasticache instance!"
|
391
488
|
)
|
392
489
|
|
490
|
+
current_auth_token = source_ec.change.before.get("auth_token")
|
491
|
+
if current_auth_token:
|
492
|
+
with suppress(KeyError):
|
493
|
+
self._elasticache_import_password(
|
494
|
+
destination_resources.get_resource_by_type("random_password"),
|
495
|
+
current_auth_token,
|
496
|
+
)
|
497
|
+
|
498
|
+
# migrate resources
|
499
|
+
for destination_resource in destination_resources:
|
500
|
+
if current_auth_token and destination_resource.type == "random_password":
|
501
|
+
# random password handled above
|
502
|
+
continue
|
503
|
+
|
504
|
+
possible_source_resouces = source_resources[destination_resource]
|
505
|
+
if not possible_source_resouces or len(possible_source_resouces) > 1:
|
506
|
+
raise ValueError(
|
507
|
+
f"Either source resource for {destination_resource} not found or more than one resource found!"
|
508
|
+
)
|
509
|
+
self.move_resource(
|
510
|
+
source_state_file=source.state_file,
|
511
|
+
source=possible_source_resouces[0],
|
512
|
+
destination=destination_resource,
|
513
|
+
)
|
514
|
+
self.commit(source)
|
515
|
+
|
393
516
|
def migrate_resources(self, source: TerraformCli) -> None:
|
394
517
|
"""Migrate the resources from source."""
|
395
518
|
# if not self.initialized or not source.initialized:
|
@@ -456,18 +579,4 @@ class TerraformCli:
|
|
456
579
|
destination=destination_resource,
|
457
580
|
)
|
458
581
|
|
459
|
-
|
460
|
-
if self.progress_spinner:
|
461
|
-
self.progress_spinner.stop()
|
462
|
-
if not Confirm.ask(
|
463
|
-
"\nEverything ok? Would you like to upload the modified terraform states",
|
464
|
-
default=False,
|
465
|
-
):
|
466
|
-
return
|
467
|
-
|
468
|
-
if self.progress_spinner:
|
469
|
-
self.progress_spinner.start()
|
470
|
-
|
471
|
-
# finally push the terraform states
|
472
|
-
self.upload_state()
|
473
|
-
source.upload_state()
|
582
|
+
self.commit(source)
|
tools/qontract_cli.py
CHANGED
@@ -4304,7 +4304,11 @@ def migrate(ctx, dry_run: bool, skip_build: bool) -> None:
|
|
4304
4304
|
progress,
|
4305
4305
|
"Migrating the resources from terraform-resources to ERv2",
|
4306
4306
|
):
|
4307
|
-
|
4307
|
+
if ctx.obj["provider"] == "elasticache":
|
4308
|
+
# Elasticache migration is a bit different
|
4309
|
+
erv2_tf_cli.migrate_elasticache_resources(source=tfr_tf_cli)
|
4310
|
+
else:
|
4311
|
+
erv2_tf_cli.migrate_resources(source=tfr_tf_cli)
|
4308
4312
|
|
4309
4313
|
rich_print(f"[b red]Please remove the temporary directory ({tempdir}) manually!")
|
4310
4314
|
|
{qontract_reconcile-0.10.1rc1173.dist-info → qontract_reconcile-0.10.1rc1175.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|