qontract-reconcile 0.10.1rc1184__py3-none-any.whl → 0.10.1rc1186__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.1rc1184
3
+ Version: 0.10.1rc1186
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
@@ -655,7 +655,7 @@ reconcile/unleash_feature_toggles/integration.py,sha256=nx7BhtzCsTfPbOp60vI5MkNw
655
655
  reconcile/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
656
656
  reconcile/utils/aggregated_list.py,sha256=km0xadW0jO4G_CqZPsXmoBURQ8c90FaTu5x4X1K1cZs,3357
657
657
  reconcile/utils/amtool.py,sha256=ngtBuVPETH6oAy5RnKzvreVbjwQCaATS_PYYwBprzjQ,2288
658
- reconcile/utils/aws_api.py,sha256=Mp5-euZUfKfnVzgMZd3LoWbegm1OrNjzpP1A-n2EiF0,67640
658
+ reconcile/utils/aws_api.py,sha256=8LeEweWeydLJB9t-neYkYSN6EneDJontGwcglg0xmS0,67652
659
659
  reconcile/utils/aws_helper.py,sha256=MDbv5jrNdqqJ5pfBxniGdJXBBO_EYc2_Uf2w9ZzeMNs,2854
660
660
  reconcile/utils/batches.py,sha256=TtEm64a8lWhFuNbUVpFEmXVdU2Q0sTBrP_I0Cjbgh7g,320
661
661
  reconcile/utils/binary.py,sha256=7MaAFBpzuBUTJ_aA6G6-eult_BPMVyiXbBLD0Y6F-DM,2301
@@ -842,7 +842,7 @@ tools/qontract_cli.py,sha256=KfmjfaCuyd8F68oY1hHd2tEf0PY0kLd6t8sWqR81nmc,142498
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
845
+ tools/cli_commands/container_images_report.py,sha256=64LXkv3eoWtMFZwBgkYE9jSnReDD7A9M0GEVXsR9aGk,5183
846
846
  tools/cli_commands/erv2.py,sha256=469qdhyaf7thpPQ4hJSurvmxBqYDJsoI8H4AigQIF7U,20737
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
@@ -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.1rc1184.dist-info/METADATA,sha256=8DHsuGLxV4LuSR7LPUvgLMjGqffzD8xy5iCyTv2Ltuc,2213
886
- qontract_reconcile-0.10.1rc1184.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
887
- qontract_reconcile-0.10.1rc1184.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
888
- qontract_reconcile-0.10.1rc1184.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
889
- qontract_reconcile-0.10.1rc1184.dist-info/RECORD,,
885
+ qontract_reconcile-0.10.1rc1186.dist-info/METADATA,sha256=0PW1mUMDbwpICxL8PDkTf4Ne7UZ-R8R0U4Ktr3KAUAQ,2213
886
+ qontract_reconcile-0.10.1rc1186.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
887
+ qontract_reconcile-0.10.1rc1186.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
888
+ qontract_reconcile-0.10.1rc1186.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
889
+ qontract_reconcile-0.10.1rc1186.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.0)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -956,36 +956,43 @@ class AWSApi: # pylint: disable=too-many-public-methods
956
956
  for s in vpc_subnets
957
957
  ]
958
958
  if hcp_vpc_endpoint_sg:
959
- endpoints = AWSApi._get_vpc_endpoints(
960
- [
961
- {"Name": "vpc-id", "Values": [vpc_id]},
962
- {
963
- "Name": "tag:AWSEndpointService",
964
- "Values": ["private-router"],
965
- },
966
- ],
967
- assumed_ec2,
959
+ api_security_group_id = self._get_api_security_group_id(
960
+ assumed_ec2, vpc_id
968
961
  )
969
- if len(endpoints) > 1:
970
- raise ValueError(
971
- f"exactly one VPC endpoint for private API router in VPC {vpc_id} expected but {len(endpoints)} found"
972
- )
973
- vpc_endpoint_id = endpoints[0]["VpcEndpointId"]
974
- # https://github.com/openshift/hypershift/blob/c855f68e84e78924ccc9c2132b75dc7e30c4e1d8/control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go#L4243
975
- security_groups = [
976
- sg
977
- for sg in endpoints[0]["Groups"]
978
- if sg["GroupName"].endswith("-default-sg")
979
- ]
980
- if len(security_groups) != 1:
981
- raise ValueError(
982
- f"exactly one VPC endpoint default security group for private API router {vpc_endpoint_id} "
983
- f"in VPC {vpc_id} expected but {len(security_groups)} found"
984
- )
985
- api_security_group_id = security_groups[0]["GroupId"]
986
962
 
987
963
  return vpc_id, route_table_ids, subnets_id_az, api_security_group_id
988
964
 
965
+ def _get_api_security_group_id(self, assumed_ec2, vpc_id):
966
+ endpoints = AWSApi._get_vpc_endpoints(
967
+ [
968
+ {"Name": "vpc-id", "Values": [vpc_id]},
969
+ {
970
+ "Name": "tag:AWSEndpointService",
971
+ "Values": ["private-router"],
972
+ },
973
+ ],
974
+ assumed_ec2,
975
+ )
976
+ if not endpoints:
977
+ return None
978
+ if len(endpoints) > 1:
979
+ raise ValueError(
980
+ f"exactly one VPC endpoint for private API router in VPC {vpc_id} expected but {len(endpoints)} found"
981
+ )
982
+ vpc_endpoint_id = endpoints[0]["VpcEndpointId"]
983
+ # https://github.com/openshift/hypershift/blob/c855f68e84e78924ccc9c2132b75dc7e30c4e1d8/control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go#L4243
984
+ security_groups = [
985
+ sg
986
+ for sg in endpoints[0]["Groups"]
987
+ if sg["GroupName"].endswith("-default-sg")
988
+ ]
989
+ if len(security_groups) != 1:
990
+ raise ValueError(
991
+ f"exactly one VPC endpoint default security group for private API router {vpc_endpoint_id} "
992
+ f"in VPC {vpc_id} expected but {len(security_groups)} found"
993
+ )
994
+ return security_groups[0]["GroupId"]
995
+
989
996
  def get_cluster_nat_gateways_egress_ips(self, account: dict[str, Any], vpc_id: str):
990
997
  assumed_role_data = self._get_account_assume_data(account)
991
998
  assumed_ec2 = self._get_assumed_role_client(*assumed_role_data)
@@ -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,
@@ -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
+ ]