From 317aeeec2279f68daf70d0b7bf8e14ee1859f599 Mon Sep 17 00:00:00 2001 From: Charles Walker Date: Sat, 27 Jan 2018 10:00:44 -0500 Subject: [PATCH 1/8] Add example for remote cluster without kube client on server --- examples/remote_cluster.py | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 examples/remote_cluster.py diff --git a/examples/remote_cluster.py b/examples/remote_cluster.py new file mode 100644 index 000000000..a73855a19 --- /dev/null +++ b/examples/remote_cluster.py @@ -0,0 +1,58 @@ +# 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. + +# This example demonstrate communication with a remove Kube cluster from a +# server outside of the cluster without kube client installed on it. +# The communication is secured with the use of Bearer token. + +from kubernetes import client, config + + +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" + + # Create a configuration object + configuration = client.Configuration() + + # Specify the endpoint of your Kube cluster + configuration.host = "https://XXX.XXX.XXX.XXX:443" + + # Security part. + # In this simple example we are not going to verify the SSL certificate of + # the remote cluster (for simplicity reason) + configuration.verify_ssl = False + # Nevertheless if you want to do it you can with these 2 parameters + # configuration.verify_ssl=True + # ssl_ca_cert is the filepath to the file that contains the certificate. + # configuration.ssl_ca_cert="certificate" + + configuration.api_key = {"authorization": "Bearer " + aToken} + + # Use our configuration + client.Configuration.set_default(configuration) + + # Do calls + v1 = client.CoreV1Api() + print("Listing pods with their IPs:") + ret = v1.list_pod_for_all_namespaces(watch=False) + for i in ret.items: + print("%s\t%s\t%s" % + (i.status.pod_ip, i.metadata.namespace, i.metadata.name)) + + +if __name__ == '__main__': + main() From 8b2b3cd40dbd334e1893a9d0043ee006b4d49871 Mon Sep 17 00:00:00 2001 From: Charles Walker Date: Wed, 25 Jul 2018 23:56:53 +0000 Subject: [PATCH 2/8] Update example for remote cluster without override of default client --- examples/remote_cluster.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/remote_cluster.py b/examples/remote_cluster.py index a73855a19..7f2497941 100644 --- a/examples/remote_cluster.py +++ b/examples/remote_cluster.py @@ -26,27 +26,27 @@ def main(): aToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # Create a configuration object - configuration = client.Configuration() + aConfiguration = client.Configuration() # Specify the endpoint of your Kube cluster - configuration.host = "https://XXX.XXX.XXX.XXX:443" + aConfiguration.host = "https://XXX.XXX.XXX.XXX:443" # Security part. # In this simple example we are not going to verify the SSL certificate of # the remote cluster (for simplicity reason) - configuration.verify_ssl = False + aConfiguration.verify_ssl = False # Nevertheless if you want to do it you can with these 2 parameters # configuration.verify_ssl=True # ssl_ca_cert is the filepath to the file that contains the certificate. # configuration.ssl_ca_cert="certificate" - configuration.api_key = {"authorization": "Bearer " + aToken} + aConfiguration.api_key = {"authorization": "Bearer " + aToken} - # Use our configuration - client.Configuration.set_default(configuration) + # Create a ApiClient with our config + aApiClient = client.ApiClient(aConfiguration) # Do calls - v1 = client.CoreV1Api() + v1 = client.CoreV1Api(aApiClient) print("Listing pods with their IPs:") ret = v1.list_pod_for_all_namespaces(watch=False) for i in ret.items: From 6ea777e647e0d839cf38dbddc1fcc85c1cd985c6 Mon Sep 17 00:00:00 2001 From: micw523 Date: Sat, 27 Oct 2018 02:29:59 -0500 Subject: [PATCH 3/8] change pep8 into pycodestyle since pep8 is depreciated --- .travis.yml | 2 +- scripts/{update-pep8.sh => update-pycodestyle.sh} | 6 +++--- test-requirements.txt | 2 +- tox.ini | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename scripts/{update-pep8.sh => update-pycodestyle.sh} (94%) diff --git a/.travis.yml b/.travis.yml index 39d081474..dba38fcec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: - python: 2.7 env: TOXENV=py27-functional - python: 2.7 - env: TOXENV=update-pep8 + env: TOXENV=update-pycodestyle - python: 2.7 env: TOXENV=docs - python: 2.7 diff --git a/scripts/update-pep8.sh b/scripts/update-pycodestyle.sh similarity index 94% rename from scripts/update-pep8.sh rename to scripts/update-pycodestyle.sh index 6ec5bca25..a3bf25dc8 100755 --- a/scripts/update-pep8.sh +++ b/scripts/update-pycodestyle.sh @@ -49,7 +49,7 @@ if [[ -z ${ENV} ]]; then trap "deactivate" EXIT SIGINT echo "--- Updating tools" - pip install --upgrade pep8 + pip install --upgrade pycodestyle pip install --upgrade autopep8 pip install --upgrade isort fi @@ -70,10 +70,10 @@ for SOURCE in $SOURCES; do isort -y $SOURCE done -echo "--- check pep8 (all need to be fixed manually)" +echo "--- check pycodestyle (all need to be fixed manually)" set +o errexit for SOURCE in $SOURCES; do - pep8 $SOURCE + pycodestyle $SOURCE done if [[ ! -z ${ENV} ]]; then diff --git a/test-requirements.txt b/test-requirements.txt index e18c5c27d..0bb8dc53c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,6 +8,6 @@ mock>=2.0.0 sphinx>=1.2.1,!=1.3b1,<1.4 # BSD recommonmark codecov>=1.4.0 -pep8 +pycodestyle autopep8 isort \ No newline at end of file diff --git a/tox.ini b/tox.ini index 307b6e87f..44aec7172 100644 --- a/tox.ini +++ b/tox.ini @@ -15,9 +15,9 @@ commands = commands = python setup.py build_sphinx -[testenv:update-pep8] +[testenv:update-pycodestyle] commands = - {toxinidir}/scripts/update-pep8.sh + {toxinidir}/scripts/update-pycodestyle.sh [testenv:py27-functional] commands = From 67ae262a512ff9b738312ec205a6ab291fb15fc5 Mon Sep 17 00:00:00 2001 From: Charles Walker Date: Sat, 27 Oct 2018 09:04:42 -0400 Subject: [PATCH 4/8] fix typo in comment --- examples/remote_cluster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/remote_cluster.py b/examples/remote_cluster.py index 7f2497941..8cf39efec 100644 --- a/examples/remote_cluster.py +++ b/examples/remote_cluster.py @@ -1,4 +1,4 @@ -# Copyright 2016 The Kubernetes Authors. +# 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. @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This example demonstrate communication with a remove Kube cluster from a +# This example demonstrate communication with a remote Kube cluster from a # server outside of the cluster without kube client installed on it. # The communication is secured with the use of Bearer token. From e63905302fd4da45471253876df2c4fb45781fbf Mon Sep 17 00:00:00 2001 From: micw523 Date: Tue, 30 Oct 2018 20:40:26 -0500 Subject: [PATCH 5/8] Upgrade Travis CI k8s version --- .travis.yml | 2 +- scripts/kube-init.sh | 85 +++++++++++++++++++++++--------------------- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39d081474..8747e6e14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ # ref: https://docs.travis-ci.com/user/languages/python language: python -dist: trusty +dist: xenial sudo: true services: - docker diff --git a/scripts/kube-init.sh b/scripts/kube-init.sh index bcdcb0e21..58cae869a 100755 --- a/scripts/kube-init.sh +++ b/scripts/kube-init.sh @@ -30,6 +30,11 @@ trap "clean_exit" EXIT # Switch off SE-Linux setenforce 0 +# Mount root to fix dns issues +# Define $HOME since somehow this is not defined +HOME=/home/travis +sudo mount --make-rshared / + # Install docker if needed path_to_executable=$(which docker) if [ -x "$path_to_executable" ] ; then @@ -40,7 +45,7 @@ fi docker --version # Get the latest stable version of kubernetes -export K8S_VERSION=$(curl -sS https://storage.googleapis.com/kubernetes-release/release/stable.txt) +K8S_VERSION=$(curl -sS https://storage.googleapis.com/kubernetes-release/release/stable.txt) echo "K8S_VERSION : ${K8S_VERSION}" echo "Starting docker service" @@ -54,48 +59,48 @@ wget -O kubectl "http://storage.googleapis.com/kubernetes-release/release/${K8S_ sudo chmod +x kubectl sudo mv kubectl /usr/local/bin/ -echo "Download localkube from minikube project" -wget -O localkube "https://storage.googleapis.com/minikube/k8sReleases/v1.7.0/localkube-linux-amd64" -sudo chmod +x localkube -sudo mv localkube /usr/local/bin/ +echo "Download minikube from minikube project" +wget -O minikube "https://storage.googleapis.com/minikube/releases/v0.30.0/minikube-linux-amd64" +sudo chmod +x minikube +sudo mv minikube /usr/local/bin/ -echo "Starting localkube" -sudo nohup localkube --logtostderr=true --enable-dns=false > localkube.log 2>&1 & +# L68-100: Set up minikube within Travis CI +# See https://github.com/kubernetes/minikube/blob/master/README.md#linux-continuous-integration-without-vm-support +echo "Set up minikube" +export MINIKUBE_WANTUPDATENOTIFICATION=false +export MINIKUBE_WANTREPORTERRORPROMPT=false +export CHANGE_MINIKUBE_NONE_USER=true +sudo mkdir -p $HOME/.kube +sudo mkdir -p $HOME/.minikube +sudo touch $HOME/.kube/config +export KUBECONFIG=$HOME/.kube/config +export MINIKUBE_HOME=$HOME +export MINIKUBE_DRIVER=${MINIKUBE_DRIVER:-none} -echo "Waiting for localkube to start..." -if ! timeout 120 sh -c "while ! curl -ks http://127.0.0.1:8080/ >/dev/null; do sleep 1; done"; then - sudo cat localkube.log - die $LINENO "localkube did not start" +# Used bootstrapper to be kubeadm for the most recent k8s version +# since localkube is depreciated and only supported up to version 1.10.0 +echo "Starting minikube" +sudo minikube start --vm-driver=$MINIKUBE_DRIVER --bootstrapper=kubeadm --kubernetes-version=$K8S_VERSION --logtostderr + +MINIKUBE_OK="false" + +echo "Waiting for minikube to start..." +# this for loop waits until kubectl can access the api server that Minikube has created +for i in {1..90}; do # timeout for 3 minutes + kubectl get po &> /dev/null + if [ $? -ne 1 ]; then + MINIKUBE_OK="true" + break + fi + sleep 2 +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" fi -echo "Check certificate permissions" -sudo chmod 644 /var/lib/localkube/certs/* -sudo ls -altr /var/lib/localkube/certs/ - -echo "Set up .kube/config" -mkdir ~/.kube -cat < ~/.kube/config -apiVersion: v1 -clusters: -- cluster: - insecure-skip-tls-verify: true - server: https://localhost:8443 - name: local -contexts: -- context: - cluster: local - user: myself - name: local -current-context: local -kind: Config -preferences: {} -users: -- name: myself - user: - client-certificate: /var/lib/localkube/certs/apiserver.crt - client-key: /var/lib/localkube/certs/apiserver.key -EOF - echo "Dump Kubernetes Objects..." kubectl get componentstatuses kubectl get configmaps @@ -124,4 +129,4 @@ kubectl get services echo "Running tests..." set -x -e # Yield execution to venv command -$* \ No newline at end of file +$* From 5a92ba65aa69318afde2bb0b87e828fa1a976b2a Mon Sep 17 00:00:00 2001 From: micw523 Date: Wed, 31 Oct 2018 19:34:18 -0500 Subject: [PATCH 6/8] Add kubectl create -f feature --- examples/create_deployment_from_yaml.py | 32 +++++ kubernetes/__init__.py | 1 + kubernetes/e2e_test/test_utils.py | 116 ++++++++++++++++++ .../e2e_test/test_yaml/api-service.yaml | 13 ++ .../e2e_test/test_yaml/apps-deployment.yaml | 21 ++++ .../test_yaml/core-namespace-dep.yaml | 6 + .../e2e_test/test_yaml/core-namespace.yaml | 6 + kubernetes/e2e_test/test_yaml/core-pod.yaml | 11 ++ .../e2e_test/test_yaml/core-service.yaml | 11 ++ .../test_yaml/extensions-deployment-dep.yaml | 18 +++ .../test_yaml/extensions-deployment.yaml | 17 +++ kubernetes/utils/__init__.py | 15 +++ kubernetes/utils/create_from_yaml.py | 74 +++++++++++ setup.py | 3 +- 14 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 examples/create_deployment_from_yaml.py create mode 100644 kubernetes/e2e_test/test_utils.py create mode 100644 kubernetes/e2e_test/test_yaml/api-service.yaml create mode 100644 kubernetes/e2e_test/test_yaml/apps-deployment.yaml create mode 100644 kubernetes/e2e_test/test_yaml/core-namespace-dep.yaml create mode 100644 kubernetes/e2e_test/test_yaml/core-namespace.yaml create mode 100644 kubernetes/e2e_test/test_yaml/core-pod.yaml create mode 100644 kubernetes/e2e_test/test_yaml/core-service.yaml create mode 100644 kubernetes/e2e_test/test_yaml/extensions-deployment-dep.yaml create mode 100644 kubernetes/e2e_test/test_yaml/extensions-deployment.yaml create mode 100644 kubernetes/utils/__init__.py create mode 100644 kubernetes/utils/create_from_yaml.py diff --git a/examples/create_deployment_from_yaml.py b/examples/create_deployment_from_yaml.py new file mode 100644 index 000000000..76452181b --- /dev/null +++ b/examples/create_deployment_from_yaml.py @@ -0,0 +1,32 @@ +# 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() + k8s_api = utils.create_from_yaml(k8s_client, "nginx-deployment.yaml") + deps = k8s_api.read_namespaced_deployment("nginx-deployment", "default") + print("Deployment {0} created".format(deps.metadata.name)) + + +if __name__ == '__main__': + main() diff --git a/kubernetes/__init__.py b/kubernetes/__init__.py index 4dbe66f56..33c19ac87 100644 --- a/kubernetes/__init__.py +++ b/kubernetes/__init__.py @@ -20,3 +20,4 @@ import kubernetes.client import kubernetes.config import kubernetes.watch import kubernetes.stream +import kubernetes.utils diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py new file mode 100644 index 000000000..b43a77c43 --- /dev/null +++ b/kubernetes/e2e_test/test_utils.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# 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. + +import unittest + +from kubernetes import utils, client +from kubernetes.e2e_test import base + +class TestUtils(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.config = base.get_e2e_configuration() + + def test_app_yaml(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + k8s_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/apps-deployment.yaml") + self.assertEqual("apps/v1beta1", + k8s_api.get_api_resources().group_version) + dep = k8s_api.read_namespaced_deployment(name="nginx-app", + namespace="default") + self.assertIsNotNone(dep) + resp = k8s_api.delete_namespaced_deployment( + name="nginx-app", namespace="default", + body={}) + + def test_extension_yaml(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + k8s_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/extensions-deployment.yaml") + self.assertEqual("extensions/v1beta1", + k8s_api.get_api_resources().group_version) + dep = k8s_api.read_namespaced_deployment(name="nginx-deployment", + namespace="default") + self.assertIsNotNone(dep) + resp = k8s_api.delete_namespaced_deployment( + name="nginx-deployment", namespace="default", + body={}) + + def test_core_pod_yaml(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + k8s_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/core-pod.yaml") + self.assertEqual("v1", + k8s_api.get_api_resources().group_version) + pod = k8s_api.read_namespaced_pod(name="myapp-pod", + namespace="default") + self.assertIsNotNone(pod) + resp = k8s_api.delete_namespaced_pod( + name="myapp-pod", namespace="default", + body={}) + + def test_core_service_yaml(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + k8s_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/core-service.yaml") + self.assertEqual("v1", + k8s_api.get_api_resources().group_version) + svc = k8s_api.read_namespaced_service(name="my-service", + namespace="default") + self.assertIsNotNone(svc) + resp = k8s_api.delete_namespaced_service( + name="my-service", namespace="default", + body={}) + + def test_core_namespace_yaml(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + k8s_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/core-namespace.yaml") + self.assertEqual("v1", + k8s_api.get_api_resources().group_version) + nmsp = k8s_api.read_namespace(name="development") + self.assertIsNotNone(nmsp) + resp = k8s_api.delete_namespace(name="development", body={}) + + def test_deployment_in_namespace(self): + k8s_client = client.ApiClient(configuration=self.config) + core_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/core-namespace-dep.yaml") + self.assertEqual("v1", + core_api.get_api_resources().group_version) + nmsp = core_api.read_namespace(name="dep") + self.assertIsNotNone(nmsp) + dep_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/extensions-deployment-dep.yaml") + dep = dep_api.read_namespaced_deployment(name="nginx-deployment", + namespace="dep") + self.assertIsNotNone(dep) + resp = dep_api.delete_namespaced_deployment( + name="nginx-deployment", namespace="dep", + body={}) + resp = core_api.delete_namespace(name="dep", body={}) + + def test_api_service(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + k8s_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/api-service.yaml") + self.assertEqual("apiregistration.k8s.io/v1beta1", + k8s_api.get_api_resources().group_version) + svc = k8s_api.read_api_service( + name="v1alpha1.wardle.k8s.io") + self.assertIsNotNone(svc) + resp = k8s_api.delete_api_service( + name="v1alpha1.wardle.k8s.io", body={}) \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/api-service.yaml b/kubernetes/e2e_test/test_yaml/api-service.yaml new file mode 100644 index 000000000..29cb16b54 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/api-service.yaml @@ -0,0 +1,13 @@ +apiVersion: apiregistration.k8s.io/v1beta1 +kind: APIService +metadata: + name: v1alpha1.wardle.k8s.io +spec: + insecureSkipTLSVerify: true + group: wardle.k8s.io + groupPriorityMinimum: 1000 + versionPriority: 15 + service: + name: api + namespace: wardle + version: v1alpha1 \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/apps-deployment.yaml b/kubernetes/e2e_test/test_yaml/apps-deployment.yaml new file mode 100644 index 000000000..a2ffa6b99 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/apps-deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: nginx-app + 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 diff --git a/kubernetes/e2e_test/test_yaml/core-namespace-dep.yaml b/kubernetes/e2e_test/test_yaml/core-namespace-dep.yaml new file mode 100644 index 000000000..38df99628 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/core-namespace-dep.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: dep + labels: + name: dep \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/core-namespace.yaml b/kubernetes/e2e_test/test_yaml/core-namespace.yaml new file mode 100644 index 000000000..dc475a757 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/core-namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: development + labels: + name: development \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/core-pod.yaml b/kubernetes/e2e_test/test_yaml/core-pod.yaml new file mode 100644 index 000000000..8276a37fb --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/core-pod.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: myapp-pod + labels: + app: myapp +spec: + containers: + - name: myapp-container + image: busybox + command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600'] \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/core-service.yaml b/kubernetes/e2e_test/test_yaml/core-service.yaml new file mode 100644 index 000000000..a805c9116 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/core-service.yaml @@ -0,0 +1,11 @@ +kind: Service +apiVersion: v1 +metadata: + name: my-service +spec: + selector: + app: MyApp + ports: + - protocol: TCP + port: 80 + targetPort: 9376 \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/extensions-deployment-dep.yaml b/kubernetes/e2e_test/test_yaml/extensions-deployment-dep.yaml new file mode 100644 index 000000000..be9b281f2 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/extensions-deployment-dep.yaml @@ -0,0 +1,18 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-deployment + namespace: dep +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/extensions-deployment.yaml b/kubernetes/e2e_test/test_yaml/extensions-deployment.yaml new file mode 100644 index 000000000..d05940d29 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/extensions-deployment.yaml @@ -0,0 +1,17 @@ +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/utils/__init__.py b/kubernetes/utils/__init__.py new file mode 100644 index 000000000..4e6d6846a --- /dev/null +++ b/kubernetes/utils/__init__.py @@ -0,0 +1,15 @@ +# 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 .create_from_yaml import create_from_yaml diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py new file mode 100644 index 000000000..696a3abcc --- /dev/null +++ b/kubernetes/utils/create_from_yaml.py @@ -0,0 +1,74 @@ +# 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 +import re +import sys + +from six import iteritems + +import yaml + +from kubernetes import client + +def create_from_yaml(k8s_client, yaml_file, verbose=False, **kwargs): + """ + 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. + k8s_cline: an ApiClient object, initialized with the client args. + + Available parameters for performing the subsequent action: + :param async_req bool + :param bool include_uninitialized: If true, partially initialized resources are included in the response. + :param str pretty: If 'true', then the output is pretty printed. + :param str dry_run: When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun 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 + """ + + with open(path.abspath(yaml_file)) as f: + yml_object = yaml.load(f) + #TODO: case of yaml file containing multiple objects + group, _, version = yml_object["apiVersion"].partition("/") + if version == "": + version = group + group = "core" + # Take care for the case e.g. api_type is "apiextensions.k8s.io" + # Only replace the last instance + group = "".join(group.rsplit(".k8s.io", 1)) + fcn_to_call = "{0}{1}Api".format(group.capitalize(), + version.capitalize()) + k8s_api = getattr(client, fcn_to_call)(k8s_client) + # Replace CamelCased action_type into snake_case + 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)): + resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))( + body=yml_object, namespace=namespace, **kwargs) + else: + resp = getattr(k8s_api, "create_{0}".format(kind))( + body=yml_object, **kwargs) + if verbose: + print("{0} created. status='{1}'".format(kind, str(resp.status))) + return k8s_api + \ No newline at end of file diff --git a/setup.py b/setup.py index 962444890..85ad8d17a 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,8 @@ setup( extras_require=EXTRAS, packages=['kubernetes', 'kubernetes.client', 'kubernetes.config', 'kubernetes.watch', 'kubernetes.client.apis', - 'kubernetes.stream', 'kubernetes.client.models'], + 'kubernetes.stream', 'kubernetes.client.models', + 'kubernetes.utils'], include_package_data=True, long_description="""\ Python client for kubernetes http://kubernetes.io/ From c09023843cafdafa87066cc0b7316b99c54070d9 Mon Sep 17 00:00:00 2001 From: Haowei Cai Date: Thu, 1 Nov 2018 16:06:11 -0700 Subject: [PATCH 7/8] Update python-base submodule, pick up change: use pycodestyle as pep8 has been deprecated (rename) --- kubernetes/base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/base b/kubernetes/base index 8d7b6f7dc..83ebb9d5f 160000 --- a/kubernetes/base +++ b/kubernetes/base @@ -1 +1 @@ -Subproject commit 8d7b6f7dc3158ff5c45fd828e965606d1c0ecfeb +Subproject commit 83ebb9d5fdc0d46bbb2e30afcd8eec42c5da4ad1 From 105c5f07b009fbb4d12303d332f5c9feaa927063 Mon Sep 17 00:00:00 2001 From: Haowei Cai Date: Thu, 1 Nov 2018 16:11:43 -0700 Subject: [PATCH 8/8] Update CHANGELOG and README on new feature and supported versions --- CHANGELOG.md | 4 ++++ README.md | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1050502cc..c614a14a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v8.0.0 +**New Feature:** +- Add utility to create API resource from yaml file [kubernetes-client/python#655](https://github.com/kubernetes-client/python/pull/655) + # v8.0.0b1 **Bug Fix:** - Update ExecProvider to use safe\_get() to tolerate kube-config file that sets diff --git a/README.md b/README.md index 824cebf4b..c3017a272 100644 --- a/README.md +++ b/README.md @@ -118,12 +118,13 @@ between client-python versions. | 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 | ✓ | +| 5.0 | Kubernetes main repo, 1.9 branch | ✗ | | 6.0 Alpha/Beta | Kubernetes main repo, 1.10 branch | ✗ | | 6.0 | Kubernetes main repo, 1.10 branch | ✓ | | 7.0 Alpha/Beta | Kubernetes main repo, 1.11 branch | ✗ | | 7.0 | Kubernetes main repo, 1.11 branch | ✓ | -| 8.0 Alpha/Beta | Kubernetes main repo, 1.12 branch | ✓ | +| 8.0 Alpha/Beta | Kubernetes main repo, 1.12 branch | ✗ | +| 8.0 | Kubernetes main repo, 1.12 branch | ✓ | Key: