kubeclient 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of kubeclient might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cef89055e5f58a17421b6a66f2ad2f3d099a2f76
4
- data.tar.gz: 01dcd7211472e9cf10df4ce73ea4eef569c24481
3
+ metadata.gz: 84f24ef09e48aade18e1be3bf794cc1b207a9bb0
4
+ data.tar.gz: cead2099decd0352e11904a082cc6b2130e2d053
5
5
  SHA512:
6
- metadata.gz: fff797cb134a26fd50e3265b1ba303fb5b22d7d02086b1c20c292e8277923f5fe0d3e74a824767bf2315a9644dc46b19da31929e5c04e866e9929b1b0f4d203d
7
- data.tar.gz: c5c57ca8ffa4da1ca041053ba43191716caf17e2aa7307559c3406c3abacc85dfaf07102e0368adf8dbf8558e6bbd6177e474db52f572b259b879f10bf00f94c
6
+ metadata.gz: 47ebfefbac872d301808cec92cd9a0869677844ba693269453150280e4155eee03560ede2e569c3c866a17f4f1293739e586b8653e9926ec981e4d0ec3dc80a4
7
+ data.tar.gz: d47f3d587234753a1e47c63807364221a9980f4f960b2ccd84486266dc770a5fe0e9d6dd7b127c6b7e6959a6b096af6b62e20c303a2f19a277b5c290f3c2eaac
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Dependency Status](https://gemnasium.com/abonas/kubeclient.svg)](https://gemnasium.com/abonas/kubeclient)
7
7
 
8
8
  A Ruby client for Kubernetes REST api.
9
- The client supports GET, POST, PUT, DELETE on nodes, pods, secrets, services, replication controllers, namespaces, resource quotas, limit ranges and endpoints.
9
+ The client supports GET, POST, PUT, DELETE on nodes, pods, secrets, services, replication controllers, namespaces, resource quotas, limit ranges, endpoints, persistent volumes and persistent volume claims.
10
10
  The client currently supports Kubernetes REST api version v1beta3.
11
11
 
12
12
  ## Installation
@@ -72,7 +72,7 @@ of the following:
72
72
 
73
73
  ```ruby
74
74
  auth_options = {
75
- user: 'username',
75
+ username: 'username',
76
76
  password: 'password'
77
77
  }
78
78
  client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1beta3',
@@ -118,7 +118,7 @@ client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1beta3',
118
118
  ## Examples:
119
119
 
120
120
  #### Get all instances of a specific entity type
121
- Such as: `get_pods`, `get_secrets`, `get_services`, `get_nodes`, `get_replication_controllers`, `get_resource_quotas`, `get_limit_ranges`
121
+ Such as: `get_pods`, `get_secrets`, `get_services`, `get_nodes`, `get_replication_controllers`, `get_resource_quotas`, `get_limit_ranges`, `get_persistent_volumes`, `get_persistent_volume_claims`
122
122
 
123
123
  ```ruby
124
124
  pods = client.get_pods
@@ -134,7 +134,7 @@ pods = client.get_pods(label_selector: 'name=redis-master,app=redis')
134
134
  ```
135
135
 
136
136
  #### Get a specific instance of an entity (by name)
137
- Such as: `get_service "service name"` , `get_pod "pod name"` , `get_replication_controller "rc name"`, `get_secret "secret name"`, `get_resource_quota "resource quota name"`, `get_limit_range "limit range name"`
137
+ Such as: `get_service "service name"` , `get_pod "pod name"` , `get_replication_controller "rc name"`, `get_secret "secret name"`, `get_resource_quota "resource quota name"`, `get_limit_range "limit range name"` , `get_persistent_volume "persistent volume name"` , `get_persistent_volume_claim "persistent volume claim name"`
138
138
 
139
139
  The GET request should include the namespace name, except for nodes and namespaces entities.
140
140
 
@@ -159,7 +159,7 @@ client.delete_service "redis-service"
159
159
  ```
160
160
 
161
161
  #### Create an entity
162
- For example: `create_pod pod_object`, `create_replication_controller rc_obj`, `create_secret secret_object`, `create_resource_quota resource_quota_object`, `create_limit_range limit_range_object`
162
+ For example: `create_pod pod_object`, `create_replication_controller rc_obj`, `create_secret secret_object`, `create_resource_quota resource_quota_object`, `create_limit_range limit_range_object`, `create_persistent_volume persistent_volume_object`, `create_persistent_volume_claim persistent_volume_claim_object`
163
163
 
164
164
  Input parameter - object of type `Service`, `Pod`, `ReplicationController`.
165
165
 
@@ -177,7 +177,7 @@ client.create_service service`
177
177
  ```
178
178
 
179
179
  #### Update an entity
180
- For example: `update_pod`, `update_service`, `update_replication_controller`, `update_secret`, `update_resource_quota`, `update_limit_range`
180
+ For example: `update_pod`, `update_service`, `update_replication_controller`, `update_secret`, `update_resource_quota`, `update_limit_range`, `update_persistent_volume`, `update_persistent_volume_claim`
181
181
 
182
182
  Input parameter - object of type `Pod`, `Service`, `ReplicationController` etc.
183
183
 
@@ -188,7 +188,7 @@ client.update_service service1
188
188
  ```
189
189
 
190
190
  #### Get all entities of all types : all_entities
191
- Returns a hash with 10 keys (node, secret, service, pod, replication_controller, namespace, resource_quota, limit_range, endpoint and event). Each key points to an EntityList of same type.
191
+ Returns a hash with 12 keys (node, secret, service, pod, replication_controller, namespace, resource_quota, limit_range, endpoint, event, persistent_volume and persistent_volume_claim). Each key points to an EntityList of same type.
192
192
 
193
193
  This method is a convenience method instead of calling each entity's get method separately.
194
194
 
@@ -212,6 +212,23 @@ It is possible to interrupt the watcher from another thread with:
212
212
  watcher.finish
213
213
  ```
214
214
 
215
+ #### Get a proxy URL
216
+ You can get a complete URL for connecting a kubernetes entity via the proxy.
217
+
218
+ ```ruby
219
+ client.proxy_url('service', 'srvname', 'srvportname', 'ns')
220
+ => "https://localhost.localdomain:8443/api/v1/proxy/namespaces/ns/services/srvname:srvportname"
221
+ ```
222
+
223
+ Note the third parameter, port, is a port name for services and an integer for pods:
224
+
225
+ ```ruby
226
+ client.proxy_url('pod', 'podname', 5001, 'ns')
227
+ => "https://localhost.localdomain:8443/api/v1/namespaces/ns/pods/podname:5001/proxy"
228
+ ```
229
+
230
+
231
+
215
232
  ## Contributing
216
233
 
217
234
  1. Fork it ( https://github.com/[my-github-username]/kubeclient/fork )
data/lib/kubeclient.rb CHANGED
@@ -10,15 +10,16 @@ require 'kubeclient/common'
10
10
 
11
11
  module Kubeclient
12
12
  # Kubernetes Client
13
- class Client < Common::Client
14
- attr_reader :api_endpoint
13
+ class Client
14
+ include ClientMixin
15
15
  # Dynamically creating classes definitions (class Pod, class Service, etc.),
16
16
  # The classes are extending RecursiveOpenStruct.
17
17
  # This cancels the need to define the classes
18
18
  # manually on every new entity addition,
19
19
  # and especially since currently the class body is empty
20
20
  ENTITY_TYPES = %w(Pod Service ReplicationController Node Event Endpoint
21
- Namespace Secret ResourceQuota LimitRange).map do |et|
21
+ Namespace Secret ResourceQuota LimitRange PersistentVolume
22
+ PersistentVolumeClaim).map do |et|
22
23
  clazz = Class.new(RecursiveOpenStruct) do
23
24
  def initialize(hash = nil, args = {})
24
25
  args.merge!(recurse_over_arrays: true)
@@ -28,6 +29,8 @@ module Kubeclient
28
29
  [Kubeclient.const_set(et, clazz), et]
29
30
  end
30
31
 
32
+ ClientMixin.define_entity_methods(ENTITY_TYPES)
33
+
31
34
  def initialize(uri,
32
35
  version = 'v1beta3',
33
36
  ssl_options: {
@@ -36,43 +39,18 @@ module Kubeclient
36
39
  ca_file: nil,
37
40
  verify_ssl: OpenSSL::SSL::VERIFY_PEER
38
41
  },
39
- auth_options: {}
42
+ auth_options: {
43
+ username: nil,
44
+ password: nil,
45
+ bearer_token: nil,
46
+ bearer_token_file: nil
47
+ }
40
48
  )
41
-
42
- fail ArgumentError, 'Missing uri' if uri.nil?
43
-
44
- validate_auth_options(auth_options)
45
-
46
- handle_uri(uri, '/api')
47
- @api_version = version
48
- @headers = {}
49
- @ssl_options = ssl_options
50
-
51
- if auth_options[:user]
52
- @basic_auth_user = auth_options[:user]
53
- @basic_auth_password = auth_options[:password]
54
- elsif auth_options[:bearer_token]
55
- bearer_token(auth_options[:bearer_token])
56
- elsif auth_options[:bearer_token_file]
57
- validate_bearer_token_file(auth_options[:bearer_token_file])
58
- bearer_token(File.read(auth_options[:bearer_token_file]))
59
- end
49
+ initialize_client(uri, '/api', version, ssl_options: ssl_options, auth_options: auth_options)
60
50
  end
61
51
 
62
52
  def all_entities
63
53
  retrieve_all_entities(ENTITY_TYPES)
64
54
  end
65
-
66
- define_entity_methods(ENTITY_TYPES)
67
-
68
- private
69
-
70
- def validate_bearer_token_file(bearer_token_file)
71
- msg = "Token file #{bearer_token_file} does not exist"
72
- fail ArgumentError, msg unless File.file?(bearer_token_file)
73
-
74
- msg = "Cannot read token file #{bearer_token_file}"
75
- fail ArgumentError, msg unless File.readable?(bearer_token_file)
76
- end
77
55
  end
78
56
  end
@@ -1,244 +1,299 @@
1
1
  require 'json'
2
2
  require 'rest-client'
3
3
  module Kubeclient
4
- module Common
5
- # Common methods
6
- class Client
7
- def handle_exception
8
- yield
9
- rescue RestClient::Exception => e
10
- begin
11
- json_error_msg = JSON.parse(e.response || '') || {}
12
- rescue JSON::ParserError
13
- json_error_msg = {}
14
- end
15
- err_message = json_error_msg['message'] || e.message
16
- raise KubeException.new(e.http_code, err_message)
17
- end
18
-
19
- def handle_uri(uri, path)
20
- @api_endpoint = (uri.is_a? URI) ? uri : URI.parse(uri)
21
- @api_endpoint.path = path if @api_endpoint.path.empty?
22
- @api_endpoint.path = @api_endpoint.path.chop \
23
- if @api_endpoint.path.end_with? '/'
4
+ # Common methods
5
+ module ClientMixin
6
+ attr_reader :api_endpoint
7
+ attr_reader :ssl_options
8
+ attr_reader :auth_options
9
+ attr_reader :headers
10
+
11
+ def initialize_client(
12
+ uri,
13
+ path,
14
+ version = nil,
15
+ ssl_options: {
16
+ client_cert: nil,
17
+ client_key: nil,
18
+ ca_file: nil,
19
+ verify_ssl: OpenSSL::SSL::VERIFY_PEER
20
+ },
21
+ auth_options: {
22
+ username: nil,
23
+ password: nil,
24
+ bearer_token: nil,
25
+ bearer_token_file: nil
26
+ }
27
+ )
28
+ validate_auth_options(auth_options)
29
+ handle_uri(uri, path)
30
+
31
+ @api_version = version
32
+ @headers = {}
33
+ @ssl_options = ssl_options
34
+ @auth_options = auth_options
35
+
36
+ if auth_options[:bearer_token]
37
+ @headers[:Authorization] = "Bearer #{@auth_options[:bearer_token]}"
38
+ elsif auth_options[:bearer_token_file]
39
+ validate_bearer_token_file
40
+ @headers[:Authorization] = "Bearer #{File.read(@auth_options[:bearer_token_file])}"
24
41
  end
42
+ end
25
43
 
26
- def build_namespace_prefix(namespace)
27
- namespace.to_s.empty? ? '' : "namespaces/#{namespace}/"
44
+ def handle_exception
45
+ yield
46
+ rescue RestClient::Exception => e
47
+ begin
48
+ json_error_msg = JSON.parse(e.response || '') || {}
49
+ rescue JSON::ParserError
50
+ json_error_msg = {}
28
51
  end
52
+ err_message = json_error_msg['message'] || e.message
53
+ raise KubeException.new(e.http_code, err_message)
54
+ end
29
55
 
30
- public
31
-
32
- def self.define_entity_methods(entity_types)
33
- entity_types.each do |klass, entity_type|
34
- entity_name = entity_type.underscore
35
- entity_name_plural = pluralize_entity(entity_name)
56
+ def handle_uri(uri, path)
57
+ fail ArgumentError, 'Missing uri' if uri.nil?
58
+ @api_endpoint = (uri.is_a? URI) ? uri : URI.parse(uri)
59
+ @api_endpoint.path = path if @api_endpoint.path.empty?
60
+ @api_endpoint.path = @api_endpoint.path.chop \
61
+ if @api_endpoint.path.end_with? '/'
62
+ end
36
63
 
37
- # get all entities of a type e.g. get_nodes, get_pods, etc.
38
- define_method("get_#{entity_name_plural}") do |options = {}|
39
- get_entities(entity_type, klass, options)
40
- end
64
+ def build_namespace_prefix(namespace)
65
+ namespace.to_s.empty? ? '' : "namespaces/#{namespace}/"
66
+ end
41
67
 
42
- # watch all entities of a type e.g. watch_nodes, watch_pods, etc.
43
- define_method("watch_#{entity_name_plural}") \
44
- do |resource_version = nil|
45
- watch_entities(entity_type, resource_version)
46
- end
68
+ public
47
69
 
48
- # get a single entity of a specific type by name
49
- define_method("get_#{entity_name}") do |name, namespace = nil|
50
- get_entity(entity_type, klass, name, namespace)
51
- end
70
+ def self.define_entity_methods(entity_types)
71
+ entity_types.each do |klass, entity_type|
72
+ entity_name = entity_type.underscore
73
+ entity_name_plural = pluralize_entity(entity_name)
52
74
 
53
- define_method("delete_#{entity_name}") do |name, namespace = nil|
54
- delete_entity(entity_type, name, namespace)
55
- end
75
+ # get all entities of a type e.g. get_nodes, get_pods, etc.
76
+ define_method("get_#{entity_name_plural}") do |options = {}|
77
+ get_entities(entity_type, klass, options)
78
+ end
56
79
 
57
- define_method("create_#{entity_name}") do |entity_config|
58
- create_entity(entity_type, entity_config, klass)
59
- end
80
+ # watch all entities of a type e.g. watch_nodes, watch_pods, etc.
81
+ define_method("watch_#{entity_name_plural}") \
82
+ do |resource_version = nil|
83
+ watch_entities(entity_type, resource_version)
84
+ end
60
85
 
61
- define_method("update_#{entity_name}") do |entity_config|
62
- update_entity(entity_type, entity_config)
63
- end
86
+ # get a single entity of a specific type by name
87
+ define_method("get_#{entity_name}") do |name, namespace = nil|
88
+ get_entity(entity_type, klass, name, namespace)
64
89
  end
65
- end
66
90
 
67
- def self.pluralize_entity(entity_name)
68
- return entity_name + 's' if entity_name.end_with? 'quota'
69
- entity_name.pluralize
70
- end
91
+ define_method("delete_#{entity_name}") do |name, namespace = nil|
92
+ delete_entity(entity_type, name, namespace)
93
+ end
71
94
 
72
- def create_rest_client(path = nil)
73
- path ||= @api_endpoint.path
74
- options = {
75
- ssl_ca_file: @ssl_options[:ca_file],
76
- verify_ssl: @ssl_options[:verify_ssl],
77
- ssl_client_cert: @ssl_options[:client_cert],
78
- ssl_client_key: @ssl_options[:client_key],
79
- user: @basic_auth_user,
80
- password: @basic_auth_password
81
- }
82
- RestClient::Resource.new(@api_endpoint.merge(path).to_s, options)
83
- end
95
+ define_method("create_#{entity_name}") do |entity_config|
96
+ create_entity(entity_type, entity_config, klass)
97
+ end
84
98
 
85
- def rest_client
86
- @rest_client ||= begin
87
- create_rest_client("#{@api_endpoint.path}/#{@api_version}")
99
+ define_method("update_#{entity_name}") do |entity_config|
100
+ update_entity(entity_type, entity_config)
88
101
  end
89
102
  end
103
+ end
90
104
 
91
- def watch_entities(entity_type, resource_version = nil)
92
- resource = resource_name(entity_type.to_s)
93
-
94
- uri = @api_endpoint
95
- .merge("#{@api_endpoint.path}/#{@api_version}/watch/#{resource}")
105
+ def self.pluralize_entity(entity_name)
106
+ return entity_name + 's' if entity_name.end_with? 'quota'
107
+ entity_name.pluralize
108
+ end
96
109
 
97
- unless resource_version.nil?
98
- uri.query = URI.encode_www_form('resourceVersion' => resource_version)
99
- end
110
+ def create_rest_client(path = nil)
111
+ path ||= @api_endpoint.path
112
+ options = {
113
+ ssl_ca_file: @ssl_options[:ca_file],
114
+ verify_ssl: @ssl_options[:verify_ssl],
115
+ ssl_client_cert: @ssl_options[:client_cert],
116
+ ssl_client_key: @ssl_options[:client_key],
117
+ user: @auth_options[:username],
118
+ password: @auth_options[:password]
119
+ }
120
+ RestClient::Resource.new(@api_endpoint.merge(path).to_s, options)
121
+ end
100
122
 
101
- options = {
102
- use_ssl: uri.scheme == 'https',
103
- ca_file: @ssl_options[:ca_file],
104
- # ruby Net::HTTP uses verify_mode instead of verify_ssl
105
- # http://ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/Net/HTTP.html
106
- verify_mode: @ssl_options[:verify_ssl],
107
- cert: @ssl_options[:client_cert],
108
- key: @ssl_options[:client_key],
109
- basic_auth_user: @basic_auth_user,
110
- basic_auth_password: @basic_auth_password,
111
- headers: @headers
112
- }
113
-
114
- WatchStream.new(uri, options)
115
- end
116
-
117
- def get_entities(entity_type, klass, options)
118
- params = {}
119
- if options[:label_selector]
120
- params['params'] = { labelSelector: options[:label_selector] }
121
- end
123
+ def rest_client
124
+ @rest_client ||= begin
125
+ create_rest_client("#{@api_endpoint.path}/#{@api_version}")
126
+ end
127
+ end
122
128
 
123
- # TODO: namespace support?
124
- response = handle_exception do
125
- rest_client[resource_name(entity_type)]
126
- .get(params.merge(@headers))
127
- end
129
+ def watch_entities(entity_type, resource_version = nil)
130
+ resource = resource_name(entity_type.to_s)
128
131
 
129
- result = JSON.parse(response)
132
+ uri = @api_endpoint
133
+ .merge("#{@api_endpoint.path}/#{@api_version}/watch/#{resource}")
130
134
 
131
- resource_version = result.fetch('resourceVersion', nil)
132
- if resource_version.nil?
133
- resource_version =
134
- result.fetch('metadata', {}).fetch('resourceVersion', nil)
135
- end
135
+ unless resource_version.nil?
136
+ uri.query = URI.encode_www_form('resourceVersion' => resource_version)
137
+ end
136
138
 
137
- collection = result['items'].map { |item| new_entity(item, klass) }
139
+ options = {
140
+ use_ssl: uri.scheme == 'https',
141
+ ca_file: @ssl_options[:ca_file],
142
+ # ruby Net::HTTP uses verify_mode instead of verify_ssl
143
+ # http://ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/Net/HTTP.html
144
+ verify_mode: @ssl_options[:verify_ssl],
145
+ cert: @ssl_options[:client_cert],
146
+ key: @ssl_options[:client_key],
147
+ basic_auth_user: @auth_options[:username],
148
+ basic_auth_password: @auth_options[:password],
149
+ headers: @headers
150
+ }
151
+
152
+ Kubeclient::Common::WatchStream.new(uri, options)
153
+ end
138
154
 
139
- EntityList.new(entity_type, resource_version, collection)
155
+ def get_entities(entity_type, klass, options)
156
+ params = {}
157
+ if options[:label_selector]
158
+ params['params'] = { labelSelector: options[:label_selector] }
140
159
  end
141
160
 
142
- def get_entity(entity_type, klass, name, namespace = nil)
143
- ns_prefix = build_namespace_prefix(namespace)
144
- response = handle_exception do
145
- rest_client[ns_prefix + resource_name(entity_type) + "/#{name}"]
146
- .get(@headers)
147
- end
148
- result = JSON.parse(response)
149
- new_entity(result, klass)
161
+ # TODO: namespace support?
162
+ response = handle_exception do
163
+ rest_client[resource_name(entity_type)]
164
+ .get(params.merge(@headers))
150
165
  end
151
166
 
152
- def delete_entity(entity_type, name, namespace = nil)
153
- ns_prefix = build_namespace_prefix(namespace)
154
- handle_exception do
155
- rest_client[ns_prefix + resource_name(entity_type) + "/#{name}"]
156
- .delete(@headers)
157
- end
167
+ result = JSON.parse(response)
168
+
169
+ resource_version = result.fetch('resourceVersion', nil)
170
+ if resource_version.nil?
171
+ resource_version =
172
+ result.fetch('metadata', {}).fetch('resourceVersion', nil)
158
173
  end
159
174
 
160
- def create_entity(entity_type, entity_config, klass)
161
- # to_hash should be called because of issue #9 in recursive open
162
- # struct
163
- hash = entity_config.to_hash
175
+ collection = result['items'].map { |item| new_entity(item, klass) }
164
176
 
165
- ns_prefix = build_namespace_prefix(entity_config.metadata.namespace)
177
+ Kubeclient::Common::EntityList.new(entity_type, resource_version, collection)
178
+ end
166
179
 
167
- # TODO: temporary solution to add "kind" and apiVersion to request
168
- # until this issue is solved
169
- # https://github.com/GoogleCloudPlatform/kubernetes/issues/6439
170
- hash['kind'] = entity_type
171
- hash['apiVersion'] = @api_version
172
- response = handle_exception do
173
- rest_client[ns_prefix + resource_name(entity_type)]
174
- .post(hash.to_json, @headers)
175
- end
176
- result = JSON.parse(response)
177
- new_entity(result, klass)
178
- end
179
-
180
- def update_entity(entity_type, entity_config)
181
- name = entity_config.metadata.name
182
- # to_hash should be called because of issue #9 in recursive open
183
- # struct
184
- hash = entity_config.to_hash
185
- ns_prefix = build_namespace_prefix(entity_config.metadata.namespace)
186
- handle_exception do
187
- rest_client[ns_prefix + resource_name(entity_type) + "/#{name}"]
188
- .put(hash.to_json, @headers)
189
- end
180
+ def get_entity(entity_type, klass, name, namespace = nil)
181
+ ns_prefix = build_namespace_prefix(namespace)
182
+ response = handle_exception do
183
+ rest_client[ns_prefix + resource_name(entity_type) + "/#{name}"]
184
+ .get(@headers)
190
185
  end
186
+ result = JSON.parse(response)
187
+ new_entity(result, klass)
188
+ end
191
189
 
192
- def new_entity(hash, klass)
193
- klass.new(hash)
190
+ def delete_entity(entity_type, name, namespace = nil)
191
+ ns_prefix = build_namespace_prefix(namespace)
192
+ handle_exception do
193
+ rest_client[ns_prefix + resource_name(entity_type) + "/#{name}"]
194
+ .delete(@headers)
194
195
  end
196
+ end
195
197
 
196
- def retrieve_all_entities(entity_types)
197
- entity_types.each_with_object({}) do |(_, entity_type), result_hash|
198
- # method call for get each entities
199
- # build hash of entity name to array of the entities
200
- entity_name = self.class.pluralize_entity entity_type.underscore
201
- method_name = "get_#{entity_name}"
202
- key_name = entity_type.underscore
203
- result_hash[key_name] = send(method_name)
204
- end
198
+ def create_entity(entity_type, entity_config, klass)
199
+ # to_hash should be called because of issue #9 in recursive open
200
+ # struct
201
+ hash = entity_config.to_hash
202
+
203
+ ns_prefix = build_namespace_prefix(entity_config.metadata['table'][:namespace])
204
+
205
+ # TODO: temporary solution to add "kind" and apiVersion to request
206
+ # until this issue is solved
207
+ # https://github.com/GoogleCloudPlatform/kubernetes/issues/6439
208
+ hash['kind'] = entity_type
209
+ hash['apiVersion'] = @api_version
210
+ response = handle_exception do
211
+ rest_client[ns_prefix + resource_name(entity_type)]
212
+ .post(hash.to_json, @headers)
205
213
  end
214
+ result = JSON.parse(response)
215
+ new_entity(result, klass)
216
+ end
206
217
 
207
- def resource_name(entity_type)
208
- self.class.pluralize_entity entity_type.downcase
218
+ def update_entity(entity_type, entity_config)
219
+ name = entity_config.metadata.name
220
+ # to_hash should be called because of issue #9 in recursive open
221
+ # struct
222
+ hash = entity_config.to_hash
223
+ ns_prefix = build_namespace_prefix(entity_config.metadata['table'][:namespace])
224
+ handle_exception do
225
+ rest_client[ns_prefix + resource_name(entity_type) + "/#{name}"]
226
+ .put(hash.to_json, @headers)
209
227
  end
228
+ end
210
229
 
211
- def api_valid?
212
- result = api
213
- result.is_a?(Hash) && (result['versions'] || []).include?(@api_version)
230
+ def new_entity(hash, klass)
231
+ klass.new(hash)
232
+ end
233
+
234
+ def retrieve_all_entities(entity_types)
235
+ entity_types.each_with_object({}) do |(_, entity_type), result_hash|
236
+ # method call for get each entities
237
+ # build hash of entity name to array of the entities
238
+ entity_name = ClientMixin.pluralize_entity entity_type.underscore
239
+ method_name = "get_#{entity_name}"
240
+ key_name = entity_type.underscore
241
+ result_hash[key_name] = send(method_name)
214
242
  end
243
+ end
215
244
 
216
- def api
217
- response = handle_exception do
218
- create_rest_client.get(@headers)
219
- end
220
- JSON.parse(response)
245
+ def proxy_url(kind, name, port, namespace = '')
246
+ entity_name_plural = ClientMixin.pluralize_entity(kind.to_s)
247
+ ns_prefix = build_namespace_prefix(namespace)
248
+ # TODO: Change this once services supports the new scheme
249
+ if entity_name_plural == 'pods'
250
+ rest_client["#{ns_prefix}#{entity_name_plural}/#{name}:#{port}/proxy"].url
251
+ else
252
+ rest_client["proxy/#{ns_prefix}#{entity_name_plural}/#{name}:#{port}"].url
221
253
  end
254
+ end
255
+
256
+ def resource_name(entity_type)
257
+ ClientMixin.pluralize_entity entity_type.downcase
258
+ end
222
259
 
223
- private
260
+ def api_valid?
261
+ result = api
262
+ result.is_a?(Hash) && (result['versions'] || []).include?(@api_version)
263
+ end
224
264
 
225
- def bearer_token(bearer_token)
226
- @headers ||= {}
227
- @headers[:Authorization] = "Bearer #{bearer_token}"
265
+ def api
266
+ response = handle_exception do
267
+ create_rest_client.get(@headers)
228
268
  end
269
+ JSON.parse(response)
270
+ end
229
271
 
230
- def validate_auth_options(opts)
231
- exclusive_keys = [:bearer_token, :bearer_token_file, :user]
272
+ private
232
273
 
233
- return if exclusive_keys.none? { |s| opts.key?(s) }
274
+ def bearer_token(bearer_token)
275
+ @headers ||= {}
276
+ @headers[:Authorization] = "Bearer #{bearer_token}"
277
+ end
234
278
 
235
- msg = 'Invalid auth options: specify only one of user/password,' \
236
- ' bearer_token or bearer_token_file'
237
- fail ArgumentError, msg unless exclusive_keys.one? { |s| opts.key?(s) }
279
+ def validate_auth_options(opts)
280
+ # maintain backward compatibility:
281
+ opts[:username] = opts[:user] if opts[:user]
238
282
 
239
- msg = 'Basic auth requires both user & password'
240
- fail ArgumentError, msg if opts.key?(:user) && !opts.key?(:password)
283
+ if [:bearer_token, :bearer_token_file, :username].count { |key| opts[key] } > 1
284
+ fail(ArgumentError, 'Invalid auth options: specify only one of username/password,' \
285
+ ' bearer_token or bearer_token_file')
286
+ elsif [:username, :password].count { |key| opts[key] } == 1
287
+ fail(ArgumentError, 'Basic auth requires both username & password')
241
288
  end
242
289
  end
290
+
291
+ def validate_bearer_token_file
292
+ msg = "Token file #{@auth_options[:bearer_token_file]} does not exist"
293
+ fail ArgumentError, msg unless File.file?(@auth_options[:bearer_token_file])
294
+
295
+ msg = "Cannot read token file #{@auth_options[:bearer_token_file]}"
296
+ fail ArgumentError, msg unless File.readable?(@auth_options[:bearer_token_file])
297
+ end
243
298
  end
244
299
  end
@@ -1,4 +1,4 @@
1
1
  # Kubernetes REST-API Client
2
2
  module Kubeclient
3
- VERSION = '0.4.0'
3
+ VERSION = '0.5.0'
4
4
  end
@@ -0,0 +1,37 @@
1
+ {
2
+ "kind": "PersistentVolume",
3
+ "apiVersion": "v1",
4
+ "metadata": {
5
+ "name": "pv0001",
6
+ "selfLink": "/api/v1/persistentvolumes/pv0001",
7
+ "uid": "c83eece1-4b38-11e5-8d27-28d2447dcefe",
8
+ "resourceVersion": "1585",
9
+ "creationTimestamp": "2015-08-25T14:51:35Z",
10
+ "labels": {
11
+ "type": "local"
12
+ }
13
+ },
14
+ "spec": {
15
+ "capacity": {
16
+ "storage": "10Gi"
17
+ },
18
+ "hostPath": {
19
+ "path": "/tmp/data01"
20
+ },
21
+ "accessModes": [
22
+ "ReadWriteOnce"
23
+ ],
24
+ "claimRef": {
25
+ "kind": "PersistentVolumeClaim",
26
+ "namespace": "default",
27
+ "name": "myclaim-1",
28
+ "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe",
29
+ "apiVersion": "v1",
30
+ "resourceVersion": "1582"
31
+ },
32
+ "persistentVolumeReclaimPolicy": "Retain"
33
+ },
34
+ "status": {
35
+ "phase": "Bound"
36
+ }
37
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "kind": "PersistentVolumeClaim",
3
+ "apiVersion": "v1",
4
+ "metadata": {
5
+ "name": "myclaim-1",
6
+ "namespace": "default",
7
+ "selfLink": "/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1",
8
+ "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe",
9
+ "resourceVersion": "1584",
10
+ "creationTimestamp": "2015-08-25T14:51:55Z"
11
+ },
12
+ "spec": {
13
+ "accessModes": [
14
+ "ReadWriteOnce"
15
+ ],
16
+ "resources": {
17
+ "requests": {
18
+ "storage": "3Gi"
19
+ }
20
+ },
21
+ "volumeName": "pv0001"
22
+ },
23
+ "status": {
24
+ "phase": "Bound",
25
+ "accessModes": [
26
+ "ReadWriteOnce"
27
+ ],
28
+ "capacity": {
29
+ "storage": "10Gi"
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "kind": "PersistentVolumeClaimList",
3
+ "apiVersion": "v1",
4
+ "metadata": {
5
+ "selfLink": "/api/v1/persistentvolumeclaims",
6
+ "resourceVersion": "3188"
7
+ },
8
+ "items": [
9
+ {
10
+ "metadata": {
11
+ "name": "myclaim-1",
12
+ "namespace": "default",
13
+ "selfLink": "/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1",
14
+ "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe",
15
+ "resourceVersion": "1584",
16
+ "creationTimestamp": "2015-08-25T14:51:55Z"
17
+ },
18
+ "spec": {
19
+ "accessModes": [
20
+ "ReadWriteOnce"
21
+ ],
22
+ "resources": {
23
+ "requests": {
24
+ "storage": "3Gi"
25
+ }
26
+ },
27
+ "volumeName": "pv0001"
28
+ },
29
+ "status": {
30
+ "phase": "Bound",
31
+ "accessModes": [
32
+ "ReadWriteOnce"
33
+ ],
34
+ "capacity": {
35
+ "storage": "10Gi"
36
+ }
37
+ }
38
+ }
39
+ ]
40
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "kind": "PersistentVolumeList",
3
+ "apiVersion": "v1",
4
+ "metadata": {
5
+ "selfLink": "/api/v1/persistentvolumes",
6
+ "resourceVersion": "2999"
7
+ },
8
+ "items": [
9
+ {
10
+ "metadata": {
11
+ "name": "pv0001",
12
+ "selfLink": "/api/v1/persistentvolumes/pv0001",
13
+ "uid": "c83eece1-4b38-11e5-8d27-28d2447dcefe",
14
+ "resourceVersion": "1585",
15
+ "creationTimestamp": "2015-08-25T14:51:35Z",
16
+ "labels": {
17
+ "type": "local"
18
+ }
19
+ },
20
+ "spec": {
21
+ "capacity": {
22
+ "storage": "10Gi"
23
+ },
24
+ "hostPath": {
25
+ "path": "/tmp/data01"
26
+ },
27
+ "accessModes": [
28
+ "ReadWriteOnce"
29
+ ],
30
+ "claimRef": {
31
+ "kind": "PersistentVolumeClaim",
32
+ "namespace": "default",
33
+ "name": "myclaim-1",
34
+ "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe",
35
+ "apiVersion": "v1",
36
+ "resourceVersion": "1582"
37
+ },
38
+ "persistentVolumeReclaimPolicy": "Retain"
39
+ },
40
+ "status": {
41
+ "phase": "Bound"
42
+ }
43
+ }
44
+ ]
45
+ }
@@ -227,9 +227,17 @@ class KubeClientTest < MiniTest::Test
227
227
  .to_return(body: open_test_json_file('limit_range_list.json'),
228
228
  status: 200)
229
229
 
230
+ stub_request(:get, %r{/persistentvolumes})
231
+ .to_return(body: open_test_json_file('persistent_volume_list.json'),
232
+ status: 200)
233
+
234
+ stub_request(:get, %r{/persistentvolumeclaims})
235
+ .to_return(body: open_test_json_file('persistent_volume_claim_list.json'),
236
+ status: 200)
237
+
230
238
  client = Kubeclient::Client.new 'http://localhost:8080/api/', 'v1beta3'
231
239
  result = client.all_entities
232
- assert_equal(10, result.keys.size)
240
+ assert_equal(12, result.keys.size)
233
241
  assert_instance_of(Kubeclient::Common::EntityList, result['node'])
234
242
  assert_instance_of(Kubeclient::Common::EntityList, result['service'])
235
243
  assert_instance_of(Kubeclient::Common::EntityList,
@@ -246,6 +254,8 @@ class KubeClientTest < MiniTest::Test
246
254
  assert_instance_of(Kubeclient::Secret, result['secret'][0])
247
255
  assert_instance_of(Kubeclient::ResourceQuota, result['resource_quota'][0])
248
256
  assert_instance_of(Kubeclient::LimitRange, result['limit_range'][0])
257
+ assert_instance_of(Kubeclient::PersistentVolume, result['persistent_volume'][0])
258
+ assert_instance_of(Kubeclient::PersistentVolumeClaim, result['persistent_volume_claim'][0])
249
259
  end
250
260
 
251
261
  def test_api_bearer_token_with_params_success
@@ -305,6 +315,26 @@ class KubeClientTest < MiniTest::Test
305
315
  .to_return(body: open_test_json_file('pod_list_b3.json'),
306
316
  status: 200)
307
317
 
318
+ client = Kubeclient::Client.new 'http://localhost:8080/api/',
319
+ auth_options: {
320
+ username: 'username',
321
+ password: 'password'
322
+ }
323
+
324
+ pods = client.get_pods
325
+
326
+ assert_equal('Pod', pods.kind)
327
+ assert_equal(1, pods.size)
328
+ assert_requested(:get,
329
+ 'http://username:password@localhost:8080/api/v1beta3/pods',
330
+ times: 1)
331
+ end
332
+
333
+ def test_api_basic_auth_back_comp_success
334
+ stub_request(:get, 'http://username:password@localhost:8080/api/v1beta3/pods')
335
+ .to_return(body: open_test_json_file('pod_list_b3.json'),
336
+ status: 200)
337
+
308
338
  client = Kubeclient::Client.new 'http://localhost:8080/api/',
309
339
  auth_options: {
310
340
  user: 'username',
@@ -328,7 +358,7 @@ class KubeClientTest < MiniTest::Test
328
358
 
329
359
  client = Kubeclient::Client.new 'http://localhost:8080/api/',
330
360
  auth_options: {
331
- user: 'username',
361
+ username: 'username',
332
362
  password: 'password'
333
363
  }
334
364
 
@@ -340,8 +370,19 @@ class KubeClientTest < MiniTest::Test
340
370
  times: 1)
341
371
  end
342
372
 
373
+ def test_init_username_no_password
374
+ expected_msg = 'Basic auth requires both username & password'
375
+ exception = assert_raises(ArgumentError) do
376
+ Kubeclient::Client.new 'http://localhost:8080',
377
+ auth_options: {
378
+ username: 'username'
379
+ }
380
+ end
381
+ assert_equal expected_msg, exception.message
382
+ end
383
+
343
384
  def test_init_user_no_password
344
- expected_msg = 'Basic auth requires both user & password'
385
+ expected_msg = 'Basic auth requires both username & password'
345
386
  exception = assert_raises(ArgumentError) do
346
387
  Kubeclient::Client.new 'http://localhost:8080',
347
388
  auth_options: {
@@ -351,13 +392,26 @@ class KubeClientTest < MiniTest::Test
351
392
  assert_equal expected_msg, exception.message
352
393
  end
353
394
 
395
+ def test_init_username_and_bearer_token
396
+ expected_msg = 'Invalid auth options: specify only one of username/password,' \
397
+ ' bearer_token or bearer_token_file'
398
+ exception = assert_raises(ArgumentError) do
399
+ Kubeclient::Client.new 'http://localhost:8080',
400
+ auth_options: {
401
+ username: 'username',
402
+ bearer_token: 'token'
403
+ }
404
+ end
405
+ assert_equal expected_msg, exception.message
406
+ end
407
+
354
408
  def test_init_user_and_bearer_token
355
- expected_msg = 'Invalid auth options: specify only one of user/password,' \
409
+ expected_msg = 'Invalid auth options: specify only one of username/password,' \
356
410
  ' bearer_token or bearer_token_file'
357
411
  exception = assert_raises(ArgumentError) do
358
412
  Kubeclient::Client.new 'http://localhost:8080',
359
413
  auth_options: {
360
- user: 'username',
414
+ username: 'username',
361
415
  bearer_token: 'token'
362
416
  }
363
417
  end
@@ -365,7 +419,7 @@ class KubeClientTest < MiniTest::Test
365
419
  end
366
420
 
367
421
  def test_bearer_token_and_bearer_token_file
368
- expected_msg = 'Invalid auth options: specify only one of user/password,' \
422
+ expected_msg = 'Invalid auth options: specify only one of username/password,' \
369
423
  ' bearer_token or bearer_token_file'
370
424
  exception = assert_raises(ArgumentError) do
371
425
  Kubeclient::Client.new 'http://localhost:8080',
@@ -406,6 +460,37 @@ class KubeClientTest < MiniTest::Test
406
460
  assert_equal(1, pods.size)
407
461
  end
408
462
 
463
+ def test_proxy_url
464
+ client = Kubeclient::Client.new 'http://host:8080', 'v1'
465
+ assert_equal('http://host:8080/api/v1/proxy/namespaces/ns/services/srvname:srvportname',
466
+ client.proxy_url('service', 'srvname', 'srvportname', 'ns'))
467
+
468
+ assert_equal('http://host:8080/api/v1/namespaces/ns/pods/srvname:srvportname/proxy',
469
+ client.proxy_url('pods', 'srvname', 'srvportname', 'ns'))
470
+
471
+ # Check no namespace provided
472
+ assert_equal('http://host:8080/api/v1/proxy/nodes/srvname:srvportname',
473
+ client.proxy_url('nodes', 'srvname', 'srvportname'))
474
+
475
+ # Check integer port
476
+ assert_equal('http://host:8080/api/v1/proxy/nodes/srvname:5001',
477
+ client.proxy_url('nodes', 'srvname', 5001))
478
+ end
479
+
480
+ def test_attr_readers
481
+ client = Kubeclient::Client.new 'http://localhost:8080/api/',
482
+ ssl_options: {
483
+ client_key: 'secret'
484
+ },
485
+ auth_options: {
486
+ bearer_token: 'token'
487
+ }
488
+ assert_equal '/api', client.api_endpoint.path
489
+ assert_equal 'secret', client.ssl_options[:client_key]
490
+ assert_equal 'token', client.auth_options[:bearer_token]
491
+ assert_equal 'Bearer token', client.headers[:Authorization]
492
+ end
493
+
409
494
  private
410
495
 
411
496
  # dup method creates a shallow copy which is not good in this case
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+
3
+ # PersistentVolume tests
4
+ class TestPersistentVolume < MiniTest::Test
5
+ def test_get_from_json_v3
6
+ stub_request(:get, %r{/persistentvolumes})
7
+ .to_return(body: open_test_json_file('persistent_volume.json'),
8
+ status: 200)
9
+
10
+ client = Kubeclient::Client.new 'http://localhost:8080/api/', 'v1'
11
+ volume = client.get_persistent_volume 'pv0001'
12
+
13
+ assert_instance_of(Kubeclient::PersistentVolume, volume)
14
+ assert_equal('pv0001', volume.metadata.name)
15
+ assert_equal('10Gi', volume.spec.capacity.storage)
16
+ assert_equal('/tmp/data01', volume.spec.hostPath.path)
17
+
18
+ assert_requested(:get,
19
+ 'http://localhost:8080/api/v1/persistentvolumes/pv0001',
20
+ times: 1)
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+
3
+ # PersistentVolumeClaim tests
4
+ class TestPersistentVolumeClaim < MiniTest::Test
5
+ def test_get_from_json_v3
6
+ stub_request(:get, %r{/persistentvolumeclaims})
7
+ .to_return(body: open_test_json_file('persistent_volume_claim.json'),
8
+ status: 200)
9
+
10
+ client = Kubeclient::Client.new 'http://localhost:8080/api/', 'v1'
11
+ claim = client.get_persistent_volume_claim 'myclaim-1', 'default'
12
+
13
+ assert_instance_of(Kubeclient::PersistentVolumeClaim, claim)
14
+ assert_equal('myclaim-1', claim.metadata.name)
15
+ assert_equal('3Gi', claim.spec.resources.requests.storage)
16
+ assert_equal('pv0001', claim.spec.volumeName)
17
+
18
+ assert_requested(:get,
19
+ 'http://localhost:8080/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1',
20
+ times: 1)
21
+ end
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubeclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alissa Bonas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-23 00:00:00.000000000 Z
11
+ date: 2015-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -172,6 +172,10 @@ files:
172
172
  - test/json/namespace_list_b3.json
173
173
  - test/json/node_b3.json
174
174
  - test/json/node_list_b3.json
175
+ - test/json/persistent_volume.json
176
+ - test/json/persistent_volume_claim.json
177
+ - test/json/persistent_volume_claim_list.json
178
+ - test/json/persistent_volume_list.json
175
179
  - test/json/pod_b3.json
176
180
  - test/json/pod_list_b3.json
177
181
  - test/json/replication_controller_b3.json
@@ -190,6 +194,8 @@ files:
190
194
  - test/test_limit_range.rb
191
195
  - test/test_namespace.rb
192
196
  - test/test_node.rb
197
+ - test/test_persistent_volume.rb
198
+ - test/test_persistent_volume_claim.rb
193
199
  - test/test_pod.rb
194
200
  - test/test_replication_controller.rb
195
201
  - test/test_resource_quota.rb
@@ -217,7 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
223
  version: '0'
218
224
  requirements: []
219
225
  rubyforge_project:
220
- rubygems_version: 2.4.5
226
+ rubygems_version: 2.0.14
221
227
  signing_key:
222
228
  specification_version: 4
223
229
  summary: A client for Kubernetes REST api
@@ -236,6 +242,10 @@ test_files:
236
242
  - test/json/namespace_list_b3.json
237
243
  - test/json/node_b3.json
238
244
  - test/json/node_list_b3.json
245
+ - test/json/persistent_volume.json
246
+ - test/json/persistent_volume_claim.json
247
+ - test/json/persistent_volume_claim_list.json
248
+ - test/json/persistent_volume_list.json
239
249
  - test/json/pod_b3.json
240
250
  - test/json/pod_list_b3.json
241
251
  - test/json/replication_controller_b3.json
@@ -254,6 +264,8 @@ test_files:
254
264
  - test/test_limit_range.rb
255
265
  - test/test_namespace.rb
256
266
  - test/test_node.rb
267
+ - test/test_persistent_volume.rb
268
+ - test/test_persistent_volume_claim.rb
257
269
  - test/test_pod.rb
258
270
  - test/test_replication_controller.rb
259
271
  - test/test_resource_quota.rb