k8s-client 0.6.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](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
|