Skip to content

Commit 8c26b4b

Browse files
committed
Support connecting to multiple clusters
1 parent 729d543 commit 8c26b4b

File tree

12 files changed

+147
-61
lines changed

12 files changed

+147
-61
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

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

examples/example4.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
from kubernetes import client, config
1616
from kubernetes.client import configuration
17+
# install pick using "pip install pick". It is not included
18+
# as a dependency because it only used in examples
19+
from pick import pick
1720

1821

1922
def main():
@@ -22,25 +25,12 @@ def main():
2225
print("Cannot find any context in kube-config file.")
2326
return
2427
contexts = [context['name'] for context in contexts]
25-
active_context = active_context['name']
26-
for i, context in enumerate(contexts):
27-
format_str = "%d. %s"
28-
if context == active_context:
29-
format_str = "* " + format_str
30-
print(format_str % (i, context))
31-
context = input("Enter context number: ")
32-
context = int(context)
33-
if context not in range(len(contexts)):
34-
print(
35-
"Number out of range. Using default context %s." %
36-
active_context)
37-
context_name = active_context
38-
else:
39-
context_name = contexts[context]
40-
28+
active_index = contexts.index(active_context['name'])
29+
option, _ = pick(contexts, title="Pick the context to load",
30+
default_index=active_index)
4131
# Configs can be set in Configuration class directly or using helper
4232
# utility
43-
config.load_kube_config(context=context_name)
33+
config.load_kube_config(context=option)
4434

4535
print("Active host is %s" % configuration.host)
4636

examples/multiple_clusters.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2016 The Kubernetes Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from kubernetes import client, config
16+
# install pick using "pip install pick". It is not included
17+
# as a dependency because it only used in examples
18+
from pick import pick
19+
20+
21+
def main():
22+
23+
contexts, active_context = config.list_kube_config_contexts()
24+
if not contexts:
25+
print("Cannot find any context in kube-config file.")
26+
return
27+
contexts = [context['name'] for context in contexts]
28+
active_index = contexts.index(active_context['name'])
29+
cluster1, first_index = pick(contexts, title="Pick the first context",
30+
default_index=active_index)
31+
cluster2, _ = pick(contexts, title="Pick the second context",
32+
default_index=first_index)
33+
34+
client1 = client.CoreV1Api(
35+
api_client=config.new_client_from_config(context=cluster1))
36+
client2 = client.CoreV1Api(
37+
api_client=config.new_client_from_config(context=cluster2))
38+
39+
print("\nList of pods on %s:" % cluster1)
40+
for i in client1.list_pod_for_all_namespaces().items:
41+
print("%s\t%s\t%s" %
42+
(i.status.pod_ip, i.metadata.namespace, i.metadata.name))
43+
44+
print("\n\nList of pods on %s:" % cluster2)
45+
for i in client2.list_pod_for_all_namespaces().items:
46+
print("%s\t%s\t%s" %
47+
(i.status.pod_ip, i.metadata.namespace, i.metadata.name))
48+
49+
50+
if __name__ == '__main__':
51+
main()

kubernetes/.swagger-codegen-ignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ test-requirements.txt
55
setup.py
66
.travis.yml
77
tox.ini
8+
client/__init__.py
9+
client/api_client.py
10+
client/configuration.py
11+
client/rest.py

kubernetes/client/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,4 @@
288288
# import ApiClient
289289
from .api_client import ApiClient
290290

291-
from .configuration import Configuration
292-
293-
configuration = Configuration()
291+
from .configuration import Configuration, ConfigurationObject, configuration

kubernetes/client/api_client.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from six import PY3, integer_types, iteritems, text_type
3939
from six.moves.urllib.parse import quote
4040

41-
from .configuration import Configuration
41+
from .configuration import configuration
4242

4343

4444
class ApiClient(object):
@@ -58,17 +58,19 @@ class ApiClient(object):
5858
:param header_name: a header to pass when making calls to the API.
5959
:param header_value: a header value to pass when making calls to the API.
6060
"""
61-
def __init__(self, host=None, header_name=None, header_value=None, cookie=None):
61+
def __init__(self, host=None, header_name=None, header_value=None,
62+
cookie=None, config=configuration):
6263

6364
"""
6465
Constructor of the class.
6566
"""
66-
self.rest_client = RESTClientObject()
67+
self.config = config
68+
self.rest_client = RESTClientObject(config=self.config)
6769
self.default_headers = {}
6870
if header_name is not None:
6971
self.default_headers[header_name] = header_value
7072
if host is None:
71-
self.host = Configuration().host
73+
self.host = self.config.host
7274
else:
7375
self.host = host
7476
self.cookie = cookie
@@ -499,13 +501,12 @@ def update_params_for_auth(self, headers, querys, auth_settings):
499501
:param querys: Query parameters tuple list to be updated.
500502
:param auth_settings: Authentication setting identifiers list.
501503
"""
502-
config = Configuration()
503504

504505
if not auth_settings:
505506
return
506507

