Skip to content

Commit e3b373f

Browse files
committed
Added proxy variable to read values from environment
What type of PR is this? /kind bug What this PR does / why we need it: This PRs will read environment variables assigned for proxy and no_proxy. Update ws_client_test.py Update configuration.py What type of PR is this? /kind bug What this PR does / why we need it: This PRs will read environment variables assigned for proxy and no_proxy. Update configuration.py Add debug logging doc and example add .readthedocs.yaml config file Added Added insert_proxy_config.sh to edit configuration.py in client Revert "Added insert_proxy_config.sh to edit configuration.py in client" This reverts commit b295c2d. To avoid condition self.no_proxy is already present
1 parent 230925f commit e3b373f

File tree

7 files changed

+352
-21
lines changed

7 files changed

+352
-21
lines changed

.readthedocs.yaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Read the Docs configuration file for Sphinx projects
2+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3+
4+
# Required
5+
version: 2
6+
7+
# Set the OS, Python version and other tools you might need
8+
build:
9+
os: ubuntu-22.04
10+
tools:
11+
python: "3.12"
12+
# You can also specify other tool versions:
13+
# nodejs: "20"
14+
# rust: "1.70"
15+
# golang: "1.20"
16+
17+
# Build documentation in the "docs/" directory with Sphinx
18+
sphinx:
19+
configuration: doc/source/conf.py
20+
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
21+
# builder: "dirhtml"
22+
# Fail on all warnings to avoid broken references
23+
# fail_on_warning: true
24+
25+
# Optionally build your docs in additional formats such as PDF and ePub
26+
# formats:
27+
# - pdf
28+
# - epub
29+
30+
# Optional but recommended, declare the Python requirements required
31+
# to build your documentation
32+
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
33+
python:
34+
install:
35+
- requirements: doc/requirements-docs.txt
36+
- requirements: test-requirements.txt
37+
38+
39+
# git clone --depth 1 https://github.com/kubernetes-client/python .
40+
# git fetch origin --force --prune --prune-tags --depth 50 refs/heads/master:refs/remotes/origin/master
41+
# git checkout --force origin/master
42+
# git clean -d -f -f
43+
# python3.7 -mvirtualenv $READTHEDOCS_VIRTUALENV_PATH
44+
# python -m pip install --upgrade --no-cache-dir pip setuptools
45+
# python -m pip install --upgrade --no-cache-dir pillow mock==1.0.1 alabaster>=0.7,<0.8,!=0.7.5 commonmark==0.9.1 recommonmark==0.5.0 sphinx<2 sphinx-rtd-theme<0.5 readthedocs-sphinx-ext<2.3 jinja2<3.1.0
46+
47+
# cat doc/source/conf.py
48+
# python -m sphinx -T -E -b html -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/html
49+
# python -m sphinx -T -E -b readthedocssinglehtmllocalmedia -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/htmlzip
50+
# python -m sphinx -T -E -b latex -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/pdf
51+
# cat latexmkrc
52+
# latexmk -r latexmkrc -pdf -f -dvi- -ps- -jobname=kubernetes -interaction=nonstopmode
53+
# python -m sphinx -T -E -b epub -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/epub

