openshift-update-proxy 1.2.0__tar.gz → 1.4.5__tar.gz

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.
Files changed (22) hide show
  1. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/PKG-INFO +21 -9
  2. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/README.md +18 -6
  3. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/pyproject.toml +3 -3
  4. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy/__init__.py +1 -1
  5. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy/app.py +32 -4
  6. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy.egg-info/PKG-INFO +21 -9
  7. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/tests/test_app.py +41 -1
  8. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/LICENSE +0 -0
  9. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/setup.cfg +0 -0
  10. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy/__main__.py +0 -0
  11. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy/cache.py +0 -0
  12. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy/catalog.py +0 -0
  13. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy/config.py +0 -0
  14. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy/graph.py +0 -0
  15. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy/lifecycle.py +0 -0
  16. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy.egg-info/SOURCES.txt +0 -0
  17. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy.egg-info/dependency_links.txt +0 -0
  18. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy.egg-info/entry_points.txt +0 -0
  19. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy.egg-info/requires.txt +0 -0
  20. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/src/openshift_update_proxy.egg-info/top_level.txt +0 -0
  21. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/tests/test_catalog.py +0 -0
  22. {openshift_update_proxy-1.2.0 → openshift_update_proxy-1.4.5}/tests/test_lifecycle.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openshift-update-proxy
3
- Version: 1.2.0
4
- Summary: Forwarding proxy for OpenShift update resources (Cincinnati API, mirror, release signatures)
3
+ Version: 1.4.5
4
+ Summary: Forwarding proxy for OpenShift update resources (update graph, mirror, release signatures, operator catalog, lifecycle API)
5
5
  Author-email: Simon Lauger <simon@lauger.de>
6
6
  License-Expression: Apache-2.0
7
7
  Project-URL: Repository, https://github.com/slauger/openshift-update-proxy
8
- Keywords: openshift,cincinnati,update-graph,proxy,disconnected,air-gapped
8
+ Keywords: openshift,cincinnati,update-graph,proxy,disconnected,air-gapped,olm,renovate
9
9
  Classifier: Development Status :: 4 - Beta
10
10
  Classifier: Environment :: Web Environment
11
11
  Classifier: Framework :: Flask
@@ -47,8 +47,8 @@ central egress proxy (or a single host with internet access) exists.
47
47
  (`/api/upgrades_info/v1/graph`) to `api.openshift.com`
48
48
  - 📦 **Mirror Proxy** - forwards requests for clients and release artifacts to
49
49
  `mirror.openshift.com/pub`
50
- - 🔏 **Signature Store** - serves release image signatures for
51
- `ClusterVersion.spec.signatureStores` (OpenShift 4.14+)
50
+ - 🔏 **Signature Store** - forwards release image signature requests to the
51
+ public signature store on `mirror.openshift.com`
52
52
  - 🗺️ **ConfigMap Generator** - renders ready-to-apply signature ConfigMaps for the
53
53
  classic disconnected verification workflow
54
54
  - 🎛️ **Operator Catalog API** - serves operator channels and versions from the
@@ -61,6 +61,8 @@ central egress proxy (or a single host with internet access) exists.
61
61
  Cosign signed
62
62
  - ⛵ **Helm Chart** - deploy to Kubernetes/OpenShift with probes and sane security defaults
63
63
  - 🩺 **Health Endpoint** - `/healthz` for liveness and readiness probes
64
+ - 🪵 **Request Logging** - logs source, method, path and status for every
65
+ request (health probes excluded)
64
66
 
65
67
  ## How it works
66
68
 
@@ -159,6 +161,9 @@ pip install openshift-update-proxy
159
161
  openshift-update-proxy
