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:
parent
230925f338
commit
e3b373fc1f
53
.readthedocs.yaml
Normal file
53
.readthedocs.yaml
Normal 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
34
devel/debug_logging.md
Normal 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)
|
||||
61
examples/enable_debug_logging.py
Normal file
61
examples/enable_debug_logging.py
Normal 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()
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
77
scripts/insert_proxy_config.sh
Normal file
77
scripts/insert_proxy_config.sh
Normal 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
|
||||
@ -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 .
|
||||
|
||||
Loading…
Reference in New Issue
Block a user