Skip to content
This repository was archived by the owner on Mar 13, 2022. It is now read-only.

Commit bf62a92

Browse files
authored
Merge pull request #1 from mbohlool/master
Copy over base folder of python-client
2 parents e92eb2d + ecb8afa commit bf62a92

14 files changed

+2648
-0
lines changed

api_client.py

+647
Large diffs are not rendered by default.

config/__init__.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 .config_exception import ConfigException
16+
from .incluster_config import load_incluster_config
17+
from .kube_config import (list_kube_config_contexts, load_kube_config,
18+
new_client_from_config)

config/config

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
config

config/config_exception.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
16+
class ConfigException(Exception):
17+
pass

config/incluster_config.py

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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+
import os
16+
17+
from kubernetes.client import configuration
18+
19+
from .config_exception import ConfigException
20+
21+
SERVICE_HOST_ENV_NAME = "KUBERNETES_SERVICE_HOST"
22+
SERVICE_PORT_ENV_NAME = "KUBERNETES_SERVICE_PORT"
23+
SERVICE_TOKEN_FILENAME = "/var/run/secrets/kubernetes.io/serviceaccount/token"
24+
SERVICE_CERT_FILENAME = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
25+
26+
27+
def _join_host_port(host, port):
28+
"""Adapted golang's net.JoinHostPort"""
29+
template = "%s:%s"
30+
host_requires_bracketing = ':' in host or '%' in host
31+
if host_requires_bracketing:
32+
template = "[%s]:%s"
33+
return template % (host, port)
34+
35+
36+
class InClusterConfigLoader(object):
37+
38+
def __init__(self, token_filename,
39+
cert_filename, environ=os.environ):
40+
self._token_filename = token_filename
41+
self._cert_filename = cert_filename
42+
self._environ = environ
43+
44+
def load_and_set(self):
45+
self._load_config()
46+
self._set_config()
47+
48+
def _load_config(self):
49+
if (SERVICE_HOST_ENV_NAME not in self._environ or
50+
SERVICE_PORT_ENV_NAME not in self._environ):
51+
raise ConfigException("Service host/port is not set.")
52+
53+
if (not self._environ[SERVICE_HOST_ENV_NAME] or
54+
not self._environ[SERVICE_PORT_ENV_NAME]):
55+
raise ConfigException("Service host/port is set but empty.")
56+
57+
self.host = (
58+
"https://" + _join_host_port(self._environ[SERVICE_HOST_ENV_NAME],
59+
self._environ[SERVICE_PORT_ENV_NAME]))
60+
61+
if not os.path.isfile(self._token_filename):
62+
raise ConfigException("Service token file does not exists.")
63+
64+
with open(self._token_filename) as f:
65+
self.token = f.read()
66+
if not self.token:
67+
raise ConfigException("Token file exists but empty.")
68+
69+
if not os.path.isfile(self._cert_filename):
70+
raise ConfigException(
71+
"Service certification file does not exists.")
72+
73+
with open(self._cert_filename) as f:
74+
if not f.read():
75+
raise ConfigException("Cert file exists but empty.")
76+
77+
self.ssl_ca_cert = self._cert_filename
78+
79+
def _set_config(self):
80+
configuration.host = self.host
81+
configuration.ssl_ca_cert = self.ssl_ca_cert
82+
configuration.api_key['authorization'] = "bearer " + self.token
83+
84+
85+
def load_incluster_config():
86+
"""Use the service account kubernetes gives to pods to connect to kubernetes
87+
cluster. It's intended for clients that expect to be running inside a pod
88+
running on kubernetes. It will raise an exception if called from a process
89+
not running in a kubernetes environment."""
90+
InClusterConfigLoader(token_filename=SERVICE_TOKEN_FILENAME,
91+
cert_filename=SERVICE_CERT_FILENAME).load_and_set()

