1717from .ws_client import get_websocket_url
1818from .ws_client import websocket_proxycare
1919from kubernetes .client .configuration import Configuration
20+ import os
21+ import socket
22+ import threading
23+ import pytest
24+ from kubernetes import stream , client , config
2025
2126try :
2227 import urllib3
2328 urllib3 .disable_warnings ()
2429except ImportError :
2530 pass
31+ @pytest .fixture (autouse = True )
32+ def dummy_kubeconfig (tmp_path , monkeypatch ):
33+ # Creating a kubeconfig
34+ content = """
35+ apiVersion: v1
36+ kind: Config
37+ clusters:
38+ - name: default
39+ cluster:
40+ server: http://127.0.0.1:8888
41+ contexts:
42+ - name: default
43+ context:
44+ cluster: default
45+ user: default
46+ users:
47+ - name: default
48+ user: {}
49+ current-context: default
50+ """
51+ cfg_file = tmp_path / "kubeconfig"
52+ cfg_file .write_text (content )
53+ monkeypatch .setenv ("KUBECONFIG" , str (cfg_file ))
2654
27- def dictval (dict , key , default = None ):
28- try :
29- val = dict [key ]
30- except KeyError :
31- val = default
32- return val
55+
56+ def dictval (dict_obj , key , default = None ):
57+
58+ return dict_obj .get (key , default )
59+
60+ class DummyProxy (threading .Thread ):
61+ """
62+ A minimal HTTP proxy that flags any CONNECT request and returns 200 OK.
63+ Listens on 127.0.0.1:8888 by default.
64+ """
65+ def __init__ (self , host = '127.0.0.1' , port = 8888 ):
66+ super ().__init__ (daemon = True )
67+ self .host = host
68+ self .port = port
69+ self .received_connect = False
70+ self ._server_sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
71+ self ._server_sock .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
72+ self ._server_sock .bind ((self .host , self .port ))
73+ self ._server_sock .listen (1 )
74+
75+ def run (self ):
76+ conn , _ = self ._server_sock .accept ()
77+ try :
78+ data = conn .recv (1024 ).decode ('utf-8' , errors = 'ignore' )
79+ if data .startswith ('CONNECT ' ):
80+ self .received_connect = True
81+ conn .sendall (b"HTTP/1.1 200 Connection established\r \n \r \n " )
82+ finally :
83+ conn .close ()
3384
3485class WSClientTest (unittest .TestCase ):
3586
@@ -56,21 +107,68 @@ def test_websocket_proxycare(self):
56107 ( 'http://proxy.example.com:8080/' , 'user:pass' , '.example.com' , 'proxy.example.com' , 8080 , ('user' ,'pass' ), ['.example.com' ]),
57108 ( 'http://proxy.example.com:8080/' , 'user:pass' , 'localhost,.local,.example.com' , 'proxy.example.com' , 8080 , ('user' ,'pass' ), ['localhost' ,'.local' ,'.example.com' ]),
58109 ]:
59- # setup input
60- config = Configuration ()
61- if proxy is not None :
62- setattr ( config , ' proxy' , proxy )
63- if idpass is not None :
64- setattr ( config , ' proxy_headers' , urllib3 .util .make_headers (proxy_basic_auth = idpass ) )
110+ # input setup
111+ cfg = Configuration ()
112+ if proxy :
113+ cfg . proxy = proxy
114+ if idpass :
115+ cfg . proxy_headers = urllib3 .util .make_headers (proxy_basic_auth = idpass )
65116 if no_proxy is not None :
66- setattr (config , 'no_proxy' , no_proxy )
67- # setup done
68- # test starts
69- connect_opt = websocket_proxycare ( {}, config , None , None )
70- self .assertEqual ( dictval (connect_opt ,'http_proxy_host' ), expect_host )
71- self .assertEqual ( dictval (connect_opt ,'http_proxy_port' ), expect_port )
72- self .assertEqual ( dictval (connect_opt ,'http_proxy_auth' ), expect_auth )
73- self .assertEqual ( dictval (connect_opt ,'http_no_proxy' ), expect_noproxy )
117+ cfg .no_proxy = no_proxy
118+
119+
120+ connect_opts = websocket_proxycare ({}, cfg , None , None )
121+ assert dictval (connect_opts , 'http_proxy_host' ) == expect_host
122+ assert dictval (connect_opts , 'http_proxy_port' ) == expect_port
123+ assert dictval (connect_opts , 'http_proxy_auth' ) == expect_auth
124+ assert dictval (connect_opts , 'http_no_proxy' ) == expect_noproxy
125+
126+ @pytest .fixture (scope = "module" )
127+ def dummy_proxy ():
128+ #Dummy Proxy
129+ proxy = DummyProxy (port = 8888 )
130+ proxy .start ()
131+ yield proxy
132+
133+ @pytest .fixture (autouse = True )
134+ def clear_proxy_env (monkeypatch ):
135+ for var in ("HTTP_PROXY" , "http_proxy" , "HTTPS_PROXY" , "https_proxy" , "NO_PROXY" , "no_proxy" ):
136+ monkeypatch .delenv (var , raising = False )
137+
138+ def apply_proxy_to_conf ():
139+ #apply HTTPS_PROXY env var and set it as global.
140+ cfg = client .Configuration .get_default_copy ()
141+ cfg .proxy = os .getenv ("HTTPS_PROXY" )
142+ cfg .no_proxy = os .getenv ("NO_PROXY" , "" )
143+ client .Configuration .set_default (cfg )
144+
145+ def test_rest_call_ignores_env (dummy_proxy , monkeypatch ):
146+ # HTTPS_PROXY to dummy proxy
147+ monkeypatch .setenv ("HTTPS_PROXY" , "http://127.0.0.1:8888" )
148+ # Avoid real HTTP request
149+ monkeypatch .setattr (client .CoreV1Api , "list_namespace" , lambda self , * _args , ** _kwargs : None )
150+ # Load config using kubeconfig
151+ config .load_kube_config (config_file = os .environ ["KUBECONFIG" ])
152+ apply_proxy_to_conf ()
153+ # HTTPS_PROXY to dummy proxy
154+ monkeypatch .setenv ("HTTPS_PROXY" , "http://127.0.0.1:8888" )
155+ config .load_kube_config (config_file = os .environ ["KUBECONFIG" ])
156+ apply_proxy_to_conf ()
157+ v1 = client .CoreV1Api ()
158+ v1 .list_namespace (_preload_content = False )
159+ assert not dummy_proxy .received_connect , "REST path should ignore HTTPS_PROXY"
160+
161+ def test_websocket_call_honors_env (dummy_proxy , monkeypatch ):
162+ # set HTTPS_PROXY again
163+ monkeypatch .setenv ("HTTPS_PROXY" , "http://127.0.0.1:8888" )
164+ # Load kubeconfig
165+ config .load_kube_config (config_file = os .environ ["KUBECONFIG" ])
166+ apply_proxy_to_conf ()
167+ opts = websocket_proxycare ({}, client .Configuration .get_default_copy (), None , None )
168+ assert opts .get ('http_proxy_host' ) == '127.0.0.1'
169+ assert opts .get ('http_proxy_port' ) == 8888
170+ # Optionally verify no_proxy parsing
171+ assert opts .get ('http_no_proxy' ) is None
74172
75173if __name__ == '__main__' :
76174 unittest .main ()
0 commit comments