k8s-client 0.3.3 → 0.3.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f007ab73089515a1766b43c947c33c8c3e8dfd902b19ce47aa6e5b3a87892536
4
- data.tar.gz: 4d3b616e388ae5334966588ea93828f3efb6dac602d4a0f25906063d4360fc82
3
+ metadata.gz: 629b1fd35aba3aa4999fb320033c537e239768a3ea2a9163b19a2ba69f00466d
4
+ data.tar.gz: 35c3043b726d29a0fb038433b4764fdd8de9b2665b1db8c6a7649c6403f9e98d
5
5
  SHA512:
6
- metadata.gz: e13b44376050184651c88518d752062657bb2c1ac820af787090d603411c0ae6a86ae0e9b069dc79ed1a9de3ab07dfbc5e93ae5e6c847b65c16aa1dacf30ae45
7
- data.tar.gz: 6344b7a1809baabb31ecd747711171928ae2a0102183732c46029285f773282e82c314e6fb22cbacecaaa281aa331a2eafb3807cb2951ff0ff628a4c024778f2
6
+ metadata.gz: cac2f739ee3dcfe3885fe4070c4497e22cc86d227a449c18c773390299e5e416458971a2d424abbcae52b163f4077fc8d85138865c1f1857ca3a83f27dae9e84
7
+ data.tar.gz: a2bd611f565e0f6e3e9f54d201f89bc2a5eef307b9d3ce7327c267139fd29744a7f721d655f6b67b9aa4126db0c97830b1e2a8330f5abde8009f3ac954c6e7f4
data/README.md CHANGED
@@ -63,6 +63,21 @@ The keyword options are [Excon](https://github.com/excon/excon/) options.
63
63
  client = K8s::Client.config(K8s::Config.load_file('~/.kube/config'))
64
64
  ```
65
65
 
66
+ #### Supported kubeconfig options
67
+
68
+ Not all kubeconfig options are supported, only the following kubeconfig options work:
69
+
70
+ * `current-context`
71
+ * `context.cluster`
72
+ * `context.user`
73
+ * `cluster.server`
74
+ * `cluster.insecure_skip_tls_verify`
75
+ * `cluster.certificate_authority`
76
+ * `cluster.certificate_authority_data`
77
+ * `user.client_certificate` + `user.client_key`
78
+ * `user.client_certificate_data` + `user.client_key_data`
79
+ * `user.token`
80
+
66
81
  ##### With overrides
67
82
 
68
83
  ```ruby
data/lib/k8s-client.rb CHANGED
@@ -1,14 +1,17 @@
1
- require 'k8s/api/metav1'
2
- require 'k8s/api/version'
1
+ # Kubernetes client library
2
+ module K8s
3
+ require 'k8s/api/metav1'
4
+ require 'k8s/api/version'
3
5
 
4
- require 'k8s/config'
5
- require 'k8s/logging'
6
+ require 'k8s/config'
7
+ require 'k8s/logging'
6
8
 
7
- require 'k8s/api_client'
8
- require "k8s/client"
9
- require "k8s/error"
10
- require 'k8s/resource'
11
- require 'k8s/resource_client'
12
- require 'k8s/stack'
13
- require 'k8s/transport'
14
- require 'k8s/util'
9
+ require 'k8s/api_client'
10
+ require "k8s/client"
11
+ require "k8s/error"
12
+ require 'k8s/resource'
13
+ require 'k8s/resource_client'
14
+ require 'k8s/stack'
15
+ require 'k8s/transport'
16
+ require 'k8s/util'
17
+ end
data/lib/k8s/api.rb CHANGED
@@ -2,11 +2,14 @@ require 'dry-types'
2
2
  require 'dry-struct'
3
3
 
4
4
  module K8s
5
+ # Kube API definitions
5
6
  module API
7
+ # Common Dry::Types used in the API
6
8
  module Types
7
9
  include Dry::Types.module
8
10
  end
9
11
 
12
+ # Common API struct type, handling JSON transforms with symbol keys
10
13
  class Struct < Dry::Struct
11
14
  # input from JSON with string keys
12
15
  transform_keys(&:to_sym)
@@ -2,6 +2,9 @@ require 'k8s/api'
2
2
 
3
3
  module K8s
4
4
  module API
5
+ # API types common to all apigroups/versions
6
+ #
7
+ # @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1
5
8
  module MetaV1
6
9
  # @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#TypeMeta
7
10
  class Resource < Struct
@@ -1,6 +1,8 @@
1
1
  module K8s
2
2
  module API
3
3
  module MetaV1
4
+ # structured list of available APIGroup versions
5
+ # groupVersion provided for convenience
4
6
  class APIGroupVersion < Struct
5
7
  attribute :groupVersion, Types::Strict::String
6
8
  attribute :version, Types::Strict::String
@@ -3,13 +3,14 @@ module K8s
3
3
  module MetaV1
4
4
  # @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Status
5
5
  class Status < Resource
6
-
6
+ # structured cause
7
7
  class Cause < Struct
8
8
  attribute :reason, Types::Strict::String.optional.default(nil)
9
9
  attribute :message, Types::Strict::String.optional.default(nil) # human-readable
10
10
  attribute :field, Types::Strict::String.optional.default(nil) # human-readable
11
11
  end
12
12
 
13
+ # structured details
13
14
  class Details < Struct
14
15
  attribute :name, Types::Strict::String.optional.default(nil)
15
16
  attribute :group, Types::Strict::String.optional.default(nil)
@@ -1,5 +1,6 @@
1
1
  module K8s
2
2
  module API
3
+ # GET /version
3
4
  class Version < Struct
4
5
  attribute :buildDate, Types::Strict::String # TODO: parse datetime?
5
6
  attribute :compiler, Types::Strict::String
@@ -1,5 +1,10 @@
1
1
  module K8s
2
+ # Per-APIGroup/version client.
3
+ #
4
+ # Offers access to {ResourceClient} instances for the APIResource types defined in this apigroup/version
2
5
  class APIClient
6
+ # @param api_version [String] either core version (v1) or apigroup/apiversion (apps/v1)
7
+ # @return [String]
3
8
  def self.path(api_version)
4
9
  if api_version.include? '/'
5
10
  File.join('/apis', api_version)
@@ -21,6 +26,8 @@ module K8s
21
26
  @api_version
22
27
  end
23
28
 
29
+ # @param path [Array<String>] join path from parts
30
+ # @return [String]
24
31
  def path(*path)
25
32
  @transport.path(self.class.path(@api_version), *path)
26
33
  end
@@ -99,7 +106,7 @@ module K8s
99
106
  # Returns flattened array with mixed resource kinds.
100
107
  #
101
108
  # @param resources [Array<K8s::ResourceClient>] default is all listable resources for api
102
- # @param **options @see [K8s::ResourceClient#list]
109
+ # @param options @see [K8s::ResourceClient#list]
103
110
  # @return [Array<K8s::Resource>]
104
111
  def list_resources(resources = nil, **options)
105
112
  resources ||= self.resources.select{|resource| resource.list? }
data/lib/k8s/client.rb CHANGED
@@ -2,13 +2,20 @@ require 'openssl'
2
2
  require 'base64'
3
3
 
4
4
  module K8s
5
+ # @param server [String] http/s URL
6
+ # @param options [Hash] @see Transport.new
5
7
  # @return [K8s::Client]
6
8
  def self.client(server, **options)
7
9
  Client.new(Transport.new(server, **options))
8
10
  end
9
11
 
12
+ # Top-level client wrapper.
13
+ # Uses a {Transport} instance to talk to the kube API.
14
+ # Offers access to {APIClient} and {ResourceClient} instances.
10
15
  class Client
11
16
  # @param config [Phraos::Kube::Config]
17
+ # @param namespace [String] @see #initialize
18
+ # @param options [Hash] @see Transport.config
12
19
  # @return [K8s::Client]
13
20
  def self.config(config, namespace: nil, **options)
14
21
  new(Transport.config(config, **options),
@@ -22,6 +29,7 @@ module K8s
22
29
  end
23
30
 
24
31
  # @param transport [K8s::Transport]
32
+ # @param namespace [String] default namespace for all operations
25
33
  def initialize(transport, namespace: nil)
26
34
  @transport = transport
27
35
  @namespace = namespace
@@ -93,7 +101,7 @@ module K8s
93
101
  # Returns flattened array with mixed resource kinds.
94
102
  #
95
103
  # @param resources [Array<K8s::ResourceClient>] default is all listable resources for api
96
- # @param **options @see [K8s::ResourceClient#list]
104
+ # @param options @see K8s::ResourceClient#list
97
105
  # @return [Array<K8s::Resource>]
98
106
  def list_resources(resources = nil, **options)
99
107
  resources ||= self.resources.select{|resource| resource.list? }
@@ -1,5 +1,6 @@
1
1
  module K8s
2
2
  class Client
3
- VERSION = "0.3.3"
3
+ # Updated on releases using semver.
4
+ VERSION = "0.3.4"
4
5
  end
5
6
  end
data/lib/k8s/config.rb CHANGED
@@ -3,9 +3,11 @@ require 'dry-types'
3
3
  require 'yaml'
4
4
 
5
5
  module K8s
6
+ # Common struct type for kubeconfigs:
7
+ #
8
+ # * converts string keys to symbols
9
+ # * normalizes foo-bar to foo_bar
6
10
  class ConfigStruct < Dry::Struct
7
- # convert string keys to symbols
8
- # normalize foo-bar to foo_bar
9
11
  transform_keys do |key|
10
12
  case key
11
13
  when String
@@ -18,10 +20,13 @@ module K8s
18
20
 
19
21
  # @see https://godoc.org/k8s.io/client-go/tools/clientcmd/api/v1#Config
20
22
  class Config < ConfigStruct
23
+
24
+ # Common dry-types for config
21
25
  class Types
22
26
  include Dry::Types.module
23
27
  end
24
28
 
29
+ # structured cluster
25
30
  class Cluster < ConfigStruct
26
31
  attribute :server, Types::String
27
32
  attribute :insecure_skip_tls_verify, Types::Bool.optional.default(nil)
@@ -29,11 +34,14 @@ module K8s
29
34
  attribute :certificate_authority_data, Types::String.optional.default(nil)
30
35
  attribute :extensions, Types::Strict::Array.optional.default(nil)
31
36
  end
37
+
38
+ # structured cluster with name
32
39
  class NamedCluster < ConfigStruct
33
40
  attribute :name, Types::String
34
41
  attribute :cluster, Cluster
35
42
  end
36
43
 
44
+ # structured user
37
45
  class User < ConfigStruct
38
46
  attribute :client_certificate, Types::String.optional.default(nil)
39
47
  attribute :client_certificate_data, Types::String.optional.default(nil)
@@ -50,17 +58,24 @@ module K8s
50
58
  attribute :exec, Types::Strict::Hash.optional.default(nil)
51
59
  attribute :extensions, Types::Strict::Array.optional.default(nil)
52
60
  end
61
+
62
+ # structured user with name
53
63
  class NamedUser < ConfigStruct
54
64
  attribute :name, Types::String
55
65
  attribute :user, User
56
66
  end
57
67
 
68
+ # structured context
69
+ #
70
+ # Referrs to other named User/cluster objects within the same config.
58
71
  class Context < ConfigStruct
59
72
  attribute :cluster, Types::Strict::String
60
73
  attribute :user, Types::Strict::String
61
74
  attribute :namespace, Types::Strict::String.optional.default(nil)
62
75
  attribute :extensions, Types::Strict::Array.optional.default(nil)
63
76
  end
77
+
78
+ # named context
64
79
  class NamedContext < ConfigStruct
65
80
  attribute :name, Types::String
66
81
  attribute :context, Context
@@ -68,7 +83,7 @@ module K8s
68
83
 
69
84
  attribute :kind, Types::Strict::String.optional.default(nil)
70
85
  attribute :apiVersion, Types::Strict::String.optional.default(nil)
71
- attribute :preferences, Types::Strict::Hash.optional
86
+ attribute :preferences, Types::Strict::Hash.optional.default(nil)
72
87
  attribute :clusters, Types::Strict::Array.of(NamedCluster)
73
88
  attribute :users, Types::Strict::Array.of(NamedUser)
74
89
  attribute :contexts, Types::Strict::Array.of(NamedContext)
data/lib/k8s/error.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module K8s
4
+ # Top-level class for all errors raised by this gem.
4
5
  class Error < StandardError
6
+
7
+ # Kube API error, related to a HTTP response with a non-2xx code
5
8
  class API < Error
6
9
  extend Forwardable
7
10
 
@@ -27,8 +30,14 @@ module K8s
27
30
  end
28
31
  end
29
32
 
33
+ # specific API error subtypes for HTTP status codes
34
+ # Hash{Integer => Class<API>}
30
35
  HTTP_STATUS_ERRORS = {}
31
36
 
37
+ # define a new API error type on the module for the given HTTP status code
38
+ #
39
+ # @param code [Integer] HTTP status code
40
+ # @param name [Symbol] API error class name
32
41
  def self.define_status_error(code, name)
33
42
  HTTP_STATUS_ERRORS[code] = self.const_set(name, Class.new(API))
34
43
  end
@@ -45,6 +54,8 @@ module K8s
45
54
  define_status_error 503, :ServiceUnavailable
46
55
  define_status_error 504, :ServerTimeout
47
56
 
57
+ # Attempt to create a ResourceClient for an unknown resource type.
58
+ # The client cannot construct the correct API URL without having the APIResource definition.
48
59
  class UndefinedResource < Error
49
60
 
50
61
  end
data/lib/k8s/logging.rb CHANGED
@@ -1,24 +1,44 @@
1
1
  require 'logger'
2
2
 
3
3
  module K8s
4
+ # Logging utilities
5
+ #
6
+ # This provides a per-class Logger that uses the class name as a logging prefix.
7
+ # Instances can optionally also use logger! to define a per-instance Logger using a custom prefix.
4
8
  module Logging
9
+ # Default log target
5
10
  LOG_TARGET = STDERR
11
+
12
+ # Default log level: show warnings.
13
+ #
14
+ # Use K8s::Logging.quiet! to supress warnings.
15
+ # Note that the K8s::Transport defaults to quiet!
6
16
  LOG_LEVEL = Logger::WARN
7
17
 
18
+ # methods defined on both the global K8s::Logging module, as well as class methods on each class including K8s::Logging
8
19
  module ModuleMethods
9
20
  # global log_level shared across all including classes
21
+ # @return Logger::*
10
22
  def log_level
11
23
  @log_level
12
24
  end
25
+
26
+ # @param level Logger::*
13
27
  def log_level=(level)
14
28
  @log_level = level
15
29
  end
30
+
31
+ # Set log_level to Logger::DEBUG
16
32
  def debug!
17
33
  self.log_level = Logger::DEBUG
18
34
  end
35
+
36
+ # Set log_level to Logger::INFO
19
37
  def verbose!
20
38
  self.log_level = Logger::INFO
21
39
  end
40
+
41
+ # Set log_level to Logger::ERROR, surpressing any warnings logged by default
22
42
  def quiet!
23
43
  self.log_level = Logger::ERROR
24
44
  end
@@ -26,7 +46,9 @@ module K8s
26
46
 
27
47
  extend ModuleMethods # global @log_level
28
48
 
49
+ # methods defined on each class including K8s::Logging
29
50
  module ClassMethods
51
+ # @return [Logger]
30
52
  def logger(target: LOG_TARGET, level: nil)
31
53
  @logger ||= Logger.new(target).tap do |logger|
32
54
  logger.progname = self.name
@@ -35,12 +57,17 @@ module K8s
35
57
  end
36
58
  end
37
59
 
60
+ # extend class/intance methods for per-class logger
38
61
  def self.included(base)
39
62
  base.extend(ModuleMethods) # per-class @log_level
40
63
  base.extend(ClassMethods)
41
64
  end
42
65
 
43
- # Use per-instance logger
66
+ # Use per-instance logger instead of the default per-class logger
67
+ #
68
+ # Sets the instance variable returned by #logger
69
+ #
70
+ # @return [Logger]
44
71
  def logger!(progname: self.class.name, target: LOG_TARGET, level: nil, debug: false)
45
72
  @logger = Logger.new(target).tap do |logger|
46
73
  level = Logger::DEBUG if debug
data/lib/k8s/resource.rb CHANGED
@@ -19,7 +19,7 @@ module K8s
19
19
  new(YAML.load_file(filename))
20
20
  end
21
21
 
22
- # @param filename [String] file path
22
+ # @param path [String] file path
23
23
  # @return [Array<K8s::Resource>]
24
24
  def self.from_files(path)
25
25
  stat = File.stat(path)
@@ -32,7 +32,7 @@ module K8s
32
32
  end
33
33
  end
34
34
 
35
- # @param attrs [Hash]
35
+ # @param hash [Hash]
36
36
  def initialize(hash, recurse_over_arrays: true, **options)
37
37
  super(hash,
38
38
  recurse_over_arrays: recurse_over_arrays,
@@ -1,5 +1,10 @@
1
1
  module K8s
2
+ # Per-APIResource type client.
3
+ #
4
+ # Used to get/list/update/patch/delete specific types of resources, optionally in some specific namespace.
2
5
  class ResourceClient
6
+
7
+ # Common helpers used in both class/instance methods
3
8
  module Utils
4
9
  # @param selector [nil, String, Hash{String => String}]
5
10
  # @return [nil, String]
data/lib/k8s/stack.rb CHANGED
@@ -5,8 +5,14 @@ module K8s
5
5
  class Stack
6
6
  include Logging
7
7
 
8
+ # Label used to identify resources belonging to this stack
8
9
  LABEL = 'k8s.kontena.io/stack'
10
+
11
+ # Annotation used to identify resource versions
9
12
  CHECKSUM_ANNOTATION = 'k8s.kontena.io/stack-checksum'
13
+
14
+ # List of apiVersion:kind combinations to skip for stack prune
15
+ # These would lead to stack prune misbehaving if not skipped.
10
16
  PRUNE_IGNORE = [
11
17
  'v1:ComponentStatus', # apiserver ignores GET /v1/componentstatuses?labelSelector=... and returns all resources
12
18
  'v1:Endpoints', # inherits stack label from service, but not checksum annotation
@@ -48,6 +54,11 @@ module K8s
48
54
  logger! progname: name, debug: debug
49
55
  end
50
56
 
57
+ # Random "checksum" used to identify different stack resource versions using an annotation.
58
+ #
59
+ # NOTE: This is not actually a checksum.
60
+ #
61
+ # @return [String]
51
62
  def checksum
52
63
  @checksum ||= SecureRandom.hex(16)
53
64
  end
data/lib/k8s/transport.rb CHANGED
@@ -2,23 +2,28 @@ require 'excon'
2
2
  require 'json'
3
3
 
4
4
  module K8s
5
+ # Excon-based HTTP transport handling request/response body JSON encoding
5
6
  class Transport
6
7
  include Logging
7
8
 
8
9
  quiet! # do not log warnings by default
9
10
 
11
+ # Excon middlewares for requests
10
12
  EXCON_MIDDLEWARES = [
11
13
  # XXX: necessary? redirected requests omit authz headers?
12
14
  Excon::Middleware::RedirectFollower,
13
15
  ] + Excon.defaults[:middlewares]
14
16
 
17
+ # Default request headers
15
18
  REQUEST_HEADERS = {
16
19
  'Accept' => 'application/json',
17
20
  }
18
21
 
19
22
  # Construct transport from kubeconfig
20
23
  #
21
- # @param config [Phraos::Kube::Config]
24
+ # @param config [K8s::Config]
25
+ # @param server [String] override cluster.server from config
26
+ # @param overrides @see #initialize
22
27
  # @return [K8s::Transport]
23
28
  def self.config(config, server: nil, **overrides)
24
29
  options = {}
@@ -58,6 +63,12 @@ module K8s
58
63
  options[:client_key_data] = Base64.decode64(key_data)
59
64
  end
60
65
 
66
+ if token = config.user.token
67
+ logger.debug "Using config with .user.token=..."
68
+
69
+ options[:auth_token] = token
70
+ end
71
+
61
72
  logger.info "Using config with server=#{server}"
62
73
 
63
74
  new(server, **options, **overrides)
@@ -80,6 +91,8 @@ module K8s
80
91
  attr_reader :server, :options
81
92
 
82
93
  # @param server [String] URL with protocol://host:port - any /path is ignored
94
+ # @param auth_token [String] optional Authorization: Bearer token
95
+ # @param options [Hash] @see Excon.new
83
96
  def initialize(server, auth_token: nil, **options)
84
97
  @server = server
85
98
  @auth_token = auth_token
@@ -98,11 +111,15 @@ module K8s
98
111
  )
99
112
  end
100
113
 
114
+ # @param path [Array<String>] join path parts together to build the full URL
101
115
  # @return [String]
102
116
  def path(*path)
103
117
  File.join('/', *path)
104
118
  end
105
119
 
120
+ # @param request_object [Object] include request body using to_json
121
+ # @param content_type [String] request body content-type
122
+ # @param options [Hash] @see Excon#request
106
123
  # @return [Hash]
107
124
  def request_options(request_object: nil, content_type: 'application/json', **options)
108
125
  options[:headers] ||= {}
@@ -119,6 +136,7 @@ module K8s
119
136
  options
120
137
  end
121
138
 
139
+ # @param options [Hash] as passed to Excon#request
122
140
  def format_request(options)
123
141
  method = options[:method]
124
142
  path = options[:path]
@@ -134,6 +152,9 @@ module K8s
134
152
  [method, path, body].compact.join " "
135
153
  end
136
154
 
155
+ # @param response [Hash] as returned by Excon#request
156
+ # @param request_options [Hash] as passed to Excon#request
157
+ # @param response_class [Class] decode response body using #from_json
137
158
  # @raise [K8s::Error]
138
159
  # @raise [Excon::Error] TODO: wrap
139
160
  # @return [response_class, Hash]
@@ -177,6 +198,8 @@ module K8s
177
198
  end
178
199
  end
179
200
 
201
+ # @param response_class [Class] decode response body using #from_json
202
+ # @param options [Hash] @see Excon#request
180
203
  def request(response_class: nil, **options)
181
204
  excon_options = request_options(**options)
182
205
 
@@ -199,9 +222,10 @@ module K8s
199
222
  return obj
200
223
  end
201
224
 
202
- # @param options [Array<Hash>]
225
+ # @param options [Array<Hash>] @see #request
203
226
  # @param skip_missing [Boolean] return nil for HTTP 404 responses
204
227
  # @param retry_errors [Boolean] retry with non-pipelined request for HTTP 503 responses
228
+ # @param common_options [Hash] @see #request, merged with the per-request options
205
229
  # @return [Array<response_class, Hash, nil>]
206
230
  def requests(*options, skip_missing: false, retry_errors: true, **common_options)
207
231
  return [] if options.empty? # excon chokes
@@ -244,7 +268,8 @@ module K8s
244
268
  return objects
245
269
  end
246
270
 
247
- # @param *path [String]
271
+ # @param path [Array<String>] @see #path
272
+ # @param options [Hash] @see #request
248
273
  def get(*path, **options)
249
274
  request(
250
275
  method: 'GET',
@@ -253,7 +278,8 @@ module K8s
253
278
  )
254
279
  end
255
280
 
256
- # @param *paths [String]
281
+ # @param paths [Array<String>]
282
+ # @param options [Hash] @see #request
257
283
  def gets(*paths, **options)
258
284
  requests(*paths.map{|path| {
259
285
  method: 'GET',
data/lib/k8s/util.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module K8s
2
+ # Miscellaneous helpers
2
3
  module Util
3
4
  # Yield with all non-nil args, returning matching array with corresponding return values or nils.
4
5
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: k8s-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kontena, Inc.