Skip to content

Commit 5c00588

Browse files
committed
feat: support setting proxy for reggie client
1 parent ceb4fcc commit 5c00588

File tree

4 files changed

+120
-0
lines changed

4 files changed

+120
-0
lines changed

opencontainers/distribution/reggie/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
WithDefaultName,
88
WithDebug,
99
WithUserAgent,
10+
WithProxy,
1011
)
1112
from .request import (
1213
WithName,

opencontainers/distribution/reggie/client.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class ClientConfig(BaseConfig):
3434
"WithDebug",
3535
"WithDefaultName",
3636
"WithAuthScope",
37+
"WithProxy",
3738
]
3839

3940
def __init__(self, address, opts=None):
@@ -48,6 +49,7 @@ def __init__(self, address, opts=None):
4849
self.DefaultName = None
4950
self.UserAgent = DEFAULT_USER_AGENT
5051
self.required = [self.Address, self.UserAgent]
52+
self.Proxy = None
5153
super().__init__()
5254

5355
def _validate(self):
@@ -118,6 +120,17 @@ def WithUserAgent(config):
118120
return WithUserAgent
119121

120122

123+
def WithProxy(proxy):
124+
"""
125+
WithProxy sets the proxy configuration setting.
126+
"""
127+
128+
def WithProxy(config):
129+
config.Proxy = proxy
130+
131+
return WithProxy
132+
133+
121134
# Client
122135

123136

@@ -178,6 +191,8 @@ def NewRequest(self, method, path, *opts):
178191
requestClient.SetUrl(url)
179192
requestClient.SetHeader("User-Agent", self.Config.UserAgent)
180193
requestClient.SetRetryCallback(rc.RetryCallback)
194+
if self.Config.Proxy:
195+
requestClient.SetProxy(self.Config.Proxy)
181196

182197
# Return the Client, which has Request and retryCallback
183198
return requestClient

opencontainers/distribution/reggie/request.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class RequestConfig(BaseConfig):
3535
"WithDigest",
3636
"WithSessionID",
3737
"WithRetryCallback",
38+
"WithProxy",
3839
]
3940

4041
def __init__(self, opts):
@@ -46,6 +47,7 @@ def __init__(self, opts):
4647
self.Digest = None
4748
self.SessionID = None
4849
self.RetryCallback = None
50+
self.Proxy = None
4951
self.required = [self.Name]
5052
super().__init__(opts or [])
5153

@@ -108,6 +110,17 @@ def WithRetryCallback(config):
108110
return WithRetryCallback
109111

110112

113+
def WithProxy(proxy):
114+
"""
115+
WithProxy sets the proxy configuration setting for requests.
116+
"""
117+
118+
def WithProxy(config):
119+
config.Proxy = proxy
120+
121+
return WithProxy
122+
123+
111124
class RequestClient(requests.Session):
112125
"""
113126
A Request Client.
@@ -244,6 +257,16 @@ def SetBasicAuth(self, username, password):
244257
auth_header = base64.b64encode(auth_str.encode("utf-8"))
245258
return self.SetHeader("Authorization", "Basic %s" % auth_header.decode("utf-8"))
246259

260+
def SetProxy(self, proxy):
261+
"""
262+
SetProxy sets the proxy configuration setting for requests.
263+
"""
264+
self.proxies.update({
265+
"http": proxy,
266+
"https": proxy,
267+
})
268+
return self
269+
247270
def Execute(self, method=None, url=None):
248271
"""
249272
Execute validates a Request and executes it.

opencontainers/tests/test_distribution.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,67 @@
1111
import os
1212
import re
1313
import pytest
14+
from http.server import BaseHTTPRequestHandler, HTTPServer
15+
from threading import Thread
1416

1517

1618
# Use the same port across tests
1719
port = get_free_port()
1820
mock_server = None
1921
mock_server_thread = None
2022

