Skip to content

Commit d1bae3d

Browse files
authored
Merge pull request #24 from CormickKneey/feat/set_proxy
feat: support setting proxy for reggie client
2 parents ceb4fcc + ad89019 commit d1bae3d

File tree

6 files changed

+134
-1
lines changed

6 files changed

+134
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Critical items to know are:
1616
Versions here coincide with releases on pypi.
1717

1818
## [master](https://github.com/vsoch/oci-python)
19+
- support setting proxy for reggie client (0.0.15)
1920
- do not set basic auth if no username/password provided (0.0.14)
2021
- allow for update of a structure attribute, if applicable (0.0.13)
2122
- fix to bug with parsing www-Authenticate (0.0.12)

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: 25 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,18 @@ 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+
{
266+
"http": proxy,
267+
"https": proxy,
268+
}
269+
)
270+
return self
271+
247272
def Execute(self, method=None, url=None):
248273
"""
249274
Execute validates a Request and executes it.

opencontainers/tests/test_distribution.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
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
@@ -19,18 +21,59 @@
1921
mock_server_thread = None
2022

2123

24+
# Simple HTTP proxy server for testing proxy functionality
25+
class SimpleProxyHandler(BaseHTTPRequestHandler):
26+
"""A simple HTTP proxy handler that logs requests"""
27+
28+
def do_GET(self):
29+
self.send_response(200)
30+
self.send_header("Content-Type", "text/plain")
31+
self.end_headers()
32+
self.wfile.write(b"Request proxied successfully")
33+
print(f"Proxy handled request: {self.path}")
34+
35+
def do_PUT(self):
36+
self.send_response(200)
37+
self.send_header("Content-Type", "text/plain")
38+
self.end_headers()
39+
self.wfile.write(b"Request proxied successfully")
40+
print(f"Proxy handled PUT request: {self.path}")
41+
42+
def log_message(self, format, *args):
43+
# Customize logging to show it's from the proxy
44+
print(f"PROXY LOG: {format % args}")
45+
46+
47+
# Global variables for proxy server
48+
proxy_port = get_free_port()
49+
proxy_server = None
50+
proxy_server_thread = None
51+
52+
2253
def setup_module(module):
2354
"""setup any state specific to the execution of the given module."""
2455
global mock_server
2556
global mock_server_thread
57+
global proxy_server
58+
global proxy_server_thread
59+
60+
# Start the mock registry server
2661
mock_server, mock_server_thread = start_mock_server(port)
2762

63+
# Start the proxy server
64+
proxy_server = HTTPServer(("localhost", proxy_port), SimpleProxyHandler)
65+
proxy_server_thread = Thread(target=proxy_server.serve_forever)
66+
proxy_server_thread.setDaemon(True)
67+
proxy_server_thread.start()
68+
print(f"Proxy server started on port {proxy_port}")
69+
2870

2971
def teardown_module(module):
3072
"""teardown any state that was previously setup with a setup_module
3173
method.
3274
"""
3375
mock_server.server_close()
76+
proxy_server.server_close()
3477

3578

3679
def test_distribution_mock_server(tmp_path):
@@ -47,6 +90,24 @@ def test_distribution_mock_server(tmp_path):
4790
)
4891
assert not client.Config.Debug
4992

93+
print("Testing creation of client with proxy")
94+
proxy_url = f"http://localhost:{proxy_port}"
95+
proxy_client = NewClient(
96+
mock_url,
97+
WithUsernamePassword("testuser", "testpass"),
98+
WithDefaultName("testname"),
99+
WithUserAgent("reggie-tests"),
100+
WithProxy(proxy_url),
101+
)
102+
assert proxy_client.Config.Proxy == proxy_url
103+
104+
# Make a request with the proxy client
105+
req = proxy_client.NewRequest("GET", "/v2/<n>/tags/list")
106+
response = proxy_client.Do(req)
107+
assert (
108+
response.status_code == 200
109+
), f"Expected status code 200, got {response.status_code}"
110+
50111
print("Testing setting debug option")
51112
clientDebug = NewClient(mock_url, WithDebug(True))
52113
assert clientDebug.Config.Debug
@@ -189,6 +250,27 @@ def test_distribution_mock_server(tmp_path):
189250
print("Check that the body did not get lost somewhere")
190251
assert req.body == "abc"
191252

253+
print("Test proxy request with different configuration")
254+
# Create a client with a different proxy configuration
255+
alt_proxy_url = f"http://localhost:{proxy_port}/alternate"
256+
alt_proxy_client = NewClient(
257+
mock_url,
258+
WithProxy(alt_proxy_url),
259+
)
260+
assert alt_proxy_client.Config.Proxy == alt_proxy_url
261+
262+
# Verify that proxy setting is correctly passed to the request
263+
proxy_req = alt_proxy_client.NewRequest("GET", "/v2/test/tags/list")
264+
assert (
265+
proxy_req.proxies
266+
), "Request should have non-empty proxies dictionary when proxy is set"
267+
assert (
268+
proxy_req.proxies.get("http") == alt_proxy_url
269+
), "HTTP proxy not correctly set"
270+
assert (
271+
proxy_req.proxies.get("https") == alt_proxy_url
272+
), "HTTPS proxy not correctly set"
273+
192274
print("Test that the retry callback is invoked, if configured.")
193275
newBody = "not the original body"
194276

@@ -214,3 +296,12 @@ def errorFunc(r):
214296
)
215297
except Exception as exc:
216298
assert "ruhroh" in str(exc)
299+
300+
print("Test proxy setting in request client")
301+
# Directly test the SetProxy method on the request client
302+
req = client.NewRequest("GET", "/test/endpoint")
303+
proxy_addr = f"http://localhost:{proxy_port}/direct-test"
304+
req.SetProxy(proxy_addr)
305+
# Verify that the proxy is set in the underlying request object when it's executed
306+
response = client.Do(req)
307+
assert response.status_code == 200, "Request through proxy should succeed"

opencontainers/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
55
# with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
66

7-
__version__ = "0.0.14"
7+
__version__ = "0.0.15"
88
AUTHOR = "Vanessa Sochat"
99
AUTHOR_EMAIL = "vsoch@users.noreply.github.com"
1010
NAME = "opencontainers"

0 commit comments

Comments
 (0)