qontract-reconcile 0.10.1rc995__py3-none-any.whl → 0.10.1rc997__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.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc995
3
+ Version: 0.10.1rc997
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
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=q96mwr2KeEQ5bpNniGlgIMZTxiuLSodcYfX-t
10
10
  reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
11
11
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
12
12
  reconcile/checkpoint.py,sha256=_JhMxrye5BgkRMxWYuf7Upli6XayPINKSsuo3ynHTRc,5010
13
- reconcile/cli.py,sha256=7-GNgB05b5MGrnEuD0resVNL3hOsQNSYcAAHrR68sS0,105755
13
+ reconcile/cli.py,sha256=lLVw-FxEUR8zU6UAKZzJk7XwRbjsXPaGUAQqXuBYSaU,105825
14
14
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=rLh16BOlBOxTmJ8Si3wWyyEpmMlhh4Znx1Gc36qsmOc,4865
15
15
  reconcile/cluster_deployment_mapper.py,sha256=5gumAaRCcFXsabUJ1dnuUy9WrP_FEEM5JnOnE8ch9sE,2326
16
16
  reconcile/dashdotdb_base.py,sha256=l34QDu1G96_Ctnh7ZXdxXgSeCE93GQMdLAkWxmN6vDA,4775
@@ -187,14 +187,14 @@ reconcile/dynatrace_token_provider/ocm.py,sha256=iHMsgbsLs-dlrB9UXmWNDF7E4UDe49J
187
187
  reconcile/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
188
188
  reconcile/external_resources/aws.py,sha256=JvjKaABy2Pg8u8Lq82Acv4zMvpE3_qGKes7OG-zlHOM,2956
189
189
  reconcile/external_resources/factories.py,sha256=DXgaLxoO87zZ76VOpRpu2GeYGhsbfOnOx5mrzgo4Gf4,4767
190
- reconcile/external_resources/integration.py,sha256=PJOpz-wUf7NzWvqCDalRWu5OxKjgi5RwiiB6HZfJs0k,5122
190
+ reconcile/external_resources/integration.py,sha256=y1gJ16woMBC3J9qniMmS5y3lCkAs7V_ETZRUwjKqaO0,6628
191
191
  reconcile/external_resources/integration_secrets_sync.py,sha256=cMEZhgCvABAMf-DWF051L6CRnJQdfbsISA_b1xuS940,1670
192
- reconcile/external_resources/manager.py,sha256=5X9HjANMUGkZgC5RUA0r2TvZIbHw0UID1odn2QWrND4,14675
192
+ reconcile/external_resources/manager.py,sha256=8lHOFyA_xF8thQXbmJp8zzA2-oJ0MUhfnCcFpkexwjU,15089
193
193
  reconcile/external_resources/meta.py,sha256=cMT9OsKcUY26qwEjlQ02EkorvOBNqWj0JVMwfJa3Mg0,634
194
194
  reconcile/external_resources/metrics.py,sha256=m2TIOao2N7pD6k45driFbBGVCC_N7ai44m-lLPfa5qk,454
195
195
  reconcile/external_resources/model.py,sha256=oXxJkjhV53lwwAuxUCBrjJ8aCJmQdgcKWv68ugJPK4k,7229
196
196
  reconcile/external_resources/reconciler.py,sha256=E50X_lnOD0OWYXMzyZld1P6dCFJFYjHGyICWff9bxlc,9323
197
- reconcile/external_resources/secrets_sync.py,sha256=WeoUANltYOjzr_Pn_pZ1ormGof9yRy2DiSB7LoPAQqM,15076
197
+ reconcile/external_resources/secrets_sync.py,sha256=6n0oDPLjd9Ql0lf6zsr1AZw8A6EEe3yCzl20XodtgkE,16229
198
198
  reconcile/external_resources/state.py,sha256=bWq51xPK4-BHVXWsRu6Y-vn69yg9Dse4x1RNNF7qw84,9614
199
199
  reconcile/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
200
200
  reconcile/glitchtip/integration.py,sha256=XtewM9nfTPLnPSpYebP50GrveYOnhTvKNq3seSvL6u8,8343
@@ -854,8 +854,8 @@ tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jr
854
854
  tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
