Class: K8s::Client

Inherits:
Object
  • Object
show all
Includes:
MonitorMixin
Defined in:
lib/k8s/client.rb,
lib/k8s/client/version.rb

Overview

Top-level client wrapper. Uses a Transport instance to talk to the kube API. Offers access to APIClient and ResourceClient instances.

Constant Summary collapse

VERSION =

Updated on releases using semver.

"0.10.3"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(transport, namespace: nil) ⇒ Client



108
109
110
111
112
113
114
# File 'lib/k8s/client.rb', line 108

def initialize(transport, namespace: nil)
  @transport = transport
  @namespace = namespace

  @api_clients = {}
  super()
end

Instance Attribute Details

#transportObject (readonly)

Returns the value of attribute transport.



104
105
106
# File 'lib/k8s/client.rb', line 104

def transport
  @transport
end

Class Method Details

.autoconfig(namespace: nil, **options) ⇒ K8s::Client

Attempts to create a K8s::Client instance automatically using environment variables, existing configuration files or in cluster configuration.

Look-up order:

- KUBE_TOKEN, KUBE_CA, KUBE_SERVER environment variables
- KUBECONFIG environment variable
- $HOME/.kube/config file
- In cluster configuration

Will raise when no means of configuration is available

Raises:

  • (K8s::Error::Config, Errno::ENOENT, Errno::EACCES)


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/k8s/client.rb', line 70

def self.autoconfig(namespace: nil, **options)
  if ENV.values_at('KUBE_TOKEN', 'KUBE_CA', 'KUBE_SERVER').none? { |v| v.nil? || v.empty? }
    unless Base64.decode64(ENV['KUBE_CA']).match?(/CERTIFICATE/)
      raise ArgumentError, 'KUBE_CA does not seem to be base64 encoded'
    end

    begin
      token = options[:auth_token] || Base64.strict_decode64(ENV['KUBE_TOKEN'])
    rescue ArgumentError
      raise ArgumentError, 'KUBE_TOKEN does not seem to be base64 encoded'
    end

    configuration = K8s::Config.build(server: ENV['KUBE_SERVER'], ca: ENV['KUBE_CA'], auth_token: token)
  elsif !ENV['KUBECONFIG'].to_s.empty?
    configuration = K8s::Config.from_kubeconfig_env(ENV['KUBECONFIG'])
  else
    found_config = [
      File.join(Dir.home, '.kube', 'config'),
      '/etc/kubernetes/admin.conf',
      '/etc/kubernetes/kubelet.conf'
    ].find { |f| File.exist?(f) && File.readable?(f) }

    configuration = K8s::Config.load_file(found_config) if found_config
  end

  if configuration
    config(configuration, namespace: namespace, **options)
  else
    in_cluster_config(namespace: namespace, **options)
  end
end

.config(config, namespace: nil, **options) ⇒ K8s::Client



38
39
40
41
42
43
# File 'lib/k8s/client.rb', line 38

def self.config(config, namespace: nil, **options)
  new(
    Transport.config(config, **options),
    namespace: namespace
  )
end

.in_cluster_config(namespace: nil, **options) ⇒ K8s::Client

An K8s::Client instance from in-cluster config within a kube pod, using the kubernetes service envs and serviceaccount secrets

Raises:

  • (K8s::Error::Config, Errno::ENOENT, Errno::EACCES)

See Also:

  • Transport#in_cluster_config


52
53
54
# File 'lib/k8s/client.rb', line 52

def self.in_cluster_config(namespace: nil, **options)
  new(Transport.in_cluster_config(**options), namespace: namespace)
end

Instance Method Details

#api(api_version = 'v1') ⇒ APIClient



124
125
126
# File 'lib/k8s/client.rb', line 124

def api(api_version = 'v1')
  @api_clients[api_version] ||= APIClient.new(@transport, api_version)
end

#api_groupsArray<String>

Cached /apis preferred group apiVersions



147
148
149
# File 'lib/k8s/client.rb', line 147

def api_groups
  @api_groups || api_groups!
end

#api_groups!Array<String>

Force-update /apis cache. Required if creating new CRDs/apiservices.



132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/k8s/client.rb', line 132

def api_groups!
  synchronize do
    @api_groups = @transport.get(
      '/apis',
      response_class: K8s::API::MetaV1::APIGroupList
    ).groups.flat_map{ |api_group| api_group.versions.map(&:groupVersion) }

    @api_clients.clear
  end

  @api_groups
end

