From ce188d5f0e0bb5d8aba83eb52c732954b14d0351 Mon Sep 17 00:00:00 2001 From: Tomasz Prus Date: Mon, 21 Jan 2019 01:17:05 +0100 Subject: [PATCH 01/53] feat: remove dependency on mvn from updating script --- scripts/update-client.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/update-client.sh b/scripts/update-client.sh index 118d95b22..506038429 100755 --- a/scripts/update-client.sh +++ b/scripts/update-client.sh @@ -21,11 +21,6 @@ set -o errexit set -o nounset set -o pipefail -if ! which mvn > /dev/null 2>&1; then - echo "Maven is not installed." - exit -fi - SCRIPT_ROOT=$(dirname "${BASH_SOURCE}") CLIENT_ROOT="${SCRIPT_ROOT}/../kubernetes" CLIENT_VERSION=$(python "${SCRIPT_ROOT}/constants.py" CLIENT_VERSION) From 9d12fe40f221cf89878201b8e6493415258e8a90 Mon Sep 17 00:00:00 2001 From: haiker2011 Date: Tue, 19 Mar 2019 15:44:35 +0800 Subject: [PATCH 02/53] Add Job examples --- examples/job_examples.py | 97 ++++++++++++++++++++++++++++++++++++++++ examples/pi-job.yaml | 15 +++++++ 2 files changed, 112 insertions(+) create mode 100644 examples/job_examples.py create mode 100644 examples/pi-job.yaml diff --git a/examples/job_examples.py b/examples/job_examples.py new file mode 100644 index 000000000..441f0bb0e --- /dev/null +++ b/examples/job_examples.py @@ -0,0 +1,97 @@ +# Copyright 2016 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from os import path + +import yaml + +from kubernetes import client, config + +JOB_NAME = "pi" + + +def create_job_object(): + # Configureate Pod template container + container = client.V1Container( + name="pi", + image="perl", + command=["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"], + ) + # Create and configurate a spec section + template = client.V1PodTemplateSpec( + metadata=client.V1ObjectMeta(labels={"app": "pi"}), + spec=client.V1PodSpec(restart_policy="Never", containers=[container])) + # Create the specification of deployment + spec = client.V1JobSpec( + template=template, + backoff_limit=4) + # Instantiate the job object + job = client.V1Job( + api_version="batch/v1", + kind="Job", + metadata=client.V1ObjectMeta(name=JOB_NAME), + spec=spec) + + return job + + +def create_job(api_instance, job): + # Create job + api_response = api_instance.create_namespaced_job( + body=job, + namespace="default") + print("Job created. status='%s'" % str(api_response.status)) + + +def update_job(api_instance, job): + # Update container image + job.spec.template.spec.containers[0].image = "perl" + # Update the job + api_response = api_instance.patch_namespaced_job( + name=JOB_NAME, + namespace="default", + body=job) + print("Job updated. status='%s'" % str(api_response.status)) + + +def delete_job(api_instance): + # Delete job + api_response = api_instance.delete_namespaced_job( + name=JOB_NAME, + namespace="default", + body=client.V1DeleteOptions( + propagation_policy='Foreground', + grace_period_seconds=5)) + print("Job deleted. status='%s'" % str(api_response.status)) + + +def main(): + # Configs can be set in Configuration class directly or using helper + # utility. If no argument provided, the config will be loaded from + # default location. + config.load_kube_config() + batch_v1 = client.BatchV1Api() + # Create a job object with client-python API. The job we + # created is same as the `pi-job.yaml` in the /examples folder. + job = create_job_object() + + create_job(batch_v1, job) + + update_job(batch_v1, job) + + delete_job(batch_v1) + + +if __name__ == '__main__': + main() diff --git a/examples/pi-job.yaml b/examples/pi-job.yaml new file mode 100644 index 000000000..dc736f2da --- /dev/null +++ b/examples/pi-job.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: pi +spec: + template: + spec: + containers: + - name: pi + image: perl + command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] + restartPolicy: Never + backoffLimit: 4 + + From 630b0ac15d46c781e4b0c30c7c5f0cf39bd99733 Mon Sep 17 00:00:00 2001 From: haiker2011 Date: Tue, 19 Mar 2019 16:03:37 +0800 Subject: [PATCH 03/53] fix code style --- examples/job_examples.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/job_examples.py b/examples/job_examples.py index 441f0bb0e..39a71b892 100644 --- a/examples/job_examples.py +++ b/examples/job_examples.py @@ -26,8 +26,7 @@ def create_job_object(): container = client.V1Container( name="pi", image="perl", - command=["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"], - ) + command=["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]) # Create and configurate a spec section template = client.V1PodTemplateSpec( metadata=client.V1ObjectMeta(labels={"app": "pi"}), From aa28bc706727f0cb855f02e1112cf5a4b50b32af Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Tue, 26 Mar 2019 19:11:57 +0100 Subject: [PATCH 04/53] Allow create from string or from dict This is a fix for #722 --- kubernetes/utils/__init__.py | 3 +- kubernetes/utils/create_from_yaml.py | 71 ++++++++++++++++------------ 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/kubernetes/utils/__init__.py b/kubernetes/utils/__init__.py index 2b8597c2f..1f84f3898 100644 --- a/kubernetes/utils/__init__.py +++ b/kubernetes/utils/__init__.py @@ -14,4 +14,5 @@ from __future__ import absolute_import -from .create_from_yaml import FailToCreateError, create_from_yaml +from .create_from_yaml import (FailToCreateError, create_from_yaml, + create_from_map) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 29be813b1..32ae24160 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -11,9 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - +import io import re + from os import path import yaml @@ -30,7 +30,7 @@ def create_from_yaml( Perform an action from a yaml file. Pass True for verbose to print confirmation information. Input: - yaml_file: string. Contains the path to yaml file. + yaml_file: string. Contains yaml string or a path to yaml file. k8s_client: an ApiClient object, initialized with the client args. verbose: If True, print confirmation from the create action. Default is False. @@ -54,35 +54,44 @@ def create_from_yaml( processing of the request. Valid values are: - All: all dry run stages will be processed """ + if path.exists(yaml_file): + with open(path.abspath(yaml_file)) as f: + yaml_file = io.StringIO(f.read()) + + yml_document_all = yaml.safe_load_all(yaml_file) + # Load all documents from a single YAML file + for yml_document in yml_document_all: + create_from_map(k8s_client, yml_document, verbose, + **kwargs) + + +def create_from_map(k8s_client, yml_document, verbose=False, **kwargs): + # If it is a list type, will need to iterate its items + api_exceptions = [] + + if "List" in yml_document["kind"]: + # Could be "List" or "Pod/Service/...List" + # This is a list type. iterate within its items + kind = yml_document["kind"].replace("List", "") + for yml_object in yml_document["items"]: + # Mitigate cases when server returns a xxxList object + # See kubernetes-client/python#586 + if kind is not "": + yml_object["apiVersion"] = yml_document["apiVersion"] + yml_object["kind"] = kind + try: + create_from_yaml_single_item( + k8s_client, yml_object, verbose, **kwargs) + except client.rest.ApiException as api_exception: + api_exceptions.append(api_exception) + else: + # This is a single object. Call the single item method + try: + create_from_yaml_single_item( + k8s_client, yml_document, verbose, **kwargs) + except client.rest.ApiException as api_exception: + api_exceptions.append(api_exception) - with open(path.abspath(yaml_file)) as f: - yml_document_all = yaml.safe_load_all(f) - api_exceptions = [] - # Load all documents from a single YAML file - for yml_document in yml_document_all: - # If it is a list type, will need to iterate its items - if "List" in yml_document["kind"]: - # Could be "List" or "Pod/Service/...List" - # This is a list type. iterate within its items - kind = yml_document["kind"].replace("List", "") - for yml_object in yml_document["items"]: - # Mitigate cases when server returns a xxxList object - # See kubernetes-client/python#586 - if kind is not "": - yml_object["apiVersion"] = yml_document["apiVersion"] - yml_object["kind"] = kind - try: - create_from_yaml_single_item( - k8s_client, yml_object, verbose, **kwargs) - except client.rest.ApiException as api_exception: - api_exceptions.append(api_exception) - else: - # This is a single object. Call the single item method - try: - create_from_yaml_single_item( - k8s_client, yml_document, verbose, **kwargs) - except client.rest.ApiException as api_exception: - api_exceptions.append(api_exception) # In case we have exceptions waiting for us, raise them if api_exceptions: raise FailToCreateError(api_exceptions) From cee4e49edad0f7c3a8081331c5f6a0e6afee731b Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Tue, 26 Mar 2019 23:30:25 +0100 Subject: [PATCH 05/53] Rename create_from_map to create_from_dict, add tests --- kubernetes/e2e_test/test_utils.py | 34 ++++++++++++++++++++++++++++ kubernetes/utils/create_from_yaml.py | 6 ++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index 6c40361ff..bf598dca7 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -14,6 +14,7 @@ import unittest +import yaml from kubernetes import utils, client from kubernetes.e2e_test import base @@ -42,6 +43,39 @@ class TestUtils(unittest.TestCase): name="nginx-app", namespace="default", body={}) + def test_create_apps_deployment_from_yaml_string(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + with open(self.path_prefix + "apps-deployment.yaml") as f: + yaml_str = f.read() + + utils.create_from_yaml( + k8s_client, yaml_str) + + app_api = client.AppsV1beta1Api(k8s_client) + dep = app_api.read_namespaced_deployment(name="nginx-app", + namespace="default") + self.assertIsNotNone(dep) + app_api.delete_namespaced_deployment( + name="nginx-app", namespace="default", + body={}) + + def test_create_apps_deployment_from_yaml_obj(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + with open(self.path_prefix + "apps-deployment.yaml") as f: + yml_obj = yaml.safe_load(f) + + utils.create_from_dict( + k8s_client, yml_obj) + + app_api = client.AppsV1beta1Api(k8s_client) + dep = app_api.read_namespaced_deployment(name="nginx-app", + namespace="default") + self.assertIsNotNone(dep) + app_api.delete_namespaced_deployment( + name="nginx-app", namespace="default", + body={}) + + def test_create_extensions_deployment_from_yaml(self): """ Should be able to create an extensions/v1beta1 deployment. diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 32ae24160..be5de7862 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -30,7 +30,7 @@ def create_from_yaml( Perform an action from a yaml file. Pass True for verbose to print confirmation information. Input: - yaml_file: string. Contains yaml string or a path to yaml file. + yaml_file: string. Contains yaml string or a path to yaml file. k8s_client: an ApiClient object, initialized with the client args. verbose: If True, print confirmation from the create action. Default is False. @@ -61,11 +61,11 @@ def create_from_yaml( yml_document_all = yaml.safe_load_all(yaml_file) # Load all documents from a single YAML file for yml_document in yml_document_all: - create_from_map(k8s_client, yml_document, verbose, + create_from_dict(k8s_client, yml_document, verbose, **kwargs) -def create_from_map(k8s_client, yml_document, verbose=False, **kwargs): +def create_from_dict(k8s_client, yml_document, verbose=False, **kwargs): # If it is a list type, will need to iterate its items api_exceptions = [] From 18f45c9ceca522d1202cb2b5b7c98f32321fe516 Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Fri, 5 Apr 2019 06:30:43 +0200 Subject: [PATCH 06/53] Fix wrong import name --- kubernetes/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/utils/__init__.py b/kubernetes/utils/__init__.py index 1f84f3898..91fb58633 100644 --- a/kubernetes/utils/__init__.py +++ b/kubernetes/utils/__init__.py @@ -15,4 +15,4 @@ from __future__ import absolute_import from .create_from_yaml import (FailToCreateError, create_from_yaml, - create_from_map) + create_from_dict) From 0779fc9c66016d3544aab5e35457f86b25fcd961 Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Fri, 5 Apr 2019 08:20:57 +0200 Subject: [PATCH 07/53] Fix collection of execptions when applying yaml This fixes of all tests and keeps the original API. It's the users responsiblility to do something the execptions when using `create_from_dict`, no air bag or breaks are supplied here. --- kubernetes/e2e_test/test_utils.py | 1 - kubernetes/utils/create_from_yaml.py | 13 ++++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index dc2be1097..df387a28a 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -75,7 +75,6 @@ class TestUtils(unittest.TestCase): name="nginx-app", namespace="default", body={}) - def test_create_extensions_deployment_from_yaml(self): """ Should be able to create an extensions/v1beta1 deployment. diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 8b3c19ba1..cf157da58 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -60,9 +60,16 @@ def create_from_yaml( yml_document_all = yaml.safe_load_all(yaml_file) # Load all documents from a single YAML file + fail_exceptions = [] + for yml_document in yml_document_all: - create_from_dict(k8s_client, yml_document, verbose, - **kwargs) + exceptions = create_from_dict(k8s_client, yml_document, verbose, + **kwargs) + if exceptions: + fail_exceptions.extend(exceptions) + + if fail_exceptions: + raise FailToCreateError(fail_exceptions) def create_from_dict(k8s_client, yml_document, verbose=False, **kwargs): @@ -94,7 +101,7 @@ def create_from_dict(k8s_client, yml_document, verbose=False, **kwargs): # In case we have exceptions waiting for us, raise them if api_exceptions: - raise FailToCreateError(api_exceptions) + return api_exceptions def create_from_yaml_single_item( From e8fd4b9af9e788fca59119b19d64f46c7b05c34f Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Fri, 5 Apr 2019 20:57:53 +0200 Subject: [PATCH 08/53] Drop Python3.4 Python3.4 become EOL on 2019.03.18. https://www.python.org/dev/peps/pep-0429/ --- .travis.yml | 2 -- tox.ini | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index fce474b42..a58d7bad0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,8 +17,6 @@ matrix: env: TOXENV=docs - python: 2.7 env: TOXENV=coverage,codecov - - python: 3.4 - env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 - python: 3.5 diff --git a/tox.ini b/tox.ini index a271d7854..a21190bf8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py34, py35, py36, py37 +envlist = py27, py35, py36, py37 [testenv] passenv = TOXENV CI TRAVIS TRAVIS_* From 2b83c683f8523a527b1815f0896d47c599aa9b09 Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Mon, 8 Apr 2019 12:35:41 +0200 Subject: [PATCH 09/53] Fix for a flaky test failing because k8s is slow We create a deployment and do the following: ``` self.assertIsNotNone(dep) ``` Which does not fail, and then the code proceeds to deletion and fails with a 404 execption in 80% of the time, but sometimes it works. The deployment is there, but for some reason not available for deletion. Travis CI also showed inconsitent behaviour on this. Python3.5 passed but all other version failed. With this commit we wait for the deployment to become available for deletion and only then continue. --- kubernetes/e2e_test/test_utils.py | 34 +++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index df387a28a..04eb1f486 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -16,6 +16,7 @@ import unittest import yaml from kubernetes import utils, client +from kubernetes.client.rest import ApiException from kubernetes.e2e_test import base @@ -39,9 +40,14 @@ class TestUtils(unittest.TestCase): dep = app_api.read_namespaced_deployment(name="nginx-app", namespace="default") self.assertIsNotNone(dep) - app_api.delete_namespaced_deployment( - name="nginx-app", namespace="default", - body={}) + while True: + try: + app_api.delete_namespaced_deployment( + name="nginx-app", namespace="default", + body={}) + break + except ApiException: + continue def test_create_apps_deployment_from_yaml_string(self): k8s_client = client.api_client.ApiClient(configuration=self.config) @@ -55,9 +61,14 @@ class TestUtils(unittest.TestCase): dep = app_api.read_namespaced_deployment(name="nginx-app", namespace="default") self.assertIsNotNone(dep) - app_api.delete_namespaced_deployment( - name="nginx-app", namespace="default", - body={}) + while True: + try: + app_api.delete_namespaced_deployment( + name="nginx-app", namespace="default", + body={}) + break + except ApiException: + continue def test_create_apps_deployment_from_yaml_obj(self): k8s_client = client.api_client.ApiClient(configuration=self.config) @@ -71,9 +82,14 @@ class TestUtils(unittest.TestCase): dep = app_api.read_namespaced_deployment(name="nginx-app", namespace="default") self.assertIsNotNone(dep) - app_api.delete_namespaced_deployment( - name="nginx-app", namespace="default", - body={}) + while True: + try: + app_api.delete_namespaced_deployment( + name="nginx-app", namespace="default", + body={}) + break + except ApiException: + continue def test_create_extensions_deployment_from_yaml(self): """ From 8d41478ed87145af480ccd0cc5863d3ed8ce2ca8 Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Mon, 8 Apr 2019 13:54:26 +0200 Subject: [PATCH 10/53] Explicitly use other deployment names for test Using `nginx-app` deployment multiple times, is problematic because we get conflicts or not found error. Instead of trying to handle all cases, explicit different names are used now. The tests now runs more reliably --- kubernetes/e2e_test/test_utils.py | 24 ++++++++----------- .../e2e_test/test_yaml/apps-deployment-2.yaml | 21 ++++++++++++++++ 2 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 kubernetes/e2e_test/test_yaml/apps-deployment-2.yaml diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index 04eb1f486..58bb57bb5 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -51,20 +51,20 @@ class TestUtils(unittest.TestCase): def test_create_apps_deployment_from_yaml_string(self): k8s_client = client.api_client.ApiClient(configuration=self.config) - with open(self.path_prefix + "apps-deployment.yaml") as f: + with open(self.path_prefix + "apps-deployment-2.yaml") as f: yaml_str = f.read() utils.create_from_yaml( k8s_client, yaml_str) app_api = client.AppsV1beta1Api(k8s_client) - dep = app_api.read_namespaced_deployment(name="nginx-app", + dep = app_api.read_namespaced_deployment(name="nginx-app-2", namespace="default") self.assertIsNotNone(dep) while True: try: app_api.delete_namespaced_deployment( - name="nginx-app", namespace="default", + name="nginx-app-2", namespace="default", body={}) break except ApiException: @@ -75,21 +75,17 @@ class TestUtils(unittest.TestCase): with open(self.path_prefix + "apps-deployment.yaml") as f: yml_obj = yaml.safe_load(f) - utils.create_from_dict( - k8s_client, yml_obj) + yml_obj["metadata"]["name"] = "nginx-app-3" + + utils.create_from_dict(k8s_client, yml_obj) app_api = client.AppsV1beta1Api(k8s_client) - dep = app_api.read_namespaced_deployment(name="nginx-app", + dep = app_api.read_namespaced_deployment(name="nginx-app-3", namespace="default") self.assertIsNotNone(dep) - while True: - try: - app_api.delete_namespaced_deployment( - name="nginx-app", namespace="default", - body={}) - break - except ApiException: - continue + app_api.delete_namespaced_deployment( + name="nginx-app-3", namespace="default", + body={}) def test_create_extensions_deployment_from_yaml(self): """ diff --git a/kubernetes/e2e_test/test_yaml/apps-deployment-2.yaml b/kubernetes/e2e_test/test_yaml/apps-deployment-2.yaml new file mode 100644 index 000000000..b2acb92f8 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/apps-deployment-2.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: nginx-app-2 + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.4 + ports: + - containerPort: 80 From 408d405704d8826dd164d828670fa37bf6ca8027 Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Tue, 9 Apr 2019 22:51:54 +0200 Subject: [PATCH 11/53] Handle StringIO for Python2 properly --- kubernetes/utils/create_from_yaml.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index cf157da58..9cfec1ab6 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -11,8 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import io import re +import sys + +if sys.version_info.major > 2: + from StringIO import StringIO +else: + from io import StringIO from os import path @@ -56,7 +61,11 @@ def create_from_yaml( """ if path.exists(yaml_file): with open(path.abspath(yaml_file)) as f: - yaml_file = io.StringIO(f.read()) + content = f.read() + try: + yaml_file = StringIO(content) + except TypeError: + yaml_file = StringIO(content.decode('utf-8')) yml_document_all = yaml.safe_load_all(yaml_file) # Load all documents from a single YAML file From 38894cc3970cf5578abd8a94a384a7602a965438 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Tue, 9 Apr 2019 23:03:02 +0200 Subject: [PATCH 12/53] Fix import for StringIO This is embarrassing. --- kubernetes/utils/create_from_yaml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 9cfec1ab6..46ee53c3c 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -14,7 +14,7 @@ import re import sys -if sys.version_info.major > 2: +if sys.version_info.major < 3: from StringIO import StringIO else: from io import StringIO From 139848efcb7a78e8577ac6c9a5b49abe97340e21 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Tue, 9 Apr 2019 23:24:16 +0200 Subject: [PATCH 13/53] Relax pycodestyle: import only allowed at the top While this is a really good convention, sometimes one must import stuff inside a try except block. This block is still at the top, but pycodestyle treats like it isn't, because it's in an idented block, and the outeer scope. --- kubernetes/utils/__init__.py | 4 ++-- kubernetes/utils/create_from_yaml.py | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/kubernetes/utils/__init__.py b/kubernetes/utils/__init__.py index 91fb58633..72f55c751 100644 --- a/kubernetes/utils/__init__.py +++ b/kubernetes/utils/__init__.py @@ -14,5 +14,5 @@ from __future__ import absolute_import -from .create_from_yaml import (FailToCreateError, create_from_yaml, - create_from_dict) +from .create_from_yaml import (FailToCreateError, create_from_dict, + create_from_yaml) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 46ee53c3c..aecc3672a 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -13,18 +13,17 @@ # limitations under the License. import re import sys - -if sys.version_info.major < 3: - from StringIO import StringIO -else: - from io import StringIO - from os import path import yaml from kubernetes import client +if sys.version_info.major < 3: + from StringIO import StringIO # noqa: F406 +else: + from io import StringIO # noqa: F406 + def create_from_yaml( k8s_client, From 7ac1070077984b428ce47050b1aabf8b43bfdf3a Mon Sep 17 00:00:00 2001 From: Quan Tian Date: Fri, 3 May 2019 10:26:37 -0700 Subject: [PATCH 14/53] Fix AttributeError in create_from_yaml Some models don't have attribute 'status', like V1ConfigMap, V1ClusterRole, and V1NetworkPolicy. AttributeError would be raised if these resources are created via create_from_yaml with verbose enabled. This patch checks if attribute 'status' exists before accessing it. --- kubernetes/e2e_test/test_utils.py | 14 ++++++++++++++ kubernetes/utils/create_from_yaml.py | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index 8353e5f32..62913b500 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -113,6 +113,20 @@ class TestUtils(unittest.TestCase): rbac_api.delete_namespaced_role( name="pod-reader", namespace="default", body={}) + def test_create_rbac_role_from_yaml_with_verbose_enabled(self): + """ + Should be able to create an rbac role with verbose enabled. + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + utils.create_from_yaml( + k8s_client, self.path_prefix + "rbac-role.yaml", verbose=True) + rbac_api = client.RbacAuthorizationV1Api(k8s_client) + rbac_role = rbac_api.read_namespaced_role( + name="pod-reader", namespace="default") + self.assertIsNotNone(rbac_role) + rbac_api.delete_namespaced_role( + name="pod-reader", namespace="default", body={}) + def test_create_deployment_non_default_namespace_from_yaml(self): """ Should be able to create a namespace "dep", diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 08efd4306..5da491c3f 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -120,7 +120,10 @@ def create_from_yaml_single_item( resp = getattr(k8s_api, "create_{0}".format(kind))( body=yml_object, **kwargs) if verbose: - print("{0} created. status='{1}'".format(kind, str(resp.status))) + msg = "{0} created.".format(kind) + if hasattr(resp, 'status'): + msg += " status='{0}'".format(str(resp.status)) + print(msg) class FailToCreateError(Exception): From 4e535772a40be6a2c07977c030f62686cf5895fa Mon Sep 17 00:00:00 2001 From: hajowieland Date: Sat, 1 Jun 2019 13:44:12 +0200 Subject: [PATCH 15/53] Correct alignment --- examples/example3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example3.py b/examples/example3.py index 25c0d9a80..2ee3685b5 100644 --- a/examples/example3.py +++ b/examples/example3.py @@ -22,7 +22,7 @@ def main(): config.load_kube_config() print("Supported APIs (* is preferred version):") - print("%-20s %s" % + print("%-40s %s" % ("core", ",".join(client.CoreApi().get_api_versions().versions))) for api in client.ApisApi().get_api_versions().groups: versions = [] From c3f36bd54a554153aac1c6cea73f8c955d36cdfc Mon Sep 17 00:00:00 2001 From: hajowieland Date: Sat, 1 Jun 2019 14:05:01 +0200 Subject: [PATCH 16/53] Configuration class to show active host --- examples/example4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example4.py b/examples/example4.py index 5c0549585..334e282c2 100644 --- a/examples/example4.py +++ b/examples/example4.py @@ -32,7 +32,7 @@ def main(): # utility config.load_kube_config(context=option) - print("Active host is %s" % configuration.host) + print("Active host is %s" % configuration.Configuration().host) v1 = client.CoreV1Api() print("Listing pods with their IPs:") From f6566ee5e6ca5d743fd81f8d216079385a3dbca4 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Thu, 20 Jun 2019 13:27:33 +0200 Subject: [PATCH 17/53] Rename yml_document paramter and update documentation Renaming `yml_document` in `create_from_dict` to data. This is a bit clearer that this it a data item and not a string (usually document read from the file system). Also update the documentation to describe better what the functions `create_from_dict` and `create_from_yaml` do. --- kubernetes/utils/create_from_yaml.py | 32 ++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index aecc3672a..97e39a33b 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -39,11 +39,6 @@ def create_from_yaml( verbose: If True, print confirmation from the create action. Default is False. - Returns: - An k8s api object or list of apis objects created from YAML. - When a single object is generated, return type is dependent - on output_list. - Throws a FailToCreateError exception if creation of any object fails with helpful messages from the server. @@ -80,19 +75,34 @@ def create_from_yaml( raise FailToCreateError(fail_exceptions) -def create_from_dict(k8s_client, yml_document, verbose=False, **kwargs): +def create_from_dict(k8s_client, data, verbose=False, **kwargs): + """ + Perform an action from a dictionary containing one or more valid kubernetes + objects + + Input: + k8s_client: an ApiClient object, initialized with the client args. + data: a dictionary holding valid kubernetes objects + verbose: If True, print confirmation from the create action. + Default is False. + + Returns: + A list of `client.rest.ApiException` instances for each object that + failed to create. The user of this function can throw discard them. + + """ # If it is a list type, will need to iterate its items api_exceptions = [] - if "List" in yml_document["kind"]: + if "List" in data["kind"]: # Could be "List" or "Pod/Service/...List" # This is a list type. iterate within its items - kind = yml_document["kind"].replace("List", "") - for yml_object in yml_document["items"]: + kind = data["kind"].replace("List", "") + for yml_object in data["items"]: # Mitigate cases when server returns a xxxList object # See kubernetes-client/python#586 if kind is not "": - yml_object["apiVersion"] = yml_document["apiVersion"] + yml_object["apiVersion"] = data["apiVersion"] yml_object["kind"] = kind try: create_from_yaml_single_item( @@ -103,7 +113,7 @@ def create_from_dict(k8s_client, yml_document, verbose=False, **kwargs): # This is a single object. Call the single item method try: create_from_yaml_single_item( - k8s_client, yml_document, verbose, **kwargs) + k8s_client, data, verbose, **kwargs) except client.rest.ApiException as api_exception: api_exceptions.append(api_exception) From 4fa0e87bd71785cce49623d51fcbb81805e3fe4c Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Thu, 20 Jun 2019 14:30:27 +0200 Subject: [PATCH 18/53] Correct the documentation for create_from_dict The function can create one or more Kuberenetes objects based on the content of data. It can handle all API objects incuding `List` type, which by itself can contain more than one Kuberenetes API objects. --- kubernetes/utils/create_from_yaml.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 1fe806f7c..ff8a6278a 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -90,8 +90,8 @@ def create_from_yaml( def create_from_dict(k8s_client, data, verbose=False, **kwargs): """ - Perform an action from a dictionary containing one or more valid kubernetes - objects + Perform an action from a dictionary containing valid kubernetes + API object (i.e. List, Service, etc). Input: k8s_client: an ApiClient object, initialized with the client args. From 5c912f986f7066a87c45315f196c4eed239061d3 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Thu, 20 Jun 2019 18:37:58 +0200 Subject: [PATCH 19/53] Throw exception from create_from_dict This is instead of doing this in create_from_yaml --- kubernetes/utils/create_from_yaml.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index ff8a6278a..20eda3ff6 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -75,17 +75,10 @@ def create_from_yaml( yaml_file = StringIO(content.decode('utf-8')) yml_document_all = yaml.safe_load_all(yaml_file) - # Load all documents from a single YAML file - fail_exceptions = [] for yml_document in yml_document_all: - exceptions = create_from_dict(k8s_client, yml_document, verbose, - **kwargs) - if exceptions: - fail_exceptions.extend(exceptions) - - if fail_exceptions: - raise FailToCreateError(fail_exceptions) + create_from_dict(k8s_client, yml_document, verbose, + **kwargs) def create_from_dict(k8s_client, data, verbose=False, **kwargs): @@ -99,10 +92,9 @@ def create_from_dict(k8s_client, data, verbose=False, **kwargs): verbose: If True, print confirmation from the create action. Default is False. - Returns: - A list of `client.rest.ApiException` instances for each object that - failed to create. The user of this function can throw discard them. - + Raises: + FailToCreateError which holds list of `client.rest.ApiException` + instances for each object that failed to create. """ # If it is a list type, will need to iterate its items api_exceptions = [] @@ -132,7 +124,7 @@ def create_from_dict(k8s_client, data, verbose=False, **kwargs): # In case we have exceptions waiting for us, raise them if api_exceptions: - return api_exceptions + raise FailToCreateError(api_exceptions) def create_from_yaml_single_item( From ed67d89f9b19545c7e88809ab6ced25af63514d4 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Thu, 20 Jun 2019 19:53:28 +0200 Subject: [PATCH 20/53] Remove un-necessary if block --- kubernetes/utils/create_from_yaml.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 20eda3ff6..b5101174c 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -66,13 +66,12 @@ def create_from_yaml( processing of the request. Valid values are: - All: all dry run stages will be processed """ - if path.exists(yaml_file): - with open(path.abspath(yaml_file)) as f: - content = f.read() - try: - yaml_file = StringIO(content) - except TypeError: - yaml_file = StringIO(content.decode('utf-8')) + with open(path.abspath(yaml_file)) as f: + content = f.read() + try: + yaml_file = StringIO(content) + except TypeError: + yaml_file = StringIO(content.decode('utf-8')) yml_document_all = yaml.safe_load_all(yaml_file) From 9e40421bcc685d8d65b900dfeba0fec26c3cfea8 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Thu, 20 Jun 2019 22:48:35 +0200 Subject: [PATCH 21/53] create_from_yaml function deals with files only Adding the ability to deal with strings containing yaml seems to repel to much. So we stay with create_from_yaml with a bad name. This removes the need fro StringIO to wrap strings. Also note: ``` with open('foo.txt') as f: y = yaml.safe_load_all(f) for i in y: print(i) \# raises ValueError: I/O operation on closed file. ``` Hence, we indent the whole method body into the open block. with open('foo.txt') as f: y = yaml.safe_load_all(f) for i in y: print(i) --- kubernetes/utils/create_from_yaml.py | 37 +++++++++++----------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index b5101174c..435ebdd38 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -14,18 +14,12 @@ import re -import sys from os import path import yaml from kubernetes import client -if sys.version_info.major < 3: - from StringIO import StringIO # noqa: F406 -else: - from io import StringIO # noqa: F406 - def create_from_yaml( k8s_client, @@ -47,14 +41,6 @@ def create_from_yaml( the yaml file already contains a namespace definition this parameter has no effect. - Returns: - An k8s api object or list of apis objects created from YAML. - When a single object is generated, return type is dependent - on output_list. - - Throws a FailToCreateError exception if creation of any object - fails with helpful messages from the server. - Available parameters for creating : :param async_req bool :param bool include_uninitialized: If true, partially initialized @@ -65,19 +51,24 @@ def create_from_yaml( directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed + + Raises: + FailToCreateError which holds list of `client.rest.ApiException` + instances for each object that failed to create. """ with open(path.abspath(yaml_file)) as f: - content = f.read() - try: - yaml_file = StringIO(content) - except TypeError: - yaml_file = StringIO(content.decode('utf-8')) + yml_document_all = yaml.safe_load_all(f) - yml_document_all = yaml.safe_load_all(yaml_file) + failures = [] - for yml_document in yml_document_all: - create_from_dict(k8s_client, yml_document, verbose, - **kwargs) + for yml_document in yml_document_all: + try: + create_from_dict(k8s_client, yml_document, verbose, + **kwargs) + except FailToCreateError as failure: + failures.extend(failure.api_exceptions) + if failures: + raise FailToCreateError(failures) def create_from_dict(k8s_client, data, verbose=False, **kwargs): From 2af7c631bd5ba67070537dbbe120d7e98eed17dc Mon Sep 17 00:00:00 2001 From: Scott Lee Date: Tue, 16 Jul 2019 17:48:13 -0700 Subject: [PATCH 22/53] #877: Remove reference to incubator --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cb54c39ab..732941f09 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ From [PyPi](https://pypi.python.org/pypi/kubernetes/) directly: pip install kubernetes ``` -## Example +## Examples list all pods: @@ -77,7 +77,7 @@ All APIs and Models' documentation can be found at the [Generated client's READM ## Compatibility `client-python` follows [semver](http://semver.org/), so until the major version of -client-python gets increased, your code will continue to work with explicitly +client-python gets increased, your code will continue to work with explicitly supported versions of Kubernetes clusters. #### Compatibility matrix @@ -142,18 +142,12 @@ Note: There would be no maintenance for alpha/beta releases except the latest on ## Community, Support, Discussion -If you have any problem on using the package or any suggestions, please start with reaching the [Kubernetes clients slack channel](https://kubernetes.slack.com/messages/C76GB48RK/), or filing an [issue](https://github.com/kubernetes-client/python/issues) to let us know. You can also reach the maintainers of this project at [SIG API Machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery). +If you have any problem on using the package or any suggestions, please start with reaching the [Kubernetes clients slack channel](https://kubernetes.slack.com/messages/C76GB48RK/), or filing an [issue](https://github.com/kubernetes-client/python/issues) to let us know. You can also reach the maintainers of this project at [SIG API Machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery), where this project falls under. ### Code of Conduct Participation in the Kubernetes community is governed by the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). -## Kubernetes Incubator - -This is a [Kubernetes Incubator project](https://github.com/kubernetes/community/blob/master/incubator.md). - -* [SIG: sig-api-machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery) - ## Troubleshooting ### SSLError on macOS From 3543417fb0d7d661e3ced46549302e3bf1c8182f Mon Sep 17 00:00:00 2001 From: Tomas Tomecek Date: Fri, 12 Jul 2019 15:27:58 +0200 Subject: [PATCH 23/53] kube-init.sh: die is not a command Signed-off-by: Tomas Tomecek --- scripts/kube-init.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/kube-init.sh b/scripts/kube-init.sh index 32cbc0472..b43890c56 100755 --- a/scripts/kube-init.sh +++ b/scripts/kube-init.sh @@ -108,7 +108,8 @@ done # Shut down CI if minikube did not start and show logs if [ $MINIKUBE_OK == "false" ]; then sudo minikube logs - die $LINENO "minikube did not start" + echo "minikube did not start (line: ${LINENO})" + exit 1 fi echo "Dump Kubernetes Objects..." From 2eb2c8dc81fe92a186ca6fdf398ef1f98e694895 Mon Sep 17 00:00:00 2001 From: Xianglong Wang Date: Mon, 22 Jul 2019 21:14:40 -0400 Subject: [PATCH 24/53] update docs python version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a58d7bad0..f80b4c34a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ matrix: env: TOXENV=py27-functional - python: 2.7 env: TOXENV=update-pycodestyle - - python: 2.7 + - python: 3.7 env: TOXENV=docs - python: 2.7 env: TOXENV=coverage,codecov From ab002f760d76e37ea6a808035555837923be9bb5 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Tue, 25 Jun 2019 22:31:41 +0200 Subject: [PATCH 25/53] Remove obsolete test The current PR no longer support creating from string --- kubernetes/e2e_test/test_utils.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index c393ba9f5..ca0f63912 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -59,27 +59,6 @@ class TestUtils(unittest.TestCase): except ApiException: continue - def test_create_apps_deployment_from_yaml_string(self): - k8s_client = client.api_client.ApiClient(configuration=self.config) - with open(self.path_prefix + "apps-deployment-2.yaml") as f: - yaml_str = f.read() - - utils.create_from_yaml( - k8s_client, yaml_str) - - app_api = client.AppsV1beta1Api(k8s_client) - dep = app_api.read_namespaced_deployment(name="nginx-app-2", - namespace="default") - self.assertIsNotNone(dep) - while True: - try: - app_api.delete_namespaced_deployment( - name="nginx-app-2", namespace="default", - body={}) - break - except ApiException: - continue - def test_create_apps_deployment_from_yaml_obj(self): k8s_client = client.api_client.ApiClient(configuration=self.config) with open(self.path_prefix + "apps-deployment.yaml") as f: From 6100392c47512abc6078065a21c83cd61ac7ef23 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Wed, 26 Jun 2019 00:23:21 +0200 Subject: [PATCH 26/53] Add optional namespace to create_from_dict This follows up on the addition to create_from_yaml, the behavior is the same. --- kubernetes/utils/create_from_yaml.py | 29 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 435ebdd38..af2e2aa38 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -60,10 +60,10 @@ def create_from_yaml( yml_document_all = yaml.safe_load_all(f) failures = [] - for yml_document in yml_document_all: try: create_from_dict(k8s_client, yml_document, verbose, + namespace=namespace, **kwargs) except FailToCreateError as failure: failures.extend(failure.api_exceptions) @@ -71,7 +71,8 @@ def create_from_yaml( raise FailToCreateError(failures) -def create_from_dict(k8s_client, data, verbose=False, **kwargs): +def create_from_dict(k8s_client, data, verbose=False, namespace='default', + **kwargs): """ Perform an action from a dictionary containing valid kubernetes API object (i.e. List, Service, etc). @@ -81,6 +82,11 @@ def create_from_dict(k8s_client, data, verbose=False, **kwargs): data: a dictionary holding valid kubernetes objects verbose: If True, print confirmation from the create action. Default is False. + namespace: string. Contains the namespace to create all + resources inside. The namespace must preexist otherwise + the resource creation will fail. If the API object in + the yaml file already contains a namespace definition + this parameter has no effect. Raises: FailToCreateError which holds list of `client.rest.ApiException` @@ -101,14 +107,15 @@ def create_from_dict(k8s_client, data, verbose=False, **kwargs): yml_object["kind"] = kind try: create_from_yaml_single_item( - k8s_client, yml_object, verbose, **kwargs) + k8s_client, yml_object, verbose, namespace=namespace, + **kwargs) except client.rest.ApiException as api_exception: api_exceptions.append(api_exception) else: # This is a single object. Call the single item method try: create_from_yaml_single_item( - k8s_client, data, verbose, **kwargs) + k8s_client, data, verbose, namespace=namespace, **kwargs) except client.rest.ApiException as api_exception: api_exceptions.append(api_exception) @@ -135,17 +142,17 @@ def create_from_yaml_single_item( kind = yml_object["kind"] kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind) kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower() - # Decide which namespace we are going to put the object in, - # if any - if "namespace" in yml_object["metadata"]: - namespace = yml_object["metadata"]["namespace"] - else: - namespace = "default" # Expect the user to create namespaced objects more often if hasattr(k8s_api, "create_namespaced_{0}".format(kind)): + # Decide which namespace we are going to put the object in, + # if any + if "namespace" in yml_object["metadata"]: + namespace = yml_object["metadata"]["namespace"] + kwargs['namespace'] = namespace resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))( - body=yml_object, namespace=namespace, **kwargs) + body=yml_object, **kwargs) else: + kwargs.pop('namespace', None) resp = getattr(k8s_api, "create_{0}".format(kind))( body=yml_object, **kwargs) if verbose: From dd72deaec237bfedebcf82bf3d24b102cddef18a Mon Sep 17 00:00:00 2001 From: Xianglong Wang Date: Tue, 23 Jul 2019 21:28:04 -0400 Subject: [PATCH 27/53] Deprecate extensions/v1beta1 deployment --- examples/create_deployment.py | 4 +- examples/create_deployment_from_yaml.py | 2 +- examples/deployment_examples.py | 18 ++++----- examples/nginx-deployment.yaml | 10 +++-- examples/notebooks/create_deployment.ipynb | 18 ++++----- examples/notebooks/intro_notebook.ipynb | 6 +-- kubernetes/e2e_test/test_utils.py | 37 ++++++------------- .../e2e_test/test_yaml/dep-deployment.yaml | 8 ++-- .../test_yaml/extensions-deployment.yaml | 17 --------- kubernetes/e2e_test/test_yaml/list.yaml | 7 +++- .../e2e_test/test_yaml/triple-nginx.yaml | 21 ++++++++--- 11 files changed, 68 insertions(+), 80 deletions(-) delete mode 100644 kubernetes/e2e_test/test_yaml/extensions-deployment.yaml diff --git a/examples/create_deployment.py b/examples/create_deployment.py index 0ce1e2fa1..87f50eeae 100644 --- a/examples/create_deployment.py +++ b/examples/create_deployment.py @@ -27,8 +27,8 @@ def main(): with open(path.join(path.dirname(__file__), "nginx-deployment.yaml")) as f: dep = yaml.safe_load(f) - k8s_beta = client.ExtensionsV1beta1Api() - resp = k8s_beta.create_namespaced_deployment( + k8s_apps_v1 = client.AppsV1Api() + resp = k8s_apps_v1.create_namespaced_deployment( body=dep, namespace="default") print("Deployment created. status='%s'" % str(resp.status)) diff --git a/examples/create_deployment_from_yaml.py b/examples/create_deployment_from_yaml.py index dcb7ac14d..d66a2b6b8 100644 --- a/examples/create_deployment_from_yaml.py +++ b/examples/create_deployment_from_yaml.py @@ -24,7 +24,7 @@ def main(): config.load_kube_config() k8s_client = client.ApiClient() utils.create_from_yaml(k8s_client, "nginx-deployment.yaml") - k8s_api = client.ExtensionsV1beta1Api(k8s_client) + k8s_api = client.AppsV1Api(k8s_client) deps = k8s_api.read_namespaced_deployment("nginx-deployment", "default") print("Deployment {0} created".format(deps.metadata.name)) diff --git a/examples/deployment_examples.py b/examples/deployment_examples.py index 29f55d34d..5ec656fc7 100644 --- a/examples/deployment_examples.py +++ b/examples/deployment_examples.py @@ -25,19 +25,19 @@ def create_deployment_object(): # Configureate Pod template container container = client.V1Container( name="nginx", - image="nginx:1.7.9", + image="nginx:1.15.4", ports=[client.V1ContainerPort(container_port=80)]) # Create and configurate a spec section template = client.V1PodTemplateSpec( metadata=client.V1ObjectMeta(labels={"app": "nginx"}), spec=client.V1PodSpec(containers=[container])) # Create the specification of deployment - spec = client.ExtensionsV1beta1DeploymentSpec( + spec = client.AppsV1beta1DeploymentSpec( replicas=3, template=template) # Instantiate the deployment object - deployment = client.ExtensionsV1beta1Deployment( - api_version="extensions/v1beta1", + deployment = client.AppsV1beta1Deployment( + api_version="apps/v1beta1", kind="Deployment", metadata=client.V1ObjectMeta(name=DEPLOYMENT_NAME), spec=spec) @@ -55,7 +55,7 @@ def create_deployment(api_instance, deployment): def update_deployment(api_instance, deployment): # Update container image - deployment.spec.template.spec.containers[0].image = "nginx:1.9.1" + deployment.spec.template.spec.containers[0].image = "nginx:1.16.0" # Update the deployment api_response = api_instance.patch_namespaced_deployment( name=DEPLOYMENT_NAME, @@ -80,16 +80,16 @@ def main(): # utility. If no argument provided, the config will be loaded from # default location. config.load_kube_config() - extensions_v1beta1 = client.ExtensionsV1beta1Api() + apps_v1beta1 = client.AppsV1beta1Api() # Create a deployment object with client-python API. The deployment we # created is same as the `nginx-deployment.yaml` in the /examples folder. deployment = create_deployment_object() - create_deployment(extensions_v1beta1, deployment) + create_deployment(apps_v1beta1, deployment) - update_deployment(extensions_v1beta1, deployment) + update_deployment(apps_v1beta1, deployment) - delete_deployment(extensions_v1beta1) + delete_deployment(apps_v1beta1) if __name__ == '__main__': diff --git a/examples/nginx-deployment.yaml b/examples/nginx-deployment.yaml index d05940d29..5dd80da37 100644 --- a/examples/nginx-deployment.yaml +++ b/examples/nginx-deployment.yaml @@ -1,9 +1,14 @@ -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment + labels: + app: nginx spec: replicas: 3 + selector: + matchLabels: + app: nginx template: metadata: labels: @@ -11,7 +16,6 @@ spec: spec: containers: - name: nginx - image: nginx:1.7.9 + image: nginx:1.15.4 ports: - containerPort: 80 - diff --git a/examples/notebooks/create_deployment.ipynb b/examples/notebooks/create_deployment.ipynb index 0fa6c0510..b4e1229dd 100644 --- a/examples/notebooks/create_deployment.ipynb +++ b/examples/notebooks/create_deployment.ipynb @@ -47,7 +47,7 @@ "outputs": [], "source": [ "config.load_kube_config()\n", - "extension = client.ExtensionsV1beta1Api()" + "apps_api = client.AppsV1beta1Api()" ] }, { @@ -70,7 +70,7 @@ }, "outputs": [], "source": [ - "deployment = client.ExtensionsV1beta1Deployment()" + "deployment = client.AppsV1beta1Deployment()" ] }, { @@ -93,7 +93,7 @@ }, "outputs": [], "source": [ - "deployment.api_version = \"extensions/v1beta1\"\n", + "deployment.api_version = \"apps/v1beta1\"\n", "deployment.kind = \"Deployment\"\n", "deployment.metadata = client.V1ObjectMeta(name=\"nginx-deployment\")" ] @@ -118,7 +118,7 @@ }, "outputs": [], "source": [ - "spec = client.ExtensionsV1beta1DeploymentSpec()\n", + "spec = client.AppsV1beta1DeploymentSpec()\n", "spec.replicas = 3" ] }, @@ -207,7 +207,7 @@ }, "outputs": [], "source": [ - "extension.create_namespaced_deployment(namespace=\"default\", body=deployment)" + "apps_api.create_namespaced_deployment(namespace=\"default\", body=deployment)" ] }, { @@ -253,7 +253,7 @@ }, "outputs": [], "source": [ - "extension.replace_namespaced_deployment(name=\"nginx-deployment\", namespace=\"default\", body=deployment)" + "apps_api.replace_namespaced_deployment(name=\"nginx-deployment\", namespace=\"default\", body=deployment)" ] }, { @@ -277,10 +277,10 @@ }, "outputs": [], "source": [ - "rollback = client.ExtensionsV1beta1DeploymentRollback()\n", - "rollback.api_version = \"extensions/v1beta1\"\n", + "rollback = client.AppsV1beta1DeploymentRollback()\n", + "rollback.api_version = \"apps/v1beta1\"\n", "rollback.kind = \"DeploymentRollback\"\n", - "rollback.rollback_to = client.ExtensionsV1beta1RollbackConfig()\n", + "rollback.rollback_to = client.AppsV1beta1RollbackConfig()\n", "rollback.rollback_to.revision = 0\n", "rollback.name = \"nginx-deployment\"" ] diff --git a/examples/notebooks/intro_notebook.ipynb b/examples/notebooks/intro_notebook.ipynb index 53bf63185..b4e3b8e8e 100644 --- a/examples/notebooks/intro_notebook.ipynb +++ b/examples/notebooks/intro_notebook.ipynb @@ -89,9 +89,9 @@ }, "outputs": [], "source": [ - "api_instance = client.ExtensionsV1beta1Api()\n", - "dep = client.ExtensionsV1beta1Deployment()\n", - "spec = client.ExtensionsV1beta1DeploymentSpec()" + "api_instance = client.AppsV1beta1Api()\n", + "dep = client.AppsV1beta1Deployment()\n", + "spec = client.AppsV1beta1DeploymentSpec()" ] }, { diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index b5684a798..edbc104f8 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -52,21 +52,6 @@ class TestUtils(unittest.TestCase): name="nginx-app", namespace="default", body={}) - def test_create_extensions_deployment_from_yaml(self): - """ - Should be able to create an extensions/v1beta1 deployment. - """ - k8s_client = client.api_client.ApiClient(configuration=self.config) - utils.create_from_yaml( - k8s_client, self.path_prefix + "extensions-deployment.yaml") - ext_api = client.ExtensionsV1beta1Api(k8s_client) - dep = ext_api.read_namespaced_deployment(name="nginx-deployment", - namespace="default") - self.assertIsNotNone(dep) - ext_api.delete_namespaced_deployment( - name="nginx-deployment", namespace="default", - body={}) - def test_create_pod_from_yaml(self): """ Should be able to create a pod. @@ -134,7 +119,7 @@ class TestUtils(unittest.TestCase): utils.create_from_yaml( k8s_client, self.path_prefix + "dep-deployment.yaml") core_api = client.CoreV1Api(k8s_client) - ext_api = client.ExtensionsV1beta1Api(k8s_client) + ext_api = client.AppsV1Api(k8s_client) nmsp = core_api.read_namespace(name="dep") self.assertIsNotNone(nmsp) dep = ext_api.read_namespaced_deployment(name="nginx-deployment", @@ -186,7 +171,7 @@ class TestUtils(unittest.TestCase): utils.create_from_yaml( k8s_client, self.path_prefix + "list.yaml") core_api = client.CoreV1Api(k8s_client) - ext_api = client.ExtensionsV1beta1Api(k8s_client) + ext_api = client.AppsV1Api(k8s_client) svc = core_api.read_namespaced_service(name="list-service-test", namespace="default") self.assertIsNotNone(svc) @@ -317,7 +302,7 @@ class TestUtils(unittest.TestCase): def test_create_from_multi_resource_yaml_with_multi_conflicts(self): """ - Should create an extensions/v1beta1 deployment + Should create an apps/v1 deployment and fail to create the same deployment twice. Should raise an exception that contains two error messages. """ @@ -327,14 +312,14 @@ class TestUtils(unittest.TestCase): k8s_client, self.path_prefix + "triple-nginx.yaml") exp_error = ('Error from server (Conflict): {"kind":"Status",' '"apiVersion":"v1","metadata":{},"status":"Failure",' - '"message":"deployments.extensions \\"triple-nginx\\" ' + '"message":"deployments.apps \\"triple-nginx\\" ' 'already exists","reason":"AlreadyExists",' - '"details":{"name":"triple-nginx","group":"extensions",' + '"details":{"name":"triple-nginx","group":"apps",' '"kind":"deployments"},"code":409}\n' ) exp_error += exp_error self.assertEqual(exp_error, str(cm.exception)) - ext_api = client.ExtensionsV1beta1Api(k8s_client) + ext_api = client.AppsV1Api(k8s_client) dep = ext_api.read_namespaced_deployment(name="triple-nginx", namespace="default") self.assertIsNotNone(dep) @@ -348,7 +333,8 @@ class TestUtils(unittest.TestCase): """ k8s_client = client.api_client.ApiClient(configuration=self.config) utils.create_from_yaml( - k8s_client, self.path_prefix + "apps-deployment.yaml", namespace=self.test_namespace) + k8s_client, self.path_prefix + "apps-deployment.yaml", + namespace=self.test_namespace) app_api = client.AppsV1beta1Api(k8s_client) dep = app_api.read_namespaced_deployment(name="nginx-app", namespace=self.test_namespace) @@ -357,14 +343,15 @@ class TestUtils(unittest.TestCase): name="nginx-app", namespace=self.test_namespace, body={}) - def test_create_from_list_in_multi_resource_yaml(self): + def test_create_from_list_in_multi_resource_yaml_namespaced(self): """ Should be able to create the items in the PodList and a deployment - specified in the multi-resource file + specified in the multi-resource file in a test namespace """ k8s_client = client.api_client.ApiClient(configuration=self.config) utils.create_from_yaml( - k8s_client, self.path_prefix + "multi-resource-with-list.yaml", namespace=self.test_namespace) + k8s_client, self.path_prefix + "multi-resource-with-list.yaml", + namespace=self.test_namespace) core_api = client.CoreV1Api(k8s_client) app_api = client.AppsV1beta1Api(k8s_client) pod_0 = core_api.read_namespaced_pod( diff --git a/kubernetes/e2e_test/test_yaml/dep-deployment.yaml b/kubernetes/e2e_test/test_yaml/dep-deployment.yaml index be9b281f2..57170d92a 100644 --- a/kubernetes/e2e_test/test_yaml/dep-deployment.yaml +++ b/kubernetes/e2e_test/test_yaml/dep-deployment.yaml @@ -1,10 +1,13 @@ -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: dep spec: replicas: 3 + selector: + matchLabels: + app: nginx template: metadata: labels: @@ -12,7 +15,6 @@ spec: spec: containers: - name: nginx - image: nginx:1.7.9 + image: nginx:1.15.4 ports: - containerPort: 80 - diff --git a/kubernetes/e2e_test/test_yaml/extensions-deployment.yaml b/kubernetes/e2e_test/test_yaml/extensions-deployment.yaml deleted file mode 100644 index d05940d29..000000000 --- a/kubernetes/e2e_test/test_yaml/extensions-deployment.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.7.9 - ports: - - containerPort: 80 - diff --git a/kubernetes/e2e_test/test_yaml/list.yaml b/kubernetes/e2e_test/test_yaml/list.yaml index 3416ec429..15aab9ad5 100644 --- a/kubernetes/e2e_test/test_yaml/list.yaml +++ b/kubernetes/e2e_test/test_yaml/list.yaml @@ -11,7 +11,7 @@ items: port: 80 selector: app: list-deployment-test -- apiVersion: extensions/v1beta1 +- apiVersion: apps/v1 kind: Deployment metadata: name: list-deployment-test @@ -19,6 +19,9 @@ items: app: list-deployment-test spec: replicas: 1 + selector: + matchLabels: + app: list-deployment-test template: metadata: labels: @@ -26,4 +29,4 @@ items: spec: containers: - name: nginx - image: nginx \ No newline at end of file + image: nginx:1.15.4 \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/triple-nginx.yaml b/kubernetes/e2e_test/test_yaml/triple-nginx.yaml index 9a4fda66c..a071c88b5 100644 --- a/kubernetes/e2e_test/test_yaml/triple-nginx.yaml +++ b/kubernetes/e2e_test/test_yaml/triple-nginx.yaml @@ -1,9 +1,12 @@ -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: triple-nginx spec: replicas: 3 + selector: + matchLabels: + app: nginx template: metadata: labels: @@ -11,16 +14,19 @@ spec: spec: containers: - name: nginx - image: nginx:1.7.9 + image: nginx:1.15.4 ports: - containerPort: 80 --- -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: triple-nginx spec: replicas: 3 + selector: + matchLabels: + app: nginx template: metadata: labels: @@ -28,16 +34,19 @@ spec: spec: containers: - name: nginx - image: nginx:1.7.9 + image: nginx:1.15.4 ports: - containerPort: 80 --- -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: triple-nginx spec: replicas: 3 + selector: + matchLabels: + app: nginx template: metadata: labels: @@ -45,6 +54,6 @@ spec: spec: containers: - name: nginx - image: nginx:1.7.9 + image: nginx:1.15.4 ports: - containerPort: 80 \ No newline at end of file From 671bf7dc68feb1fc59a3a301534af9a123b58c70 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Thu, 25 Jul 2019 07:38:02 +0200 Subject: [PATCH 28/53] Python 3.4 is no longer supported This update the setup metadata to indicate this --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index ec50eb2de..c3f9cd48f 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,6 @@ setup( "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", From eace0bd3fe6a36c55a3bfeb8cc24eddd7dd51d77 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 25 Jul 2019 15:19:35 +0200 Subject: [PATCH 29/53] Travis CI: The sudo: tag is now fully deprecated in Travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f80b4c34a..86e7187bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ # ref: https://docs.travis-ci.com/user/languages/python language: python dist: xenial -sudo: true services: - docker From f3e3170dd9fad56757a89dfb7f69054128f436d5 Mon Sep 17 00:00:00 2001 From: micw523 Date: Thu, 25 Jul 2019 20:06:52 -0400 Subject: [PATCH 30/53] Update urllib3 for CVE-2019-11324 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 56079f48e..74e38b7cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ ipaddress>=1.0.17;python_version=="2.7" # PSF websocket-client>=0.32.0,!=0.40.0,!=0.41.*,!=0.42.* # LGPLv2+ requests # Apache-2.0 requests-oauthlib # ISC -urllib3>=1.23 # MIT +urllib3>=1.24.2 # MIT From 6709b753b4ad2e09aa472b6452bbad9f96e264e3 Mon Sep 17 00:00:00 2001 From: Tomasz Prus Date: Tue, 4 Jun 2019 23:53:39 +0200 Subject: [PATCH 31/53] add example of using custom resources --- examples/create_thirdparty_resource.md | 40 ----------- examples/custom_object.py | 93 ++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 40 deletions(-) delete mode 100644 examples/create_thirdparty_resource.md create mode 100644 examples/custom_object.py diff --git a/examples/create_thirdparty_resource.md b/examples/create_thirdparty_resource.md deleted file mode 100644 index ec5975ad0..000000000 --- a/examples/create_thirdparty_resource.md +++ /dev/null @@ -1,40 +0,0 @@ -## Creating a Third Party Resource - -``` -from __future__ import print_function - -from pprint import pprint - -import kubernetes -from kubernetes import config -from kubernetes.rest import ApiException - -config.load_kube_config() -api_instance = kubernetes.ThirdPartyResources() - -namespace = 'default' -resource = 'repos' -fqdn = 'git.k8s.com' - -body = {} -body['apiVersion'] = "git.k8s.com/v1" -body['kind'] = "RePo" -body['metadata'] = {} -body['metadata']['name'] = "blog-repo" -body['repo'] = "github.com/user/my-blog" -body['username'] = "username" -body['password'] = "password" -body['branch'] = "branch" - - - -try: - # Create a Resource - api_response = api_instance.apis_fqdn_v1_namespaces_namespace_resource_post( - namespace, fqdn, resource, body) - pprint(api_response) -except ApiException as e: - print( - "Exception when calling DefaultApi->apis_fqdn_v1_namespaces_namespace_resource_post: %s\n" % - e) -``` \ No newline at end of file diff --git a/examples/custom_object.py b/examples/custom_object.py new file mode 100644 index 000000000..87524c006 --- /dev/null +++ b/examples/custom_object.py @@ -0,0 +1,93 @@ +# Copyright 2019 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This example use an example CRD from this tutorial: +# https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/ +# +# The following yaml manifest has to be applied first: +# +# apiVersion: apiextensions.k8s.io/v1beta1 +# kind: CustomResourceDefinition +# metadata: +# name: crontabs.stable.example.com +# spec: +# group: stable.example.com +# versions: +# - name: v1 +# served: true +# storage: true +# scope: Namespaced +# names: +# plural: crontabs +# singular: crontab +# kind: CronTab +# shortNames: +# - ct + + +from pprint import pprint + +from kubernetes import client, config + + +def main(): + + config.load_kube_config() + + api = client.CustomObjectsApi() + + # it's my custom resource defined as Dict + my_resource = { + "apiVersion": "stable.example.com/v1", + "kind": "CronTab", + "metadata": {"name": "my-new-cron-object"}, + "cronSpec": "* * * * */5", + "image": "my-awesome-cron-image", + } + + # create the resource + api.create_namespaced_custom_object( + group="stable.example.com", + version="v1", + namespace="default", + plural="crontabs", + body=my_resource, + ) + print("Resource created") + + # get the resource and print out data + resource = api.get_namespaced_custom_object( + group="stable.example.com", + version="v1", + name="my-new-cron-object", + namespace="default", + plural="crontabs", + ) + print("Resource details:") + pprint(resource) + + # delete it + api.delete_namespaced_custom_object( + group="stable.example.com", + version="v1", + name="my-new-cron-object", + namespace="default", + plural="crontabs", + body=client.V1DeleteOptions(), + ) + print("Resource deleted") + + +if __name__ == "__main__": + main() From c558b127bf8228c9b778d47c7cfb81ce2404a663 Mon Sep 17 00:00:00 2001 From: micw523 Date: Tue, 30 Jul 2019 17:56:27 -0400 Subject: [PATCH 32/53] Add micw523 to reviewers --- OWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWNERS b/OWNERS index dd6cbaeb2..4f81dde10 100644 --- a/OWNERS +++ b/OWNERS @@ -6,3 +6,5 @@ approvers: - lavalamp - yliaog - roycaihw +reviewers: + - micw523 From 3b842166529b86d58d398a9ba34ad30b01b01439 Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Wed, 24 Jul 2019 23:55:29 +0200 Subject: [PATCH 33/53] Mulitple docs fixes * Document how to build the doumentation with sphinx For convinience, I added a Makefile which spares one to memorize the long sphinx command, or type python setup.py build_sphinx You simply use `make html` and you will get the docs. * Render README with markdown properly conf.py includes some code to work around a bug in common mark. The markdown is now properly converted to HTML. * Fix rendering of CONTRIBUTING.md --- .gitignore | 4 ++++ doc/Makefile | 21 +++++++++++++++++++++ doc/README.md | 11 +++++++++++ doc/requirements-docs.txt | 2 ++ doc/source/conf.py | 32 +++++++++++++++++++++++++++----- doc/source/contributing.rst | 4 ---- doc/source/index.rst | 4 ++-- doc/source/readme.rst | 4 ---- setup.py | 4 +--- test-requirements.txt | 5 +++-- 10 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 doc/Makefile create mode 100644 doc/README.md create mode 100644 doc/requirements-docs.txt delete mode 100644 doc/source/contributing.rst delete mode 100644 doc/source/readme.rst diff --git a/.gitignore b/.gitignore index bdd5055d1..a5e2becbb 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,7 @@ target/ .idea/* *.iml .vscode + +# created by sphinx documentation build +doc/source/README.md +doc/_build diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 000000000..d5b5edf76 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,21 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = -c source +SPHINXBUILD = sphinx-build +SPHINXPROJ = kubernetes-python +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +html: + $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @echo "\nDocs rendered successfully, open _/build/html/index.html to view" diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 000000000..5b635c577 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,11 @@ +Building the documentation +========================== + +Install the test requirements with: + +``` +$ pip install -r ../test-requirements.txt +``` + +Use `make html` to build the docs in html format. + diff --git a/doc/requirements-docs.txt b/doc/requirements-docs.txt new file mode 100644 index 000000000..eb69200af --- /dev/null +++ b/doc/requirements-docs.txt @@ -0,0 +1,2 @@ +recommonmark +sphinx_markdown_tables diff --git a/doc/source/conf.py b/doc/source/conf.py index a5f0a1fc6..2d1d6acc9 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -13,22 +13,37 @@ # limitations under the License. import os +import re +import shutil import sys -from recommonmark.parser import CommonMarkParser +from recommonmark.transform import AutoStructify + +# Work around https://github.com/readthedocs/recommonmark/issues/152 +new_readme = [] + +with open("../../README.md", "r") as r: + lines = r.readlines() + for l in lines: + nl = re.sub("\[!\[[\w\s]+\]\(", "[![](", l) + new_readme.append(nl) + +with open("README.md", "w") as n: + n.writelines(new_readme) + +# apparently index.rst can't search for markdown not in the same directory +shutil.copy("../../CONTRIBUTING.md", ".") sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- -source_parsers = { - '.md': CommonMarkParser, -} - source_suffix = ['.rst', '.md'] # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ + 'sphinx_markdown_tables', + 'recommonmark', 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', ] @@ -80,3 +95,10 @@ latex_documents = [ # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} +def setup(app): + app.add_config_value('recommonmark_config', { + 'auto_toc_tree_section': 'Contents', + 'enable_eval_rst': True, + }, True) + app.add_transform(AutoStructify) + diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst deleted file mode 100644 index 2a4789889..000000000 --- a/doc/source/contributing.rst +++ /dev/null @@ -1,4 +0,0 @@ -============ -Contributing -============ -.. include:: ../../CONTRIBUTING.md diff --git a/doc/source/index.rst b/doc/source/index.rst index fc6b82629..038a82f72 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -11,11 +11,11 @@ Contents: .. toctree:: :maxdepth: 2 - readme + README installation usage modules - contributing + contributing Indices and tables ================== diff --git a/doc/source/readme.rst b/doc/source/readme.rst deleted file mode 100644 index 77f974f64..000000000 --- a/doc/source/readme.rst +++ /dev/null @@ -1,4 +0,0 @@ -====== -Readme -====== -.. include:: ../../README.md diff --git a/setup.py b/setup.py index ec50eb2de..aa4a33b66 100644 --- a/setup.py +++ b/setup.py @@ -62,9 +62,7 @@ setup( 'kubernetes.stream', 'kubernetes.client.models', 'kubernetes.utils'], include_package_data=True, - long_description="""\ - Python client for kubernetes http://kubernetes.io/ - """, + long_description="Python client for kubernetes http://kubernetes.io/", classifiers=[ "Development Status :: %s" % DEVELOPMENT_STATUS, "Topic :: Utilities", diff --git a/test-requirements.txt b/test-requirements.txt index 0bb8dc53c..6200389e4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,9 +5,10 @@ pluggy>=0.3.1 py>=1.4.31 randomize>=0.13 mock>=2.0.0 -sphinx>=1.2.1,!=1.3b1,<1.4 # BSD +sphinx>=1.4 # BSD recommonmark +sphinx_markdown_tables codecov>=1.4.0 pycodestyle autopep8 -isort \ No newline at end of file +isort From 3f8d583ee08133880c6985d70fb773f458834abe Mon Sep 17 00:00:00 2001 From: Richard Godden <7768980+goddenrich@users.noreply.github.com> Date: Thu, 1 Aug 2019 22:16:00 +0100 Subject: [PATCH 34/53] update submodule to include parsing microseconds --- kubernetes/base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/base b/kubernetes/base index 474e9fb32..ec31e05c9 160000 --- a/kubernetes/base +++ b/kubernetes/base @@ -1 +1 @@ -Subproject commit 474e9fb32293fa05098e920967bb0e0645182d5b +Subproject commit ec31e05c90218a915dea948703ed0d543d5d6835 From 959c8630515d0a0e0e27d79ad2cfd73a3b92c407 Mon Sep 17 00:00:00 2001 From: Scott Lee Date: Fri, 2 Aug 2019 16:24:00 -0700 Subject: [PATCH 35/53] Move inactive members to emeritus --- OWNERS | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OWNERS b/OWNERS index 4f81dde10..605d99b55 100644 --- a/OWNERS +++ b/OWNERS @@ -1,10 +1,11 @@ # See the OWNERS docs at https://go.k8s.io/owners approvers: - - mbohlool + - roycaihw + - yliaog +emeritus_approvers: - caesarxuchao - lavalamp - - yliaog - - roycaihw + - mbohlool reviewers: - micw523 From b403562b940ce356cb4875bac34cd448d7a055f7 Mon Sep 17 00:00:00 2001 From: "Haowei Cai (Roy)" Date: Tue, 6 Aug 2019 18:48:15 -0700 Subject: [PATCH 36/53] CHANGELOG for removing use of alpha initializers --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7fff70fe..b1e119d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Introduce RuntimeClass to NodeV1alpha1Api and NodeV1beta1Api [kubernetes/kubernetes#74433](https://github.com/kubernetes/kubernetes/pull/74433) - Graduate PriorityClass API to GA SchedulingV1Api [kubernetes/kubernetes#73555](https://github.com/kubernetes/kubernetes/pull/73555) - Introduce CSINodeInfo and CSIDriver to StorageV1beta1Api [kubernetes/kubernetes#74283](https://github.com/kubernetes/kubernetes/pull/74283) +- The alpha Initializers feature, `admissionregistration.k8s.io/v1alpha1` API version, `Initializers` admission plugin, and use of the `metadata.initializers` API field have been removed. Discontinue use of the alpha feature and delete any existing `InitializerConfiguration` API objects before upgrading. The `metadata.initializers` field will be removed in a future release. The parameter `include_uninitialized` has been removed. [kubernetes/kubernetes#72972](https://github.com/kubernetes/kubernetes/pull/72972) # v9.0.0 **Bug Fix:** From d300fd78094d766ae99d4e863f9ccc9519036e0e Mon Sep 17 00:00:00 2001 From: Haowei Cai Date: Tue, 6 Aug 2019 15:02:49 -0700 Subject: [PATCH 37/53] use minikube 1.2.0 --- scripts/kube-init.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/kube-init.sh b/scripts/kube-init.sh index b43890c56..c6445c0f4 100755 --- a/scripts/kube-init.sh +++ b/scripts/kube-init.sh @@ -70,7 +70,8 @@ sudo chmod +x kubectl sudo mv kubectl /usr/local/bin/ echo "Download minikube from minikube project" -wget -O minikube "https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64" +# Temporary pin minikube to 1.2.0 due to regression in 1.3.0 (https://github.com/kubernetes/minikube/issues/5014) +wget -O minikube "https://storage.googleapis.com/minikube/releases/v1.2.0/minikube-linux-amd64" sudo chmod +x minikube sudo mv minikube /usr/local/bin/ From c630528065903153a90ed00826122b12c23e2ad3 Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Fri, 9 Aug 2019 07:37:21 +0200 Subject: [PATCH 38/53] Remove old client versions (>v1.9) from support matrix * Add updating of support matrix to REALEASE.md This will help future maintainers and prevent the table from becoming huge again. --- README.md | 30 +++++++++--------------------- RELEASE.md | 8 ++++++-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 732941f09..90426bf2b 100644 --- a/README.md +++ b/README.md @@ -82,19 +82,15 @@ supported versions of Kubernetes clusters. #### Compatibility matrix -| | Kubernetes 1.5 | Kubernetes 1.6 | Kubernetes 1.7 | Kubernetes 1.8 | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 | -|--------------------|----------------|----------------|----------------|----------------|----------------|-----------------|-----------------|-----------------|-----------------|-----------------| -| client-python 1.0 | ✓ | - | - |- |- |- |- |- |- |- | -| client-python 2.0 | + | ✓ | - |- |- |- |- |- |- |- | -| client-python 3.0 | + | + | ✓ |- |- |- |- |- |- |- | -| client-python 4.0 | + | + | + |✓ |- |- |- |- |- |- | -| client-python 5.0 | + | + | + |+ |✓ |- |- |- |- |- | -| client-python 6.0 | + | + | + |+ |+ |✓ |- |- |- |- | -| client-python 7.0 | + | + | + |+ |+ |+ |✓ |- |- |- | -| client-python 8.0 | + | + | + |+ |+ |+ |+ |✓ |- |- | -| client-python 9.0 | + | + | + |+ |+ |+ |+ |+ |✓ |- | -| client-python 10.0 | + | + | + |+ |+ |+ |+ |+ |+ |✓ | -| client-python HEAD | + | + | + |+ |+ |+ |+ |+ |+ |✓ | +| | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 | +|--------------------|----------------|-----------------|-----------------|-----------------|-----------------|-----------------| +| client-python 5.0 |✓ |- |- |- |- |- | +| client-python 6.0 |+ |✓ |- |- |- |- | +| client-python 7.0 |+ |+ |✓ |- |- |- | +| client-python 8.0 |+ |+ |+ |✓ |- |- | +| client-python 9.0 |+ |+ |+ |+ |✓ |- | +| client-python 10.0 |+ |+ |+ |+ |+ |✓ | +| client-python HEAD |+ |+ |+ |+ |+ |✓ | Key: @@ -110,14 +106,6 @@ between client-python versions. | Client version | Canonical source for OpenAPI spec | Maintenance status | |-----------------|--------------------------------------|-------------------------------| -| 1.0 Alpha/Beta | Kubernetes main repo, 1.5 branch | ✗ | -| 1.0.x | Kubernetes main repo, 1.5 branch | ✗ | -| 2.0 Alpha/Beta | Kubernetes main repo, 1.6 branch | ✗ | -| 2.0.x | Kubernetes main repo, 1.6 branch | ✗ | -| 3.0 Alpha/Beta | Kubernetes main repo, 1.7 branch | ✗ | -| 3.0 | Kubernetes main repo, 1.7 branch | ✗ | -| 4.0 Alpha/Beta | Kubernetes main repo, 1.8 branch | ✗ | -| 4.0 | Kubernetes main repo, 1.8 branch | ✗ | | 5.0 Alpha/Beta | Kubernetes main repo, 1.9 branch | ✗ | | 5.0 | Kubernetes main repo, 1.9 branch | ✗ | | 6.0 Alpha/Beta | Kubernetes main repo, 1.10 branch | ✗ | diff --git a/RELEASE.md b/RELEASE.md index 824f13a4e..0b0eed35e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,7 +3,11 @@ The Kubernetes Python client is released on an as-needed basis. The process is as follows: 1. An issue is proposing a new release with a changelog since the last release +1. Update the support matrix in README.md removing the oldest version and adding the + proposed release. 1. All [OWNERS](OWNERS) must LGTM this release -1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION` +1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes + the tag with `git push $VERSION` 1. The release issue is closed -1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-python-client $VERSION is released` +1. An announcement email is sent to `kubernetes-dev@googlegroups.com` + with the subject `[ANNOUNCE] kubernetes-python-client $VERSION is released` From ab8b903488f7bd64ba16454679ad479a2a5895d9 Mon Sep 17 00:00:00 2001 From: micw523 Date: Tue, 13 Aug 2019 14:08:49 -0400 Subject: [PATCH 39/53] Restore latest release of minikube --- scripts/kube-init.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/kube-init.sh b/scripts/kube-init.sh index c6445c0f4..b43890c56 100755 --- a/scripts/kube-init.sh +++ b/scripts/kube-init.sh @@ -70,8 +70,7 @@ sudo chmod +x kubectl sudo mv kubectl /usr/local/bin/ echo "Download minikube from minikube project" -# Temporary pin minikube to 1.2.0 due to regression in 1.3.0 (https://github.com/kubernetes/minikube/issues/5014) -wget -O minikube "https://storage.googleapis.com/minikube/releases/v1.2.0/minikube-linux-amd64" +wget -O minikube "https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64" sudo chmod +x minikube sudo mv minikube /usr/local/bin/ From 9dbbc6959ad3ecda8dda17f1d44135d64ccb0db1 Mon Sep 17 00:00:00 2001 From: Scott Lee Date: Tue, 13 Aug 2019 12:31:42 -0700 Subject: [PATCH 40/53] Add issue templates (#916) * Add issue templates * Include feedback fixes * Fix wording --- .github/ISSUE_TEMPLATE/bug.md | 20 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/documentation.md | 10 ++++++++++ .github/ISSUE_TEMPLATE/feature.md | 10 ++++++++++ 3 files changed, 40 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/documentation.md create mode 100644 .github/ISSUE_TEMPLATE/feature.md diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 000000000..ad1b1203d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,20 @@ +--- +name: Bug report +about: Share about things that are not working as expected +labels: kind/bug + +--- + +**What happened (please include outputs or screenshots)**: + +**What you expected to happen**: + +**How to reproduce it (as minimally and precisely as possible)**: + +**Anything else we need to know?**: + +**Environment**: +- Kubernetes version (`kubectl version`): +- OS (e.g., MacOS 10.13.6): +- Python version (`python --version`) +- Python client version (`pip list | grep kubernetes`) diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 000000000..75bef6b7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,10 @@ +--- +name: Documentation +about: Report any mistakes or missing information from the documentation or the examples +labels: kind/documentation + +--- + +**Link to the issue (please include a link to the specific documentation or example)**: + +**Description of the issue (please include outputs or screenshots if possible)**: diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 000000000..466b4f87b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,10 @@ +--- +name: Feature request +about: Suggest a new feature for the project +labels: kind/feature + +--- + +**What is the feature and why do you need it**: + +**Describe the solution you'd like to see**: From 2e3b50e08e005861ee0e612366c3b3cc75fd831b Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Tue, 13 Aug 2019 22:57:43 +0200 Subject: [PATCH 41/53] Update examples (#922) * Consolidate both examples for create deployments * Update deployment_examples.py: use V1Deployment * Update sphinx documentation with deployment examples --- doc/source/index.rst | 1 + doc/source/usage.rst | 17 +++++++++++-- examples/create_deployment.py | 2 +- examples/create_deployment_from_yaml.py | 33 ------------------------- examples/deployment_examples.py | 24 +++++++++--------- 5 files changed, 30 insertions(+), 47 deletions(-) delete mode 100644 examples/create_deployment_from_yaml.py diff --git a/doc/source/index.rst b/doc/source/index.rst index 038a82f72..83a649b8e 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -14,6 +14,7 @@ Contents: README installation usage + examples modules contributing diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 6f67af930..114306a8c 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -2,6 +2,19 @@ Usage ======== -To use kubernetes-python-client in a project:: +The directory ``examples`` contains a few examples on how to use the client. - import kubernetes + +Deployments +----------- + +Here is a simple usage of creating a deployment from a yaml file: + + +.. literalinclude:: ../../examples/create_deployment.py + + +The following example demostrates how to create, update and delete deployments +without the need to read a file from the disk: + +.. literalinclude:: ../../examples/deployment_examples.py diff --git a/examples/create_deployment.py b/examples/create_deployment.py index 87f50eeae..ba13440ff 100644 --- a/examples/create_deployment.py +++ b/examples/create_deployment.py @@ -30,7 +30,7 @@ def main(): k8s_apps_v1 = client.AppsV1Api() resp = k8s_apps_v1.create_namespaced_deployment( body=dep, namespace="default") - print("Deployment created. status='%s'" % str(resp.status)) + print("Deployment created. status='%s'" % resp.metadata.name) if __name__ == '__main__': diff --git a/examples/create_deployment_from_yaml.py b/examples/create_deployment_from_yaml.py deleted file mode 100644 index d66a2b6b8..000000000 --- a/examples/create_deployment_from_yaml.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2018 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 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from os import path - -from kubernetes import client, config, utils - - -def main(): - # Configs can be set in Configuration class directly or using helper - # utility. If no argument provided, the config will be loaded from - # default location. - config.load_kube_config() - k8s_client = client.ApiClient() - utils.create_from_yaml(k8s_client, "nginx-deployment.yaml") - k8s_api = client.AppsV1Api(k8s_client) - deps = k8s_api.read_namespaced_deployment("nginx-deployment", "default") - print("Deployment {0} created".format(deps.metadata.name)) - - -if __name__ == '__main__': - main() diff --git a/examples/deployment_examples.py b/examples/deployment_examples.py index 5ec656fc7..9d354f7d3 100644 --- a/examples/deployment_examples.py +++ b/examples/deployment_examples.py @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from os import path - -import yaml +""" +This example shows how to work with AppsV1Api to create, modify and delete +deployments +""" from kubernetes import client, config @@ -32,12 +33,13 @@ def create_deployment_object(): metadata=client.V1ObjectMeta(labels={"app": "nginx"}), spec=client.V1PodSpec(containers=[container])) # Create the specification of deployment - spec = client.AppsV1beta1DeploymentSpec( + spec = client.V1DeploymentSpec( replicas=3, - template=template) + template=template, + selector={'matchLabels': {'app': 'nginx'}}) # Instantiate the deployment object - deployment = client.AppsV1beta1Deployment( - api_version="apps/v1beta1", + deployment = client.V1Deployment( + api_version="apps/v1", kind="Deployment", metadata=client.V1ObjectMeta(name=DEPLOYMENT_NAME), spec=spec) @@ -80,16 +82,16 @@ def main(): # utility. If no argument provided, the config will be loaded from # default location. config.load_kube_config() - apps_v1beta1 = client.AppsV1beta1Api() + apps_v1 = client.AppsV1Api() # Create a deployment object with client-python API. The deployment we # created is same as the `nginx-deployment.yaml` in the /examples folder. deployment = create_deployment_object() - create_deployment(apps_v1beta1, deployment) + create_deployment(apps_v1, deployment) - update_deployment(apps_v1beta1, deployment) + update_deployment(apps_v1, deployment) - delete_deployment(apps_v1beta1) + delete_deployment(apps_v1) if __name__ == '__main__': From a796e1e3f3a4c351979cef4fd4309efb00fa86c5 Mon Sep 17 00:00:00 2001 From: Ratan Boddu Date: Thu, 15 Aug 2019 15:52:12 +0530 Subject: [PATCH 42/53] Added Ingress Example --- examples/ingress-example.py | 111 ++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 examples/ingress-example.py diff --git a/examples/ingress-example.py b/examples/ingress-example.py new file mode 100644 index 000000000..072225906 --- /dev/null +++ b/examples/ingress-example.py @@ -0,0 +1,111 @@ +# Copyright 2019 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from kubernetes import client, config + + +def create_deployment(extensions_v1_beta1): + container = client.V1Container( + name="deployment", + image="ratanboddu/flaskapp:basic", + image_pull_policy="Never", + ports=[client.V1ContainerPort(container_port=5678)], + ) + # Template + template = client.V1PodTemplateSpec( + metadata=client.V1ObjectMeta(labels={"app": "deployment"}), + spec=client.V1PodSpec(containers=[container])) + # Spec + spec = client.ExtensionsV1beta1DeploymentSpec( + replicas=1, + template=template) + # Deployment + deployment = client.ExtensionsV1beta1Deployment( + api_version="extensions/v1beta1", + kind="Deployment", + metadata=client.V1ObjectMeta(name="deployment"), + spec=spec) + # Creation of the Deployment in specified namespace + # (Can replace "default" with a namespace you may have created) + extensions_v1_beta1.create_namespaced_deployment( + namespace="default", body=deployment + ) + + +def create_service(): + core_v1_api = client.CoreV1Api() + body = client.V1Service( + api_version="v1", + kind="Service", + metadata=client.V1ObjectMeta( + name="service-example" + ), + spec=client.V1ServiceSpec( + selector={"app": "deployment"}, + ports=[client.V1ServicePort( + port=5678, + target_port=5678 + )] + ) + ) + # Creation of the Deployment in specified namespace + # (Can replace "default" with a namespace you may have created) + core_v1_api.create_namespaced_service(namespace="default", body=body) + + +def create_ingress(extensions_v1_beta1): + body = client.ExtensionsV1beta1Ingress( + api_version="networking.k8s.io/v1beta1", + kind="Ingress", + metadata=client.V1ObjectMeta(name="ingress-example", annotations={ + "nginx.ingress.kubernetes.io/rewrite-target": "/" + }), + spec=client.ExtensionsV1beta1IngressSpec( + rules=[client.ExtensionsV1beta1IngressRule( + host="boddulabs.com", + http=client.ExtensionsV1beta1HTTPIngressRuleValue( + paths=[client.ExtensionsV1beta1HTTPIngressPath( + path="/", + backend=client.ExtensionsV1beta1IngressBackend( + service_port=5678, + service_name="service-example") + + )] + ) + ) + ] + ) + ) + # Creation of the Deployment in specified namespace + # (Can replace "default" with a namespace you may have created) + extensions_v1_beta1.create_namespaced_ingress( + namespace="default", + body=body + ) + + +def main(): + # Fetching and loading local Kubernetes Information + config.load_kube_config() + # For incluster details + # config.load_incluster_config() + extensions_v1_beta1 = client.ExtensionsV1beta1Api() + + create_deployment(extensions_v1_beta1) + create_service() + create_ingress(extensions_v1_beta1) + + +if __name__ == "__main__": + main() From 60f0ba99f0e08eb20f0a4f1cf9f577b0280e2e20 Mon Sep 17 00:00:00 2001 From: Ratan Boddu Date: Sat, 17 Aug 2019 02:58:22 +0530 Subject: [PATCH 43/53] Suggested changes --- examples/ingress-example.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/examples/ingress-example.py b/examples/ingress-example.py index 072225906..35b9385cb 100644 --- a/examples/ingress-example.py +++ b/examples/ingress-example.py @@ -15,10 +15,10 @@ from kubernetes import client, config -def create_deployment(extensions_v1_beta1): +def create_deployment(apps_v1_api): container = client.V1Container( name="deployment", - image="ratanboddu/flaskapp:basic", + image="gcr.io/google-appengine/fluentd-logger", image_pull_policy="Never", ports=[client.V1ContainerPort(container_port=5678)], ) @@ -27,18 +27,18 @@ def create_deployment(extensions_v1_beta1): metadata=client.V1ObjectMeta(labels={"app": "deployment"}), spec=client.V1PodSpec(containers=[container])) # Spec - spec = client.ExtensionsV1beta1DeploymentSpec( + spec = client.V1DeploymentSpec( replicas=1, template=template) # Deployment - deployment = client.ExtensionsV1beta1Deployment( - api_version="extensions/v1beta1", + deployment = client.V1Deployment( + api_version="apps/v1", kind="Deployment", metadata=client.V1ObjectMeta(name="deployment"), spec=spec) # Creation of the Deployment in specified namespace # (Can replace "default" with a namespace you may have created) - extensions_v1_beta1.create_namespaced_deployment( + apps_v1_api.create_namespaced_deployment( namespace="default", body=deployment ) @@ -64,20 +64,20 @@ def create_service(): core_v1_api.create_namespaced_service(namespace="default", body=body) -def create_ingress(extensions_v1_beta1): - body = client.ExtensionsV1beta1Ingress( +def create_ingress(networking_v1_beta1_api): + body = client.NetworkingV1beta1Ingress( api_version="networking.k8s.io/v1beta1", kind="Ingress", metadata=client.V1ObjectMeta(name="ingress-example", annotations={ "nginx.ingress.kubernetes.io/rewrite-target": "/" }), - spec=client.ExtensionsV1beta1IngressSpec( - rules=[client.ExtensionsV1beta1IngressRule( + spec=client.NetworkingV1beta1IngressSpec( + rules=[client.NetworkingV1beta1IngressRule( host="boddulabs.com", - http=client.ExtensionsV1beta1HTTPIngressRuleValue( - paths=[client.ExtensionsV1beta1HTTPIngressPath( + http=client.NetworkingV1beta1HTTPIngressRuleValue( + paths=[client.NetworkingV1beta1HTTPIngressPath( path="/", - backend=client.ExtensionsV1beta1IngressBackend( + backend=client.NetworkingV1beta1IngressBackend( service_port=5678, service_name="service-example") @@ -89,7 +89,7 @@ def create_ingress(extensions_v1_beta1): ) # Creation of the Deployment in specified namespace # (Can replace "default" with a namespace you may have created) - extensions_v1_beta1.create_namespaced_ingress( + networking_v1_beta1_api.create_namespaced_ingress( namespace="default", body=body ) @@ -98,13 +98,12 @@ def create_ingress(extensions_v1_beta1): def main(): # Fetching and loading local Kubernetes Information config.load_kube_config() - # For incluster details - # config.load_incluster_config() - extensions_v1_beta1 = client.ExtensionsV1beta1Api() + apps_v1_api = client.AppsV1Api() + networking_v1_beta1_api = client.NetworkingV1beta1Api() - create_deployment(extensions_v1_beta1) + create_deployment(apps_v1_api) create_service() - create_ingress(extensions_v1_beta1) + create_ingress(networking_v1_beta1_api) if __name__ == "__main__": From 8b385a87dc83fc66bf16d4f36169d82a8df3dbbe Mon Sep 17 00:00:00 2001 From: Julian Taylor Date: Mon, 26 Aug 2019 21:48:35 +0200 Subject: [PATCH 44/53] add function to parse canonical quantities (e.g. resources) This utility function is useful to parse values like the 200m or 300Gi memory and cpu resources stored in kubernetes manifests. It uses Decimal as output format as it usually represents typical input values more accurately and reduces rounding errors. --- kubernetes/test/test_quantity.py | 112 +++++++++++++++++++++++++++++++ kubernetes/utils/__init__.py | 1 + kubernetes/utils/quantity.py | 75 +++++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 kubernetes/test/test_quantity.py create mode 100644 kubernetes/utils/quantity.py diff --git a/kubernetes/test/test_quantity.py b/kubernetes/test/test_quantity.py new file mode 100644 index 000000000..35bef5661 --- /dev/null +++ b/kubernetes/test/test_quantity.py @@ -0,0 +1,112 @@ +# coding: utf-8 +# Copyright 2019 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import + +import unittest +from kubernetes.utils import parse_quantity +from decimal import Decimal + + +class TestQuantity(unittest.TestCase): + def test_parse(self): + self.assertIsInstance(parse_quantity(2.2), Decimal) + # input, expected output + tests = [ + (0, 0), + (2, 2), + (2, Decimal("2")), + (2., 2), + (Decimal("2.2"), Decimal("2.2")), + (2., Decimal(2)), + (Decimal("2."), 2), + ("123", 123), + ("2", 2), + ("2n", Decimal("2") * Decimal(1000)**-3), + ("2u", Decimal("0.000002")), + ("2m", Decimal("0.002")), + ("0m", Decimal("0")), + ("0M", Decimal("0")), + ("223k", 223000), + ("002M", 2 * 1000**2), + ("2M", 2 * 1000**2), + ("4123G", 4123 * 1000**3), + ("2T", 2 * 1000**4), + ("2P", 2 * 1000**5), + ("2E", 2 * 1000**6), + + ("223Ki", 223 * 1024), + ("002Mi", 2 * 1024**2), + ("2Mi", 2 * 1024**2), + ("2Gi", 2 * 1024**3), + ("4123Gi", 4123 * 1024**3), + ("2Ti", 2 * 1024**4), + ("2Pi", 2 * 1024**5), + ("2Ei", 2 * 1024**6), + + ("2.34n", Decimal("2.34") * Decimal(1000)**-3), + ("2.34u", Decimal("2.34") * Decimal(1000)**-2), + ("2.34m", Decimal("2.34") * Decimal(1000)**-1), + ("2.34Ki", Decimal("2.34") * 1024), + ("2.34", Decimal("2.34")), + (".34", Decimal("0.34")), + ("34.", 34), + (".34M", Decimal("0.34") * 1000**2), + + ("2e2K", Decimal("2e2") * 1000), + ("2e2Ki", Decimal("2e2") * 1024), + ("2e-2Ki", Decimal("2e-2") * 1024), + ("2.34E1", Decimal("2.34E1")), + (".34e-2", Decimal("0.34e-2")), + ] + + for inp, out in tests: + self.assertEqual(parse_quantity(inp), out) + if isinstance(inp, (int, float, Decimal)): + self.assertEqual(parse_quantity(-1 * inp), -out) + else: + self.assertEqual(parse_quantity("-" + inp), -out) + self.assertEqual(parse_quantity("+" + inp), out) + + def test_parse_invalid(self): + self.assertRaises(ValueError, parse_quantity, []) + self.assertRaises(ValueError, parse_quantity, "") + self.assertRaises(ValueError, parse_quantity, "-") + self.assertRaises(ValueError, parse_quantity, "i") + self.assertRaises(ValueError, parse_quantity, "2i") + self.assertRaises(ValueError, parse_quantity, "2mm") + self.assertRaises(ValueError, parse_quantity, "2mmKi") + self.assertRaises(ValueError, parse_quantity, "2KKi") + self.assertRaises(ValueError, parse_quantity, "2e") + self.assertRaises(ValueError, parse_quantity, "2.2i") + self.assertRaises(ValueError, parse_quantity, "bla") + self.assertRaises(ValueError, parse_quantity, "Ki") + self.assertRaises(ValueError, parse_quantity, "M") + self.assertRaises(ValueError, parse_quantity, "2ki") + self.assertRaises(ValueError, parse_quantity, "2Ki ") + self.assertRaises(ValueError, parse_quantity, "20Ki ") + self.assertRaises(ValueError, parse_quantity, "20B") + self.assertRaises(ValueError, parse_quantity, "20Bi") + self.assertRaises(ValueError, parse_quantity, "20.2Bi") + self.assertRaises(ValueError, parse_quantity, "2MiKi") + self.assertRaises(ValueError, parse_quantity, "2MK") + self.assertRaises(ValueError, parse_quantity, "2MKi") + self.assertRaises(ValueError, parse_quantity, "234df") + self.assertRaises(ValueError, parse_quantity, "df234") + self.assertRaises(ValueError, parse_quantity, tuple()) + + +if __name__ == '__main__': + unittest.main() diff --git a/kubernetes/utils/__init__.py b/kubernetes/utils/__init__.py index 72f55c751..8add80bcf 100644 --- a/kubernetes/utils/__init__.py +++ b/kubernetes/utils/__init__.py @@ -16,3 +16,4 @@ from __future__ import absolute_import from .create_from_yaml import (FailToCreateError, create_from_dict, create_from_yaml) +from .quantity import parse_quantity diff --git a/kubernetes/utils/quantity.py b/kubernetes/utils/quantity.py new file mode 100644 index 000000000..df373ae46 --- /dev/null +++ b/kubernetes/utils/quantity.py @@ -0,0 +1,75 @@ +# Copyright 2019 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from decimal import Decimal, InvalidOperation + + +def parse_quantity(quantity): + """ + Parse kubernetes canonical form quantity like 200Mi to a decimal number. + Supported SI suffixes: + base1024: Ki | Mi | Gi | Ti | Pi | Ei + base1000: n | u | m | "" | k | M | G | T | P | E + + See https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go + + Input: + quanity: string. kubernetes canonical form quantity + + Returns: + Decimal + + Raises: + ValueError on invalid or unknown input + """ + if isinstance(quantity, (int, float, Decimal)): + return Decimal(quantity) + + exponents = {"n": -3, "u": -2, "m": -1, "K": 1, "k": 1, "M": 2, + "G": 3, "T": 4, "P": 5, "E": 6} + + quantity = str(quantity) + number = quantity + suffix = None + if len(quantity) >= 2 and quantity[-1] == "i": + if quantity[-2] in exponents: + number = quantity[:-2] + suffix = quantity[-2:] + elif len(quantity) >= 1 and quantity[-1] in exponents: + number = quantity[:-1] + suffix = quantity[-1:] + + try: + number = Decimal(number) + except InvalidOperation: + raise ValueError("Invalid number format: {}".format(number)) + + if suffix is None: + return number + + if suffix.endswith("i"): + base = 1024 + elif len(suffix) == 1: + base = 1000 + else: + raise ValueError("{} has unknown suffix".format(quantity)) + + # handly SI inconsistency + if suffix == "ki": + raise ValueError("{} has unknown suffix".format(quantity)) + + if suffix[0] not in exponents: + raise ValueError("{} has unknown suffix".format(quantity)) + + exponent = Decimal(exponents[suffix[0]]) + return number * (base ** exponent) From 04c499c3957d4dbe4dc92d260c466f9bc7559f09 Mon Sep 17 00:00:00 2001 From: Scott Lee Date: Tue, 20 Aug 2019 18:48:29 -0700 Subject: [PATCH 45/53] 884: Cleanup examples folder --- README.md | 4 +- examples/README.md | 17 +++ examples/__init__.py | 2 +- examples/{example3.py => api_discovery.py} | 5 + examples/custom_object.py | 47 +++---- ...ate_deployment.py => deployment_create.py} | 0 ...loyment_examples.py => deployment_crud.py} | 3 +- examples/exec.py | 97 ------------- examples/in_cluster_config.py | 71 +++++----- .../{ingress-example.py => ingress_create.py} | 7 +- examples/{job_examples.py => job_crud.py} | 7 +- examples/multiple_clusters.py | 44 +++--- .../{manage_node_labels.py => node_labels.py} | 13 +- examples/notebooks/test.ipynb | 104 -------------- examples/notebooks/watch_notebook.ipynb | 129 ------------------ .../{example1.py => out_of_cluster_config.py} | 4 + examples/pi-job.yaml | 2 - ...xample4.py => pick_kube_config_context.py} | 10 +- examples/pod_config_list.py | 54 ++++++++ examples/pod_exec.py | 129 ++++++++++++++++++ .../{example2.py => pod_namespace_watch.py} | 19 ++- examples/remote_cluster.py | 2 +- 22 files changed, 337 insertions(+), 433 deletions(-) create mode 100644 examples/README.md rename examples/{example3.py => api_discovery.py} (92%) rename examples/{create_deployment.py => deployment_create.py} (100%) rename examples/{deployment_examples.py => deployment_crud.py} (97%) delete mode 100644 examples/exec.py rename examples/{ingress-example.py => ingress_create.py} (95%) rename examples/{job_examples.py => job_crud.py} (98%) rename examples/{manage_node_labels.py => node_labels.py} (82%) delete mode 100644 examples/notebooks/test.ipynb delete mode 100644 examples/notebooks/watch_notebook.ipynb rename examples/{example1.py => out_of_cluster_config.py} (93%) rename examples/{example4.py => pick_kube_config_context.py} (88%) create mode 100644 examples/pod_config_list.py create mode 100644 examples/pod_exec.py rename examples/{example2.py => pod_namespace_watch.py} (63%) diff --git a/README.md b/README.md index 732941f09..99f2a8ddd 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ More examples can be found in [examples](examples/) folder. To run examples, run python -m examples.example1 ``` -(replace example1 with the example base filename) +(replace example1 with one of the filenames in the examples folder) ## Documentation @@ -178,4 +178,4 @@ Specifically check `ipaddress` and `urllib3` package versions to make sure they Starting from 4.0 release, we do not support directly calling exec or attach calls. you should use stream module to call them. so instead of `resp = api.connect_get_namespaced_pod_exec(name, ...` you should call `resp = stream(api.connect_get_namespaced_pod_exec, name, ...`. -See more at [exec example](examples/exec.py). +See more at [exec example](examples/pod_exec.py). diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..3fac49785 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,17 @@ +# Python Client Examples + +This directory contains various examples how to use the Python client. Please +read the description at the top of each script for more information about what +it does and any prequisite steps. Most scripts also include comments throughout +the code. + +## Setup + +These scripts require Python 2.7 or 3.5+ and the Kubernetes client which can be +installed via the directions +[here](https://github.com/kubernetes-client/python#installation). + +## Contributions + +If you find a problem please file an +[issue](https://github.com/kubernetes-client/python/issues). diff --git a/examples/__init__.py b/examples/__init__.py index 13d0123d1..a010399d9 100644 --- a/examples/__init__.py +++ b/examples/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Empty init file to make examples folder a python module. +# Empty init file to make examples folder a python module diff --git a/examples/example3.py b/examples/api_discovery.py similarity index 92% rename from examples/example3.py rename to examples/api_discovery.py index 2ee3685b5..8c040ecc0 100644 --- a/examples/example3.py +++ b/examples/api_discovery.py @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Reads the list of the available API versions and prints them. +Similar to running `kubectl api-versions`. +""" + from kubernetes import client, config diff --git a/examples/custom_object.py b/examples/custom_object.py index 87524c006..0c2b36aef 100644 --- a/examples/custom_object.py +++ b/examples/custom_object.py @@ -12,29 +12,31 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This example use an example CRD from this tutorial: -# https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/ -# -# The following yaml manifest has to be applied first: -# -# apiVersion: apiextensions.k8s.io/v1beta1 -# kind: CustomResourceDefinition -# metadata: -# name: crontabs.stable.example.com -# spec: -# group: stable.example.com -# versions: -# - name: v1 -# served: true -# storage: true -# scope: Namespaced -# names: -# plural: crontabs -# singular: crontab -# kind: CronTab -# shortNames: -# - ct +""" +Uses a Custom Resource Definition (CRD) to create a custom object, in this case +a CronTab. This example use an example CRD from this tutorial: +https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/ +The following yaml manifest has to be applied first: + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: crontabs.stable.example.com +spec: + group: stable.example.com + versions: + - name: v1 + served: true + storage: true + scope: Namespaced + names: + plural: crontabs + singular: crontab + kind: CronTab + shortNames: + - ct +""" from pprint import pprint @@ -42,7 +44,6 @@ from kubernetes import client, config def main(): - config.load_kube_config() api = client.CustomObjectsApi() diff --git a/examples/create_deployment.py b/examples/deployment_create.py similarity index 100% rename from examples/create_deployment.py rename to examples/deployment_create.py diff --git a/examples/deployment_examples.py b/examples/deployment_crud.py similarity index 97% rename from examples/deployment_examples.py rename to examples/deployment_crud.py index 9d354f7d3..a25bb518a 100644 --- a/examples/deployment_examples.py +++ b/examples/deployment_crud.py @@ -13,8 +13,7 @@ # limitations under the License. """ -This example shows how to work with AppsV1Api to create, modify and delete -deployments +Creates, updates, and deletes a deployment using AppsV1Api. """ from kubernetes import client, config diff --git a/examples/exec.py b/examples/exec.py deleted file mode 100644 index d1f9e9e30..000000000 --- a/examples/exec.py +++ /dev/null @@ -1,97 +0,0 @@ -import time - -from kubernetes import config -from kubernetes.client import Configuration -from kubernetes.client.apis import core_v1_api -from kubernetes.client.rest import ApiException -from kubernetes.stream import stream - -config.load_kube_config() -c = Configuration() -c.assert_hostname = False -Configuration.set_default(c) -api = core_v1_api.CoreV1Api() -name = 'busybox-test' - -resp = None -try: - resp = api.read_namespaced_pod(name=name, - namespace='default') -except ApiException as e: - if e.status != 404: - print("Unknown error: %s" % e) - exit(1) - -if not resp: - print("Pod %s does not exist. Creating it..." % name) - pod_manifest = { - 'apiVersion': 'v1', - 'kind': 'Pod', - 'metadata': { - 'name': name - }, - 'spec': { - 'containers': [{ - 'image': 'busybox', - 'name': 'sleep', - "args": [ - "/bin/sh", - "-c", - "while true;do date;sleep 5; done" - ] - }] - } - } - resp = api.create_namespaced_pod(body=pod_manifest, - namespace='default') - while True: - resp = api.read_namespaced_pod(name=name, - namespace='default') - if resp.status.phase != 'Pending': - break - time.sleep(1) - print("Done.") - - -# calling exec and wait for response. -exec_command = [ - '/bin/sh', - '-c', - 'echo This message goes to stderr >&2; echo This message goes to stdout'] -resp = stream(api.connect_get_namespaced_pod_exec, name, 'default', - command=exec_command, - stderr=True, stdin=False, - stdout=True, tty=False) -print("Response: " + resp) - -# Calling exec interactively. -exec_command = ['/bin/sh'] -resp = stream(api.connect_get_namespaced_pod_exec, name, 'default', - command=exec_command, - stderr=True, stdin=True, - stdout=True, tty=False, - _preload_content=False) -commands = [ - "echo test1", - "echo \"This message goes to stderr\" >&2", -] -while resp.is_open(): - resp.update(timeout=1) - if resp.peek_stdout(): - print("STDOUT: %s" % resp.read_stdout()) - if resp.peek_stderr(): - print("STDERR: %s" % resp.read_stderr()) - if commands: - c = commands.pop(0) - print("Running command... %s\n" % c) - resp.write_stdin(c + "\n") - else: - break - -resp.write_stdin("date\n") -sdate = resp.readline_stdout(timeout=3) -print("Server date command returns: %s" % sdate) -resp.write_stdin("whoami\n") -user = resp.readline_stdout(timeout=3) -print("Server user is: %s" % user) -resp.close() diff --git a/examples/in_cluster_config.py b/examples/in_cluster_config.py index 86b8704e2..9aafbb27b 100644 --- a/examples/in_cluster_config.py +++ b/examples/in_cluster_config.py @@ -12,47 +12,46 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Simple example to show loading config from the cluster -# -# It works only from a pod. You can start an image with Python -# (for example python:latest), exec into the pod, install the library, -# then try out this example. -# -# If you get 403 errors from API server you will have to configure -# RBAC to add the permission to list pods. -# -# --- -# kind: ClusterRole -# apiVersion: rbac.authorization.k8s.io/v1 -# metadata: -# name: pods-list -# rules: -# - apiGroups: [""] -# resources: ["pods"] -# verbs: ["list"] -# --- -# kind: ClusterRoleBinding -# apiVersion: rbac.authorization.k8s.io/v1 -# metadata: -# name: pods-list -# subjects: -# - kind: ServiceAccount -# name: default -# namespace: default -# roleRef: -# kind: ClusterRole -# name: pods-list -# apiGroup: rbac.authorization.k8s.io -# --- -# -# Doc: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ +""" +Showcases loading the Kubernetes config from within the cluster. This script +must be run within a pod. You can start a pod with a Python image (for +example, `python:latest`), exec into the pod, install the library, then run +this example. + +If you get 403 errors from the API server you will have to configure RBAC to +add the permission to list pods by applying the following manifest: + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: pods-list +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["list"] + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: pods-list +subjects: +- kind: ServiceAccount + name: default + namespace: default +roleRef: + kind: ClusterRole + name: pods-list + apiGroup: rbac.authorization.k8s.io + +Documentation: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ +""" from kubernetes import client, config def main(): - - # it works only if this script is run by K8s as a POD config.load_incluster_config() v1 = client.CoreV1Api() diff --git a/examples/ingress-example.py b/examples/ingress_create.py similarity index 95% rename from examples/ingress-example.py rename to examples/ingress_create.py index 35b9385cb..cf7ca732e 100644 --- a/examples/ingress-example.py +++ b/examples/ingress_create.py @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Creates deployment, service, and ingress objects. The ingress allows external +network access within the cluster. +""" + from kubernetes import client, config @@ -73,7 +78,7 @@ def create_ingress(networking_v1_beta1_api): }), spec=client.NetworkingV1beta1IngressSpec( rules=[client.NetworkingV1beta1IngressRule( - host="boddulabs.com", + host="example.com", http=client.NetworkingV1beta1HTTPIngressRuleValue( paths=[client.NetworkingV1beta1HTTPIngressPath( path="/", diff --git a/examples/job_examples.py b/examples/job_crud.py similarity index 98% rename from examples/job_examples.py rename to examples/job_crud.py index 39a71b892..521050ee2 100644 --- a/examples/job_examples.py +++ b/examples/job_crud.py @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Creates, updates, and deletes a Job object. +""" + from os import path import yaml @@ -46,7 +50,6 @@ def create_job_object(): def create_job(api_instance, job): - # Create job api_response = api_instance.create_namespaced_job( body=job, namespace="default") @@ -56,7 +59,6 @@ def create_job(api_instance, job): def update_job(api_instance, job): # Update container image job.spec.template.spec.containers[0].image = "perl" - # Update the job api_response = api_instance.patch_namespaced_job( name=JOB_NAME, namespace="default", @@ -65,7 +67,6 @@ def update_job(api_instance, job): def delete_job(api_instance): - # Delete job api_response = api_instance.delete_namespaced_job( name=JOB_NAME, namespace="default", diff --git a/examples/multiple_clusters.py b/examples/multiple_clusters.py index 68a5d2ff8..962639669 100644 --- a/examples/multiple_clusters.py +++ b/examples/multiple_clusters.py @@ -12,39 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Allows you to pick a context and then lists all pods in the chosen context. + +Please install the pick library before running this example. +""" + from kubernetes import client, config -# install pick using "pip install pick". It is not included -# as a dependency because it only used in examples -from pick import pick +from kubernetes.client import configuration +from pick import pick # install pick using `pip install pick` def main(): - contexts, active_context = config.list_kube_config_contexts() if not contexts: print("Cannot find any context in kube-config file.") return contexts = [context['name'] for context in contexts] active_index = contexts.index(active_context['name']) - cluster1, first_index = pick(contexts, title="Pick the first context", - default_index=active_index) - cluster2, _ = pick(contexts, title="Pick the second context", - default_index=first_index) + option, _ = pick(contexts, title="Pick the context to load", + default_index=active_index) + # Configs can be set in Configuration class directly or using helper + # utility + config.load_kube_config(context=option) - client1 = client.CoreV1Api( - api_client=config.new_client_from_config(context=cluster1)) - client2 = client.CoreV1Api( - api_client=config.new_client_from_config(context=cluster2)) + print("Active host is %s" % configuration.Configuration().host) - print("\nList of pods on %s:" % cluster1) - for i in client1.list_pod_for_all_namespaces().items: - print("%s\t%s\t%s" % - (i.status.pod_ip, i.metadata.namespace, i.metadata.name)) - - print("\n\nList of pods on %s:" % cluster2) - for i in client2.list_pod_for_all_namespaces().items: - print("%s\t%s\t%s" % - (i.status.pod_ip, i.metadata.namespace, i.metadata.name)) + v1 = client.CoreV1Api() + print("Listing pods with their IPs:") + ret = v1.list_pod_for_all_namespaces(watch=False) + for item in ret.items: + print( + "%s\t%s\t%s" % + (item.status.pod_ip, + item.metadata.namespace, + item.metadata.name)) if __name__ == '__main__': diff --git a/examples/manage_node_labels.py b/examples/node_labels.py similarity index 82% rename from examples/manage_node_labels.py rename to examples/node_labels.py index e350ad372..22ac3197a 100644 --- a/examples/manage_node_labels.py +++ b/examples/node_labels.py @@ -12,19 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Changes the labels of the "minikube" node. Adds the label "foo" with value +"bar" and will overwrite the "foo" label if it already exists. Removes the +label "baz". +""" + from pprint import pprint from kubernetes import client, config def main(): - """ - Change labels of the "minikube" node: - - Add label "foo" with value "bar". This will overwrite the "foo" label - if it already exists. - - Remove the label "baz" from the node. - """ - config.load_kube_config() api_instance = client.CoreV1Api() diff --git a/examples/notebooks/test.ipynb b/examples/notebooks/test.ipynb deleted file mode 100644 index 9f7aa6b83..000000000 --- a/examples/notebooks/test.ipynb +++ /dev/null @@ -1,104 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from kubernetes import client, config" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "config.load_incluster_config()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "v1=client.CoreV1Api()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "default\n", - "kube-system\n", - "kubeless\n" - ] - } - ], - "source": [ - "for ns in v1.list_namespace().items:\n", - " print ns.metadata.name" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} - diff --git a/examples/notebooks/watch_notebook.ipynb b/examples/notebooks/watch_notebook.ipynb deleted file mode 100644 index e7ec43c67..000000000 --- a/examples/notebooks/watch_notebook.ipynb +++ /dev/null @@ -1,129 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "How to watch changes to an object\n", - "==================\n", - "\n", - "In this notebook, we learn how kubernetes API resource Watch endpoint is used to observe resource changes. It can be used to get information about changes to any kubernetes object." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "from kubernetes import client, config, watch" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load config from default location." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "config.load_kube_config()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "### Create API instance" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "api_instance = client.CoreV1Api()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "### Run a Watch on the Pods endpoint. \n", - "Watch would be executed and produce output about changes to any Pod. After running the cell below, You can test this by running the Pod notebook [create_pod.ipynb](create_pod.ipynb) and observing the additional output here. You can stop the cell from running by restarting the kernel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "w = watch.Watch()\n", - "for event in w.stream(api_instance.list_pod_for_all_namespaces):\n", - " print(\"Event: %s %s %s\" % (event['type'],event['object'].kind, event['object'].metadata.name))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/example1.py b/examples/out_of_cluster_config.py similarity index 93% rename from examples/example1.py rename to examples/out_of_cluster_config.py index 214fd190f..be7d271ab 100644 --- a/examples/example1.py +++ b/examples/out_of_cluster_config.py @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Showcases loading the Kubernetes config from outside of the cluster. +""" + from kubernetes import client, config diff --git a/examples/pi-job.yaml b/examples/pi-job.yaml index dc736f2da..ee1d89fdd 100644 --- a/examples/pi-job.yaml +++ b/examples/pi-job.yaml @@ -11,5 +11,3 @@ spec: command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restartPolicy: Never backoffLimit: 4 - - diff --git a/examples/example4.py b/examples/pick_kube_config_context.py similarity index 88% rename from examples/example4.py rename to examples/pick_kube_config_context.py index 334e282c2..962639669 100644 --- a/examples/example4.py +++ b/examples/pick_kube_config_context.py @@ -12,11 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Allows you to pick a context and then lists all pods in the chosen context. + +Please install the pick library before running this example. +""" + from kubernetes import client, config from kubernetes.client import configuration -# install pick using "pip install pick". It is not included -# as a dependency because it only used in examples -from pick import pick +from pick import pick # install pick using `pip install pick` def main(): diff --git a/examples/pod_config_list.py b/examples/pod_config_list.py new file mode 100644 index 000000000..09bbde9b6 --- /dev/null +++ b/examples/pod_config_list.py @@ -0,0 +1,54 @@ +# Copyright 2016 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Allows you to pick a context and then lists all pods in the chosen context. A +context includes a cluster, a user, and a namespace. + +Please install the pick library before running this example. +""" + +from kubernetes import client, config +from kubernetes.client import configuration +from pick import pick # install pick using `pip install pick` + + +def main(): + contexts, active_context = config.list_kube_config_contexts() + if not contexts: + print("Cannot find any context in kube-config file.") + return + contexts = [context['name'] for context in contexts] + active_index = contexts.index(active_context['name']) + option, _ = pick(contexts, title="Pick the context to load", + default_index=active_index) + # Configs can be set in Configuration class directly or using helper + # utility + config.load_kube_config(context=option) + + print("Active host is %s" % configuration.Configuration().host) + + v1 = client.CoreV1Api() + print("Listing pods with their IPs:") + ret = v1.list_pod_for_all_namespaces(watch=False) + for item in ret.items: + print( + "%s\t%s\t%s" % + (item.status.pod_ip, + item.metadata.namespace, + item.metadata.name)) + + +if __name__ == '__main__': + main() diff --git a/examples/pod_exec.py b/examples/pod_exec.py new file mode 100644 index 000000000..4e6d85ded --- /dev/null +++ b/examples/pod_exec.py @@ -0,0 +1,129 @@ +# Copyright 2019 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Showcases the functionality of exec using a Busybox container. +""" + +import time + +from kubernetes import config +from kubernetes.client import Configuration +from kubernetes.client.apis import core_v1_api +from kubernetes.client.rest import ApiException +from kubernetes.stream import stream + + +def exec_commands(api_instance): + name = 'busybox-test' + resp = None + try: + resp = api_instance.read_namespaced_pod(name=name, + namespace='default') + except ApiException as e: + if e.status != 404: + print("Unknown error: %s" % e) + exit(1) + + if not resp: + print("Pod %s does not exist. Creating it..." % name) + pod_manifest = { + 'apiVersion': 'v1', + 'kind': 'Pod', + 'metadata': { + 'name': name + }, + 'spec': { + 'containers': [{ + 'image': 'busybox', + 'name': 'sleep', + "args": [ + "/bin/sh", + "-c", + "while true;do date;sleep 5; done" + ] + }] + } + } + resp = api_instance.create_namespaced_pod(body=pod_manifest, + namespace='default') + while True: + resp = api_instance.read_namespaced_pod(name=name, + namespace='default') + if resp.status.phase != 'Pending': + break + time.sleep(1) + print("Done.") + + # Calling exec and waiting for response + exec_command = [ + '/bin/sh', + '-c', + 'echo This message goes to stderr; echo This message goes to stdout'] + resp = stream(api_instance.connect_get_namespaced_pod_exec, + name, + 'default', + command=exec_command, + stderr=True, stdin=False, + stdout=True, tty=False) + print("Response: " + resp) + + # Calling exec interactively + exec_command = ['/bin/sh'] + resp = stream(api_instance.connect_get_namespaced_pod_exec, + name, + 'default', + command=exec_command, + stderr=True, stdin=True, + stdout=True, tty=False, + _preload_content=False) + commands = [ + "echo This message goes to stdout", + "echo \"This message goes to stderr\" >&2", + ] + + while resp.is_open(): + resp.update(timeout=1) + if resp.peek_stdout(): + print("STDOUT: %s" % resp.read_stdout()) + if resp.peek_stderr(): + print("STDERR: %s" % resp.read_stderr()) + if commands: + c = commands.pop(0) + print("Running command... %s\n" % c) + resp.write_stdin(c + "\n") + else: + break + + resp.write_stdin("date\n") + sdate = resp.readline_stdout(timeout=3) + print("Server date command returns: %s" % sdate) + resp.write_stdin("whoami\n") + user = resp.readline_stdout(timeout=3) + print("Server user is: %s" % user) + resp.close() + + +def main(): + config.load_kube_config() + c = Configuration() + c.assert_hostname = False + Configuration.set_default(c) + core_v1 = core_v1_api.CoreV1Api() + + exec_commands(core_v1) + + +if __name__ == '__main__': + main() diff --git a/examples/example2.py b/examples/pod_namespace_watch.py similarity index 63% rename from examples/example2.py rename to examples/pod_namespace_watch.py index 003d5c960..5a87792ea 100644 --- a/examples/example2.py +++ b/examples/pod_namespace_watch.py @@ -12,6 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Uses watch to print the stream of events from list namespaces and list pods. +The script will wait for 10 events related to namespaces to occur within +the `timeout_seconds` threshold and then move on to waiting for 10 events +related to pods to occur within the `timeout_seconds` threshold. +""" + from kubernetes import client, config, watch @@ -29,8 +36,18 @@ def main(): count -= 1 if not count: w.stop() + print("Finished namespace stream.") - print("Ended.") + for event in w.stream(v1.list_pod_for_all_namespaces, timeout_seconds=10): + print("Event: %s %s %s" % ( + event['type'], + event['object'].kind, + event['object'].metadata.name) + ) + count -= 1 + if not count: + w.stop() + print("Finished pod stream.") if __name__ == '__main__': diff --git a/examples/remote_cluster.py b/examples/remote_cluster.py index 8cf39efec..b72b39b4e 100644 --- a/examples/remote_cluster.py +++ b/examples/remote_cluster.py @@ -23,7 +23,7 @@ def main(): # Define the barer token we are going to use to authenticate. # See here to create the token: # https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/ - aToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + aToken = "" # Create a configuration object aConfiguration = client.Configuration() From 2958cf0195581cbdf4c1a142610666a2e1cabd33 Mon Sep 17 00:00:00 2001 From: Scott Lee Date: Fri, 13 Sep 2019 16:16:54 -0700 Subject: [PATCH 46/53] Address PR comments --- examples/README.md | 10 +++++----- examples/api_discovery.py | 4 ++-- examples/in_cluster_config.py | 4 ++-- examples/ingress_create.py | 2 +- examples/job_crud.py | 2 +- examples/multiple_clusters.py | 32 ++++++++++++++++--------------- examples/out_of_cluster_config.py | 2 +- examples/pod_exec.py | 2 +- examples/pod_namespace_watch.py | 2 +- 9 files changed, 31 insertions(+), 29 deletions(-) diff --git a/examples/README.md b/examples/README.md index 3fac49785..0c4d513e8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,14 +1,14 @@ # Python Client Examples -This directory contains various examples how to use the Python client. Please -read the description at the top of each script for more information about what -it does and any prequisite steps. Most scripts also include comments throughout -the code. +This directory contains various examples of how to use the Python client. +Please read the description at the top of each example for more information +about what the script does and any prequisites. Most scripts also include +comments throughout the code. ## Setup These scripts require Python 2.7 or 3.5+ and the Kubernetes client which can be -installed via the directions +installed following the directions [here](https://github.com/kubernetes-client/python#installation). ## Contributions diff --git a/examples/api_discovery.py b/examples/api_discovery.py index 8c040ecc0..9c91fe429 100644 --- a/examples/api_discovery.py +++ b/examples/api_discovery.py @@ -13,8 +13,8 @@ # limitations under the License. """ -Reads the list of the available API versions and prints them. -Similar to running `kubectl api-versions`. +Reads the list of available API versions and prints them. Similar to running +`kubectl api-versions`. """ from kubernetes import client, config diff --git a/examples/in_cluster_config.py b/examples/in_cluster_config.py index 9aafbb27b..55f9eb792 100644 --- a/examples/in_cluster_config.py +++ b/examples/in_cluster_config.py @@ -13,13 +13,13 @@ # limitations under the License. """ -Showcases loading the Kubernetes config from within the cluster. This script +Shows how to load a Kubernetes config from within a cluster. This script must be run within a pod. You can start a pod with a Python image (for example, `python:latest`), exec into the pod, install the library, then run this example. If you get 403 errors from the API server you will have to configure RBAC to -add the permission to list pods by applying the following manifest: +add permission to list pods by applying the following manifest: --- kind: ClusterRole diff --git a/examples/ingress_create.py b/examples/ingress_create.py index cf7ca732e..fa5739c8f 100644 --- a/examples/ingress_create.py +++ b/examples/ingress_create.py @@ -14,7 +14,7 @@ """ Creates deployment, service, and ingress objects. The ingress allows external -network access within the cluster. +network access to the cluster. """ from kubernetes import client, config diff --git a/examples/job_crud.py b/examples/job_crud.py index 521050ee2..b18b152d4 100644 --- a/examples/job_crud.py +++ b/examples/job_crud.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Creates, updates, and deletes a Job object. +Creates, updates, and deletes a job object. """ from os import path diff --git a/examples/multiple_clusters.py b/examples/multiple_clusters.py index 962639669..94b0458cd 100644 --- a/examples/multiple_clusters.py +++ b/examples/multiple_clusters.py @@ -30,23 +30,25 @@ def main(): return contexts = [context['name'] for context in contexts] active_index = contexts.index(active_context['name']) - option, _ = pick(contexts, title="Pick the context to load", - default_index=active_index) - # Configs can be set in Configuration class directly or using helper - # utility - config.load_kube_config(context=option) + cluster1, first_index = pick(contexts, title="Pick the first context", + default_index=active_index) + cluster2, _ = pick(contexts, title="Pick the second context", + default_index=first_index) - print("Active host is %s" % configuration.Configuration().host) + client1 = client.CoreV1Api( + api_client=config.new_client_from_config(context=cluster1)) + client2 = client.CoreV1Api( + api_client=config.new_client_from_config(context=cluster2)) - v1 = client.CoreV1Api() - print("Listing pods with their IPs:") - ret = v1.list_pod_for_all_namespaces(watch=False) - for item in ret.items: - print( - "%s\t%s\t%s" % - (item.status.pod_ip, - item.metadata.namespace, - item.metadata.name)) + print("\nList of pods on %s:" % cluster1) + for i in client1.list_pod_for_all_namespaces().items: + print("%s\t%s\t%s" % + (i.status.pod_ip, i.metadata.namespace, i.metadata.name)) + + print("\n\nList of pods on %s:" % cluster2) + for i in client2.list_pod_for_all_namespaces().items: + print("%s\t%s\t%s" % + (i.status.pod_ip, i.metadata.namespace, i.metadata.name)) if __name__ == '__main__': diff --git a/examples/out_of_cluster_config.py b/examples/out_of_cluster_config.py index be7d271ab..f391be236 100644 --- a/examples/out_of_cluster_config.py +++ b/examples/out_of_cluster_config.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Showcases loading the Kubernetes config from outside of the cluster. +Shows how to load a Kubernetes config from outside of the cluster. """ from kubernetes import client, config diff --git a/examples/pod_exec.py b/examples/pod_exec.py index 4e6d85ded..98b717f4a 100644 --- a/examples/pod_exec.py +++ b/examples/pod_exec.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Showcases the functionality of exec using a Busybox container. +Shows the functionality of exec using a Busybox container. """ import time diff --git a/examples/pod_namespace_watch.py b/examples/pod_namespace_watch.py index 5a87792ea..f09768cf7 100644 --- a/examples/pod_namespace_watch.py +++ b/examples/pod_namespace_watch.py @@ -15,7 +15,7 @@ """ Uses watch to print the stream of events from list namespaces and list pods. The script will wait for 10 events related to namespaces to occur within -the `timeout_seconds` threshold and then move on to waiting for 10 events +the `timeout_seconds` threshold and then move on to wait for another 10 events related to pods to occur within the `timeout_seconds` threshold. """ From f4634711ec2decf273e915d42e43824b64a15b87 Mon Sep 17 00:00:00 2001 From: "Haowei Cai (Roy)" Date: Tue, 17 Sep 2019 17:06:02 -0700 Subject: [PATCH 47/53] Dummy change to trigger readthedocs build trigger the build in https://readthedocs.org/projects/kubernetes/builds/ through the webhook integration From 752373b1f9c8f1d0e38d193ff8e0f1d4c652aafc Mon Sep 17 00:00:00 2001 From: Oz Tiram Date: Tue, 17 Sep 2019 17:08:26 +0200 Subject: [PATCH 48/53] Test getting the returncode of execution in a pod This tests demonstrate how to execute a command in a pod and what behavior is expected. As discussed in the commit bf367ed6ddc63369f76df0a07b248a6711328605 in python-base this behavior would be familiar to Python users, as `subprocess.Popen` has the same property. --- kubernetes/base | 2 +- kubernetes/e2e_test/test_client.py | 79 +++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/kubernetes/base b/kubernetes/base index ec31e05c9..5092d9613 160000 --- a/kubernetes/base +++ b/kubernetes/base @@ -1 +1 @@ -Subproject commit ec31e05c90218a915dea948703ed0d543d5d6835 +Subproject commit 5092d9613476673908fa9df7c55da83bc3be53d8 diff --git a/kubernetes/e2e_test/test_client.py b/kubernetes/e2e_test/test_client.py index 57000b982..d7a450c6a 100644 --- a/kubernetes/e2e_test/test_client.py +++ b/kubernetes/e2e_test/test_client.py @@ -29,6 +29,26 @@ def short_uuid(): return id[-12:] +def manifest_with_command(name, command): + return { + 'apiVersion': 'v1', + 'kind': 'Pod', + 'metadata': { + 'name': name + }, + 'spec': { + 'containers': [{ + 'image': 'busybox', + 'name': 'sleep', + "args": [ + "/bin/sh", + "-c", + command + ] + }] + } + } + class TestClient(unittest.TestCase): @classmethod @@ -40,25 +60,7 @@ class TestClient(unittest.TestCase): api = core_v1_api.CoreV1Api(client) name = 'busybox-test-' + short_uuid() - pod_manifest = { - 'apiVersion': 'v1', - 'kind': 'Pod', - 'metadata': { - 'name': name - }, - 'spec': { - 'containers': [{ - 'image': 'busybox', - 'name': 'sleep', - "args": [ - "/bin/sh", - "-c", - "while true;do date;sleep 5; done" - ] - }] - } - } - + pod_manifest = manifest_with_command(name, "while true;do date;sleep 5; done") resp = api.create_namespaced_pod(body=pod_manifest, namespace='default') self.assertEqual(name, resp.metadata.name) @@ -117,6 +119,45 @@ class TestClient(unittest.TestCase): resp = api.delete_namespaced_pod(name=name, body={}, namespace='default') + def test_exit_code(self): + client = api_client.ApiClient(configuration=self.config) + api = core_v1_api.CoreV1Api(client) + + name = 'busybox-test-' + short_uuid() + pod_manifest = manifest_with_command(name, "while true;do date;sleep 5; done") + resp = api.create_namespaced_pod(body=pod_manifest, + namespace='default') + self.assertEqual(name, resp.metadata.name) + self.assertTrue(resp.status.phase) + + while True: + resp = api.read_namespaced_pod(name=name, + namespace='default') + self.assertEqual(name, resp.metadata.name) + self.assertTrue(resp.status.phase) + if resp.status.phase == 'Running': + break + time.sleep(1) + + commands_expected_values = ( + (["false", 1]), + (["/bin/sh", "-c", "sleep 1; exit 3"], 3), + (["true", 0]), + (["/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) + + self.assertIsNone(client.returncode) + client.run_forever(timeout=10) + self.assertEqual(client.returncode, value) + + resp = api.delete_namespaced_pod(name=name, body={}, + namespace='default') def test_service_apis(self): client = api_client.ApiClient(configuration=self.config) From fd9de42babef226ae8bf67f316593fc422645318 Mon Sep 17 00:00:00 2001 From: micw523 Date: Tue, 24 Sep 2019 16:30:10 -0400 Subject: [PATCH 49/53] Fix deprecations introduced in v1.16 --- kubernetes/base | 2 +- .../{test_extensions.py => test_apps.py} | 24 ++++++++++++------- kubernetes/e2e_test/test_utils.py | 17 ++++++------- .../e2e_test/test_yaml/apps-deployment.yaml | 2 +- .../test_yaml/multi-resource-with-list.yaml | 2 +- 5 files changed, 27 insertions(+), 20 deletions(-) rename kubernetes/e2e_test/{test_extensions.py => test_apps.py} (84%) diff --git a/kubernetes/base b/kubernetes/base index 5092d9613..f2ae80b53 160000 --- a/kubernetes/base +++ b/kubernetes/base @@ -1 +1 @@ -Subproject commit 5092d9613476673908fa9df7c55da83bc3be53d8 +Subproject commit f2ae80b53a9516474fb321926576755973785f4d diff --git a/kubernetes/e2e_test/test_extensions.py b/kubernetes/e2e_test/test_apps.py similarity index 84% rename from kubernetes/e2e_test/test_extensions.py rename to kubernetes/e2e_test/test_apps.py index b65991031..1908374a3 100644 --- a/kubernetes/e2e_test/test_extensions.py +++ b/kubernetes/e2e_test/test_apps.py @@ -17,12 +17,12 @@ import uuid import yaml from kubernetes.client import api_client -from kubernetes.client.apis import extensions_v1beta1_api +from kubernetes.client.apis import apps_v1_api from kubernetes.client.models import v1_delete_options from kubernetes.e2e_test import base -class TestClientExtensions(unittest.TestCase): +class TestClientApps(unittest.TestCase): @classmethod def setUpClass(cls): @@ -30,14 +30,17 @@ class TestClientExtensions(unittest.TestCase): def test_create_deployment(self): client = api_client.ApiClient(configuration=self.config) - api = extensions_v1beta1_api.ExtensionsV1beta1Api(client) + api = apps_v1_api.AppsV1Api(client) name = 'nginx-deployment-' + str(uuid.uuid4()) - deployment = '''apiVersion: extensions/v1beta1 + deployment = '''apiVersion: apps/v1 kind: Deployment metadata: name: %s spec: replicas: 3 + selector: + matchLabels: + app: nginx template: metadata: labels: @@ -45,7 +48,7 @@ spec: spec: containers: - name: nginx - image: nginx:1.7.9 + image: nginx:1.15.4 ports: - containerPort: 80 ''' @@ -60,16 +63,19 @@ spec: def test_create_daemonset(self): client = api_client.ApiClient(configuration=self.config) - api = extensions_v1beta1_api.ExtensionsV1beta1Api(client) + api = apps_v1_api.AppsV1Api(client) name = 'nginx-app-' + str(uuid.uuid4()) daemonset = { - 'apiVersion': 'extensions/v1beta1', + 'apiVersion': 'apps/v1', 'kind': 'DaemonSet', 'metadata': { 'labels': {'app': 'nginx'}, 'name': '%s' % name, }, 'spec': { + 'selector': { + 'matchLabels': {'app': 'nginx'}, + }, 'template': { 'metadata': { 'labels': {'app': 'nginx'}, @@ -77,7 +83,7 @@ spec: 'spec': { 'containers': [ {'name': 'nginx-app', - 'image': 'nginx:1.10'}, + 'image': 'nginx:1.15.4'}, ], }, }, @@ -91,4 +97,4 @@ spec: self.assertIsNotNone(resp) options = v1_delete_options.V1DeleteOptions() - resp = api.delete_namespaced_daemon_set(name, 'default', body=options) \ No newline at end of file + resp = api.delete_namespaced_daemon_set(name, 'default', body=options) diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index 075a7baf2..ab752dff7 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -41,12 +41,12 @@ class TestUtils(unittest.TestCase): def test_create_apps_deployment_from_yaml(self): """ - Should be able to create an apps/v1beta1 deployment. + Should be able to create an apps/v1 deployment. """ k8s_client = client.api_client.ApiClient(configuration=self.config) utils.create_from_yaml( k8s_client, self.path_prefix + "apps-deployment.yaml") - app_api = client.AppsV1beta1Api(k8s_client) + app_api = client.AppsV1Api(k8s_client) dep = app_api.read_namespaced_deployment(name="nginx-app", namespace="default") self.assertIsNotNone(dep) @@ -68,7 +68,7 @@ class TestUtils(unittest.TestCase): utils.create_from_dict(k8s_client, yml_obj) - app_api = client.AppsV1beta1Api(k8s_client) + app_api = client.AppsV1Api(k8s_client) dep = app_api.read_namespaced_deployment(name="nginx-app-3", namespace="default") self.assertIsNotNone(dep) @@ -289,7 +289,7 @@ class TestUtils(unittest.TestCase): utils.create_from_yaml( k8s_client, self.path_prefix + "multi-resource-with-list.yaml") core_api = client.CoreV1Api(k8s_client) - app_api = client.AppsV1beta1Api(k8s_client) + app_api = client.AppsV1Api(k8s_client) pod_0 = core_api.read_namespaced_pod( name="mock-pod-0", namespace="default") self.assertIsNotNone(pod_0) @@ -365,15 +365,16 @@ class TestUtils(unittest.TestCase): name="triple-nginx", namespace="default", body={}) - def test_create_namespaces_apps_deployment_from_yaml(self): + def test_create_namespaced_apps_deployment_from_yaml(self): """ - Should be able to create an apps/v1beta1 deployment. + Should be able to create an apps/v1beta1 deployment + in a test namespace. """ k8s_client = client.api_client.ApiClient(configuration=self.config) utils.create_from_yaml( k8s_client, self.path_prefix + "apps-deployment.yaml", namespace=self.test_namespace) - app_api = client.AppsV1beta1Api(k8s_client) + app_api = client.AppsV1Api(k8s_client) dep = app_api.read_namespaced_deployment(name="nginx-app", namespace=self.test_namespace) self.assertIsNotNone(dep) @@ -391,7 +392,7 @@ class TestUtils(unittest.TestCase): k8s_client, self.path_prefix + "multi-resource-with-list.yaml", namespace=self.test_namespace) core_api = client.CoreV1Api(k8s_client) - app_api = client.AppsV1beta1Api(k8s_client) + app_api = client.AppsV1Api(k8s_client) pod_0 = core_api.read_namespaced_pod( name="mock-pod-0", namespace=self.test_namespace) self.assertIsNotNone(pod_0) diff --git a/kubernetes/e2e_test/test_yaml/apps-deployment.yaml b/kubernetes/e2e_test/test_yaml/apps-deployment.yaml index a2ffa6b99..eb33bbac5 100644 --- a/kubernetes/e2e_test/test_yaml/apps-deployment.yaml +++ b/kubernetes/e2e_test/test_yaml/apps-deployment.yaml @@ -1,4 +1,4 @@ -apiVersion: apps/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: nginx-app diff --git a/kubernetes/e2e_test/test_yaml/multi-resource-with-list.yaml b/kubernetes/e2e_test/test_yaml/multi-resource-with-list.yaml index b3228b8cd..996e4b696 100644 --- a/kubernetes/e2e_test/test_yaml/multi-resource-with-list.yaml +++ b/kubernetes/e2e_test/test_yaml/multi-resource-with-list.yaml @@ -24,7 +24,7 @@ items: image: busybox command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600'] --- -apiVersion: apps/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: mock From 67bac5d22bc9750275007acc8e00ff384d849ced Mon Sep 17 00:00:00 2001 From: Richard Godden <7768980+goddenrich@users.noreply.github.com> Date: Wed, 25 Sep 2019 10:49:12 +0100 Subject: [PATCH 50/53] support false values and missing fields in configs --- kubernetes/base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/base b/kubernetes/base index f2ae80b53..9f73cc68c 160000 --- a/kubernetes/base +++ b/kubernetes/base @@ -1 +1 @@ -Subproject commit f2ae80b53a9516474fb321926576755973785f4d +Subproject commit 9f73cc68c1a93725f89842a7cd8c595204c1b901 From fa12fea2e47892c41678bdbd31ffb8dfdd6dc3ff Mon Sep 17 00:00:00 2001 From: Xianglong Wang Date: Tue, 8 Oct 2019 14:42:55 -0500 Subject: [PATCH 51/53] Change py.test to pytest --- tox.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index a21190bf8..241d294e3 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ deps = -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = python -V - py.test -vvv -s --ignore=kubernetes/e2e_test + pytest -vvv -s --ignore=kubernetes/e2e_test [testenv:docs] commands = @@ -22,22 +22,22 @@ commands = [testenv:py27-functional] commands = python -V - {toxinidir}/scripts/kube-init.sh py.test -vvv -s [] + {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] [testenv:py35-functional] commands = python -V - {toxinidir}/scripts/kube-init.sh py.test -vvv -s [] + {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] [testenv:py36-functional] commands = python -V - {toxinidir}/scripts/kube-init.sh py.test -vvv -s [] + {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] [testenv:py37-functional] commands = python -V - {toxinidir}/scripts/kube-init.sh py.test -vvv -s [] + {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] [testenv:coverage] commands = From 064b80e0cd7377d0161db93f09fcb269c259705d Mon Sep 17 00:00:00 2001 From: Nabarun Pal Date: Sun, 6 Oct 2019 09:21:22 +0530 Subject: [PATCH 52/53] Moves coverage report generation to pytest from nosetests Adds pytest-cov to requirements --- test-requirements.txt | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 6200389e4..5e6aac088 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,7 @@ coverage>=4.0.3 nose>=1.3.7 pytest +pytest-cov pluggy>=0.3.1 py>=1.4.31 randomize>=0.13 diff --git a/tox.ini b/tox.ini index 241d294e3..deaa4daf6 100644 --- a/tox.ini +++ b/tox.ini @@ -42,7 +42,7 @@ commands = [testenv:coverage] commands = python -V - nosetests --with-coverage --cover-package=kubernetes.config,kubernetes.watch --cover-tests + pytest --cov=kubernetes/watch --cov=kubernetes/config kubernetes/watch kubernetes/config [testenv:codecov] commands = From 4d858922f15ed6facfd497ecb6c78ef3140778fb Mon Sep 17 00:00:00 2001 From: Aliaksei Urbanski Date: Wed, 16 Oct 2019 23:50:01 +0300 Subject: [PATCH 53/53] Enable testing for Python 3.8 Python 3.8 is there, so I believe that it would be nice to declare support and add tests for it on CI. Python 3.8.0 release announcement: https://discuss.python.org/t/python-3-8-0-is-now-available/2478 --- .travis.yml | 4 ++++ setup.py | 1 + tox.ini | 27 +++++---------------------- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 86e7187bd..00a7ba50f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,10 @@ matrix: env: TOXENV=py37 - python: 3.7 env: TOXENV=py37-functional + - python: 3.8 + env: TOXENV=py38 + - python: 3.8 + env: TOXENV=py38-functional install: - pip install tox diff --git a/setup.py b/setup.py index d17b573a0..9cdfcb611 100644 --- a/setup.py +++ b/setup.py @@ -77,5 +77,6 @@ setup( "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", ], ) diff --git a/tox.ini b/tox.ini index deaa4daf6..0930582f4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,7 @@ [tox] -envlist = py27, py35, py36, py37 +envlist = + py27, py3{5,6,7,8} + py27-functional, py3{5,6,7,8}-functional [testenv] passenv = TOXENV CI TRAVIS TRAVIS_* @@ -9,7 +11,8 @@ deps = -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = python -V - pytest -vvv -s --ignore=kubernetes/e2e_test + !functional: pytest -vvv -s --ignore=kubernetes/e2e_test + functional: {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] [testenv:docs] commands = @@ -19,26 +22,6 @@ commands = commands = {toxinidir}/scripts/update-pycodestyle.sh -[testenv:py27-functional] -commands = - python -V - {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] - -[testenv:py35-functional] -commands = - python -V - {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] - -[testenv:py36-functional] -commands = - python -V - {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] - -[testenv:py37-functional] -commands = - python -V - {toxinidir}/scripts/kube-init.sh pytest -vvv -s [] - [testenv:coverage] commands = python -V