qontract-reconcile 0.10.2.dev20__py3-none-any.whl → 0.10.2.dev22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev20
3
+ Version: 0.10.2.dev22
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
@@ -657,7 +657,7 @@ reconcile/utils/clusterhealth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
657
657
  reconcile/utils/clusterhealth/providerbase.py,sha256=DXomGYogckBLqWtXn0PXU0hWYxB6K0F7ernldrkHhVY,1140
658
658
  reconcile/utils/clusterhealth/telemeter.py,sha256=PllSLsJXvGNatmTF4mxCNPVbDrpr_MPk0m5pWj-LT6g,1534
659
659
  reconcile/utils/dynatrace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
660
- reconcile/utils/dynatrace/client.py,sha256=H8EjqmZlB1a2ionAjV8_R1ozs9lWbmPCYKLe0J8kZAs,2838
660
+ reconcile/utils/dynatrace/client.py,sha256=RUk6KH-3CJyfJ1jolrdGQR4Hhz-tIWWJo9dsZ1IgJVw,3736
661
661
  reconcile/utils/glitchtip/__init__.py,sha256=FT6iBhGqoe7KExFdbgL8AYUb64iW_4snF5__Dcl7yt0,258
662
662
  reconcile/utils/glitchtip/client.py,sha256=ovh4tx-ajlihjvcq6nyY4chulbuMJYvzDPv9j9CuAKM,7867
663
663
  reconcile/utils/glitchtip/models.py,sha256=AJuGq4_A6G_T7asBKIw69-fOZLmT8HFrTKBEys7Tp00,6481
@@ -739,12 +739,12 @@ tools/app_interface_metrics_exporter.py,sha256=f1qwTmQfEcs98uBVRyBa0k7GQXdiSwd7w
739
739
  tools/app_interface_reporter.py,sha256=gR2EgHmgSIxzK5xxDW1SduFU6OkPaf2LlAQjhV3NYIg,17623
740
740
  tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
741
741
  tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
742
- tools/qontract_cli.py,sha256=5fpYOGtB7M-VHDAopTdKI0v85Md8YUiKPPNBhKfZgkU,144013
742
+ tools/qontract_cli.py,sha256=T637u3EVpodta2SSIjMa-3doLqXci1AEDt3Bspng4mE,145561
743
743
  tools/sd_app_sre_alert_report.py,sha256=jQpJdXVID68bSNtJNOGDh0-ei1CfEUS4Itr4MAaBNFA,5062
744
744
  tools/template_validation.py,sha256=qpKYaTgk0GOPGa2Ct5_5sKdwIHtCAKIBGzsMPuJU5fw,3371
745
745
  tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
746
746
  tools/cli_commands/container_images_report.py,sha256=8fG9XU-eEhJ7hKCdQzBcdPpvIJR-8WGkHOgFEulpfYQ,5213
747
- tools/cli_commands/erv2.py,sha256=pWjd6yBhzakB1Lz4DUaESTbzEIO9-Njc7p7k_gvZj1U,22040
747
+ tools/cli_commands/erv2.py,sha256=1eilft_xwfGDQAMuOitKCzZw21Abmh0aDdCTafT1unw,23380
748
748
  tools/cli_commands/gpg_encrypt.py,sha256=NhzwN49UN7P5_FJgTUN5A4BIwNbFokIE4lwDax2iP5k,4891
749
749
  tools/cli_commands/systems_and_tools.py,sha256=EMHOF1AtUDaoSk0bbjl6oUKYAz4rTZjIBaF-6E6GspM,16816
750
750
  tools/cli_commands/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -766,7 +766,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
766
766
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
767
767
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
768
768
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
769
- qontract_reconcile-0.10.2.dev20.dist-info/METADATA,sha256=dMYY6LBhL21XhtZxss8dwyRBMyK2ce1Yh7UvoUBK2bM,24665
770
- qontract_reconcile-0.10.2.dev20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
771
- qontract_reconcile-0.10.2.dev20.dist-info/entry_points.txt,sha256=JniHZPadNOILPyfSl0LF2YSp3Db7K2_W2CN7i9f3Gos,540
772
- qontract_reconcile-0.10.2.dev20.dist-info/RECORD,,
769
+ qontract_reconcile-0.10.2.dev22.dist-info/METADATA,sha256=1szHp6oUOSn63z-WQ3M1bafK9dvg3AKmmfpNIyBboyY,24665
770
+ qontract_reconcile-0.10.2.dev22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
771
+ qontract_reconcile-0.10.2.dev22.dist-info/entry_points.txt,sha256=JniHZPadNOILPyfSl0LF2YSp3Db7K2_W2CN7i9f3Gos,540
772
+ qontract_reconcile-0.10.2.dev22.dist-info/RECORD,,
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Iterable
4
+ from datetime import datetime
5
+ from unittest.mock import patch
4
6
 
5
7
  from dynatrace import Dynatrace
6
8
  from dynatrace.environment_v2.tokens_api import ApiTokenUpdate
@@ -33,11 +35,32 @@ class DynatraceAPIToken(BaseModel):
33
35
  scopes: list[str]
34
36
 
35
37
 
38
+ # TODO: Remove once APPSRE-11428 is resolved #######
39
+ ISO_8601 = "%Y-%m-%dT%H:%M:%S.%fZ"
40
+ FIXED_ISO_8601 = "%Y-%m-%dT%H:%M:%SZ"
41
+
42
+
43
+ def custom_iso8601_to_datetime(timestamp: str | None) -> datetime | None:
44
+ if isinstance(timestamp, str):
45
+ try:
46
+ return datetime.strptime(timestamp, ISO_8601)
47
+ except ValueError:
48
+ return datetime.strptime(timestamp, FIXED_ISO_8601)
49
+ return timestamp
50
+
51
+
52
+ ################################################
53
+
54
+
36
55
  class DynatraceClient:
37
56
  def __init__(self, environment_url: str, api: Dynatrace) -> None:
38
57
  self._environment_url = environment_url
39
58
  self._api = api
40
59
 
60
+ @patch(
61
+ "dynatrace.environment_v2.tokens_api.iso8601_to_datetime",
62
+ custom_iso8601_to_datetime,
63
+ )
41
64
  def create_api_token(
42
65
  self, name: str, scopes: Iterable[str]
43
66
  ) -> DynatraceAPITokenCreated:
@@ -49,6 +72,10 @@ class DynatraceClient:
49
72
  ) from e
50
73
  return DynatraceAPITokenCreated(token=token.token, id=token.id)
51
74
 
75
+ @patch(
76
+ "dynatrace.environment_v2.tokens_api.iso8601_to_datetime",
77
+ custom_iso8601_to_datetime,
78
+ )
52
79
  def get_token_ids_map_for_name_prefix(self, prefix: str) -> dict[str, str]:
53
80
  try:
54
81
  dt_tokens = self._api.tokens.list()
@@ -60,6 +87,10 @@ class DynatraceClient:
60
87
  token.id: token.name for token in dt_tokens if token.name.startswith(prefix)
61
88
  }
62
89
 
90
+ @patch(
91
+ "dynatrace.environment_v2.tokens_api.iso8601_to_datetime",
92
+ custom_iso8601_to_datetime,
93
+ )
63
94
  def get_token_by_id(self, token_id: str) -> DynatraceAPIToken:
64
95
  try:
65
96
  token = self._api.tokens.get(token_id=token_id)
@@ -255,6 +255,41 @@ class Erv2Cli:
255
255
  print(e.stdout.decode("utf-8"))
256
256
  raise
257
257
 
258
+ def force_unlock(self, credentials: Path, lock_id: str) -> None:
259
+ """Run 'terraform force-unlock' in a CDKTF container."""
260
+ input_file = self.temp / "input.json"
261
+ input_file.write_text(self.input_data)
262
+
263
+ try:
264
+ run(["docker", "pull", self.image], check=True, capture_output=True)
265
+ run(
266
+ [
267
+ "docker",
268
+ "run",
269
+ "--name",
270
+ "erv2-unlock",
271
+ "--rm",
272
+ "--mount",
273
+ f"type=bind,source={input_file!s},target=/inputs/input.json",
274
+ "--mount",
275
+ f"type=bind,source={credentials!s},target=/credentials",
276
+ "-e",
277
+ "AWS_SHARED_CREDENTIALS_FILE=/credentials",
278
+ "--entrypoint",
279
+ "/bin/bash",
280
+ self.image,
281
+ "-c",
282
+ f"cdktf synth && cd cdktf.out/stacks/CDKTF/ && terraform init && terraform force-unlock -force '{lock_id}'",
283
+ ],
284
+ check=True,
285
+ )
286
+ except CalledProcessError as e:
287
+ if e.stderr:
288
+ print(e.stderr.decode("utf-8"))
289
+ if e.stdout:
290
+ print(e.stdout.decode("utf-8"))
291
+ raise
292
+
258
293
 
259
294
  class TfRun(Protocol):
260
295
  def __call__(self, path: Path, cmd: list[str]) -> str: ...
tools/qontract_cli.py CHANGED
@@ -4359,6 +4359,47 @@ def debug_shell(ctx) -> None:
4359
4359
  erv2cli.enter_shell(credentials_file)
4360
4360
 
4361
4361
 
4362
+ @external_resources.command()
4363
+ @binary(["docker"])
4364
+ @click.option(
4365
+ "--lock-id",
4366
+ required=True,
4367
+ help="The terraform lock ID to unlock",
4368
+ prompt=True,
4369
+ )
4370
+ @click.pass_context
4371
+ def force_unlock(ctx, lock_id: str) -> None:
4372
+ """Manually unlock the ERv2 terraform state."""
4373
+ # use a temporary directory in $HOME. The MacOS colima default configuration allows docker mounts from $HOME.
4374
+ with tempfile.TemporaryDirectory(
4375
+ dir=Path.home(), prefix="erv2-unlock."
4376
+ ) as _tempdir:
4377
+ tempdir = Path(_tempdir)
4378
+ with progress_spinner() as progress:
4379
+ with task(progress, "Preparing environment ..."):
4380
+ credentials_file = tempdir / "credentials"
4381
+ credentials_file.write_text(
4382
+ ctx.obj["secret_reader"].read_with_parameters(
4383
+ path=f"app-sre/external-resources/{ctx.obj['provisioner']}",
4384
+ field="credentials",
4385
+ format=None,
4386
+ version=None,
4387
+ )
4388
+ )
4389
+ os.environ["AWS_SHARED_CREDENTIALS_FILE"] = str(credentials_file)
4390
+
4391
+ erv2cli = Erv2Cli(
4392
+ provision_provider=ctx.obj["provision_provider"],
4393
+ provisioner=ctx.obj["provisioner"],
4394
+ provider=ctx.obj["provider"],
4395
+ identifier=ctx.obj["identifier"],
4396
+ secret_reader=ctx.obj["secret_reader"],
4397
+ temp_dir=tempdir,
4398
+ progress_spinner=progress,
4399
+ )
4400
+ erv2cli.force_unlock(credentials_file, lock_id)
4401
+
4402
+
4362
4403
  @get.command(help="Get all container images in app-interface defined namespaces")
4363
4404
  @cluster_name
4364
4405
  @namespace_name