#apis(api_versions = nil, prefetch_resources: false, skip_missing: false) ⇒ Array<APIClient>



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/k8s/client.rb', line 155

def apis(api_versions = nil, prefetch_resources: false, skip_missing: false)
  api_versions ||= ['v1'] + api_groups

  if prefetch_resources
    # api groups that are missing their api_resources
    api_paths = api_versions
                .uniq
                .reject{ |api_version| api(api_version).api_resources? }
                .map{ |api_version| APIClient.path(api_version) }

    # load into APIClient.api_resources=
    begin
      @transport.gets(*api_paths, response_class: K8s::API::MetaV1::APIResourceList, skip_missing: skip_missing).each do |api_resource_list|
        api(api_resource_list.groupVersion).api_resources = api_resource_list.resources if api_resource_list
      end
    rescue K8s::Error::NotFound, K8s::Error::ServiceUnavailable # rubocop:disable Lint/HandleExceptions
      # kubernetes api is in unstable state
      # because this is only performance optimization, better to skip prefetch and move on
    end
  end

  api_versions.map{ |api_version| api(api_version) }
end

#client_for_resource(resource, namespace: nil) ⇒ K8s::ResourceClient

Raises:



218
219
220
# File 'lib/k8s/client.rb', line 218

def client_for_resource(resource, namespace: nil)
  api(resource.apiVersion).client_for_resource(resource, namespace: namespace)
end

#create_resource(resource) ⇒ K8s::Resource



224
225
226
# File 'lib/k8s/client.rb', line 224

def create_resource(resource)
  client_for_resource(resource).create_resource(resource)
end

#delete_resource(resource, **options) ⇒ K8s::Resource

See Also:



272
273
274
# File 'lib/k8s/client.rb', line 272

def delete_resource(resource, **options)
  client_for_resource(resource).delete_resource(resource, **options)
end

#get_resource(resource) ⇒ K8s::Resource



230
231
232
# File 'lib/k8s/client.rb', line 230

def get_resource(resource)
  client_for_resource(resource).get_resource(resource)
end

#get_resources(resources) ⇒ Array<K8s::Resource, nil>

Returns nils for any resources that do not exist. This includes custom resources that were not yet defined.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/k8s/client.rb', line 239

def get_resources(resources)
  # prefetch api resources, skip missing APIs
  resource_apis = apis(resources.map(&:apiVersion), prefetch_resources: true, skip_missing: true)

  # map each resource to excon request options, or nil if resource is not (yet) defined
  requests = resources.zip(resource_apis).map{ |resource, api_client|
    next nil unless api_client.api_resources?

    resource_client = api_client.client_for_resource(resource)

    {
      method: 'GET',
      path: resource_client.path(resource..name, namespace: resource..namespace),
      response_class: resource_client.resource_class
    }
  }

  # map non-nil requests to response objects, or nil for nil request options
  Util.compact_map(requests) { |reqs|
    @transport.requests(*reqs, skip_missing: true)
  }
end

#list_resources(resources = nil, **options) ⇒ Array<K8s::Resource>

Pipeline list requests for multiple resource types.

Returns flattened array with mixed resource kinds.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/k8s/client.rb', line 198

def list_resources(resources = nil, **options)
  cached_clients = @api_clients.size.positive?
  resources ||= self.resources.select(&:list?)

  begin
    ResourceClient.list(resources, @transport, **options)
  rescue K8s::Error::NotFound
    raise unless cached_clients

    cached_clients = false
    api_groups!
    retry
  end
end

#patch_resource(resource, attrs) ⇒ K8s::Client



279
280
281
# File 'lib/k8s/client.rb', line 279

def patch_resource(resource, attrs)
  client_for_resource(resource).json_patch(resource..name, attrs)
end

#resources(namespace: nil) ⇒ Array<K8s::ResourceClient>



181
182
183
184
185
186
187
188
189
# File 'lib/k8s/client.rb', line 181

def resources(namespace: nil)
  apis(prefetch_resources: true).map { |api|
    begin
      api.resources(namespace: namespace)
    rescue K8s::Error::ServiceUnavailable, K8s::Error::NotFound
      []
    end
  }.flatten
end

#update_resource(resource) ⇒ K8s::Resource



264
265
266
# File 'lib/k8s/client.rb', line 264

def update_resource(resource)
  client_for_resource(resource).update_resource(resource)
end

#versionK8s::API::Version

Raises:



118
119
120
# File 'lib/k8s/client.rb', line 118

def version
  @version ||= @transport.version
end