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 b295c2ddcbb838196823c4d7a55a67fd1d1dc290.

To avoid condition self.no_proxy is already present
This commit is contained in:
Raj Bhargav 2025-04-29 01:57:06 +05:30
parent 230925f338
commit e3b373fc1f
7 changed files with 352 additions and 21 deletions

53
.readthedocs.yaml Normal file
View File

@ -0,0 +1,53 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: doc/source/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: doc/requirements-docs.txt
- requirements: test-requirements.txt
# git clone --depth 1 https://github.com/kubernetes-client/python .
# git fetch origin --force --prune --prune-tags --depth 50 refs/heads/master:refs/remotes/origin/master
# git checkout --force origin/master
# git clean -d -f -f
# python3.7 -mvirtualenv $READTHEDOCS_VIRTUALENV_PATH
# python -m pip install --upgrade --no-cache-dir pip setuptools
# 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
# cat doc/source/conf.py
# python -m sphinx -T -E -b html -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/html
# python -m sphinx -T -E -b readthedocssinglehtmllocalmedia -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/htmlzip
# python -m sphinx -T -E -b latex -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/pdf
# cat latexmkrc
# latexmk -r latexmkrc -pdf -f -dvi- -ps- -jobname=kubernetes -interaction=nonstopmode
# python -m sphinx -T -E -b epub -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/epub

34
devel/debug_logging.md Normal file
View File