855
855
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
856
856
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
857
- qontract_reconcile-0.10.1rc995.dist-info/METADATA,sha256=O45HKoKnz-vWM-qm9KKu_RKXfZNVmO9fhTPKN4PKROk,2262
858
- qontract_reconcile-0.10.1rc995.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
859
- qontract_reconcile-0.10.1rc995.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
860
- qontract_reconcile-0.10.1rc995.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
861
- qontract_reconcile-0.10.1rc995.dist-info/RECORD,,
857
+ qontract_reconcile-0.10.1rc997.dist-info/METADATA,sha256=zxGaYZt6G_4RJZBpfsYs23oXou1bP0MNIEkxLY83WxA,2262
858
+ qontract_reconcile-0.10.1rc997.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
859
+ qontract_reconcile-0.10.1rc997.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
860
+ qontract_reconcile-0.10.1rc997.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
861
+ qontract_reconcile-0.10.1rc997.dist-info/RECORD,,
reconcile/cli.py CHANGED
@@ -3714,20 +3714,20 @@ def deadmanssnitch(ctx):
3714
3714
  )
3715
3715
  def external_resources(
3716
3716
  ctx,
3717
- workers_cluster: str,
3718
- workers_namespace: str,
3719
3717
  dry_run_job_suffix: str,
3720
3718
  thread_pool_size: int,
3719
+ workers_cluster: str,
3720
+ workers_namespace: str,
3721
3721
  ):
3722
3722
  import reconcile.external_resources.integration
3723
3723
 
3724
3724
  run_integration(
3725
3725
  reconcile.external_resources.integration,
3726
3726
  ctx.obj,
3727
- dry_run_job_suffix,
3728
- thread_pool_size,
3729
- workers_cluster,
3730
- workers_namespace,
3727
+ dry_run_job_suffix=dry_run_job_suffix,
3728
+ thread_pool_size=thread_pool_size,
3729
+ workers_cluster=workers_cluster,
3730
+ workers_namespace=workers_namespace,
3731
3731
  )
3732
3732
 
3733
3733
 
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from collections.abc import Callable
3
+ from typing import Any
3
4
 
