k8s-client 0.6.4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +6 -2
- data/k8s-client.gemspec +1 -0
- data/lib/k8s/api.rb +1 -1
- data/lib/k8s/api_client.rb +6 -4
- data/lib/k8s/client.rb +41 -3
- data/lib/k8s/client/version.rb +1 -1
- data/lib/k8s/config.rb +90 -7
- data/lib/k8s/error.rb +2 -0
- data/lib/k8s/resource.rb +21 -6
- data/lib/k8s/resource_client.rb +31 -24
- data/lib/k8s/stack.rb +18 -0
- data/lib/k8s/transport.rb +22 -11
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15012c4385d7320b4026e0da41c3092623ff3b157e3115e4d6a908e56a5af926
|
4
|
+
data.tar.gz: 2a8b4a339cff5223c5c7a601b01d60af5313abde8ba42ad9cace8cda0fd6b510
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce43ce55808589458ce1f6e002fd3687e78adc015c57f200791109fd88e664a9a11c80284cd60514a7822fc89662f7b23617b2802c8b90a99be31c8355ad62cc
|
7
|
+
data.tar.gz: bbf4c9f9b66412bf179555a4d8ba3ae807f24a2629709efe8ed2ecbd466eeeca7597571685518afc246cec98e05602a050ba148409f8955114b3de373b1c0e1f
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
[![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/kontena/k8s-client/master)
|
6
6
|
|
7
7
|
|
8
|
-
Ruby client library for the Kubernetes (1.
|
8
|
+
Ruby client library for the Kubernetes (1.9+) API
|
9
9
|
|
10
10
|
## Highlights
|
11
11
|
|
@@ -60,7 +60,11 @@ The keyword options are [Excon](https://github.com/excon/excon/) options.
|
|
60
60
|
#### Client from kubeconfig
|
61
61
|
|
62
62
|
```ruby
|
63
|
-
client = K8s::Client.config(
|
63
|
+
client = K8s::Client.config(
|
64
|
+
K8s::Config.load_file(
|
65
|
+
File.expand_path '~/.kube/config'
|
66
|
+
)
|
67
|
+
)
|
64
68
|
```
|
65
69
|
|
66
70
|
#### Supported kubeconfig options
|
data/k8s-client.gemspec
CHANGED
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.add_runtime_dependency 'hashdiff', '~> 0.3.7'
|
31
31
|
spec.add_runtime_dependency 'jsonpath', '~> 0.9.5'
|
32
32
|
spec.add_runtime_dependency 'yajl-ruby', '~> 1.4.0'
|
33
|
+
spec.add_runtime_dependency "yaml-safe_load_stream", "~> 0.1"
|
33
34
|
|
34
35
|
spec.add_development_dependency "bundler", "~> 1.16"
|
35
36
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/lib/k8s/api.rb
CHANGED
data/lib/k8s/api_client.rb
CHANGED
@@ -55,8 +55,11 @@ module K8s
|
|
55
55
|
end
|
56
56
|
|
57
57
|
# @param resource_name [String]
|
58
|
+
# @raise [K8s::Error::UndefinedResource]
|
59
|
+
# @return [K8s::API::MetaV1::APIResource]
|
58
60
|
def find_api_resource(resource_name)
|
59
61
|
found_resource = api_resources.find{ |api_resource| api_resource.name == resource_name }
|
62
|
+
found_resource ||= api_resources!.find{ |api_resource| api_resource.name == resource_name }
|
60
63
|
raise K8s::Error::UndefinedResource, "Unknown resource #{resource_name} for #{@api_version}" unless found_resource
|
61
64
|
|
62
65
|
found_resource
|
@@ -64,7 +67,7 @@ module K8s
|
|
64
67
|
|
65
68
|
# @param resource_name [String]
|
66
69
|
# @param namespace [String, nil]
|
67
|
-
# @raise [K8s::Error]
|
70
|
+
# @raise [K8s::Error::UndefinedResource]
|
68
71
|
# @return [K8s::ResourceClient]
|
69
72
|
def resource(resource_name, namespace: nil)
|
70
73
|
ResourceClient.new(@transport, self, find_api_resource(resource_name), namespace: namespace)
|
@@ -72,7 +75,6 @@ module K8s
|
|
72
75
|
|
73
76
|
# @param resource [K8s::Resource]
|
74
77
|
# @param namespace [String, nil] default if resource is missing namespace
|
75
|
-
# @raise [K8s::Error::NotFound] API Group does not exist
|
76
78
|
# @raise [K8s::Error::UndefinedResource]
|
77
79
|
# @return [K8s::ResourceClient]
|
78
80
|
def client_for_resource(resource, namespace: nil)
|
@@ -81,10 +83,10 @@ module K8s
|
|
81
83
|
end
|
82
84
|
|
83
85
|
found_resource = api_resources.find{ |api_resource| api_resource.kind == resource.kind }
|
86
|
+
found_resource ||= api_resources!.find{ |api_resource| api_resource.kind == resource.kind }
|
84
87
|
raise K8s::Error::UndefinedResource, "Unknown resource kind=#{resource.kind} for #{@api_version}" unless found_resource
|
85
88
|
|
86
|
-
ResourceClient.new(@transport, self, found_resource,
|
87
|
-
namespace: resource.metadata.namespace || namespace)
|
89
|
+
ResourceClient.new(@transport, self, found_resource, namespace: resource.metadata.namespace || namespace)
|
88
90
|
end
|
89
91
|
|
90
92
|
# TODO: skip non-namespaced resources if namespace is given, or ignore namespace?
|
data/lib/k8s/client.rb
CHANGED
@@ -41,15 +41,50 @@ module K8s
|
|
41
41
|
)
|
42
42
|
end
|
43
43
|
|
44
|
+
# An K8s::Client instance from in-cluster config within a kube pod, using the kubernetes service envs and serviceaccount secrets
|
45
|
+
# @see K8s::Transport#in_cluster_config
|
46
|
+
#
|
47
|
+
# @param namespace [String] default namespace for all operations
|
48
|
+
# @return [K8s::Client]
|
49
|
+
# @raise [K8s::Error::Config,Errno::ENOENT,Errno::EACCES]
|
50
|
+
def self.in_cluster_config(namespace: nil)
|
51
|
+
new(Transport.in_cluster_config, namespace: namespace)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Attempts to create a K8s::Client instance automatically using environment variables, existing configuration
|
55
|
+
# files or in cluster configuration.
|
56
|
+
#
|
57
|
+
# Look-up order:
|
58
|
+
# - KUBE_TOKEN, KUBE_CA, KUBE_SERVER environment variables
|
59
|
+
# - KUBECONFIG environment variable
|
60
|
+
# - $HOME/.kube/config file
|
61
|
+
# - In cluster configuration
|
62
|
+
#
|
63
|
+
# Will raise when no means of configuration is available
|
64
|
+
#
|
65
|
+
# @param options [Hash] default namespace for all operations
|
66
|
+
# @raise [K8s::Error::Config,Errno::ENOENT,Errno::EACCES]
|
44
67
|
# @return [K8s::Client]
|
45
|
-
def self.
|
46
|
-
|
68
|
+
def self.autoconfig(namespace: nil, **options)
|
69
|
+
if ENV.values_at('KUBE_TOKEN', 'KUBE_CA', 'KUBE_SERVER').none? { |v| v.nil? || v.empty? }
|
70
|
+
configuration = K8s::Config.build(server: ENV['KUBE_SERVER'], ca: ENV['KUBE_CA'], auth_token: options[:auth_token] || ENV['KUBE_TOKEN'])
|
71
|
+
elsif !ENV['KUBECONFIG'].to_s.empty?
|
72
|
+
configuration = K8s::Config.from_kubeconfig_env(ENV['KUBECONFIG'], auth_token: options[:auth_token])
|
73
|
+
elsif File.exist?(File.join(Dir.home, '.kube', 'config'))
|
74
|
+
configuration = K8s::Config.load_file(File.join(Dir.home, '.kube', 'config'))
|
75
|
+
end
|
76
|
+
|
77
|
+
if configuration
|
78
|
+
config(configuration, namespace: namespace, **options)
|
79
|
+
else
|
80
|
+
in_cluster_config(namespace: namespace, **options)
|
81
|
+
end
|
47
82
|
end
|
48
83
|
|
49
84
|
attr_reader :transport
|
50
85
|
|
51
86
|
# @param transport [K8s::Transport]
|
52
|
-
# @param namespace [String] default namespace for all operations
|
87
|
+
# @param namespace [String, nil] default namespace for all operations
|
53
88
|
def initialize(transport, namespace: nil)
|
54
89
|
@transport = transport
|
55
90
|
@namespace = namespace
|
@@ -191,6 +226,9 @@ module K8s
|
|
191
226
|
client_for_resource(resource).delete_resource(resource, **options)
|
192
227
|
end
|
193
228
|
|
229
|
+
# @param resource [K8s::Resource]
|
230
|
+
# @param attrs [Hash]
|
231
|
+
# @return [K8s::Client]
|
194
232
|
def patch_resource(resource, attrs)
|
195
233
|
client_for_resource(resource).json_patch(resource.metadata.name, attrs)
|
196
234
|
end
|
data/lib/k8s/client/version.rb
CHANGED
data/lib/k8s/config.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'dry-struct'
|
4
4
|
require 'dry-types'
|
5
|
+
require 'base64'
|
5
6
|
require 'yaml'
|
6
7
|
|
7
8
|
module K8s
|
@@ -90,30 +91,112 @@ module K8s
|
|
90
91
|
|
91
92
|
attribute :kind, Types::Strict::String.optional.default(nil)
|
92
93
|
attribute :apiVersion, Types::Strict::String.optional.default(nil)
|
93
|
-
attribute :preferences, Types::Strict::Hash.optional.default(
|
94
|
-
attribute :clusters, Types::Strict::Array.of(NamedCluster)
|
95
|
-
attribute :users, Types::Strict::Array.of(NamedUser)
|
96
|
-
attribute :contexts, Types::Strict::Array.of(NamedContext)
|
97
|
-
attribute :current_context, Types::Strict::String
|
98
|
-
attribute :extensions, Types::Strict::Array.optional.default(
|
94
|
+
attribute :preferences, Types::Strict::Hash.optional.default(proc { {} })
|
95
|
+
attribute :clusters, Types::Strict::Array.of(NamedCluster).optional.default(proc { [] })
|
96
|
+
attribute :users, Types::Strict::Array.of(NamedUser).optional.default(proc { [] })
|
97
|
+
attribute :contexts, Types::Strict::Array.of(NamedContext).optional.default(proc { [] })
|
98
|
+
attribute :current_context, Types::Strict::String.optional.default(nil)
|
99
|
+
attribute :extensions, Types::Strict::Array.optional.default(proc { [] })
|
99
100
|
|
101
|
+
# Loads a configuration from a YAML file
|
102
|
+
#
|
100
103
|
# @param path [String]
|
101
104
|
# @return [K8s::Config]
|
102
105
|
def self.load_file(path)
|
103
|
-
new(YAML.
|
106
|
+
new(YAML.safe_load(File.read(path)))
|
107
|
+
end
|
108
|
+
|
109
|
+
# Loads configuration files listed in KUBE_CONFIG environment variable and
|
110
|
+
# merged using the configuration merge rules, @see K8s::Config.merge
|
111
|
+
#
|
112
|
+
# @param kubeconfig [String] by default read from ENV['KUBECONFIG']
|
113
|
+
def self.from_kubeconfig_env(kubeconfig = nil)
|
114
|
+
kubeconfig ||= ENV.fetch('KUBECONFIG', '')
|
115
|
+
return if kubeconfig.empty?
|
116
|
+
|
117
|
+
paths = kubeconfig.split(/(?!\\):/)
|
118
|
+
return load_file(paths.first) if paths.size == 1
|
119
|
+
|
120
|
+
paths.inject(load_file(paths.shift)) do |memo, other_cfg|
|
121
|
+
memo.merge(load_file(other_cfg))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Build a minimal configuration from at least a server address, server certificate authority data and an access token.
|
126
|
+
#
|
127
|
+
# @param server [String] kubernetes server address
|
128
|
+
# @param ca [String] server certificate authority data
|
129
|
+
# @param token [String] access token (optionally base64 encoded)
|
130
|
+
# @param cluster_name [String] cluster name
|
131
|
+
# @param user [String] user name
|
132
|
+
# @param context [String] context name
|
133
|
+
# @param options [Hash] (see #initialize)
|
134
|
+
def self.build(server:, ca:, auth_token:, cluster_name: 'kubernetes', user: 'k8s-client', context: 'k8s-client', **options)
|
135
|
+
begin
|
136
|
+
decoded_token = Base64.strict_decode64(auth_token)
|
137
|
+
rescue ArgumentError
|
138
|
+
decoded_token = nil
|
139
|
+
end
|
140
|
+
|
141
|
+
new(
|
142
|
+
{
|
143
|
+
clusters: [{ name: cluster_name, cluster: { server: server, certificate_authority_data: ca } }],
|
144
|
+
users: [{ name: user, user: { token: decoded_token || auth_token } }],
|
145
|
+
contexts: [{ name: context, context: { cluster: cluster_name, user: user } }],
|
146
|
+
current_context: context
|
147
|
+
}.merge(options)
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Merges configuration according to the rules specified in
|
152
|
+
# https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files
|
153
|
+
#
|
154
|
+
# @param other [Hash, K8s::Config]
|
155
|
+
# @return [K8s::Config]
|
156
|
+
def merge(other)
|
157
|
+
old_attributes = attributes
|
158
|
+
other_attributes = other.is_a?(Hash) ? other : other.attributes
|
159
|
+
|
160
|
+
old_attributes.merge!(other_attributes) do |key, old_value, new_value|
|
161
|
+
case key
|
162
|
+
when :clusters, :contexts, :users
|
163
|
+
old_value + new_value.reject do |new_mapping|
|
164
|
+
old_value.any? { |old_mapping| old_mapping[:name] == new_mapping[:name] }
|
165
|
+
end
|
166
|
+
else
|
167
|
+
case old_value
|
168
|
+
when Array
|
169
|
+
(old_value + new_value).uniq
|
170
|
+
when Hash
|
171
|
+
old_value.merge(new_value) do |_key, inner_old_value, inner_new_value|
|
172
|
+
inner_old_value.nil? ? inner_new_value : inner_old_value
|
173
|
+
end
|
174
|
+
when NilClass
|
175
|
+
new_value
|
176
|
+
else
|
177
|
+
STDERR.puts "key is #{key} old val is #{old_value.inspect} and new val is #{new_value.inspect}"
|
178
|
+
old_value
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
self.class.new(old_attributes)
|
104
184
|
end
|
105
185
|
|
186
|
+
# @param name [String]
|
106
187
|
# TODO: raise error if not found
|
107
188
|
# @return [K8s::Config::Context]
|
108
189
|
def context(name = current_context)
|
109
190
|
contexts.find{ |context| context.name == name }.context
|
110
191
|
end
|
111
192
|
|
193
|
+
# @param name [String]
|
112
194
|
# @return [K8s::Config::Cluster]
|
113
195
|
def cluster(name = context.cluster)
|
114
196
|
clusters.find{ |cluster| cluster.name == name }.cluster
|
115
197
|
end
|
116
198
|
|
199
|
+
# @param name [String]
|
117
200
|
# @return [K8s::Config::User]
|
118
201
|
def user(name = context.user)
|
119
202
|
users.find{ |user| user.name == name }.user
|
data/lib/k8s/error.rb
CHANGED
@@ -60,5 +60,7 @@ module K8s
|
|
60
60
|
# Attempt to create a ResourceClient for an unknown resource type.
|
61
61
|
# The client cannot construct the correct API URL without having the APIResource definition.
|
62
62
|
UndefinedResource = Class.new(Error)
|
63
|
+
|
64
|
+
Configuration = Class.new(Error)
|
63
65
|
end
|
64
66
|
end
|
data/lib/k8s/resource.rb
CHANGED
@@ -4,6 +4,7 @@ require 'deep_merge'
|
|
4
4
|
require 'recursive-open-struct'
|
5
5
|
require 'hashdiff'
|
6
6
|
require 'forwardable'
|
7
|
+
require 'yaml/safe_load_stream'
|
7
8
|
|
8
9
|
module K8s
|
9
10
|
# generic untyped resource
|
@@ -11,16 +12,18 @@ module K8s
|
|
11
12
|
extend Forwardable
|
12
13
|
include Comparable
|
13
14
|
|
14
|
-
|
15
|
+
using YAMLSafeLoadStream
|
16
|
+
|
17
|
+
# @param data [String]
|
15
18
|
# @return [self]
|
16
19
|
def self.from_json(data)
|
17
|
-
new(data)
|
20
|
+
new(Yajl::Parser.parse(data))
|
18
21
|
end
|
19
22
|
|
20
23
|
# @param filename [String] file path
|
21
24
|
# @return [K8s::Resource]
|
22
25
|
def self.from_file(filename)
|
23
|
-
new(YAML.
|
26
|
+
new(YAML.safe_load(File.read(filename), [], [], true, filename))
|
24
27
|
end
|
25
28
|
|
26
29
|
# @param path [String] file path
|
@@ -32,11 +35,13 @@ module K8s
|
|
32
35
|
# recurse
|
33
36
|
Dir.glob("#{path}/*.{yml,yaml}").sort.map { |dir| from_files(dir) }.flatten
|
34
37
|
else
|
35
|
-
|
38
|
+
YAML.safe_load_stream(File.read(path), path).map{ |doc| new(doc) }
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
39
42
|
# @param hash [Hash]
|
43
|
+
# @param recurse_over_arrays [Boolean]
|
44
|
+
# @param options [Hash] see RecursiveOpenStruct#initialize
|
40
45
|
def initialize(hash, recurse_over_arrays: true, **options)
|
41
46
|
super(hash,
|
42
47
|
recurse_over_arrays: recurse_over_arrays,
|
@@ -44,6 +49,7 @@ module K8s
|
|
44
49
|
)
|
45
50
|
end
|
46
51
|
|
52
|
+
# @param options [Hash] see Hash#to_json
|
47
53
|
# @return [String]
|
48
54
|
def to_json(**options)
|
49
55
|
to_hash.to_json(**options)
|
@@ -69,34 +75,43 @@ module K8s
|
|
69
75
|
self.class.new(h)
|
70
76
|
end
|
71
77
|
|
78
|
+
# @return [String]
|
72
79
|
def checksum
|
73
80
|
@checksum ||= Digest::MD5.hexdigest(Marshal.dump(to_hash))
|
74
81
|
end
|
75
82
|
|
83
|
+
# @param attrs [Hash]
|
84
|
+
# @param config_annotation [String]
|
85
|
+
# @return [Hash]
|
76
86
|
def merge_patch_ops(attrs, config_annotation)
|
77
87
|
Util.json_patch(current_config(config_annotation), stringify_hash(attrs))
|
78
88
|
end
|
79
89
|
|
80
90
|
# Gets the existing resources (on kube api) configuration, an empty hash if not present
|
81
91
|
#
|
92
|
+
# @param config_annotation [String]
|
82
93
|
# @return [Hash]
|
83
94
|
def current_config(config_annotation)
|
84
95
|
current_cfg = metadata.annotations&.dig(config_annotation)
|
85
96
|
return {} unless current_cfg
|
86
97
|
|
87
|
-
current_hash =
|
98
|
+
current_hash = Yajl::Parser.parse(current_cfg)
|
88
99
|
# kubectl adds empty metadata.namespace, let's fix it
|
89
100
|
current_hash['metadata'].delete('namespace') if current_hash.dig('metadata', 'namespace').to_s.empty?
|
90
101
|
|
91
102
|
current_hash
|
92
103
|
end
|
93
104
|
|
105
|
+
# @param config_annotation [String]
|
106
|
+
# @return [Boolean]
|
94
107
|
def can_patch?(config_annotation)
|
95
108
|
!!metadata.annotations&.dig(config_annotation)
|
96
109
|
end
|
97
110
|
|
111
|
+
# @param hash [Hash]
|
112
|
+
# @return [Hash]
|
98
113
|
def stringify_hash(hash)
|
99
|
-
|
114
|
+
Yajl::Parser.parse(JSON.dump(hash))
|
100
115
|
end
|
101
116
|
end
|
102
117
|
end
|
data/lib/k8s/resource_client.rb
CHANGED
@@ -7,8 +7,8 @@ module K8s
|
|
7
7
|
class ResourceClient
|
8
8
|
# Common helpers used in both class/instance methods
|
9
9
|
module Utils
|
10
|
-
# @param selector [
|
11
|
-
# @return [
|
10
|
+
# @param selector [NilClass, String, Hash{String => String}]
|
11
|
+
# @return [NilClass, String]
|
12
12
|
def selector_query(selector)
|
13
13
|
case selector
|
14
14
|
when nil
|
@@ -23,7 +23,7 @@ module K8s
|
|
23
23
|
end
|
24
24
|
|
25
25
|
# @param options [Hash]
|
26
|
-
# @return [Hash,
|
26
|
+
# @return [Hash, NilClass]
|
27
27
|
def make_query(options)
|
28
28
|
query = options.compact
|
29
29
|
|
@@ -66,6 +66,7 @@ module K8s
|
|
66
66
|
# @param api_client [K8s::APIClient]
|
67
67
|
# @param api_resource [K8s::API::MetaV1::APIResource]
|
68
68
|
# @param namespace [String]
|
69
|
+
# @param resource_class [Class]
|
69
70
|
def initialize(transport, api_client, api_resource, namespace: nil, resource_class: K8s::Resource)
|
70
71
|
@transport = transport
|
71
72
|
@api_client = api_client
|
@@ -115,11 +116,14 @@ module K8s
|
|
115
116
|
# @return [class] K8s::Resource
|
116
117
|
attr_reader :resource_class
|
117
118
|
|
118
|
-
# @return [
|
119
|
+
# @return [Boolean]
|
119
120
|
def namespaced?
|
120
121
|
!!@api_resource.namespaced
|
121
122
|
end
|
122
123
|
|
124
|
+
# @param name [NilClass, String]
|
125
|
+
# @param subresource [String, NilClass]
|
126
|
+
# @param namespace [String, NilClass]
|
123
127
|
# @return [String]
|
124
128
|
def path(name = nil, subresource: @subresource, namespace: @namespace)
|
125
129
|
namespace_part = namespace ? ['namespaces', namespace] : []
|
@@ -138,8 +142,8 @@ module K8s
|
|
138
142
|
@api_resource.verbs.include? 'create'
|
139
143
|
end
|
140
144
|
|
141
|
-
# @param resource [
|
142
|
-
# @return [resource_class
|
145
|
+
# @param resource [#metadata] with metadata.namespace and metadata.name set
|
146
|
+
# @return [Object] instance of resource_class
|
143
147
|
def create_resource(resource)
|
144
148
|
@transport.request(
|
145
149
|
method: 'POST',
|
@@ -154,7 +158,9 @@ module K8s
|
|
154
158
|
@api_resource.verbs.include? 'get'
|
155
159
|
end
|
156
160
|
|
157
|
-
# @
|
161
|
+
# @param name [String]
|
162
|
+
# @param namespace [String, NilClass]
|
163
|
+
# @return [Object] instance of resource_class
|
158
164
|
def get(name, namespace: @namespace)
|
159
165
|
@transport.request(
|
160
166
|
method: 'GET',
|
@@ -164,7 +170,7 @@ module K8s
|
|
164
170
|
end
|
165
171
|
|
166
172
|
# @param resource [resource_class]
|
167
|
-
# @return [resource_class
|
173
|
+
# @return [Object] instance of resource_class
|
168
174
|
def get_resource(resource)
|
169
175
|
@transport.request(
|
170
176
|
method: 'GET',
|
@@ -179,7 +185,7 @@ module K8s
|
|
179
185
|
end
|
180
186
|
|
181
187
|
# @param list [K8s::API::MetaV1::List]
|
182
|
-
# @return [Array<
|
188
|
+
# @return [Array<Object>] array of instances of resource_class
|
183
189
|
def process_list(list)
|
184
190
|
list.items.map { |item|
|
185
191
|
# list items omit kind/apiVersion
|
@@ -190,7 +196,7 @@ module K8s
|
|
190
196
|
# @param labelSelector [nil, String, Hash{String => String}]
|
191
197
|
# @param fieldSelector [nil, String, Hash{String => String}]
|
192
198
|
# @param namespace [nil, String]
|
193
|
-
# @return [Array<
|
199
|
+
# @return [Array<Object>] array of instances of resource_class
|
194
200
|
def list(labelSelector: nil, fieldSelector: nil, namespace: @namespace)
|
195
201
|
list = meta_list(labelSelector: labelSelector, fieldSelector: fieldSelector, namespace: namespace)
|
196
202
|
process_list(list)
|
@@ -243,13 +249,13 @@ module K8s
|
|
243
249
|
)
|
244
250
|
end
|
245
251
|
|
246
|
-
# @return [
|
252
|
+
# @return [Boolean]
|
247
253
|
def update?
|
248
254
|
@api_resource.verbs.include? 'update'
|
249
255
|
end
|
250
256
|
|
251
|
-
# @param resource [
|
252
|
-
# @return [resource_class
|
257
|
+
# @param resource [#metadata] with metadata.resourceVersion set
|
258
|
+
# @return [Object] instance of resource_class
|
253
259
|
def update_resource(resource)
|
254
260
|
@transport.request(
|
255
261
|
method: 'PUT',
|
@@ -259,16 +265,16 @@ module K8s
|
|
259
265
|
)
|
260
266
|
end
|
261
267
|
|
262
|
-
# @return [
|
268
|
+
# @return [Boolean]
|
263
269
|
def patch?
|
264
270
|
@api_resource.verbs.include? 'patch'
|
265
271
|
end
|
266
272
|
|
267
273
|
# @param name [String]
|
268
|
-
# @param obj [
|
269
|
-
# @param namespace [String]
|
274
|
+
# @param obj [#to_json]
|
275
|
+
# @param namespace [String, nil]
|
270
276
|
# @param strategic_merge [Boolean] use kube Strategic Merge Patch instead of standard Merge Patch (arrays of objects are merged by name)
|
271
|
-
# @return [resource_class
|
277
|
+
# @return [Object] instance of resource_class
|
272
278
|
def merge_patch(name, obj, namespace: @namespace, strategic_merge: true)
|
273
279
|
@transport.request(
|
274
280
|
method: 'PATCH',
|
@@ -281,8 +287,8 @@ module K8s
|
|
281
287
|
|
282
288
|
# @param name [String]
|
283
289
|
# @param ops [Hash] json-patch operations
|
284
|
-
# @param namespace [String]
|
285
|
-
# @return [resource_class
|
290
|
+
# @param namespace [String, nil]
|
291
|
+
# @return [Object] instance of resource_class
|
286
292
|
def json_patch(name, ops, namespace: @namespace)
|
287
293
|
@transport.request(
|
288
294
|
method: 'PATCH',
|
@@ -293,14 +299,14 @@ module K8s
|
|
293
299
|
)
|
294
300
|
end
|
295
301
|
|
296
|
-
# @return [
|
302
|
+
# @return [Boolean]
|
297
303
|
def delete?
|
298
304
|
@api_resource.verbs.include? 'delete'
|
299
305
|
end
|
300
306
|
|
301
307
|
# @param name [String]
|
302
|
-
# @param namespace [String]
|
303
|
-
# @param propagationPolicy [String] The propagationPolicy to use for the API call. Possible values include “Orphan”, “Foreground”, or “Background”
|
308
|
+
# @param namespace [String, nil]
|
309
|
+
# @param propagationPolicy [String, nil] The propagationPolicy to use for the API call. Possible values include “Orphan”, “Foreground”, or “Background”
|
304
310
|
# @return [K8s::API::MetaV1::Status]
|
305
311
|
def delete(name, namespace: @namespace, propagationPolicy: nil)
|
306
312
|
@transport.request(
|
@@ -313,10 +319,11 @@ module K8s
|
|
313
319
|
)
|
314
320
|
end
|
315
321
|
|
316
|
-
# @param namespace [String]
|
322
|
+
# @param namespace [String, nil]
|
317
323
|
# @param labelSelector [nil, String, Hash{String => String}]
|
318
324
|
# @param fieldSelector [nil, String, Hash{String => String}]
|
319
|
-
# @
|
325
|
+
# @param propagationPolicy [String, nil]
|
326
|
+
# @return [Array<Object>] array of instances of resource_class
|
320
327
|
def delete_collection(namespace: @namespace, labelSelector: nil, fieldSelector: nil, propagationPolicy: nil)
|
321
328
|
list = @transport.request(
|
322
329
|
method: 'DELETE',
|
data/lib/k8s/stack.rb
CHANGED
@@ -25,6 +25,7 @@ module K8s
|
|
25
25
|
|
26
26
|
# @param name [String] unique name for stack
|
27
27
|
# @param path [String] load resources from YAML files
|
28
|
+
# @param options [Hash] see Stack#initialize
|
28
29
|
# @return [K8s::Stack]
|
29
30
|
def self.load(name, path, **options)
|
30
31
|
resources = K8s::Resource.from_files(path)
|
@@ -35,6 +36,8 @@ module K8s
|
|
35
36
|
# @param path [String] load resources from YAML files
|
36
37
|
# @param client [K8s::Client] apply using client
|
37
38
|
# @param prune [Boolean] delete old resources
|
39
|
+
# @param options [Hash] see Stack#initialize
|
40
|
+
# @return [K8s::Stack]
|
38
41
|
def self.apply(name, path, client, prune: true, **options)
|
39
42
|
load(name, path, **options).apply(client, prune: prune)
|
40
43
|
end
|
@@ -49,6 +52,12 @@ module K8s
|
|
49
52
|
|
50
53
|
attr_reader :name, :resources
|
51
54
|
|
55
|
+
# @param name [String]
|
56
|
+
# @param resources [Array<K8s::Resource>]
|
57
|
+
# @param debug [Boolean]
|
58
|
+
# @param label [String]
|
59
|
+
# @param checksum_annotation [String]
|
60
|
+
# @param last_config_annotation [String]
|
52
61
|
def initialize(name, resources = [], debug: false, label: self.class::LABEL, checksum_annotation: self.class::CHECKSUM_ANNOTATION, last_configuration_annotation: self.class::LAST_CONFIG_ANNOTATION)
|
53
62
|
@name = name
|
54
63
|
@resources = resources
|
@@ -83,6 +92,7 @@ module K8s
|
|
83
92
|
end
|
84
93
|
# rubocop:enable Lint/UnusedMethodArgument
|
85
94
|
|
95
|
+
# @param client [K8s::Client]
|
86
96
|
# @return [Array<K8s::Resource>]
|
87
97
|
def apply(client, prune: true)
|
88
98
|
server_resources = client.get_resources(resources)
|
@@ -110,15 +120,22 @@ module K8s
|
|
110
120
|
end
|
111
121
|
|
112
122
|
# key MUST NOT include resource.apiVersion: the same kind can be aliased in different APIs
|
123
|
+
# @param resource [K8s::Resource]
|
124
|
+
# @return [K8s::Resource]
|
113
125
|
def keep_resource!(resource)
|
114
126
|
@keep_resources["#{resource.kind}:#{resource.metadata.name}@#{resource.metadata.namespace}"] = resource.metadata.annotations[@checksum_annotation]
|
115
127
|
end
|
116
128
|
|
129
|
+
# @param resource [K8s::Resource]
|
130
|
+
# @return [Boolean]
|
117
131
|
def keep_resource?(resource)
|
118
132
|
@keep_resources["#{resource.kind}:#{resource.metadata.name}@#{resource.metadata.namespace}"] == resource.metadata.annotations[@checksum_annotation]
|
119
133
|
end
|
120
134
|
|
121
135
|
# Delete all stack resources that were not applied
|
136
|
+
# @param client [K8s::Client]
|
137
|
+
# @param keep_resources [NilClass, Boolean]
|
138
|
+
# @param skip_forbidden [Boolean]
|
122
139
|
def prune(client, keep_resources:, skip_forbidden: true)
|
123
140
|
# using skip_forbidden: assume we can't create resource types that we are forbidden to list, so we don't need to prune them either
|
124
141
|
client.list_resources(labelSelector: { @label => name }, skip_forbidden: skip_forbidden).sort do |a, b|
|
@@ -156,6 +173,7 @@ module K8s
|
|
156
173
|
end
|
157
174
|
|
158
175
|
# Delete all stack resources
|
176
|
+
# @param client [K8s::Client]
|
159
177
|
def delete(client)
|
160
178
|
prune(client, keep_resources: false)
|
161
179
|
end
|
data/lib/k8s/transport.rb
CHANGED
@@ -92,16 +92,23 @@ module K8s
|
|
92
92
|
|
93
93
|
# In-cluster config within a kube pod, using the kubernetes service envs and serviceaccount secrets
|
94
94
|
#
|
95
|
+
# @param options [Hash] see #new
|
95
96
|
# @return [K8s::Transport]
|
96
|
-
|
97
|
-
|
98
|
-
|
97
|
+
# @raise [K8s::Error::Config] when the environment variables KUBERNETES_SEVICE_HOST and KUBERNETES_SERVICE_PORT_HTTPS are not set
|
98
|
+
# @raise [Errno::ENOENT,Errno::EACCES] when /var/run/secrets/kubernetes.io/serviceaccount/ca.crt or /var/run/secrets/kubernetes.io/serviceaccount/token can not be read
|
99
|
+
def self.in_cluster_config(**options)
|
100
|
+
host = ENV['KUBERNETES_SERVICE_HOST'].to_s
|
101
|
+
raise(K8s::Error::Config, "in_cluster_config failed: KUBERNETES_SERVICE_HOST environment not set") if host.empty?
|
102
|
+
|
103
|
+
port = ENV['KUBERNETES_SERVICE_PORT_HTTPS'].to_s
|
104
|
+
raise(K8s::Error::Config, "in_cluster_config failed: KUBERNETES_SERVICE_HOST environment not set") if port.empty?
|
99
105
|
|
100
106
|
new(
|
101
107
|
"https://#{host}:#{port}",
|
102
|
-
ssl_verify_peer: true,
|
103
|
-
ssl_ca_file: '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt',
|
104
|
-
auth_token: File.read('/var/run/secrets/kubernetes.io/serviceaccount/token')
|
108
|
+
ssl_verify_peer: options.key?(:ssl_verify_peer) ? options.delete(:ssl_verify_peer) : true,
|
109
|
+
ssl_ca_file: options.delete(:ssl_ca_file) || '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt',
|
110
|
+
auth_token: options.delete(:auth_token) || File.read('/var/run/secrets/kubernetes.io/serviceaccount/token'),
|
111
|
+
**options
|
105
112
|
)
|
106
113
|
end
|
107
114
|
|
@@ -155,6 +162,7 @@ module K8s
|
|
155
162
|
end
|
156
163
|
|
157
164
|
# @param options [Hash] as passed to Excon#request
|
165
|
+
# @return [String]
|
158
166
|
def format_request(options)
|
159
167
|
method = options[:method]
|
160
168
|
path = options[:path]
|
@@ -173,7 +181,7 @@ module K8s
|
|
173
181
|
|
174
182
|
# @param response [Hash] as returned by Excon#request
|
175
183
|
# @param request_options [Hash] as passed to Excon#request
|
176
|
-
# @param response_class [Class]
|
184
|
+
# @param response_class [Class] coerce into response body using #new
|
177
185
|
# @raise [K8s::Error]
|
178
186
|
# @raise [Excon::Error] TODO: wrap
|
179
187
|
# @return [response_class, Hash]
|
@@ -201,7 +209,7 @@ module K8s
|
|
201
209
|
|
202
210
|
return response_data unless response_class
|
203
211
|
|
204
|
-
response_class.
|
212
|
+
response_class.new(response_data)
|
205
213
|
else
|
206
214
|
error_class = K8s::Error::HTTP_STATUS_ERRORS[response.status] || K8s::Error::API
|
207
215
|
|
@@ -217,8 +225,9 @@ module K8s
|
|
217
225
|
end
|
218
226
|
end
|
219
227
|
|
220
|
-
# @param response_class [Class]
|
228
|
+
# @param response_class [Class] coerce into response class using #new
|
221
229
|
# @param options [Hash] @see Excon#request
|
230
|
+
# @return [response_class, Hash]
|
222
231
|
def request(response_class: nil, **options)
|
223
232
|
if options[:method] == 'DELETE' && need_delete_body?
|
224
233
|
options[:request_object] = options.delete(:query)
|
@@ -248,7 +257,7 @@ module K8s
|
|
248
257
|
# @param skip_forbidden [Boolean] return nil for HTTP 403 responses
|
249
258
|
# @param retry_errors [Boolean] retry with non-pipelined request for HTTP 503 responses
|
250
259
|
# @param common_options [Hash] @see #request, merged with the per-request options
|
251
|
-
# @return [Array<response_class, Hash,
|
260
|
+
# @return [Array<response_class, Hash, NilClass>]
|
252
261
|
def requests(*options, skip_missing: false, skip_forbidden: false, retry_errors: true, **common_options)
|
253
262
|
return [] if options.empty? # excon chokes
|
254
263
|
|
@@ -297,13 +306,14 @@ module K8s
|
|
297
306
|
)
|
298
307
|
end
|
299
308
|
|
300
|
-
# @return [
|
309
|
+
# @return [Boolean] true if delete options should be sent as bode of the DELETE request
|
301
310
|
def need_delete_body?
|
302
311
|
@need_delete_body ||= Gem::Version.new(version.gitVersion.match(/v*(.*)/)[1]) < DELETE_OPTS_BODY_VERSION_MIN
|
303
312
|
end
|
304
313
|
|
305
314
|
# @param path [Array<String>] @see #path
|
306
315
|
# @param options [Hash] @see #request
|
316
|
+
# @return [Array<response_class, Hash, NilClass>]
|
307
317
|
def get(*path, **options)
|
308
318
|
request(
|
309
319
|
method: 'GET',
|
@@ -314,6 +324,7 @@ module K8s
|
|
314
324
|
|
315
325
|
# @param paths [Array<String>]
|
316
326
|
# @param options [Hash] @see #request
|
327
|
+
# @return [Array<response_class, Hash, NilClass>]
|
317
328
|
def gets(*paths, **options)
|
318
329
|
requests(
|
319
330
|
*paths.map do |path|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: k8s-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kontena, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: excon
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 1.4.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: yaml-safe_load_stream
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.1'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.1'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: bundler
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|