@ -0,0 +1,34 @@
# Enabling Debug Logging in Kubernetes Python Client
This document describes how to enable debug logging, view logged information, and provides examples for creating, patching, and deleting Kubernetes resources.
## 1. Why Enable Debug Logging?
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.
---
## 2. Enabling Debug Logging
To enable debug logging in the Kubernetes Python client, follow these steps:
1. **Modify the Configuration Object:**
Enable debug mode by setting the `debug` attribute of the `client.Configuration` object to `True`.
2. **Example Code to Enable Debug Logging:**
Below is an example showing how to enable debug logging:
```python
from kubernetes import client, config
# Load kube config
config.load_kube_config()
# Enable debug logging
c = client.Configuration()
c.debug = True
# Pass the updated configuration to the API client
api_client = client.ApiClient(configuration=c)
# Use the API client with debug logging enabled
apps_v1 = client.AppsV1Api(api_client=api_client)

View File

@ -0,0 +1,61 @@
# Copyright 2025 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# This example demonstrates how to enable debug logging in the Kubernetes
# Python client and how it can be used for troubleshooting requests/responses.
from kubernetes import client, config
def main():
# Load kubeconfig from default location
config.load_kube_config()
# Enable debug logging
configuration = client.Configuration()
configuration.debug = True
api_client = client.ApiClient(configuration=configuration)
# Use AppsV1Api with debug logging enabled
apps_v1 = client.AppsV1Api(api_client=api_client)
# Example: Create a dummy deployment (adjust namespace as needed)
deployment = client.V1Deployment(
api_version="apps/v1",
kind="Deployment",
metadata=client.V1ObjectMeta(name="debug-example"),
spec=client.V1DeploymentSpec(
replicas=1,
selector={"matchLabels": {"app": "debug"}},
template=client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(labels={"app": "debug"}),
spec=client.V1PodSpec(
containers=[
client.V1Container(
name="busybox",
image="busybox",
command=["sh", "-c", "echo Hello, Kubernetes! && sleep 3600"]
)
]
),
),
),
)
# Create the deployment
try:
print("[INFO] Creating deployment...")
apps_v1.create_namespaced_deployment(
namespace="default", body=deployment
)
except client.exceptions.ApiException as e:
print("[ERROR] Exception occurred:", e)
if __name__ == "__main__":
main()

View File

@ -17,19 +17,70 @@ import unittest
from .ws_client import get_websocket_url
from .ws_client import websocket_proxycare
from kubernetes.client.configuration import Configuration
import os
import socket
import threading
import pytest
from kubernetes import stream, client, config
try:
import urllib3
urllib3.disable_warnings()
except ImportError:
pass
@pytest.fixture(autouse=True)
def dummy_kubeconfig(tmp_path, monkeypatch):
# Creating a kubeconfig
content = """
apiVersion: v1
kind: Config
clusters:
- name: default
cluster:
server: http://127.0.0.1:8888
contexts:
- name: default
context:
cluster: default
user: default
users:
- name: default
user: {}
current-context: default
"""
cfg_file = tmp_path / "kubeconfig"
cfg_file.write_text(content)
monkeypatch.setenv("KUBECONFIG", str(cfg_file))
def dictval(dict, key, default=None):
try:
val = dict[key]
except KeyError:
val = default
return val
def dictval(dict_obj, key, default=None):
return dict_obj.get(key, default)
class DummyProxy(threading.Thread):
"""
A minimal HTTP proxy that flags any CONNECT request and returns 200 OK.
Listens on 127.0.0.1:8888 by default.
"""
def __init__(self, host='127.0.0.1', port=8888):
super().__init__(daemon=True)
self.host = host
self.port = port
self.received_connect = False
self._server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._server_sock.bind((self.host, self.port))
self._server_sock.listen(1)
def run(self):
conn, _ = self._server_sock.accept()
try:
data = conn.recv(1024).decode('utf-8', errors='ignore')
if data.startswith('CONNECT '):
self.received_connect = True
conn.sendall(b"HTTP/1.1 200 Connection established\r\n\r\n")
finally:
conn.close()
class WSClientTest(unittest.TestCase):
@ -56,21 +107,68 @@ class WSClientTest(unittest.TestCase):
( 'http://proxy.example.com:8080/', 'user:pass', '.example.com', 'proxy.example.com', 8080, ('user','pass'), ['.example.com']),
( 'http://proxy.example.com:8080/', 'user:pass', 'localhost,.local,.example.com', 'proxy.example.com', 8080, ('user','pass'), ['localhost','.local','.example.com']),
]:
# setup input
config = Configuration()
if proxy is not None:
setattr(config, 'proxy', proxy)
if idpass is not None:
setattr(config, 'proxy_headers', urllib3.util.make_headers(proxy_basic_auth=idpass))
# input setup
cfg = Configuration()
if proxy:
cfg.proxy = proxy
if idpass:
cfg.proxy_headers = urllib3.util.make_headers(proxy_basic_auth=idpass)
if no_proxy is not None:
setattr(config, 'no_proxy', no_proxy)
# setup done
# test starts
connect_opt = websocket_proxycare( {}, config, None, None)
self.assertEqual( dictval(connect_opt,'http_proxy_host'), expect_host)
self.assertEqual( dictval(connect_opt,'http_proxy_port'), expect_port)
self.assertEqual( dictval(connect_opt,'http_proxy_auth'), expect_auth)
self.assertEqual( dictval(connect_opt,'http_no_proxy'), expect_noproxy)
cfg.no_proxy = no_proxy
connect_opts = websocket_proxycare({}, cfg, None, None)
assert dictval(connect_opts, 'http_proxy_host') == expect_host
assert dictval(connect_opts, 'http_proxy_port') == expect_port
assert dictval(connect_opts, 'http_proxy_auth') == expect_auth
assert dictval(connect_opts, 'http_no_proxy') == expect_noproxy
@pytest.fixture(scope="module")
def dummy_proxy():
#Dummy Proxy
proxy = DummyProxy(port=8888)
proxy.start()
yield proxy
@pytest.fixture(autouse=True)
def clear_proxy_env(monkeypatch):
for var in ("HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "NO_PROXY", "no_proxy"):
monkeypatch.delenv(var, raising=False)
def apply_proxy_to_conf():
#apply HTTPS_PROXY env var and set it as global.
cfg = client.Configuration.get_default_copy()
cfg.proxy = os.getenv("HTTPS_PROXY")
cfg.no_proxy = os.getenv("NO_PROXY", "")
client.Configuration.set_default(cfg)
def test_rest_call_ignores_env(dummy_proxy, monkeypatch):
# HTTPS_PROXY to dummy proxy
monkeypatch.setenv("HTTPS_PROXY", "http://127.0.0.1:8888")
# Avoid real HTTP request
monkeypatch.setattr(client.CoreV1Api, "list_namespace", lambda self, *_args, **_kwargs: None)
# Load config using kubeconfig
config.load_kube_config(config_file=os.environ["KUBECONFIG"])
apply_proxy_to_conf()
# HTTPS_PROXY to dummy proxy
monkeypatch.setenv("HTTPS_PROXY", "http://127.0.0.1:8888")
config.load_kube_config(config_file=os.environ["KUBECONFIG"])
apply_proxy_to_conf()
v1 = client.CoreV1Api()
v1.list_namespace(_preload_content=False)
assert not dummy_proxy.received_connect, "REST path should ignore HTTPS_PROXY"
def test_websocket_call_honors_env(dummy_proxy, monkeypatch):
# set HTTPS_PROXY again
monkeypatch.setenv("HTTPS_PROXY", "http://127.0.0.1:8888")
# Load kubeconfig
config.load_kube_config(config_file=os.environ["KUBECONFIG"])
apply_proxy_to_conf()
opts = websocket_proxycare({}, client.Configuration.get_default_copy(), None, None)
assert opts.get('http_proxy_host') == '127.0.0.1'
assert opts.get('http_proxy_port') == 8888
# Optionally verify no_proxy parsing
assert opts.get('http_no_proxy') is None
if __name__ == '__main__':
unittest.main()

View File

@ -17,6 +17,7 @@ import logging
import multiprocessing
import sys
import urllib3
import os
import six
from six.moves import http_client as httplib
@ -158,9 +159,15 @@ class Configuration(object):
"""
self.proxy = None
if(os.getenv("HTTPS_PROXY")):self.proxy=os.getenv("HTTPS_PROXY")
if(os.getenv("https_proxy")):self.proxy=os.getenv("https_proxy")
if(os.getenv("HTTP_PROXY")):self.proxy=os.getenv("HTTP_PROXY")
if(os.getenv("http_proxy")):self.proxy=os.getenv("http_proxy")
"""Proxy URL
"""
self.no_proxy = None
if(os.getenv("NO_PROXY")):self.no_proxy=os.getenv("NO_PROXY")
if(os.getenv("no_proxy")):self.no_proxy=os.getenv("no_proxy")
"""bypass proxy for host in the no_proxy list.
"""
self.proxy_headers = None

View File

@ -0,0 +1,77 @@
#!/usr/bin/env bash
# insert_proxy_config.sh - run this after openapi-generator release.sh
CONFIG_PATH="../python_kubernetes/kubernetes/client"
# Compute the full file path
CONFIG_FILE="$CONFIG_PATH/configuration.py"
# --- Normalize Windows-style backslashes to Unix forward slashes ---
CONFIG_FILE="$(echo "$CONFIG_FILE" | sed 's|\\|/|g')"
# --- Ensure the target file exists and is writable ---
if [ ! -f "$CONFIG_FILE" ] || [ ! -w "$CONFIG_FILE" ]; then
echo "Error: $CONFIG_FILE does not exist or is not writable." >&2
exit 1
fi
# --- Step 1: Ensure 'import os' follows existing imports (idempotent) ---
if ! grep -qE '^import os$' "$CONFIG_FILE"; then
LAST_IMPORT=$(grep -nE '^(import |from )' "$CONFIG_FILE" | tail -n1 | cut -d: -f1)
if [ -n "$LAST_IMPORT" ]; then
sed -i "$((LAST_IMPORT+1))i import os" "$CONFIG_FILE"
else
if head -n1 "$CONFIG_FILE" | grep -q '^#!'; then
sed -i '2i import os' "$CONFIG_FILE"
else
sed -i '1i import os' "$CONFIG_FILE"
fi
fi
echo "Inserted 'import os' after existing imports in $CONFIG_FILE."
else
echo "'import os' already present; no changes made."
fi
# --- Step 2: Insert proxy & no_proxy environment code ---
if ! grep -q 'os.getenv("HTTPS_PROXY"' "$CONFIG_FILE"; then
PROXY_LINE=$(grep -n "self.proxy = None" "$CONFIG_FILE" | cut -d: -f1)
NO_PROXY_LINE=$(grep -n "^[[:space:]]*self\.no_proxy[[:space:]]*=[[:space:]]*None" "$CONFIG_FILE" | cut -d: -f1)
if [ -n "$PROXY_LINE" ]; then
INDENT=$(sed -n "${PROXY_LINE}s/^\( *\).*/\1/p" "$CONFIG_FILE")
BLOCK=""
if [ -z "$NO_PROXY_LINE" ]; then
# self.no_proxy = None is not present → insert full block after self.proxy = None
BLOCK+="${INDENT}# Load proxy from environment variables (if set)\n"
BLOCK+="${INDENT}if os.getenv(\"HTTPS_PROXY\"): self.proxy = os.getenv(\"HTTPS_PROXY\")\n"
BLOCK+="${INDENT}if os.getenv(\"https_proxy\"): self.proxy = os.getenv(\"https_proxy\")\n"
BLOCK+="${INDENT}if os.getenv(\"HTTP_PROXY\"): self.proxy = os.getenv(\"HTTP_PROXY\")\n"
BLOCK+="${INDENT}if os.getenv(\"http_proxy\"): self.proxy = os.getenv(\"http_proxy\")\n"
BLOCK+="${INDENT}self.no_proxy = None\n"
BLOCK+="${INDENT}# Load no_proxy from environment variables (if set)\n"
BLOCK+="${INDENT}if os.getenv(\"NO_PROXY\"): self.no_proxy = os.getenv(\"NO_PROXY\")\n"
BLOCK+="${INDENT}if os.getenv(\"no_proxy\"): self.no_proxy = os.getenv(\"no_proxy\")"
sed -i "${PROXY_LINE}a $BLOCK" "$CONFIG_FILE"
echo "Inserted full proxy + no_proxy block after 'self.proxy = None'."
else
# self.no_proxy = None exists → insert only env logic after that
BLOCK+="${INDENT}# Load proxy from environment variables (if set)\n"
BLOCK+="${INDENT}if os.getenv(\"HTTPS_PROXY\"): self.proxy = os.getenv(\"HTTPS_PROXY\")\n"
BLOCK+="${INDENT}if os.getenv(\"https_proxy\"): self.proxy = os.getenv(\"https_proxy\")\n"
BLOCK+="${INDENT}if os.getenv(\"HTTP_PROXY\"): self.proxy = os.getenv(\"HTTP_PROXY\")\n"
BLOCK+="${INDENT}if os.getenv(\"http_proxy\"): self.proxy = os.getenv(\"http_proxy\")\n"
BLOCK+="${INDENT}# Load no_proxy from environment variables (if set)\n"
BLOCK+="${INDENT}if os.getenv(\"NO_PROXY\"): self.no_proxy = os.getenv(\"NO_PROXY\")\n"
BLOCK+="${INDENT}if os.getenv(\"no_proxy\"): self.no_proxy = os.getenv(\"no_proxy\")"
sed -i "${NO_PROXY_LINE}a $BLOCK" "$CONFIG_FILE"
echo "Inserted environment block after 'self.no_proxy = None'."
fi
else
echo "Warning: 'self.proxy = None' not found in $CONFIG_FILE. No proxy code inserted."
fi
else
echo "Proxy environment code already present; no changes made."
fi

View File

@ -207,7 +207,8 @@ git diff-index --quiet --cached HEAD || git commit -am "update changelog"
# Re-generate the client
scripts/update-client.sh
#edit comfiguration.py files
scripts/insert_proxy_config.sh
# Apply hotfixes
rm -r kubernetes/test/
git add .