507508
for auth in auth_settings:
508-
auth_setting = config.auth_settings().get(auth)
509+
auth_setting = self.config.auth_settings().get(auth)
509510
if auth_setting:
510511
if not auth_setting['value']:
511512
continue
@@ -526,9 +527,7 @@ def __deserialize_file(self, response):
526527
:param response: RESTResponse.
527528
:return: file path.
528529
"""
529-
config = Configuration()
530-
531-
fd, path = tempfile.mkstemp(dir=config.temp_folder_path)
530+
fd, path = tempfile.mkstemp(dir=self.config.temp_folder_path)
532531
os.close(fd)
533532
os.remove(path)
534533

kubernetes/client/configuration.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,7 @@
3333
from six.moves import http_client as httplib
3434

3535

36-
def singleton(cls, *args, **kw):
37-
instances = {}
38-
39-
def _singleton():
40-
if cls not in instances:
41-
instances[cls] = cls(*args, **kw)
42-
return instances[cls]
43-
return _singleton
44-
45-
46-
@singleton
47-
class Configuration(object):
36+
class ConfigurationObject(object):
4837
"""
4938
NOTE: This class is auto generated by the swagger code generator program.
5039
Ref: https://github.com/swagger-api/swagger-codegen
@@ -235,3 +224,11 @@ def to_debug_report(self):
235224
"Version of the API: v1.5.0-snapshot\n"\
236225
"SDK Package Version: 1.0.0-snapshot".\
237226
format(env=sys.platform, pyversion=sys.version)
227+
228+
229+
configuration = ConfigurationObject()
230+
231+
232+
def Configuration():
233+
"""Simulate a singelton Configuration object for backward compatibility."""
234+
return configuration

kubernetes/client/rest.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from six import PY3
3636
from six.moves.urllib.parse import urlencode
3737

38-
from .configuration import Configuration
38+
from .configuration import configuration
3939

4040
try:
4141
import urllib3
@@ -69,31 +69,31 @@ def getheader(self, name, default=None):
6969

7070
class RESTClientObject(object):
7171

72-
def __init__(self, pools_size=4):
72+
def __init__(self, pools_size=4, config=configuration):
7373
# urllib3.PoolManager will pass all kw parameters to connectionpool
7474
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75
7575
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680
7676
# ca_certs vs cert_file vs key_file
7777
# http://stackoverflow.com/a/23957365/2985775
7878

7979
# cert_reqs
80-
if Configuration().verify_ssl:
80+
if config.verify_ssl:
8181
cert_reqs = ssl.CERT_REQUIRED
8282
else:
8383
cert_reqs = ssl.CERT_NONE
8484

8585
# ca_certs
86-
if Configuration().ssl_ca_cert:
87-
ca_certs = Configuration().ssl_ca_cert
86+
if config.ssl_ca_cert:
87+
ca_certs = config.ssl_ca_cert
8888
else:
8989
# if not set certificate file, use Mozilla's root certificates.
9090
ca_certs = certifi.where()
9191

9292
# cert_file
93-
cert_file = Configuration().cert_file
93+
cert_file = config.cert_file
9494

9595
# key file
96-
key_file = Configuration().key_file
96+
key_file = config.key_file
9797

9898
# https pool manager
9999
self.pool_manager = urllib3.PoolManager(

kubernetes/config/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@
1414

1515
from .config_exception import ConfigException
1616
from .incluster_config import load_incluster_config
17-
from .kube_config import list_kube_config_contexts
18-
from .kube_config import load_kube_config
17+
from .kube_config import (list_kube_config_contexts, load_kube_config,
18+
new_client_from_config)

kubernetes/config/kube_config.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import urllib3
2121
import yaml
22-
from kubernetes.client import configuration
22+
from kubernetes.client import ApiClient, ConfigurationObject, configuration
2323
from oauth2client.client import GoogleCredentials
2424

2525
from .config_exception import ConfigException
@@ -97,7 +97,8 @@ def as_data(self):
9797
class KubeConfigLoader(object):
9898

9999
def __init__(self, config_dict, active_context=None,
100-
get_google_credentials=None, client_configuration=None,
100+
get_google_credentials=None,
101+
client_configuration=configuration,
101102
config_base_path=""):
102103
self._config = ConfigNode('kube-config', config_dict)
103104
self._current_context = None
@@ -111,10 +112,7 @@ def __init__(self, config_dict, active_context=None,
111112
self._get_google_credentials = lambda: (
112113
GoogleCredentials.get_application_default()
113114
.get_access_token().access_token)
114-
if client_configuration:
115-
self._client_configuration = client_configuration
116-
else:
117-
self._client_configuration = configuration
115+
self._client_configuration = client_configuration
118116

119117
def set_active_context(self, context_name=None):
120118
if context_name is None:
@@ -279,17 +277,31 @@ def list_kube_config_contexts(config_file=None):
279277
return loader.list_contexts(), loader.current_context
280278

281279

282-
def load_kube_config(config_file=None, context=None):
280+
def load_kube_config(config_file=None, context=None,
281+
client_configuration=configuration):
283282
"""Loads authentication and cluster information from kube-config file
284283
and stores them in kubernetes.client.configuration.
285284
286285
:param config_file: Name of the kube-config file.
287286
:param context: set the active context. If is set to None, current_context
288-
from config file will be used.
287+
from config file will be used.
288+
:param client_configuration: The kubernetes.client.ConfigurationObject to
289+
set configs to.
289290
"""
290291

291292
if config_file is None:
292293
config_file = os.path.expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
293294

294295
_get_kube_config_loader_for_yaml_file(
295-
config_file, active_context=context).load_and_set()
296+
config_file, active_context=context,
297+
client_configuration=client_configuration).load_and_set()
298+
299+
300+
def new_client_from_config(config_file=None, context=None):
301+
"""Loads configuration the same as load_kube_config but returns an ApiClient
302+
to be used with any API object. This will allow the caller to concurrently
303+
talk with multiple clusters."""
304+
client_config = ConfigurationObject()
305+
load_kube_config(config_file=config_file, context=context,
306+
client_configuration=client_config)
307+
return ApiClient(config=client_config)

0 commit comments

Comments
 (0)