config/incluster_config_test.py

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
import os
16+
import tempfile
17+
import unittest
18+
19+
from .config_exception import ConfigException
20+
from .incluster_config import (SERVICE_HOST_ENV_NAME, SERVICE_PORT_ENV_NAME,
21+
InClusterConfigLoader, _join_host_port)
22+
23+
_TEST_TOKEN = "temp_token"
24+
_TEST_CERT = "temp_cert"
25+
_TEST_HOST = "127.0.0.1"
26+
_TEST_PORT = "80"
27+
_TEST_HOST_PORT = "127.0.0.1:80"
28+
_TEST_IPV6_HOST = "::1"
29+
_TEST_IPV6_HOST_PORT = "[::1]:80"
30+
31+
_TEST_ENVIRON = {SERVICE_HOST_ENV_NAME: _TEST_HOST,
32+
SERVICE_PORT_ENV_NAME: _TEST_PORT}
33+
_TEST_IPV6_ENVIRON = {SERVICE_HOST_ENV_NAME: _TEST_IPV6_HOST,
34+
SERVICE_PORT_ENV_NAME: _TEST_PORT}
35+
36+
37+
class InClusterConfigTest(unittest.TestCase):
38+
39+
def setUp(self):
40+
self._temp_files = []
41+
42+
def tearDown(self):
43+
for f in self._temp_files:
44+
os.remove(f)
45+
46+
def _create_file_with_temp_content(self, content=""):
47+
handler, name = tempfile.mkstemp()
48+
self._temp_files.append(name)
49+
os.write(handler, str.encode(content))
50+
os.close(handler)
51+
return name
52+
53+
def get_test_loader(
54+
self,
55+
token_filename=None,
56+
cert_filename=None,
57+
environ=_TEST_ENVIRON):
58+
if not token_filename:
59+
token_filename = self._create_file_with_temp_content(_TEST_TOKEN)
60+
if not cert_filename:
61+
cert_filename = self._create_file_with_temp_content(_TEST_CERT)
62+
return InClusterConfigLoader(
63+
token_filename=token_filename,
64+
cert_filename=cert_filename,
65+
environ=environ)
66+
67+
def test_join_host_port(self):
68+
self.assertEqual(_TEST_HOST_PORT,
69+
_join_host_port(_TEST_HOST, _TEST_PORT))
70+
self.assertEqual(_TEST_IPV6_HOST_PORT,
71+
_join_host_port(_TEST_IPV6_HOST, _TEST_PORT))
72+
73+
def test_load_config(self):
74+
cert_filename = self._create_file_with_temp_content(_TEST_CERT)
75+
loader = self.get_test_loader(cert_filename=cert_filename)
76+
loader._load_config()
77+
self.assertEqual("https://" + _TEST_HOST_PORT, loader.host)
78+
self.assertEqual(cert_filename, loader.ssl_ca_cert)
79+
self.assertEqual(_TEST_TOKEN, loader.token)
80+
81+
def _should_fail_load(self, config_loader, reason):
82+
try:
83+
config_loader.load_and_set()
84+
self.fail("Should fail because %s" % reason)
85+
except ConfigException:
86+
# expected
87+
pass
88+
89+
def test_no_port(self):
90+
loader = self.get_test_loader(
91+
environ={SERVICE_HOST_ENV_NAME: _TEST_HOST})
92+
self._should_fail_load(loader, "no port specified")
93+
94+
def test_empty_port(self):
95+
loader = self.get_test_loader(
96+
environ={SERVICE_HOST_ENV_NAME: _TEST_HOST,
97+
SERVICE_PORT_ENV_NAME: ""})
98+
self._should_fail_load(loader, "empty port specified")
99+
100+
def test_no_host(self):
101+
loader = self.get_test_loader(
102+
environ={SERVICE_PORT_ENV_NAME: _TEST_PORT})
103+
self._should_fail_load(loader, "no host specified")
104+
105+
def test_empty_host(self):
106+
loader = self.get_test_loader(
107+
environ={SERVICE_HOST_ENV_NAME: "",
108+
SERVICE_PORT_ENV_NAME: _TEST_PORT})
109+
self._should_fail_load(loader, "empty host specified")
110+
111+
def test_no_cert_file(self):
112+
loader = self.get_test_loader(cert_filename="not_exists_file_1123")
113+
self._should_fail_load(loader, "cert file does not exists")
114+
115+
def test_empty_cert_file(self):
116+
loader = self.get_test_loader(
117+
cert_filename=self._create_file_with_temp_content())
118+
self._should_fail_load(loader, "empty cert file provided")
119+
120+
def test_no_token_file(self):
121+
loader = self.get_test_loader(token_filename="not_exists_file_1123")
122+
self._should_fail_load(loader, "token file does not exists")
123+
124+
def test_empty_token_file(self):
125+
loader = self.get_test_loader(
126+
token_filename=self._create_file_with_temp_content())
127+
self._should_fail_load(loader, "empty token file provided")
128+
129+
130+
if __name__ == '__main__':
131+
unittest.main()

0 commit comments

Comments
 (0)