qontract-reconcile 0.10.1rc1185__py3-none-any.whl → 0.10.1rc1187__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.1rc1185
3
+ Version: 0.10.1rc1187
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
@@ -838,12 +838,12 @@ 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=KfmjfaCuyd8F68oY1hHd2tEf0PY0kLd6t8sWqR81nmc,142498
841
+ tools/qontract_cli.py,sha256=EsrEPKyeVBeViz0x21hfA25vkaHVEKBxHRyhozhdXVY,143933
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/container_images_report.py,sha256=PCJIzvUqiYmTdn5xJFcxHocCYp6dprrsJ_lkYdl3ET8,4417
846
- tools/cli_commands/erv2.py,sha256=469qdhyaf7thpPQ4hJSurvmxBqYDJsoI8H4AigQIF7U,20737
845
+ tools/cli_commands/container_images_report.py,sha256=64LXkv3eoWtMFZwBgkYE9jSnReDD7A9M0GEVXsR9aGk,5183
846
+ tools/cli_commands/erv2.py,sha256=f_Zc5ZCPenXbFj8zv_kcKJgmGkErYHGNIhbzOfOaD4Q,22041
847
847
  tools/cli_commands/gpg_encrypt.py,sha256=x02JOMn834z89YSNvr5B-oJky7rR1C0begCkPh45eHk,4958
848
848
  tools/cli_commands/systems_and_tools.py,sha256=EMHOF1AtUDaoSk0bbjl6oUKYAz4rTZjIBaF-6E6GspM,16816
849
849
  tools/cli_commands/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -877,13 +877,13 @@ tools/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
877
877
  tools/test/conftest.py,sha256=CsDbu4otrxb7X7kXKKGyV3ZEzu3pCkgjCoCGiHNx6zc,2401
878
878
  tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvftCWEEf-g1mfXOtgCog-g,1271
879
879
  tools/test/test_erv2.py,sha256=EAS7QuJkHisRVO9bMGxm662L5B6i66wF_mT9PAjVzrU,3128
880
- tools/test/test_get_container_images.py,sha256=L2XzfmYAd6WZ17UXNnr8Z4iwoGcCvQ0vN6gxAZ7gEws,6097
880
+ tools/test/test_get_container_images.py,sha256=QUSb0PjZnL35_dhniMux1_D2G_2vvpcasr8tpwPLwIQ,7125
881
881
  tools/test/test_qontract_cli.py,sha256=iuzKbQ6ahinvjoQmQLBrG4shey0z-1rB6qCgS8T6dgU,5789
882
882
  tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
