Skip to content

Commit 7f26817

Browse files
authored
Merge pull request #8 from yuanying/support-kube-config
Support kube config
2 parents bb2d66d + 7f0dce5 commit 7f26817

18 files changed

+1074
-7
lines changed

kubernetes/README.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,10 @@ Please follow the [installation](#installation) procedure and then run the follo
5353
```ruby
5454
# Load the gem
5555
require 'kubernetes'
56+
require 'kubernetes/utils'
5657

57-
# Setup authorization
58-
Kubernetes.configure do |config|
59-
# Configure API key authorization: BearerToken
60-
config.api_key['authorization'] = 'YOUR API KEY'
61-
# Uncomment the following line to set a prefix for the API key, e.g. 'Bearer' (defaults to nil)
62-
#config.api_key_prefix['authorization'] = 'Bearer'
63-
end
58+
# Configs can be set in Configuration class directly or using helper utility
59+
Kubernetes.load_kube_config
6460

6561
api_instance = Kubernetes::AdmissionregistrationApi.new
6662

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2017 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+
module Kubernetes
16+
class ConfigError < RuntimeError; end
17+
end
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright 2017 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+
require 'kubernetes/configuration'
16+
require 'kubernetes/config/error'
17+
18+
module Kubernetes
19+
20+
class InClusterConfig
21+
22+
SERVICE_HOST_ENV_NAME = "KUBERNETES_SERVICE_HOST"
23+
SERVICE_PORT_ENV_NAME = "KUBERNETES_SERVICE_PORT"
24+
SERVICE_TOKEN_FILENAME = "/var/run/secrets/kubernetes.io/serviceaccount/token"
25+
SERVICE_CA_CERT_FILENAME = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
26+
27+
attr_accessor :host
28+
attr_accessor :port
29+
attr_accessor :token
30+
31+
def validate
32+
unless (self.host = self.env[SERVICE_HOST_ENV_NAME]) && (self.port = self.env[SERVICE_PORT_ENV_NAME])
33+
raise ConfigError.new("Service host/port is not set")
34+
end
35+
raise ConfigError.new("Service token file does not exists") unless File.file?(self.token_file)
36+
raise ConfigError.new("Service token file does not exists") unless File.file?(self.ca_cert)
37+
end
38+
39+
def env
40+
@env ||= ENV
41+
@env
42+
end
43+
44+
def ca_cert
45+
@ca_cert ||= SERVICE_CA_CERT_FILENAME
46+
@ca_cert
47+
end
48+
49+
def token_file
50+
@token_file ||= SERVICE_TOKEN_FILENAME
51+
@token_file
52+
end
53+
54+
def load_token
55+
open(self.token_file) do |io|
56+
self.token = io.read.chomp
57+
end
58+
end
59+
60+
def configure(configuration)
61+
validate
62+
load_token
63+
configuration.api_key['authorization'] = "Bearer #{self.token}"
64+
configuration.scheme = 'https'
65+
configuration.host = "#{self.host}:#{self.port}"
66+
configuration.ssl_ca_cert = self.ca_cert
67+
end
68+
end
69+
70+
end
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Copyright 2017 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+
require 'base64'
16+
require 'yaml'
17+
require 'tempfile'
18+
require 'uri'
19+
20+
require 'kubernetes/api_client'
21+
require 'kubernetes/configuration'
22+
require 'kubernetes/config/error'
23+
24+
module Kubernetes
25+
26+
class KubeConfig
27+
28+
KUBE_CONFIG_DEFAULT_LOCATION = File.expand_path('~/.kube/config')
29+
30+
class << self
31+
32+
def list_context_names(config_file=KUBE_CONFIG_DEFAULT_LOCATION)
33+
config = self.new(config_file)
34+
return config.list_context_names
35+
end
36+
37+
end
38+
39+
@@temp_files = {}
40+
attr_accessor :path
41+
attr_writer :config
42+
43+
def initialize(path, config_hash=nil)
44+
@path = path
45+
@config = config_hash
46+
end
47+
48+
def base_path
49+
File.dirname(self.path)
50+
end
51+
52+
def config
53+
@config ||= open(self.path) do |io|
54+
::YAML.load(io.read)
55+
end
56+
end
57+
58+
def configure(configuration, context_name=nil)
59+
context = context_name ? self.find_context(context_name) : self.current_context
60+
user = context['user'] || {}
61+
cluster = context['cluster'] || {}
62+
63+
configuration.tap do |c|
64+
if user['authorization']
65+
c.api_key['authorization'] = user['authorization']
66+
end
67+
if server = cluster['server']
68+
server = URI.parse(server)
69+
c.scheme = server.scheme
70+
host = "#{server.host}:#{server.port}"
71+
host = "#{server.userinfo}@#{host}" if server.userinfo
72+
c.host = host
73+
c.base_path = server.path
74+
75+
if server.scheme == 'https'
76+
c.verify_ssl = cluster['verify_ssl']
77+
c.ssl_ca_cert = cluster['certificate-authority']
78+
c.cert_file = user['client-certificate']
79+
c.key_file = user['client-key']
80+
end
81+
end
82+
end
83+
end
84+
85+
def find_cluster(name)
86+
self.find_by_name(self.config['clusters'], 'cluster', name).tap do |cluster|
87+
create_temp_file_and_set(cluster, 'certificate-authority')
88+
cluster['verify_ssl'] = !cluster['insecure-skip-tls-verify']
89+
end
90+
end
91+
92+
def find_user(name)
93+
self.find_by_name(self.config['users'], 'user', name).tap do |user|
94+
create_temp_file_and_set(user, 'client-certificate')
95+
create_temp_file_and_set(user, 'client-key')
96+
# If tokenFile is specified, then set token
97+
if !user['token'] && user['tokenFile']
98+
open(user['tokenFile']) do |io|
99+
user['token'] = io.read.chomp
100+
end
101+
end
102+
# Convert token field to http header
103+
if user['token']
104+
user['authorization'] = "Bearer #{user['token']}"
105+
elsif user['username'] && user['password']
106+
user_pass = "#{user['username']}:#{user['password']}"
107+
user['authorization'] = "Basic #{Base64.strict_encode64(user_pass)}"
108+
end
109+
end
110+
end
111+
112+
def list_context_names
113+
self.config['contexts'].map { |e| e['name'] }
114+
end
115+
116+
def find_context(name)
117+
self.find_by_name(self.config['contexts'], 'context', name).tap do |context|
118+
context['cluster'] = find_cluster(context['cluster']) if context['cluster']
119+
context['user'] = find_user(context['user']) if context['user']
120+
end
121+
end
122+
123+
def current_context
124+
find_context(self.config['current-context'])
125+
end
126+
127+
protected
128+
def find_by_name(list, key, name)
129+
obj = list.find {|item| item['name'] == name }
130+
raise ConfigError.new("#{key}: #{name} not found") unless obj
131+
obj[key].dup
132+
end
133+
134+
def create_temp_file_and_set(obj, key)
135+
if !obj[key] && obj["#{key}-data"]
136+
obj[key] = create_temp_file_with_base64content(obj["#{key}-data"])
137+
end
138+
end
139+
140+
def create_temp_file_with_base64content(content)
141+
@@temp_files[content] ||= Tempfile.open('kube') do |temp|
142+
temp.write(Base64.strict_decode64(content))
143+
temp.path
144+
end
145+
end
146+
end
147+
end

kubernetes/lib/kubernetes/utils.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2017 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+
require 'kubernetes/config/incluster_config'
16+
require 'kubernetes/config/kube_config'
17+
18+
module Kubernetes
19+
20+
#
21+
# Use the service account kubernetes gives to pods to connect to kubernetes
22+
# cluster. It's intended for clients that expect to be running inside a pod
23+
# running on kubernetes. It will raise an exception if called from a process
24+
# not running in a kubernetes environment.
25+
def load_incluster_config(client_configuration: Configuration.default)
26+
config = InClusterConfig.new
27+
config.configure(client_configuration)
28+
end
29+
30+
#
31+
# Loads authentication and cluster information from kube-config file
32+
# and stores them in Kubernetes::Configuration.
33+
# @param config_file [String] Path of the kube-config file.
34+
# @param context [String] Set the active context. If is set to nil, current_context from config file will be used.
35+
# @param client_configuration [Kubernetes::Configuration] The Kubernetes::Configuration tp set configs to.
36+
def load_kube_config(
37+
config_file=ENV['KUBECONFIG'],
38+
context: nil,
39+
client_configuration: Configuration.default
40+
)
41+
config_file ||= KubeConfig::KUBE_CONFIG_DEFAULT_LOCATION
42+
config = KubeConfig.new(config_file)
43+
config.configure(client_configuration, context)
44+
end
45+
46+
#
47+
# Loads configuration the same as load_kube_config but returns an ApiClient
48+
# to be used with any API object. This will allow the caller to concurrently
49+
# talk with multiple clusters.
50+
# @param config_file [String] Path of the kube-config file.
51+
# @param context [String] Set the active context. If is set to nil, current_context from config file will be used.
52+
# @return [Kubernetes::ApiClient] Api client for Kubernetes cluster
53+
def new_client_from_config(
54+
config_file=ENV['KUBECONFIG'],
55+
context: nil
56+
)
57+
config_file ||= KubeConfig::KUBE_CONFIG_DEFAULT_LOCATION
58+
client_configuration = Configuration.new
59+
load_kube_config(config_file, context: context, client_configuration: client_configuration)
60+
ApiClient.new(client_configuration)
61+
end
62+
63+
extend self
64+
end

0 commit comments

Comments
 (0)