diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index d220507cec..0000000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# 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 \ No newline at end of file diff --git a/devel/debug_logging.md b/devel/debug_logging.md deleted file mode 100644 index 966e3d38e4..0000000000 --- a/devel/debug_logging.md +++ /dev/null @@ -1,34 +0,0 @@ -# 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) diff --git a/examples/enable_debug_logging.py b/examples/enable_debug_logging.py deleted file mode 100644 index 573948f3c1..0000000000 --- a/examples/enable_debug_logging.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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() diff --git a/kubernetes/base/stream/ws_client_test.py b/kubernetes/base/stream/ws_client_test.py index a7a11f5c91..575ec1cd44 100644 --- a/kubernetes/base/stream/ws_client_test.py +++ b/kubernetes/base/stream/ws_client_test.py @@ -17,19 +17,70 @@ 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 @@ def test_websocket_proxycare(self): ( '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() diff --git a/kubernetes/client/configuration.py b/kubernetes/client/configuration.py index e1c0ff2dc5..f88fad9371 100644 --- a/kubernetes/client/configuration.py +++ b/kubernetes/client/configuration.py @@ -17,6 +17,7 @@ import multiprocessing import sys import urllib3 +import os import six from six.moves import http_client as httplib @@ -158,9 +159,15 @@ def __init__(self, host="http://localhost", """ 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 diff --git a/scripts/insert_proxy_config.sh b/scripts/insert_proxy_config.sh new file mode 100644 index 0000000000..a4f0b70956 --- /dev/null +++ b/scripts/insert_proxy_config.sh @@ -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 \ No newline at end of file diff --git a/scripts/release.sh b/scripts/release.sh index dce27298d2..b037204c02 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -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 .