4
5
  from reconcile.external_resources.manager import (
5
6
  ExternalResourcesInventory,
@@ -73,6 +74,62 @@ def get_aws_api(
73
74
  return AWSApi(aws_credentials)
74
75
 
75
76
 
77
+ def create_er_manager(
78
+ aws_api: AWSApi,
79
+ workers_cluster: str | None,
80
+ workers_namespace: str | None,
81
+ thread_pool_size: int,
82
+ dry_run: bool,
83
+ dry_run_job_suffix: str,
84
+ ) -> ExternalResourcesManager:
85
+ vault_settings = get_app_interface_vault_settings()
86
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
87
+ er_settings = get_settings()[0]
88
+ m_inventory = load_module_inventory(get_modules())
89
+ namespaces = [ns for ns in get_namespaces() if ns.external_resources]
90
+ er_inventory = ExternalResourcesInventory(namespaces)
91
+
92
+ if not workers_cluster:
93
+ workers_cluster = er_settings.workers_cluster.name
94
+ if not workers_namespace:
95
+ workers_namespace = er_settings.workers_namespace.name
96
+
97
+ return ExternalResourcesManager(
98
+ thread_pool_size=thread_pool_size,
99
+ settings=er_settings,
100
+ secret_reader=secret_reader,
101
+ factories=setup_factories(
102
+ er_settings, m_inventory, er_inventory, secret_reader
103
+ ),
104
+ er_inventory=er_inventory,
105
+ module_inventory=m_inventory,
106
+ state_manager=ExternalResourcesStateDynamoDB(
107
+ aws_api=aws_api,
108
+ table_name=er_settings.state_dynamodb_table,
109
+ ),
110
+ reconciler=K8sExternalResourcesReconciler(
111
+ controller=build_job_controller(
112
+ integration=QONTRACT_INTEGRATION,
113
+ integration_version=QONTRACT_INTEGRATION_VERSION,
114
+ cluster=workers_cluster,
115
+ namespace=workers_namespace,
116
+ secret_reader=secret_reader,
117
+ dry_run=dry_run,
118
+ ),
119
+ dry_run=dry_run,
120
+ dry_run_job_suffix=dry_run_job_suffix,
121
+ ),
122
+ secrets_reconciler=build_incluster_secrets_reconciler(
123
+ workers_cluster,
124
+ workers_namespace,
125
+ secret_reader,
126
+ vault_path=er_settings.vault_secrets_path,
127
+ thread_pool_size=thread_pool_size,
128
+ dry_run=dry_run,
129
+ ),
130
+ )
131
+
132
+
76
133
  def run(
77
134
  dry_run: bool,
78
135
  dry_run_job_suffix: str,
@@ -83,9 +140,6 @@ def run(
83
140
  vault_settings = get_app_interface_vault_settings()
84
141
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
85
142
  er_settings = get_settings()[0]
86
- m_inventory = load_module_inventory(get_modules())
87
- namespaces = [ns for ns in get_namespaces() if ns.external_resources]
88
- er_inventory = ExternalResourcesInventory(namespaces)
89
143
 
90
144
  if not workers_cluster:
91
145
  workers_cluster = er_settings.workers_cluster.name
@@ -98,41 +152,14 @@ def run(
98
152
  region=er_settings.state_dynamodb_region,
99
153
  secret_reader=secret_reader,
100
154
  ) as aws_api:
101
- er_mgr = ExternalResourcesManager(
102
- thread_pool_size=thread_pool_size,
103
- settings=er_settings,
104
- secret_reader=secret_reader,
105
- factories=setup_factories(
106
- er_settings, m_inventory, er_inventory, secret_reader
107
- ),
108
- er_inventory=er_inventory,
109
- module_inventory=m_inventory,
110
- state_manager=ExternalResourcesStateDynamoDB(
111
- aws_api=aws_api,
112
- table_name=er_settings.state_dynamodb_table,
113
- ),
114
- reconciler=K8sExternalResourcesReconciler(
115
- controller=build_job_controller(
116
- integration=QONTRACT_INTEGRATION,
117
- integration_version=QONTRACT_INTEGRATION_VERSION,
118
- cluster=workers_cluster,
119
- namespace=workers_namespace,
120
- secret_reader=secret_reader,
121
- dry_run=dry_run,
122
- ),
123
- dry_run=dry_run,
124
- dry_run_job_suffix=dry_run_job_suffix,
125
- ),
126
- secrets_reconciler=build_incluster_secrets_reconciler(
127
- workers_cluster,
128
- workers_namespace,
129
- secret_reader,
130
- vault_path=er_settings.vault_secrets_path,
131
- thread_pool_size=thread_pool_size,
132
- dry_run=dry_run,
133
- ),
155
+ er_mgr = create_er_manager(
156
+ aws_api,
157
+ workers_cluster,
158
+ workers_namespace,
159
+ thread_pool_size,
160
+ dry_run,
161
+ dry_run_job_suffix,
134
162
  )
135
-
136
163
  if dry_run:
137
164
  er_mgr.handle_dry_run_resources()
138
165
  if er_mgr.errors:
@@ -141,3 +168,25 @@ def run(
141
168
  logging.error("ExternalResourceKey: %s, Error: %s" % (k, e))
142
169
  else:
143
170
  er_mgr.handle_resources()
171
+
172
+
173
+ def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
174
+ vault_settings = get_app_interface_vault_settings()
175
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
176
+ er_settings = get_settings()[0]
177
+
178
+ with get_aws_api(
179
+ query_func=gql.get_api().query,
180
+ account_name=er_settings.state_dynamodb_account.name,
181
+ region=er_settings.state_dynamodb_region,
182
+ secret_reader=secret_reader,
183
+ ) as aws_api:
184
+ er_mgr = create_er_manager(
185
+ aws_api,
186
+ workers_cluster=kwargs["workers_cluster"],
187
+ workers_namespace=kwargs["workers_namespace"],
188
+ thread_pool_size=kwargs["thread_pool_size"],
189
+ dry_run=True,
190
+ dry_run_job_suffix=kwargs["dry_run_job_suffix"],
191
+ )
192
+ return er_mgr.get_all_reconciliations()
@@ -116,7 +116,7 @@ class ExternalResourcesManager:
116
116
  return ReconcileAction.APPLY_NOT_EXISTS
117
117
  case ResourceStatus.ERROR:
118
118
  return ReconcileAction.APPLY_ERROR
119
- case ResourceStatus.CREATED:
119
+ case ResourceStatus.CREATED | ResourceStatus.PENDING_SECRET_SYNC:
120
120
  if (
121
121
  reconciliation.resource_hash
122
122
  != state.reconciliation.resource_hash
@@ -154,6 +154,14 @@ class ExternalResourcesManager:
154
154
  )
155
155
  return reconcile
156
156
 
157
+ def get_all_reconciliations(self) -> dict[str, set[Reconciliation]]:
158
+ """Returns all reconciliations in a dict. Useful to return all data
159
+ from app-interface to make comparisions (early-exit)"""
160
+ return {
161
+ "desired": self._get_desired_objects_reconciliations(),
162
+ "deleted": self._get_deleted_objects_reconciliations(),
163
+ }
164
+
157
165
  def _get_desired_objects_reconciliations(self) -> set[Reconciliation]:
158
166
  r: set[Reconciliation] = set()
159
167
  for key, spec in self.er_inventory.items():
@@ -95,6 +95,43 @@ class SecretHelper:
95
95
  return three_way_diff_using_hash(cmp_current, cmp_desired)
96
96
 
97
97
 
98
+ class OutputSecretsFormatter:
99
+ """Class to format Module output keys/values into suitable values for K8s Secrets. It currently implements the same
100
+ behavior as Terraform-Resources."""
101
+
102
+ def __init__(self, secret_reader: SecretReaderBase) -> None:
103
+ self.secret_reader = secret_reader
104
+
105
+ def _key_must_be_populated(self, key: str) -> bool:
106
+ "Only keys containing '__' must be populated to Secrets"
107
+ return "__" in key
108
+
109
+ def _format_key(self, key: str) -> str:
110
+ if "__" not in key:
111
+ return key
112
+ k_split = key.split("__")
113
+ output_key = k_split[1]
114
+ if output_key.startswith("db"):
115
+ output_key = output_key.replace("db_", "db.")
116
+ return output_key
117
+
118
+ def _format_value(self, value: str) -> str:
119
+ decoded_value = base64.b64decode(value).decode("utf-8")
120
+ if decoded_value.startswith("__vault__:"):
121
+ _secret_ref = json.loads(decoded_value.replace("__vault__:", ""))
122
+ secret_ref = VaultSecret(**_secret_ref)
123
+ return self.secret_reader.read_secret(secret_ref)
124
+ else:
125
+ return decoded_value
126
+
127
+ def format(self, data: Mapping[str, str]) -> dict[str, str]:
128
+ return {
129
+ self._format_key(key): self._format_value(value)
130
+ for key, value in data.items()
131
+ if self._key_must_be_populated(key)
132
+ }
133
+
134
+
98
135
  class SecretsReconciler:
99
136
  def __init__(
100
137
  self,
@@ -281,6 +318,7 @@ class InClusterSecretsReconciler(SecretsReconciler):
281
318
  cluster: str,
282
319
  namespace: str,
283
320
  oc: OCCli,
321
+ output_secrets_formatter: OutputSecretsFormatter,
284
322
  thread_pool_size: int,
285
323
  dry_run: bool,
286
324
  ):
@@ -292,6 +330,7 @@ class InClusterSecretsReconciler(SecretsReconciler):
292
330
  self.source_secrets: list[str] = []
293
331
  self.vault_client = vault_client
294
332
  self.vault_path = vault_path
333
+ self.output_secrets_formatter = output_secrets_formatter
295
334
 
296
335
  def _get_spec_hash(self, spec: ExternalResourceSpec) -> str:
297
336
  secret_key = f"{spec.provision_provider}-{spec.provisioner_name}-{spec.provider}-{spec.identifier}"
@@ -313,21 +352,10 @@ class InClusterSecretsReconciler(SecretsReconciler):
313
352
  for secret in secrets:
314
353
  secret_name = secret["metadata"]["name"]
315
354
  spec = secrets_map[secret_name]
316
- data = dict[str, str]()
317
- for k, v in secret["data"].items():
318
- decoded = base64.b64decode(v).decode("utf-8")
319
-
320
- if decoded.startswith("__vault__:"):
321
- _secret_ref = json.loads(decoded.replace("__vault__:", ""))
322
- secret_ref = VaultSecret(**_secret_ref)
323
- data[k] = self.secrets_reader.read_secret(secret_ref)
324
- else:
325
- data[k] = decoded
326
-
355
+ spec.secret = self.output_secrets_formatter.format(secret["data"])
327
356
  spec.metadata[SECRET_UPDATED_AT] = datetime.now(UTC).strftime(
328
357
  SECRET_UPDATED_AT_TIMEFORMAT
329
358
  )
330
- spec.secret = data
331
359
 
332
360
  def _delete_source_secret(self, spec: ExternalResourceSpec) -> None:
333
361
  secret_name = self._get_spec_outputs_secret_name(spec)
@@ -396,6 +424,7 @@ def build_incluster_secrets_reconciler(
396
424
  vault_path=vault_path,
397
425
  vault_client=VaultClient(),
398
426
  secrets_reader=secrets_reader,
427
+ output_secrets_formatter=OutputSecretsFormatter(secrets_reader),
399
428
  thread_pool_size=thread_pool_size,
400
429
  dry_run=dry_run,
401
430
  )