23+
# Simple HTTP proxy server for testing proxy functionality
24+
class SimpleProxyHandler(BaseHTTPRequestHandler):
25+
"""A simple HTTP proxy handler that logs requests"""
26+
27+
def do_GET(self):
28+
self.send_response(200)
29+
self.send_header('Content-Type', 'text/plain')
30+
self.end_headers()
31+
self.wfile.write(b'Request proxied successfully')
32+
print(f"Proxy handled request: {self.path}")
33+
34+
def do_PUT(self):
35+
self.send_response(200)
36+
self.send_header('Content-Type', 'text/plain')
37+
self.end_headers()
38+
self.wfile.write(b'Request proxied successfully')
39+
print(f"Proxy handled PUT request: {self.path}")
40+
41+
def log_message(self, format, *args):
42+
# Customize logging to show it's from the proxy
43+
print(f"PROXY LOG: {format % args}")
44+
45+
# Global variables for proxy server
46+
proxy_port = get_free_port()
47+
proxy_server = None
48+
proxy_server_thread = None
49+
2150

2251
def setup_module(module):
2352
"""setup any state specific to the execution of the given module."""
2453
global mock_server
2554
global mock_server_thread
55+
global proxy_server
56+
global proxy_server_thread
57+
58+
# Start the mock registry server
2659
mock_server, mock_server_thread = start_mock_server(port)
60+
61+
# Start the proxy server
62+
proxy_server = HTTPServer(('localhost', proxy_port), SimpleProxyHandler)
63+
proxy_server_thread = Thread(target=proxy_server.serve_forever)
64+
proxy_server_thread.setDaemon(True)
65+
proxy_server_thread.start()
66+
print(f"Proxy server started on port {proxy_port}")
2767

2868

2969
def teardown_module(module):
3070
"""teardown any state that was previously setup with a setup_module
3171
method.
3272
"""
3373
mock_server.server_close()
74+
proxy_server.server_close()
3475

3576

3677
def test_distribution_mock_server(tmp_path):
@@ -46,6 +87,22 @@ def test_distribution_mock_server(tmp_path):
4687
WithUserAgent("reggie-tests"),
4788
)
4889
assert not client.Config.Debug
90+
91+
print("Testing creation of client with proxy")
92+
proxy_url = f"http://localhost:{proxy_port}"
93+
proxy_client = NewClient(
94+
mock_url,
95+
WithUsernamePassword("testuser", "testpass"),
96+
WithDefaultName("testname"),
97+
WithUserAgent("reggie-tests"),
98+
WithProxy(proxy_url),
99+
)
100+
assert proxy_client.Config.Proxy == proxy_url
101+
102+
# Make a request with the proxy client
103+
req = proxy_client.NewRequest("GET", "/v2/<n>/tags/list")
104+
response = proxy_client.Do(req)
105+
assert response.status_code == 200, f"Expected status code 200, got {response.status_code}"
49106

50107
print("Testing setting debug option")
51108
clientDebug = NewClient(mock_url, WithDebug(True))
@@ -188,6 +245,21 @@ def test_distribution_mock_server(tmp_path):
188245

189246
print("Check that the body did not get lost somewhere")
190247
assert req.body == "abc"
248+
249+
print("Test proxy request with different configuration")
250+
# Create a client with a different proxy configuration
251+
alt_proxy_url = f"http://localhost:{proxy_port}/alternate"
252+
alt_proxy_client = NewClient(
253+
mock_url,
254+
WithProxy(alt_proxy_url),
255+
)
256+
assert alt_proxy_client.Config.Proxy == alt_proxy_url
257+
258+
# Verify that proxy setting is correctly passed to the request
259+
proxy_req = alt_proxy_client.NewRequest("GET", "/v2/test/tags/list")
260+
assert proxy_req.proxies, "Request should have non-empty proxies dictionary when proxy is set"
261+
assert proxy_req.proxies.get("http") == alt_proxy_url, "HTTP proxy not correctly set"
262+
assert proxy_req.proxies.get("https") == alt_proxy_url, "HTTPS proxy not correctly set"
191263

192264
print("Test that the retry callback is invoked, if configured.")
193265
newBody = "not the original body"
@@ -214,3 +286,12 @@ def errorFunc(r):
214286
)
215287
except Exception as exc:
216288
assert "ruhroh" in str(exc)
289+
290+
print("Test proxy setting in request client")
291+
# Directly test the SetProxy method on the request client
292+
req = client.NewRequest("GET", "/test/endpoint")
293+
proxy_addr = f"http://localhost:{proxy_port}/direct-test"
294+
req.SetProxy(proxy_addr)
295+
# Verify that the proxy is set in the underlying request object when it's executed
296+
response = client.Do(req)
297+
assert response.status_code == 200, "Request through proxy should succeed"

0 commit comments

Comments
 (0)