diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 65640995e..2a2cd5a1f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index c5bd62571..eb5a56d3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Kubernetes API Version: v1.19.15 - Type checking in `Client.serialize_body()` was made more restrictive and robust. ([kubernetes-client/python-base#241](https://github.com/kubernetes-client/python-base/pull/241), [@piglei](https://github.com/piglei)) ### Feature +- Support Proxy Authentication in websocket client(stream/ws_client) like REST client. ([kubernetes-client/python-base#256](https://github.com/kubernetes-client/python-base/pull/256), [@itaru2622](https://github.com/itaru2622)) - Support for the dryRun parameter has been added to the dynamic client. ([kubernetes-client/python-base#247](https://github.com/kubernetes-client/python-base/pull/247), [@gravesm](https://github.com/gravesm)) ### API Change diff --git a/kubernetes/base b/kubernetes/base index b0afc93ff..09dbbe521 160000 --- a/kubernetes/base +++ b/kubernetes/base @@ -1 +1 @@ -Subproject commit b0afc93ffabb66d930abcdfb1255214d167bf8d5 +Subproject commit 09dbbe521e203634154764b903208bb28cd70f9d diff --git a/kubernetes/client/configuration.py b/kubernetes/client/configuration.py index b99f9f0d4..4dc1a0bfd 100644 --- a/kubernetes/client/configuration.py +++ b/kubernetes/client/configuration.py @@ -156,6 +156,9 @@ class Configuration(object): self.proxy = None """Proxy URL """ + self.no_proxy = None + """bypass proxy for host in the no_proxy list. + """ self.proxy_headers = None """Proxy headers """ diff --git a/kubernetes/client/rest.py b/kubernetes/client/rest.py index 2f3241611..9e1b3859d 100644 --- a/kubernetes/client/rest.py +++ b/kubernetes/client/rest.py @@ -25,6 +25,7 @@ from six.moves.urllib.parse import urlencode import urllib3 from kubernetes.client.exceptions import ApiException, ApiValueError +from requests.utils import should_bypass_proxies logger = logging.getLogger(__name__) @@ -83,7 +84,7 @@ class RESTClientObject(object): maxsize = 4 # https pool manager - if configuration.proxy: + if configuration.proxy and not should_bypass_proxies(configuration.host, no_proxy=configuration.no_proxy or ''): self.pool_manager = urllib3.ProxyManager( num_pools=pools_size, maxsize=maxsize, diff --git a/kubernetes/e2e_test/test_batch.py b/kubernetes/e2e_test/test_batch.py index a8ea590db..d68c80854 100644 --- a/kubernetes/e2e_test/test_batch.py +++ b/kubernetes/e2e_test/test_batch.py @@ -26,7 +26,6 @@ class TestClientBatch(unittest.TestCase): def setUpClass(cls): cls.config = base.get_e2e_configuration() - def test_job_apis(self): client = api_client.ApiClient(configuration=self.config) api = batch_v1_api.BatchV1Api(client) @@ -56,4 +55,4 @@ class TestClientBatch(unittest.TestCase): self.assertEqual(name, resp.metadata.name) resp = api.delete_namespaced_job( - name=name, body={}, namespace='default') \ No newline at end of file + name=name, namespace='default', propagation_policy='Background') diff --git a/kubernetes/e2e_test/test_client.py b/kubernetes/e2e_test/test_client.py index 1034f0ef0..eed3c909e 100644 --- a/kubernetes/e2e_test/test_client.py +++ b/kubernetes/e2e_test/test_client.py @@ -35,6 +35,7 @@ if six.PY3: else: import httplib + def short_uuid(): id = str(uuid.uuid4()) return id[-12:] @@ -60,6 +61,7 @@ def manifest_with_command(name, command): } } + class TestClient(unittest.TestCase): @classmethod @@ -71,7 +73,8 @@ class TestClient(unittest.TestCase): api = core_v1_api.CoreV1Api(client) name = 'busybox-test-' + short_uuid() - pod_manifest = manifest_with_command(name, "while true;do date;sleep 5; done") + pod_manifest = manifest_with_command( + name, "while true;do date;sleep 5; done") # wait for the default service account to be created timeout = time.time() + 30 @@ -84,9 +87,10 @@ class TestClient(unittest.TestCase): namespace='default') except ApiException as e: if (six.PY3 and e.status != HTTPStatus.NOT_FOUND) or ( - six.PY3 is False and e.status != httplib.NOT_FOUND): + six.PY3 is False and e.status != httplib.NOT_FOUND): print('error: %s' % e) - self.fail(msg="unexpected error getting default service account") + self.fail( + msg="unexpected error getting default service account") print('default service not found yet: %s' % e) time.sleep(1) continue @@ -111,25 +115,25 @@ class TestClient(unittest.TestCase): '-c', 'for i in $(seq 1 3); do date; done'] resp = stream(api.connect_get_namespaced_pod_exec, name, 'default', - command=exec_command, - stderr=False, stdin=False, - stdout=True, tty=False) + command=exec_command, + stderr=False, stdin=False, + stdout=True, tty=False) print('EXEC response : %s' % resp) self.assertEqual(3, len(resp.splitlines())) exec_command = 'uptime' resp = stream(api.connect_post_namespaced_pod_exec, name, 'default', - command=exec_command, - stderr=False, stdin=False, - stdout=True, tty=False) + command=exec_command, + stderr=False, stdin=False, + stdout=True, tty=False) print('EXEC response : %s' % resp) self.assertEqual(1, len(resp.splitlines())) resp = stream(api.connect_post_namespaced_pod_exec, name, 'default', - command='/bin/sh', - stderr=True, stdin=True, - stdout=True, tty=False, - _preload_content=False) + command='/bin/sh', + stderr=True, stdin=True, + stdout=True, tty=False, + _preload_content=False) resp.write_stdin("echo test string 1\n") line = resp.readline_stdout(timeout=5) self.assertFalse(resp.peek_stderr()) @@ -157,7 +161,8 @@ class TestClient(unittest.TestCase): api = core_v1_api.CoreV1Api(client) name = 'busybox-test-' + short_uuid() - pod_manifest = manifest_with_command(name, "while true;do date;sleep 5; done") + pod_manifest = manifest_with_command( + name, "while true;do date;sleep 5; done") # wait for the default service account to be created timeout = time.time() + 30 @@ -171,9 +176,10 @@ class TestClient(unittest.TestCase): namespace='default') except ApiException as e: if (six.PY3 and e.status != HTTPStatus.NOT_FOUND) or ( - six.PY3 is False and e.status != httplib.NOT_FOUND): + six.PY3 is False and e.status != httplib.NOT_FOUND): print('error: %s' % e) - self.fail(msg="unexpected error getting default service account") + self.fail( + msg="unexpected error getting default service account") print('default service not found yet: %s' % e) time.sleep(1) continue @@ -201,11 +207,16 @@ class TestClient(unittest.TestCase): (["/bin/sh", "-c", "ls /"], 0) ) for command, value in commands_expected_values: - client = stream(api.connect_get_namespaced_pod_exec, name, 'default', - command=command, - stderr=True, stdin=False, - stdout=True, tty=False, - _preload_content=False) + client = stream( + api.connect_get_namespaced_pod_exec, + name, + 'default', + command=command, + stderr=True, + stdin=False, + stdout=True, + tty=False, + _preload_content=False) self.assertIsNone(client.returncode) client.run_forever(timeout=10) @@ -337,7 +348,8 @@ class TestClient(unittest.TestCase): for sock in (sock1234, sock1235): self.assertTrue(pf.connected) - sent = b'Another test using fileno %s' % str(sock.fileno()).encode() + sent = b'Another test using fileno %s' % str( + sock.fileno()).encode() sock.sendall(sent) reply = b'' while True: @@ -361,7 +373,7 @@ class TestClient(unittest.TestCase): client = api_client.ApiClient(configuration=self.config) api = core_v1_api.CoreV1Api(client) - name = 'portforward-http-' + short_uuid() + name = 'portforward-http-' + short_uuid() pod_manifest = { 'apiVersion': 'v1', 'kind': 'Pod', @@ -404,7 +416,8 @@ class TestClient(unittest.TestCase): socket_create_connection = socket.create_connection try: socket.create_connection = kubernetes_create_connection - response = urllib_request.urlopen('http://%s.default.kubernetes/' % name) + response = urllib_request.urlopen( + 'http://%s.default.kubernetes/' % name) html = response.read().decode('utf-8') finally: socket.create_connection = socket_create_connection @@ -485,7 +498,7 @@ class TestClient(unittest.TestCase): self.assertEqual(2, resp.spec.replicas) resp = api.delete_namespaced_replication_controller( - name=name, body={}, namespace='default') + name=name, namespace='default', propagation_policy='Background') def test_configmap_apis(self): client = api_client.ApiClient(configuration=self.config) @@ -521,7 +534,8 @@ class TestClient(unittest.TestCase): resp = api.delete_namespaced_config_map( name=name, body={}, namespace='default') - resp = api.list_namespaced_config_map('default', pretty=True, label_selector="e2e-tests=true") + resp = api.list_namespaced_config_map( + 'default', pretty=True, label_selector="e2e-tests=true") self.assertEqual([], resp.items) def test_node_apis(self): diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index 05a056c5f..73d1a0c62 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -31,7 +31,9 @@ class TestUtils(unittest.TestCase): cls.test_namespace = "e2e-test-utils" k8s_client = client.api_client.ApiClient(configuration=cls.config) core_v1 = client.CoreV1Api(api_client=k8s_client) - body = client.V1Namespace(metadata=client.V1ObjectMeta(name=cls.test_namespace)) + body = client.V1Namespace( + metadata=client.V1ObjectMeta( + name=cls.test_namespace)) core_v1.create_namespace(body=body) @classmethod @@ -304,7 +306,7 @@ class TestUtils(unittest.TestCase): name="mock", namespace="default") self.assertIsNotNone(ctr) core_api.delete_namespaced_replication_controller( - name="mock", namespace="default", body={}) + name="mock", namespace="default", propagation_policy="Background") core_api.delete_namespaced_service(name="mock", namespace="default", body={}) @@ -362,7 +364,7 @@ class TestUtils(unittest.TestCase): name="mock-2", namespace="default") self.assertIsNotNone(ctr) core_api.delete_namespaced_replication_controller( - name="mock-2", namespace="default", body={}) + name="mock-2", namespace="default", propagation_policy="Background") core_api.delete_namespaced_service(name="mock-2", namespace="default", body={}) @@ -396,7 +398,7 @@ class TestUtils(unittest.TestCase): def test_create_namespaced_apps_deployment_from_yaml(self): """ Should be able to create an apps/v1beta1 deployment - in a test namespace. + in a test namespace. """ k8s_client = client.api_client.ApiClient(configuration=self.config) utils.create_from_yaml( diff --git a/kubernetes/test/test_api_client.py b/kubernetes/test/test_api_client.py index f0a9416cf..486b4ac5b 100644 --- a/kubernetes/test/test_api_client.py +++ b/kubernetes/test/test_api_client.py @@ -6,7 +6,8 @@ import weakref import unittest import kubernetes - +from kubernetes.client.configuration import Configuration +import urllib3 class TestApiClient(unittest.TestCase): @@ -23,3 +24,28 @@ class TestApiClient(unittest.TestCase): self.assertIsNotNone(client._pool) atexit._run_exitfuncs() self.assertIsNone(client._pool) + + def test_rest_proxycare(self): + + pool = { 'proxy': urllib3.ProxyManager, 'direct': urllib3.PoolManager } + + for dst, proxy, no_proxy, expected_pool in [ + ( 'http://kube.local/', None, None, pool['direct']), + ( 'http://kube.local/', 'http://proxy.local:8080/', None, pool['proxy']), + ( 'http://127.0.0.1:8080/', 'http://proxy.local:8080/', 'localhost,127.0.0.0/8,.local', pool['direct']), + ( 'http://kube.local/', 'http://proxy.local:8080/', 'localhost,127.0.0.0/8,.local', pool['direct']), + ( 'http://kube.others.com:1234/','http://proxy.local:8080/', 'localhost,127.0.0.0/8,.local', pool['proxy']), + ( 'http://kube.others.com:1234/','http://proxy.local:8080/', '*', pool['direct']), + ]: + # setup input + config = Configuration() + setattr(config, 'host', dst) + if proxy is not None: + setattr(config, 'proxy', proxy) + if no_proxy is not None: + setattr(config, 'no_proxy', no_proxy) + # setup done + + # test + client = kubernetes.client.ApiClient(configuration=config) + self.assertEqual( expected_pool, type(client.rest_client.pool_manager) ) diff --git a/scripts/release.sh b/scripts/release.sh index e034e64ec..07c4018cb 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -56,6 +56,9 @@ # - add a sentence about "changes since {last release}". In most cases our # releases should be sequential. This script (the workflow above) is based on # this assumption, and we should make the release note clear about that. +# - update readme; if it's a real release (instead of a snapshot in master +# branch), also create a PR to update changelog and readme in the master +# branch # # Usage: # $ KUBERNETES_BRANCH=release-1.19 CLIENT_VERSION=19.0.0-snapshot DEVELOPMENT_STATUS="3 - Alpha" scripts/release.sh @@ -64,6 +67,9 @@ set -o errexit set -o nounset set -o pipefail +# used by the client generator: https://github.com/kubernetes-client/gen/blob/729332ad08f0f4d98983b7beb027e2f657236ef9/openapi/openapi-generator/client-generator.sh#L52 +export USERNAME=kubernetes + repo_root="$(git rev-parse --show-toplevel)" declare -r repo_root cd "${repo_root}" diff --git a/scripts/update-submodule.sh b/scripts/update-submodule.sh index 2ef51eebb..45431b37e 100755 --- a/scripts/update-submodule.sh +++ b/scripts/update-submodule.sh @@ -40,7 +40,7 @@ source scripts/util/changelog.sh source scripts/util/common.sh util::common::check_sed -go get k8s.io/release/cmd/release-notes +go install k8s.io/release/cmd/release-notes@latest TARGET_RELEASE=${TARGET_RELEASE:-"v$(grep "^CLIENT_VERSION = \"" scripts/constants.py | sed "s/CLIENT_VERSION = \"//g" | sed "s/\"//g")"} diff --git a/setup.py b/setup.py index 45cf5498e..f8e72906f 100644 --- a/setup.py +++ b/setup.py @@ -78,5 +78,6 @@ setup( "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ], )