devel/debug_logging.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Enabling Debug Logging in Kubernetes Python Client
2+
3+
This document describes how to enable debug logging, view logged information, and provides examples for creating, patching, and deleting Kubernetes resources.
4+
5+
## 1. Why Enable Debug Logging?
6+
7+
Debug logging is useful for troubleshooting as it shows details like HTTP request and response headers and bodies. These details can help identify issues during interactions with the Kubernetes API server.
8+
9+
---
10+
11+
## 2. Enabling Debug Logging
12+
13+
To enable debug logging in the Kubernetes Python client, follow these steps:
14+
15+
1. **Modify the Configuration Object:**
16+
Enable debug mode by setting the `debug` attribute of the `client.Configuration` object to `True`.
17+
18+
2. **Example Code to Enable Debug Logging:**
19+
Below is an example showing how to enable debug logging:
20+
```python
21+
from kubernetes import client, config
22+
23+
# Load kube config
24+
config.load_kube_config()
25+
26+
# Enable debug logging
27+
c = client.Configuration()
28+
c.debug = True
29+
30+
# Pass the updated configuration to the API client
31+
api_client = client.ApiClient(configuration=c)
32+
33+
# Use the API client with debug logging enabled
34+
apps_v1 = client.AppsV1Api(api_client=api_client)

examples/enable_debug_logging.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2025 The Kubernetes Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# You may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# This example demonstrates how to enable debug logging in the Kubernetes
10+
# Python client and how it can be used for troubleshooting requests/responses.
11+
12+
from kubernetes import client, config
13+
14+
15+
def main():
16+
# Load kubeconfig from default location
17+
config.load_kube_config()
18+
19+
# Enable debug logging
20+
configuration = client.Configuration()
21+
configuration.debug = True
22+
api_client = client.ApiClient(configuration=configuration)
23+
24+
# Use AppsV1Api with debug logging enabled
25+
apps_v1 = client.AppsV1Api(api_client=api_client)
26+
27+
# Example: Create a dummy deployment (adjust namespace as needed)
28+
deployment = client.V1Deployment(
29+
api_version="apps/v1",
30+
kind="Deployment",
31+
metadata=client.V1ObjectMeta(name="debug-example"),
32+
spec=client.V1DeploymentSpec(
33+
replicas=1,
34+
selector={"matchLabels": {"app": "debug"}},
35+
template=client.V1PodTemplateSpec(
36+
metadata=client.V1ObjectMeta(labels={"app": "debug"}),
37+
spec=client.V1PodSpec(
38+
containers=[
39+
client.V1Container(
40+
name="busybox",
41+
image="busybox",
42+
command=["sh", "-c", "echo Hello, Kubernetes! && sleep 3600"]
43+
)
44+
]
45+
),
46+
),
47+
),
48+
)
49+
50+
# Create the deployment
51+
try:
52+
print("[INFO] Creating deployment...")
53+
apps_v1.create_namespaced_deployment(
54+
namespace="default", body=deployment
55+
)
56+
except client.exceptions.ApiException as e:
57+
print("[ERROR] Exception occurred:", e)
58+
59+
60+
if __name__ == "__main__":
61+
main()

kubernetes/base/stream/ws_client_test.py

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,70 @@
1717
from .ws_client import get_websocket_url
1818
from .ws_client import websocket_proxycare
1919
from 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

2126
try:
2227
import urllib3
2328
urllib3.disable_warnings()
2429
except 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

3485
class 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

75173
if __name__ == '__main__':
76174
unittest.main()

kubernetes/client/configuration.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import multiprocessing
1818
import sys
1919
import urllib3
20+
import os
2021

2122
import six
2223
from six.moves import http_client as httplib
@@ -158,9 +159,15 @@ def __init__(self, host="http://localhost",
158159
"""
159160

160161
self.proxy = None
162+
if(os.getenv("HTTPS_PROXY")):self.proxy=os.getenv("HTTPS_PROXY")
163+
if(os.getenv("https_proxy")):self.proxy=os.getenv("https_proxy")
164+
if(os.getenv("HTTP_PROXY")):self.proxy=os.getenv("HTTP_PROXY")
165+
if(os.getenv("http_proxy")):self.proxy=os.getenv("http_proxy")
161166
"""Proxy URL
162167
"""
163168
self.no_proxy = None
169+
if(os.getenv("NO_PROXY")):self.no_proxy=os.getenv("NO_PROXY")
170+
if(os.getenv("no_proxy")):self.no_proxy=os.getenv("no_proxy")
164171
"""bypass proxy for host in the no_proxy list.
165172
"""
166173
self.proxy_headers = None

0 commit comments

Comments
 (0)