Merge pull request #71 from mbohlool/multiple

Support connecting to multiple clusters
This commit is contained in:
mbohlool 2016-12-19 14:17:44 -08:00 committed by GitHub
commit f125cc4959
12 changed files with 147 additions and 61 deletions

View File

@ -2,6 +2,7 @@
- Add context switch to kube config loader #46
- Add default kube config location #64
- Add suport for accessing multiple clusters #7
- Bugfix: Python client does not resolve relative paths in kubeconfig #68
- Bugfix: `read_namespaced_pod_log` get None response #57
- Improved test coverage #54

View File

@ -14,6 +14,9 @@
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
def main():
@ -22,25 +25,12 @@ def main():
print("Cannot find any context in kube-config file.")
return
contexts = [context['name'] for context in contexts]
active_context = active_context['name']
for i, context in enumerate(contexts):
format_str = "%d. %s"
if context == active_context:
format_str = "* " + format_str
print(format_str % (i, context))
context = input("Enter context number: ")
context = int(context)
if context not in range(len(contexts)):
print(
"Number out of range. Using default context %s." %
active_context)
context_name = active_context
else:
context_name = contexts[context]
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=context_name)
config.load_kube_config(context=option)
print("Active host is %s" % configuration.host)

View File

@ -0,0 +1,51 @@
# 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 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
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)
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("\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__':
main()

View File

@ -5,3 +5,7 @@ test-requirements.txt
setup.py
.travis.yml
tox.ini
client/__init__.py
client/api_client.py
client/configuration.py
client/rest.py

View File

@ -288,6 +288,4 @@ from .apis.version_api import VersionApi
# import ApiClient
from .api_client import ApiClient
from .configuration import Configuration
configuration = Configuration()
from .configuration import Configuration, ConfigurationObject, configuration

View File

@ -38,7 +38,7 @@ from datetime import date
from six import PY3, integer_types, iteritems, text_type
from six.moves.urllib.parse import quote
from .configuration import Configuration
from .configuration import configuration
class ApiClient(object):
@ -58,17 +58,19 @@ class ApiClient(object):
:param header_name: a header to pass when making calls to the API.
:param header_value: a header value to pass when making calls to the API.
"""
def __init__(self, host=None, header_name=None, header_value=None, cookie=None):
def __init__(self, host=None, header_name=None, header_value=None,
cookie=None, config=configuration):
"""
Constructor of the class.
"""
self.rest_client = RESTClientObject()
self.config = config
self.rest_client = RESTClientObject(config=self.config)
self.default_headers = {}
if header_name is not None:
self.default_headers[header_name] = header_value
if host is None:
self.host = Configuration().host
self.host = self.config.host
else:
self.host = host
self.cookie = cookie
@ -499,13 +501,12 @@ class ApiClient(object):
:param querys: Query parameters tuple list to be updated.
:param auth_settings: Authentication setting identifiers list.
"""
config = Configuration()
if not auth_settings:
return
for auth in auth_settings:
auth_setting = config.auth_settings().get(auth)
auth_setting = self.config.auth_settings().get(auth)
if auth_setting:
if not auth_setting['value']:
continue
@ -526,9 +527,7 @@ class ApiClient(object):
:param response: RESTResponse.
:return: file path.
"""
config = Configuration()
fd, path = tempfile.mkstemp(dir=config.temp_folder_path)
fd, path = tempfile.mkstemp(dir=self.config.temp_folder_path)
os.close(fd)
os.remove(path)

View File

@ -33,18 +33,7 @@ from six import iteritems
from six.moves import http_client as httplib
def singleton(cls, *args, **kw):
instances = {}
def _singleton():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return _singleton
@singleton
class Configuration(object):
class ConfigurationObject(object):
"""
NOTE: This class is auto generated by the swagger code generator program.
Ref: https://github.com/swagger-api/swagger-codegen
@ -235,3 +224,11 @@ class Configuration(object):
"Version of the API: v1.5.0-snapshot\n"\
"SDK Package Version: 1.0.0-snapshot".\
format(env=sys.platform, pyversion=sys.version)
configuration = ConfigurationObject()
def Configuration():
"""Simulate a singelton Configuration object for backward compatibility."""
return configuration

View File

@ -35,7 +35,7 @@ import re
from six import PY3
from six.moves.urllib.parse import urlencode
from .configuration import Configuration
from .configuration import configuration
try:
import urllib3
@ -69,7 +69,7 @@ class RESTResponse(io.IOBase):
class RESTClientObject(object):
def __init__(self, pools_size=4):
def __init__(self, pools_size=4, config=configuration):
# urllib3.PoolManager will pass all kw parameters to connectionpool
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680
@ -77,23 +77,23 @@ class RESTClientObject(object):
# http://stackoverflow.com/a/23957365/2985775
# cert_reqs
if Configuration().verify_ssl:
if config.verify_ssl:
cert_reqs = ssl.CERT_REQUIRED
else:
cert_reqs = ssl.CERT_NONE
# ca_certs
if Configuration().ssl_ca_cert:
ca_certs = Configuration().ssl_ca_cert
if config.ssl_ca_cert:
ca_certs = config.ssl_ca_cert
else:
# if not set certificate file, use Mozilla's root certificates.
ca_certs = certifi.where()
# cert_file
cert_file = Configuration().cert_file
cert_file = config.cert_file
# key file
key_file = Configuration().key_file
key_file = config.key_file
# https pool manager
self.pool_manager = urllib3.PoolManager(

View File

@ -14,5 +14,5 @@
from .config_exception import ConfigException
from .incluster_config import load_incluster_config
from .kube_config import list_kube_config_contexts
from .kube_config import load_kube_config
from .kube_config import (list_kube_config_contexts, load_kube_config,
new_client_from_config)

View File

@ -19,7 +19,7 @@ import tempfile
import urllib3
import yaml
from kubernetes.client import configuration
from kubernetes.client import ApiClient, ConfigurationObject, configuration
from oauth2client.client import GoogleCredentials
from .config_exception import ConfigException
@ -97,7 +97,8 @@ class FileOrData(object):
class KubeConfigLoader(object):
def __init__(self, config_dict, active_context=None,
get_google_credentials=None, client_configuration=None,
get_google_credentials=None,
client_configuration=configuration,
config_base_path=""):
self._config = ConfigNode('kube-config', config_dict)
self._current_context = None
@ -111,10 +112,7 @@ class KubeConfigLoader(object):
self._get_google_credentials = lambda: (
GoogleCredentials.get_application_default()
.get_access_token().access_token)
if client_configuration:
self._client_configuration = client_configuration
else:
self._client_configuration = configuration
self._client_configuration = client_configuration
def set_active_context(self, context_name=None):
if context_name is None:
@ -279,17 +277,31 @@ def list_kube_config_contexts(config_file=None):
return loader.list_contexts(), loader.current_context
def load_kube_config(config_file=None, context=None):
def load_kube_config(config_file=None, context=None,
client_configuration=configuration):
"""Loads authentication and cluster information from kube-config file
and stores them in kubernetes.client.configuration.
:param config_file: Name of the kube-config file.
:param context: set the active context. If is set to None, current_context
from config file will be used.
from config file will be used.
:param client_configuration: The kubernetes.client.ConfigurationObject to
set configs to.
"""
if config_file is None:
config_file = os.path.expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
_get_kube_config_loader_for_yaml_file(
config_file, active_context=context).load_and_set()
config_file, active_context=context,
client_configuration=client_configuration).load_and_set()
def new_client_from_config(config_file=None, context=None):
"""Loads configuration the same as load_kube_config but returns an ApiClient
to be used with any API object. This will allow the caller to concurrently
talk with multiple clusters."""
client_config = ConfigurationObject()
load_kube_config(config_file=config_file, context=context,
client_configuration=client_config)
return ApiClient(config=client_config)

View File

@ -17,10 +17,14 @@ import os
import shutil
import tempfile
import unittest
import yaml
from six import PY3
from .config_exception import ConfigException
from .kube_config import (ConfigNode, FileOrData, KubeConfigLoader,
_cleanup_temp_files, _create_temp_file_with_content)
_cleanup_temp_files, _create_temp_file_with_content,
load_kube_config, list_kube_config_contexts,
new_client_from_config)
NON_EXISTING_FILE = "zz_non_existing_file_472398324"
@ -534,6 +538,35 @@ class TestKubeConfigLoader(BaseTestCase):
finally:
shutil.rmtree(temp_dir)
def test_load_kube_config(self):
expected = FakeConfig(host=TEST_HOST, token=TEST_DATA_BASE64)
config_file = self._create_temp_file(yaml.dump(self.TEST_KUBE_CONFIG))
actual = FakeConfig()
load_kube_config(config_file=config_file,context="simple_token",
client_configuration=actual)
self.assertEqual(expected, actual)
def test_list_kube_config_contexts(self):
config_file = self._create_temp_file(yaml.dump(self.TEST_KUBE_CONFIG))
contexts, active_context = list_kube_config_contexts(
config_file=config_file)
self.assertDictEqual(self.TEST_KUBE_CONFIG['contexts'][0],
active_context)
if PY3:
self.assertCountEqual(self.TEST_KUBE_CONFIG['contexts'],
contexts)
else:
self.assertItemsEqual(self.TEST_KUBE_CONFIG['contexts'],
contexts)
def test_new_client_from_config(self):
config_file = self._create_temp_file(yaml.dump(self.TEST_KUBE_CONFIG))
client = new_client_from_config(
config_file=config_file, context="simple_token")
self.assertEqual(TEST_HOST, client.config.host)
self.assertEqual(TEST_DATA_BASE64,
client.config.api_key['authorization'])
if __name__ == '__main__':
unittest.main()

View File

@ -43,7 +43,8 @@ echo "--- Downloading and processing OpenAPI spec"
python "${SCRIPT_ROOT}/preprocess_spec.py"
echo "--- Cleaning up previously generated folders"
rm -rf "${CLIENT_ROOT}/${PACKAGE_NAME}"
rm -rf "${CLIENT_ROOT}/${PACKAGE_NAME}/apis"
rm -rf "${CLIENT_ROOT}/${PACKAGE_NAME}/models"
rm -rf "${CLIENT_ROOT}/docs"
rm -rf "${CLIENT_ROOT}/test"