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.
- {qontract_reconcile-0.10.1rc1184.dist-info → qontract_reconcile-0.10.1rc1186.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc1184.dist-info → qontract_reconcile-0.10.1rc1186.dist-info}/RECORD +8 -8
- {qontract_reconcile-0.10.1rc1184.dist-info → qontract_reconcile-0.10.1rc1186.dist-info}/WHEEL +1 -1
- reconcile/utils/aws_api.py +33 -26
- tools/cli_commands/container_images_report.py +35 -12
- tools/test/test_get_container_images.py +57 -24
- {qontract_reconcile-0.10.1rc1184.dist-info → qontract_reconcile-0.10.1rc1186.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc1184.dist-info → qontract_reconcile-0.10.1rc1186.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc1184.dist-info → qontract_reconcile-0.10.1rc1186.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.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
|
{qontract_reconcile-0.10.1rc1184.dist-info → qontract_reconcile-0.10.1rc1186.dist-info}/RECORD
RENAMED
@@ -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=
|
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=
|
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=
|
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.
|
886
|
-
qontract_reconcile-0.10.
|
887
|
-
qontract_reconcile-0.10.
|
888
|
-
qontract_reconcile-0.10.
|
889
|
-
qontract_reconcile-0.10.
|
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,,
|
reconcile/utils/aws_api.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
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
|
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/
|
122
|
-
"namespaces": "app-sre-
|
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/
|
132
|
-
"namespaces": "app-sre-
|
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": "
|
142
|
-
"namespaces": "app-sre-
|
143
|
-
"count":
|
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
|
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
|
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
|
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
|
+
]
|
File without changes
|
File without changes
|