160
162
  ```
161
163
 
164
+ For running as a service on a plain Linux host, see the systemd unit example in
165
+ [examples/update-proxy.service](examples/update-proxy.service).
166
+
162
167
  ## Cluster integration
163
168
 
164
169
  ### Update graph
@@ -189,7 +194,11 @@ curl -s "http://update-proxy.example.com:5000/configmaps/4.16.8" | oc apply -f -
189
194
 
190
195
  The optional `arch` (default `amd64`) and `channel_prefix` (default `stable`) query
191
196
  parameters select the architecture and the update channel used for the lookup, e.g.
192
- `/configmaps/4.16.8?arch=arm64`.
197
+ `/configmaps/4.16.8?arch=arm64`. Version lookups name the ConfigMap
198
+ `release-signature-<version>-<arch>` - the signed digest differs per
199
+ architecture, so the name has to carry it. Digest lookups use
200
+ `signature-sha256-<digest prefix>` (first 16 characters) as neither version nor
201
+ architecture are known there.
193
202
 
194
203
  Alternatively, a release digest can be passed directly:
195
204
 
@@ -289,9 +298,12 @@ The optional `channel_prefix` (default `stable`, e.g. `eus`, `fast`, `candidate`
289
298
  lifecycle API is available under `/lifecycle/`, e.g.
290
299
  `/lifecycle/products?name=OpenShift Container Platform`.
291
300
 
292
- [examples/create-configmaps.sh](examples/create-configmaps.sh) combines this with
293
- the `/configmaps/` endpoint: it fetches the release signatures for a set of update
294
- channels through the proxy and applies them as ConfigMaps.
301
+ [examples/create-configmaps/](examples/create-configmaps/) combines this with the
302
+ `/configmaps/` endpoint: it fetches the release signatures for a set of update
303
+ channels through the proxy and writes one manifest per release
304
+ (`manifests/release-signature-<version>-<arch>.yaml`), ready to be committed to a GitOps
305
+ repository. The directory also contains a scheduled GitHub Actions workflow that
306
+ keeps the manifests up to date.
295
307
 
296
308
  ## Local Development
297
309
 
@@ -16,8 +16,8 @@ central egress proxy (or a single host with internet access) exists.
16
16
  (`/api/upgrades_info/v1/graph`) to `api.openshift.com`
17
17
  - 📦 **Mirror Proxy** - forwards requests for clients and release artifacts to
18
18
  `mirror.openshift.com/pub`
19
- - 🔏 **Signature Store** - serves release image signatures for
20
- `ClusterVersion.spec.signatureStores` (OpenShift 4.14+)
19
+ - 🔏 **Signature Store** - forwards release image signature requests to the
20
+ public signature store on `mirror.openshift.com`
21
21
  - 🗺️ **ConfigMap Generator** - renders ready-to-apply signature ConfigMaps for the
22
22
  classic disconnected verification workflow
23
23
  - 🎛️ **Operator Catalog API** - serves operator channels and versions from the
@@ -30,6 +30,8 @@ central egress proxy (or a single host with internet access) exists.
30
30
  Cosign signed
31
31
  - ⛵ **Helm Chart** - deploy to Kubernetes/OpenShift with probes and sane security defaults
32
32
  - 🩺 **Health Endpoint** - `/healthz` for liveness and readiness probes
33
+ - 🪵 **Request Logging** - logs source, method, path and status for every
34
+ request (health probes excluded)
33
35
 
34
36
  ## How it works
35
37
 
@@ -128,6 +130,9 @@ pip install openshift-update-proxy
128
130
  openshift-update-proxy
129
131
  ```
130
132
 
133
+ For running as a service on a plain Linux host, see the systemd unit example in
134
+ [examples/update-proxy.service](examples/update-proxy.service).
135
+
131
136
  ## Cluster integration
132
137
 
133
138
  ### Update graph
@@ -158,7 +163,11 @@ curl -s "http://update-proxy.example.com:5000/configmaps/4.16.8" | oc apply -f -
158
163
 
159
164
  The optional `arch` (default `amd64`) and `channel_prefix` (default `stable`) query
160
165
  parameters select the architecture and the update channel used for the lookup, e.g.
161
- `/configmaps/4.16.8?arch=arm64`.
166
+ `/configmaps/4.16.8?arch=arm64`. Version lookups name the ConfigMap
167
+ `release-signature-<version>-<arch>` - the signed digest differs per
168
+ architecture, so the name has to carry it. Digest lookups use
169
+ `signature-sha256-<digest prefix>` (first 16 characters) as neither version nor
170
+ architecture are known there.
162
171
 
163
172
  Alternatively, a release digest can be passed directly:
164
173
 