883
883
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
884
884
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
885
- qontract_reconcile-0.10.1rc1185.dist-info/METADATA,sha256=QatFHWfiRw29TrdKQtJhxXlwWCTa6pwrNTKTnxBMJtY,2213
886
- qontract_reconcile-0.10.1rc1185.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
887
- qontract_reconcile-0.10.1rc1185.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
888
- qontract_reconcile-0.10.1rc1185.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
889
- qontract_reconcile-0.10.1rc1185.dist-info/RECORD,,
885
+ qontract_reconcile-0.10.1rc1187.dist-info/METADATA,sha256=ELcUNPGIHkuAtwY7r9iKGDrX38eFrABpKVzusb48UB8,2213
886
+ qontract_reconcile-0.10.1rc1187.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
887
+ qontract_reconcile-0.10.1rc1187.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
888
+ qontract_reconcile-0.10.1rc1187.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
889
+ qontract_reconcile-0.10.1rc1187.dist-info/RECORD,,
@@ -20,7 +20,8 @@ IMAGE_NAME_REGEX = re.compile(r"^(?P<name>[a-zA-Z0-9][a-zA-Z0-9/_.-]+)(?:@sha256
20
20
 
21
21
  class NamespaceImages(BaseModel):
22
22
  namespace_name: str
23
- image_names: list[str]
23
+ image_names: list[str] | None = None
24
+ error_message: str | None = None
24
25
 
25
26
 
26
27
  def get_all_pods_images(
@@ -77,9 +78,14 @@ def fetch_pods_images_from_namespaces(
77
78
  oc_map=oc_map,
78
79
  )
79
80
 
81
+ errors: defaultdict = defaultdict(int)
80
82
  result: defaultdict = defaultdict(_get_all_images_default)
81
83
  for ni in all_namespace_images:
82
- for name in ni.image_names:
84
+ if ni.error_message:
85
+ errors[f"{ni.namespace_name}/{ni.error_message}"] += 1
86
+ continue
87
+
88
+ for name in ni.image_names or []:
83
89
  result[name]["namespaces"].add(ni.namespace_name)
84
90
  result[name]["count"] += 1
85
91
 
@@ -92,7 +98,7 @@ def fetch_pods_images_from_namespaces(
92
98
  include_pattern_compiled = re.compile(include_pattern)
93
99
 
94
100
  result_filtered_flattened: list[dict[str, Any]] = []
95
- for name, value in result.items():
101
+ for name, value in sorted(result.items()):
96
102
  if include_pattern_compiled and not include_pattern_compiled.match(name):
97
103
  continue
98
104
  if exclude_pattern_compiled and exclude_pattern_compiled.match(name):
@@ -104,6 +110,16 @@ def fetch_pods_images_from_namespaces(
104
110
  "count": value["count"],
105
111
  })
106
112
 
113
+ # append errors if they exist in the filtered result
114
+ # not very canonical, but it is better than ignoring them
115
+ if errors:
116
+ for message, count in sorted(errors.items()):
117
+ result_filtered_flattened.append({
118
+ "name": "error",
119
+ "namespaces": message,
120
+ "count": count,
121
+ })
122
+
107
123
  return result_filtered_flattened
108
124
 
109
125
 
@@ -113,15 +129,22 @@ def _get_all_images_default() -> dict[str, Any]:
113
129
 
114
130
  def _get_namespace_images(ns: NamespaceV1, oc_map: OCMap) -> NamespaceImages:
115
131
  image_names = []
116
- oc = oc_map.get_cluster(ns.cluster.name)
117
- pod_items = oc.get_items("Pod", namespace=ns.name)
118
- for pod in pod_items:
119
- containers = pod.get("spec", {}).get("containers", [])
120
- containers.extend(pod.get("spec", {}).get("initContainers", []))
121
-
122
- for c in containers:
123
- if m := IMAGE_NAME_REGEX.match(c["image"]):
124
- image_names.append(m.group("name"))
132
+
133
+ try:
134
+ oc = oc_map.get_cluster(ns.cluster.name)
135
+ pod_items = oc.get_items("Pod", namespace=ns.name)
136
+ for pod in pod_items:
137
+ containers = pod.get("spec", {}).get("containers", [])
138
+ containers.extend(pod.get("spec", {}).get("initContainers", []))
139
+
140
+ for c in containers:
141
+ if m := IMAGE_NAME_REGEX.match(c["image"]):
142
+ image_names.append(m.group("name"))
143
+ except Exception as exc:
144
+ return NamespaceImages(
145
+ namespace_name=ns.name,
146
+ error_message=str(exc),
147
+ )
125
148
 
126
149
  return NamespaceImages(
127
150
  namespace_name=ns.name,
@@ -215,8 +215,44 @@ class Erv2Cli:
215
215
  capture_output=True,
216
216
  )
217
217
  except CalledProcessError as e:
218
- print(e.stderr.decode("utf-8"))
219
- print(e.stdout.decode("utf-8"))
218
+ if e.stderr:
219
+ print(e.stderr.decode("utf-8"))
220
+ if e.stdout:
221
+ print(e.stdout.decode("utf-8"))
222
+ raise
223
+
224
+ def enter_shell(self, credentials: Path) -> None:
225
+ """Run the CDKTF container and enter the shell."""
226
+ input_file = self.temp / "input.json"
227
+ input_file.write_text(self.input_data)
228
+
229
+ try:
230
+ run(["docker", "pull", self.image], check=True, capture_output=True)
231
+ run(
232
+ [
233
+ "docker",
234
+ "run",
235
+ "--name",
236
+ "erv2-debug-shell",
237
+ "--rm",
238
+ "-it",
239
+ "--mount",
240
+ f"type=bind,source={input_file!s},target=/inputs/input.json",
241
+ "--mount",
242
+ f"type=bind,source={credentials!s},target=/credentials",
243
+ "-e",
244
+ "AWS_SHARED_CREDENTIALS_FILE=/credentials",
245
+ "--entrypoint",
246
+ "/bin/bash",
247
+ self.image,
248
+ ],
249
+ check=True,
250
+ )
251
+ except CalledProcessError as e:
252
+ if e.stderr:
253
+ print(e.stderr.decode("utf-8"))
254
+ if e.stdout:
255
+ print(e.stdout.decode("utf-8"))
220
256
  raise
221
257
 
222
258
 
@@ -236,8 +272,10 @@ def tf_run(path: Path, cmd: list[str]) -> str:
236
272
  env=env,
237
273
  ).stdout.decode("utf-8")
238
274
  except CalledProcessError as e:
239
- print(e.stderr.decode("utf-8"))
240
- print(e.stdout.decode("utf-8"))
275
+ if e.stderr:
276
+ print(e.stderr.decode("utf-8"))
277
+ if e.stdout:
278
+ print(e.stdout.decode("utf-8"))
241
279
  raise
242
280
 
243
281
 
tools/qontract_cli.py CHANGED
@@ -7,6 +7,7 @@ import logging
7
7
  import os
8
8
  import re
9
9
  import sys
10
+ import tempfile
10
11
  import textwrap
11
12
  from collections import defaultdict
12
13
  from datetime import (
@@ -4203,7 +4204,7 @@ def request_reconciliation(ctx):
4203
4204
 
4204
4205
 
4205
4206
  @external_resources.command()
4206
- @binary(["terraform"])
4207
+ @binary(["terraform", "docker"])
4207
4208
  @binary_version("terraform", ["version"], TERRAFORM_VERSION_REGEX, TERRAFORM_VERSION)
4208
4209
  @click.option(
4209
4210
  "--dry-run/--no-dry-run",
@@ -4320,6 +4321,39 @@ def migrate(ctx, dry_run: bool, skip_build: bool) -> None:
4320
4321
  rich_print(f"[b red]Please remove the temporary directory ({tempdir}) manually!")
4321
4322
 
4322
4323
 
4324
+ @external_resources.command()
4325
+ @binary(["docker"])
4326
+ @click.pass_context
4327
+ def debug_shell(ctx) -> None:
4328
+ """Enter an ERv2 debug shell to manually migrate resources."""
4329
+ # use a temporary directory in $HOME. The MacOS colima default configuration allows docker mounts from $HOME.
4330
+ with tempfile.TemporaryDirectory(dir=Path.home(), prefix="erv2-debug.") as _tempdir:
4331
+ tempdir = Path(_tempdir)
4332
+ with progress_spinner() as progress:
4333
+ with task(progress, "Preparing environment ..."):
4334
+ credentials_file = tempdir / "credentials"
4335
+ credentials_file.write_text(
4336
+ ctx.obj["secret_reader"].read_with_parameters(
4337
+ path=f"app-sre/external-resources/{ctx.obj['provisioner']}",
4338
+ field="credentials",
4339
+ format=None,
4340
+ version=None,
4341
+ )
4342
+ )
4343
+ os.environ["AWS_SHARED_CREDENTIALS_FILE"] = str(credentials_file)
4344
+
4345
+ erv2cli = Erv2Cli(
4346
+ provision_provider=ctx.obj["provision_provider"],
4347
+ provisioner=ctx.obj["provisioner"],
4348
+ provider=ctx.obj["provider"],
4349
+ identifier=ctx.obj["identifier"],
4350
+ secret_reader=ctx.obj["secret_reader"],
4351
+ temp_dir=tempdir,
4352
+ progress_spinner=progress,
4353
+ )
4354
+ erv2cli.enter_shell(credentials_file)
4355
+
4356
+
4323
4357
  @get.command(help="Get all container images in app-interface defined namespaces")
4324
4358
  @cluster_name
4325
4359
  @namespace_name
@@ -99,27 +99,17 @@ def oc_map(mocker: MockerFixture, oc: OCNative) -> OCMap:
99
99
  return oc_map
100
100
 
101
101
 
102
- # convert a list of dicts into a set of tuples to use it in assertions
103
- def _to_set(list_of_dicts: list[dict]) -> set[tuple]:
104
- return {tuple(d.items()) for d in list_of_dicts}
105
-
106
-
107
- def testfetch_no_filter(namespaces: list[NamespaceV1], oc_map: OCMap) -> None:
102
+ def test_fetch_no_filter(namespaces: list[NamespaceV1], oc_map: OCMap) -> None:
108
103
  images = fetch_pods_images_from_namespaces(
109
104
  namespaces=namespaces,
110
105
  oc_map=oc_map,
111
106
  thread_pool_size=2,
112
107
  )
113
108
 
114
- assert _to_set(images) == _to_set([
115
- {
116
- "name": "quay.io/prometheus/blackbox-exporter",
117
- "namespaces": "app-sre-observability-stage",
118
- "count": 1,
119
- },
109
+ assert images == [
120
110
  {
121
- "name": "quay.io/redhat-services-prod/app-sre-tenant/gitlab-project-exporter-main/gitlab-project-exporter-main",
122
- "namespaces": "app-sre-observability-stage",
111
+ "name": "quay.io/app-sre/clamav",
112
+ "namespaces": "app-sre-pipelines",
123
113
  "count": 1,
124
114
  },
125
115
  {
@@ -128,8 +118,8 @@ def testfetch_no_filter(namespaces: list[NamespaceV1], oc_map: OCMap) -> None:
128
118
  "count": 3,
129
119
  },
130
120
  {
131
- "name": "quay.io/app-sre/clamav",
132
- "namespaces": "app-sre-pipelines",
121
+ "name": "quay.io/prometheus/blackbox-exporter",
122
+ "namespaces": "app-sre-observability-stage",
133
123
  "count": 1,
134
124
  },
135
125
  {
@@ -138,26 +128,31 @@ def testfetch_no_filter(namespaces: list[NamespaceV1], oc_map: OCMap) -> None:
138
128
  "count": 1,
139
129
  },
140
130
  {
141
- "name": "registry.redhat.io/openshift-pipelines/pipelines-entrypoint-rhel8",
142
- "namespaces": "app-sre-pipelines",
143
- "count": 3,
131
+ "name": "quay.io/redhat-services-prod/app-sre-tenant/gitlab-project-exporter-main/gitlab-project-exporter-main",
132
+ "namespaces": "app-sre-observability-stage",
133
+ "count": 1,
144
134
  },
145
135
  {
146
136
  "name": "quay.io/redhatproductsecurity/rapidast",
147
137
  "namespaces": "app-sre-pipelines",
148
138
  "count": 1,
149
139
  },
150
- ])
140
+ {
141
+ "name": "registry.redhat.io/openshift-pipelines/pipelines-entrypoint-rhel8",
142
+ "namespaces": "app-sre-pipelines",
143
+ "count": 3,
144
+ },
145
+ ]
151
146
 
152
147
 
153
- def testfetch_exclude_pattern(namespaces: list[NamespaceV1], oc_map: OCMap) -> None:
148
+ def test_fetch_exclude_pattern(namespaces: list[NamespaceV1], oc_map: OCMap) -> None:
154
149
  images = fetch_pods_images_from_namespaces(
155
150
  namespaces=namespaces,
156
151
  oc_map=oc_map,
157
152
  thread_pool_size=2,
158
153
  exclude_pattern="quay.io/redhat|quay.io/app-sre",
159
154
  )
160
- assert _to_set(images) == _to_set([
155
+ assert images == [
161
156
  {
162
157
  "name": "quay.io/prometheus/blackbox-exporter",
163
158
  "namespaces": "app-sre-observability-stage",
@@ -168,10 +163,10 @@ def testfetch_exclude_pattern(namespaces: list[NamespaceV1], oc_map: OCMap) -> N
168
163
  "namespaces": "app-sre-pipelines",
169
164
  "count": 3,
170
165
  },
171
- ])
166
+ ]
172
167
 
173
168
 
174
- def testfetch_include_pattern(namespaces: list[NamespaceV1], oc_map: OCMap) -> None:
169
+ def test_fetch_include_pattern(namespaces: list[NamespaceV1], oc_map: OCMap) -> None:
175
170
  images = fetch_pods_images_from_namespaces(
176
171
  namespaces=namespaces,
177
172
  oc_map=oc_map,
@@ -185,3 +180,41 @@ def testfetch_include_pattern(namespaces: list[NamespaceV1], oc_map: OCMap) -> N
185
180
  "count": 3,
186
181
  },
187
182
  ]
183
+
184
+
185
+ def test_fetch_exception(
186
+ namespaces: list[NamespaceV1], mocker: MockerFixture, observability_pods: list[dict]
187
+ ) -> None:
188
+ oc = mocker.patch("reconcile.utils.oc.OCNative", autospec=True)
189
+ oc.get_items.side_effect = [observability_pods, Exception("generic error")]
190
+ oc_map = mocker.patch("reconcile.utils.oc_map.OCMap", autospec=True)
191
+ oc_map.get_cluster.return_value = oc
192
+
193
+ images = fetch_pods_images_from_namespaces(
194
+ namespaces=namespaces,
195
+ oc_map=oc_map,
196
+ thread_pool_size=2,
197
+ )
198
+
199
+ assert images == [
200
+ {
201
+ "name": "quay.io/app-sre/internal-redhat-ca",
202
+ "namespaces": "app-sre-observability-stage",
203
+ "count": 2,
204
+ },
205
+ {
206
+ "name": "quay.io/prometheus/blackbox-exporter",
207
+ "namespaces": "app-sre-observability-stage",
208
+ "count": 1,
209
+ },
210
+ {
211
+ "name": "quay.io/redhat-services-prod/app-sre-tenant/gitlab-project-exporter-main/gitlab-project-exporter-main",
212
+ "namespaces": "app-sre-observability-stage",
213
+ "count": 1,
214
+ },
215
+ {
216
+ "name": "error",
217
+ "namespaces": "app-sre-pipelines/generic error",
218
+ "count": 1,
219
+ },
220
+ ]