@@ -258,9 +267,12 @@ The optional `channel_prefix` (default `stable`, e.g. `eus`, `fast`, `candidate`
258
267
  lifecycle API is available under `/lifecycle/`, e.g.
259
268
  `/lifecycle/products?name=OpenShift Container Platform`.
260
269
 
261
- [examples/create-configmaps.sh](examples/create-configmaps.sh) combines this with
262
- the `/configmaps/` endpoint: it fetches the release signatures for a set of update
263
- channels through the proxy and applies them as ConfigMaps.
270
+ [examples/create-configmaps/](examples/create-configmaps/) combines this with the
271
+ `/configmaps/` endpoint: it fetches the release signatures for a set of update
272
+ channels through the proxy and writes one manifest per release
273
+ (`manifests/release-signature-<version>-<arch>.yaml`), ready to be committed to a GitOps
274
+ repository. The directory also contains a scheduled GitHub Actions workflow that
275
+ keeps the manifests up to date.
264
276
 
265
277
  ## Local Development
266
278
 
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "openshift-update-proxy"
7
- version = "1.2.0"
8
- description = "Forwarding proxy for OpenShift update resources (Cincinnati API, mirror, release signatures)"
7
+ version = "1.4.5"
8
+ description = "Forwarding proxy for OpenShift update resources (update graph, mirror, release signatures, operator catalog, lifecycle API)"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
11
11
  requires-python = ">=3.14"
12
12
  authors = [
13
13
  {name = "Simon Lauger", email = "simon@lauger.de"},
14
14
  ]
15
- keywords = ["openshift", "cincinnati", "update-graph", "proxy", "disconnected", "air-gapped"]
15
+ keywords = ["openshift", "cincinnati", "update-graph", "proxy", "disconnected", "air-gapped", "olm", "renovate"]
16
16
  classifiers = [
17
17
  "Development Status :: 4 - Beta",
18
18
  "Environment :: Web Environment",
@@ -1,3 +1,3 @@
1
1
  """OpenShift update proxy for disconnected environments."""
2
2
 
3
- __version__ = "1.2.0"
3
+ __version__ = "1.4.5"
@@ -5,6 +5,7 @@ import logging
5
5
  import re
6
6
 
7
7
  import requests
8
+ import urllib3
8
9
  from flask import Flask, Response, jsonify, request
9
10
 
10
11
  from openshift_update_proxy import __version__, catalog, graph, lifecycle
@@ -34,7 +35,7 @@ VERSION_PATTERN = re.compile(r"^(?P<major>\d+)\.(?P<minor>\d+)\.\d+[A-Za-z0-9.+-
34
35
  CONFIGMAP_TEMPLATE = """apiVersion: v1
35
36
  kind: ConfigMap
36
37
  metadata:
37
- name: signature-sha256-{short_digest}
38
+ name: {name}
38
39
  namespace: openshift-config-managed
39
40
  labels:
40
41
  release.openshift.io/verification-signatures: ""
@@ -46,6 +47,23 @@ def create_app(config: Config | None = None) -> Flask:
46
47
  app = Flask(__name__)
47
48
  app.config["proxy"] = config or Config()
48
49
 
50
+ # the insecure request warning would be emitted on every upstream call;
51
+ # skipping verification is an explicit opt-in, so silence it
52
+ if not app.config["proxy"].ssl_verify:
53
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
54
+
55
+ @app.after_request
56
+ def log_request(response: Response) -> Response:
57
+ if request.path != "/healthz":
58
+ logger.info(
59
+ '%s "%s %s" %s',
60
+ request.remote_addr,
61
+ request.method,
62
+ request.full_path if request.query_string else request.path,
63
+ response.status_code,
64
+ )
65
+ return response
66
+
49
67
  @app.route("/")
50
68
  def index() -> Response:
51
69
  return jsonify(
@@ -190,6 +208,7 @@ def create_app(config: Config | None = None) -> Flask:
190
208
  digest_match = DIGEST_PATTERN.match(ref)
191
209
  version_match = VERSION_PATTERN.match(ref)
192
210
 
211
+ name = None
193
212
  if digest_match:
194
213
  digest = digest_match.group("digest")
195
214
  elif version_match:
@@ -200,6 +219,10 @@ def create_app(config: Config | None = None) -> Flask:
200
219
  if error:
201
220
  return error
202
221
 
222
+ # the signed digest differs per architecture, so the name has to
223
+ # carry it - the version alone would collide across architectures
224
+ name = f"release-signature-{ref}-{arch}"
225
+
203
226
  channel = (
204
227
  f"{channel_prefix}-{version_match.group('major')}.{version_match.group('minor')}"
205
228
  )
@@ -228,7 +251,9 @@ def create_app(config: Config | None = None) -> Flask:
228
251
  if not signatures:
229
252
  return Response("no signatures found for digest\n", status=404, mimetype="text/plain")
230
253
 
231
- return Response(_render_configmap(digest, signatures), mimetype="application/yaml")
254
+ return Response(
255
+ _render_configmap(digest, signatures, name=name), mimetype="application/yaml"
256
+ )
232
257
 
233
258
  return app
234
259
 
@@ -290,10 +315,13 @@ def _fetch_signatures(cfg: Config, digest: str, limit: int = 10) -> list[bytes]:
290
315
  return signatures
291
316
 
292
317
 
293
- def _render_configmap(digest: str, signatures: list[bytes]) -> str:
318
+ def _render_configmap(digest: str, signatures: list[bytes], name: str | None = None) -> str:
294
319
  binary_data = "\n".join(
295
320
  f" sha256-{digest}-{index}: {base64.b64encode(signature).decode('ascii')}"
296
321
  for index, signature in enumerate(signatures, start=1)
297
322
  )
298
323
 
299
- return CONFIGMAP_TEMPLATE.format(short_digest=digest[:16], binary_data=binary_data)
324
+ return CONFIGMAP_TEMPLATE.format(
325
+ name=name or f"signature-sha256-{digest[:16]}",
326
+ binary_data=binary_data,
327
+ )
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openshift-update-proxy
3
- Version: 1.2.0
4
- Summary: Forwarding proxy for OpenShift update resources (Cincinnati API, mirror, release signatures)
3
+ Version: 1.4.5
4
+ Summary: Forwarding proxy for OpenShift update resources (update graph, mirror, release signatures, operator catalog, lifecycle API)
5
5
  Author-email: Simon Lauger <simon@lauger.de>
6
6
  License-Expression: Apache-2.0
7
7
  Project-URL: Repository, https://github.com/slauger/openshift-update-proxy
8
- Keywords: openshift,cincinnati,update-graph,proxy,disconnected,air-gapped
8
+ Keywords: openshift,cincinnati,update-graph,proxy,disconnected,air-gapped,olm,renovate
9
9
  Classifier: Development Status :: 4 - Beta
10
10
  Classifier: Environment :: Web Environment
11
11
  Classifier: Framework :: Flask
@@ -47,8 +47,8 @@ central egress proxy (or a single host with internet access) exists.
47
47
  (`/api/upgrades_info/v1/graph`) to `api.openshift.com`
48
48
  - 📦 **Mirror Proxy** - forwards requests for clients and release artifacts to
49
49
  `mirror.openshift.com/pub`
50
- - 🔏 **Signature Store** - serves release image signatures for
51
- `ClusterVersion.spec.signatureStores` (OpenShift 4.14+)
50
+ - 🔏 **Signature Store** - forwards release image signature requests to the
51
+ public signature store on `mirror.openshift.com`
52
52
  - 🗺️ **ConfigMap Generator** - renders ready-to-apply signature ConfigMaps for the
53
53
  classic disconnected verification workflow
54
54
  - 🎛️ **Operator Catalog API** - serves operator channels and versions from the
@@ -61,6 +61,8 @@ central egress proxy (or a single host with internet access) exists.
61
61
  Cosign signed
62
62
  - ⛵ **Helm Chart** - deploy to Kubernetes/OpenShift with probes and sane security defaults
63
63
  - 🩺 **Health Endpoint** - `/healthz` for liveness and readiness probes
64
+ - 🪵 **Request Logging** - logs source, method, path and status for every
65
+ request (health probes excluded)
64
66
 
65
67
  ## How it works
66
68
 
@@ -159,6 +161,9 @@ pip install openshift-update-proxy
159
161
  openshift-update-proxy
160
162
  ```
161
163
 
164
+ For running as a service on a plain Linux host, see the systemd unit example in
165
+ [examples/update-proxy.service](examples/update-proxy.service).
166
+
162
167
  ## Cluster integration
163
168
 
164
169
  ### Update graph
@@ -189,7 +194,11 @@ curl -s "http://update-proxy.example.com:5000/configmaps/4.16.8" | oc apply -f -
189
194
 
190
195
  The optional `arch` (default `amd64`) and `channel_prefix` (default `stable`) query
191
196
  parameters select the architecture and the update channel used for the lookup, e.g.
192
- `/configmaps/4.16.8?arch=arm64`.
197
+ `/configmaps/4.16.8?arch=arm64`. Version lookups name the ConfigMap
198
+ `release-signature-<version>-<arch>` - the signed digest differs per
199
+ architecture, so the name has to carry it. Digest lookups use
200
+ `signature-sha256-<digest prefix>` (first 16 characters) as neither version nor
201
+ architecture are known there.
193
202
 
194
203
  Alternatively, a release digest can be passed directly:
195
204
 
@@ -289,9 +298,12 @@ The optional `channel_prefix` (default `stable`, e.g. `eus`, `fast`, `candidate`
289
298
  lifecycle API is available under `/lifecycle/`, e.g.
290
299
  `/lifecycle/products?name=OpenShift Container Platform`.
291
300
 
292
- [examples/create-configmaps.sh](examples/create-configmaps.sh) combines this with
293
- the `/configmaps/` endpoint: it fetches the release signatures for a set of update
294
- channels through the proxy and applies them as ConfigMaps.
301
+ [examples/create-configmaps/](examples/create-configmaps/) combines this with the
302
+ `/configmaps/` endpoint: it fetches the release signatures for a set of update
303
+ channels through the proxy and writes one manifest per release
304
+ (`manifests/release-signature-<version>-<arch>.yaml`), ready to be committed to a GitOps
305
+ repository. The directory also contains a scheduled GitHub Actions workflow that
306
+ keeps the manifests up to date.
295
307
 
296
308
  ## Local Development
297
309
 
@@ -168,7 +168,9 @@ def test_configmap_by_version_resolves_digest(client, monkeypatch):
168
168
  assert response.status_code == 200
169
169
  assert graph_calls["params"]["channel"] == "stable-4.16"
170
170
  assert graph_calls["params"]["arch"] == "arm64"
171
- assert f"sha256-{DIGEST}-1:" in response.data.decode()
171
+ body = response.data.decode()
172
+ assert "name: release-signature-4.16.8-arm64" in body
173
+ assert f"sha256-{DIGEST}-1:" in body
172
174
 
173
175
 
174
176
  def test_configmap_by_version_returns_404_for_unknown_version(client, monkeypatch):
@@ -203,6 +205,44 @@ def test_configmap_by_version_rejects_invalid_arch(client):
203
205
  assert response.status_code == 400
204
206
 
205
207
 
208
+ def test_requests_are_logged(client, caplog, monkeypatch):
209
+ monkeypatch.setattr(app_module.requests, "get", lambda *a, **kw: FakeResponse(b"{}"))
210
+
211
+ with caplog.at_level("INFO", logger="openshift-update-proxy"):
212
+ client.get("/api/upgrades_info/v1/graph?channel=stable-4.16")
213
+
214
+ assert any(
215
+ '"GET /api/upgrades_info/v1/graph?channel=stable-4.16" 200' in record.message
216
+ for record in caplog.records
217
+ )
218
+
219
+
220
+ def test_healthz_is_not_logged(client, caplog):
221
+ with caplog.at_level("INFO", logger="openshift-update-proxy"):
222
+ client.get("/healthz")
223
+
224
+ assert not caplog.records
225
+
226
+
227
+ def test_insecure_warnings_disabled_when_verify_off(monkeypatch):
228
+ from openshift_update_proxy.app import create_app
229
+ from openshift_update_proxy.config import Config
230
+
231
+ calls = []
232
+ monkeypatch.setattr(
233
+ app_module.urllib3, "disable_warnings", lambda category: calls.append(category)
234
+ )
235
+
236
+ monkeypatch.setenv("INSECURE_SKIP_TLS_VERIFY", "true")
237
+ create_app(Config())
238
+ assert calls == [app_module.urllib3.exceptions.InsecureRequestWarning]
239
+
240
+ calls.clear()
241
+ monkeypatch.delenv("INSECURE_SKIP_TLS_VERIFY")
242
+ create_app(Config())
243
+ assert calls == []
244
+
245
+
206
246
  def test_ssl_verify_enabled_by_default(config):
207
247
  assert config.ssl_verify is True
208
248