k8y 0.1.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 +7 -0
- data/.github/workflows/main.yml +34 -0
- data/.gitignore +24 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +1 -0
- data/Rakefile +23 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/k8y.gemspec +44 -0
- data/lib/k8y/client/api.rb +46 -0
- data/lib/k8y/client/api_builder.rb +162 -0
- data/lib/k8y/client/apis.rb +30 -0
- data/lib/k8y/client/client.rb +48 -0
- data/lib/k8y/client/resource_description.rb +56 -0
- data/lib/k8y/client/response_formatter.rb +46 -0
- data/lib/k8y/client/rest_client.rb +90 -0
- data/lib/k8y/client.rb +17 -0
- data/lib/k8y/error.rb +2 -0
- data/lib/k8y/group_version.rb +19 -0
- data/lib/k8y/kubeconfig/auth_info.rb +49 -0
- data/lib/k8y/kubeconfig/cluster.rb +30 -0
- data/lib/k8y/kubeconfig/config.rb +69 -0
- data/lib/k8y/kubeconfig/context.rb +28 -0
- data/lib/k8y/kubeconfig/user.rb +23 -0
- data/lib/k8y/kubeconfig.rb +16 -0
- data/lib/k8y/version.rb +5 -0
- data/lib/k8y.rb +9 -0
- metadata +242 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4af0d1dcdd836cab3430faa1d86ebad78e6aa9ed4d935a86a11ba0f4cc11f245
|
4
|
+
data.tar.gz: f79b384ede82574419718223ab399ca2b4b1ee778d001433a4c0e721529cac69
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3048c108fb78383700dbfbb3527645cde925ad2cd56dbe01f36d2728642240e9ce93ad6555150a519f16605f1c6d5337019e7dff6548fc416ec887bdf58f3390
|
7
|
+
data.tar.gz: 0af26a9f575cbbe9b785c6ecb761df8d1cd97f731905b33f98a5d462562a0d2117f69fbe235c4dae311d05d3b281854c3121480bae3ffe3d9164c6419c2fc16a
|
@@ -0,0 +1,34 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
ruby:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 2.7.2
|
14
|
+
bundler-cache: true
|
15
|
+
- name: Run the default task
|
16
|
+
run: bundle exec rake
|
17
|
+
- name: Lint
|
18
|
+
run: bundle exec rubocop
|
19
|
+
kind:
|
20
|
+
runs-on: ubuntu-latest
|
21
|
+
steps:
|
22
|
+
- uses: actions/checkout@master
|
23
|
+
- name: Set up Ruby
|
24
|
+
uses: ruby/setup-ruby@v1
|
25
|
+
with:
|
26
|
+
ruby-version: 2.7.2
|
27
|
+
bundler-cache: true
|
28
|
+
- name: Set up KinD
|
29
|
+
uses: engineerd/setup-kind@v0.5.0
|
30
|
+
with:
|
31
|
+
version: "v0.11.1"
|
32
|
+
- name: In-cluster tests
|
33
|
+
run: |
|
34
|
+
bundle exec rake test_integration
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/_yardoc/
|
4
|
+
/coverage/
|
5
|
+
/doc/
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/tmp/
|
9
|
+
/.DS_Store
|
10
|
+
|
11
|
+
# Rake-able scratch working directory
|
12
|
+
/scratch
|
13
|
+
|
14
|
+
# Ignore lockfile for gem
|
15
|
+
Gemfile.lock
|
16
|
+
|
17
|
+
# debug logs
|
18
|
+
.byebug_history
|
19
|
+
|
20
|
+
# development kubeconfig
|
21
|
+
/kubeconfig
|
22
|
+
|
23
|
+
# A handy file for keeping scraps around
|
24
|
+
/scratch
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
## next
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Kir Shatrov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Kubernetes client for Ruby
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
task default: ["test"]
|
7
|
+
|
8
|
+
desc("Run test suite")
|
9
|
+
Rake::TestTask.new(:test) do |task|
|
10
|
+
task.libs << "test"
|
11
|
+
task.libs << "lib"
|
12
|
+
task.test_files = FileList["test/unit/**/*_test.rb"]
|
13
|
+
end
|
14
|
+
|
15
|
+
desc("Run in-cluster integrations tests")
|
16
|
+
Rake::TestTask.new(:test_integration) do |task|
|
17
|
+
ENV["PARALLELIZE_ME"] = ENV.fetch("PARALLELIZE_ME", "1")
|
18
|
+
ENV["MT_CPU"] = ENV.fetch("MT_CPU", "8")
|
19
|
+
task.libs << "test"
|
20
|
+
task.libs << "lib"
|
21
|
+
task.test_files = FileList["test/integration/**/*_test.rb"]
|
22
|
+
task.warning = false
|
23
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "k8y"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/k8y.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/k8y/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "k8y"
|
7
|
+
spec.version = K8y::VERSION
|
8
|
+
spec.authors = ["Timothy Smith"]
|
9
|
+
spec.email = ["tsontario@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Ruby client for interacting with kubernetes clusters"
|
12
|
+
spec.description = "Ruby client for interacting with kubernetes clusters"
|
13
|
+
spec.homepage = "https://github.com/tsontario/k8y"
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
15
|
+
|
16
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
%x(git ls-files -z).split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency("faraday", "~> 1.6")
|
32
|
+
spec.add_dependency("recursive-open-struct", "~>1.1")
|
33
|
+
spec.add_dependency("railties", "~> 6.0")
|
34
|
+
spec.add_dependency("activesupport", "~> 6.0")
|
35
|
+
|
36
|
+
spec.add_development_dependency("byebug", "~> 11")
|
37
|
+
spec.add_development_dependency("minitest", "~> 5")
|
38
|
+
spec.add_development_dependency("minitest-reporters")
|
39
|
+
spec.add_development_dependency("mocha", "~> 1")
|
40
|
+
spec.add_development_dependency("rubocop", "~> 1")
|
41
|
+
spec.add_development_dependency("rubocop-shopify", "~> 2")
|
42
|
+
spec.add_development_dependency("simplecov")
|
43
|
+
spec.add_development_dependency("webmock", "~> 3.0")
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module K8y
|
6
|
+
module Client
|
7
|
+
class API
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :group_version
|
11
|
+
|
12
|
+
def_delegators(:@group_version, :group, :version)
|
13
|
+
|
14
|
+
def initialize(group_version:)
|
15
|
+
@group_version = group_version
|
16
|
+
@discovered = false
|
17
|
+
@api_methods = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def discovered?
|
21
|
+
discovered
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_api_method?(method)
|
25
|
+
api_methods.dig(method)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Core/v1 resources are in `/api/#{version}`. In general, all other resources are found in `/apis/GROUP/VERSION`
|
29
|
+
def path
|
30
|
+
@path ||= if group == "core"
|
31
|
+
"/api/#{version}"
|
32
|
+
else
|
33
|
+
"/apis/#{group}/#{version}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_accessor :api_methods, :discovered
|
40
|
+
|
41
|
+
def register_method(method)
|
42
|
+
api_methods[method] = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "api"
|
4
|
+
require_relative "resource_description"
|
5
|
+
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
module K8y
|
9
|
+
module Client
|
10
|
+
class APIBuilder < Module
|
11
|
+
attr_reader :api, :config, :context
|
12
|
+
|
13
|
+
VERBS_TO_METHODS = {
|
14
|
+
create: :define_create_resource,
|
15
|
+
delete: :define_delete_resource,
|
16
|
+
get: :define_get_resource,
|
17
|
+
list: :define_get_resources,
|
18
|
+
patch: :define_patch_and_apply_resource,
|
19
|
+
update: :define_update_resource,
|
20
|
+
}
|
21
|
+
|
22
|
+
def initialize(api:, config:, context:)
|
23
|
+
super()
|
24
|
+
@api = api
|
25
|
+
@config = config
|
26
|
+
@context = context
|
27
|
+
end
|
28
|
+
|
29
|
+
def build!
|
30
|
+
rest_client = RESTClient.new(config: config, context: context, path: api.path)
|
31
|
+
response = rest_client.get(as: :raw)
|
32
|
+
resource_descriptions = JSON.parse(response.body)["resources"].map do |resource_description|
|
33
|
+
ResourceDescription.from_hash(resource_description)
|
34
|
+
end
|
35
|
+
|
36
|
+
resource_descriptions.each do |resource_description|
|
37
|
+
next if resource_description.subresource?
|
38
|
+
|
39
|
+
VERBS_TO_METHODS
|
40
|
+
.filter { |verb, _| resource_description.has_verb?(verb) }
|
41
|
+
.each_value { |method| send(method, api, rest_client, resource_description) }
|
42
|
+
api.instance_eval { self.discovered = true }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def define_create_resource(api, rest_client, resource_description)
|
49
|
+
method_name = "create_#{resource_description.singular_name}".to_sym
|
50
|
+
json_content_type = { "Content-Type": "application/json" }
|
51
|
+
api.instance_eval do
|
52
|
+
define_singleton_method(method_name) do |kwargs = {}|
|
53
|
+
namespace = kwargs.fetch(:namespace) if resource_description.namespaced
|
54
|
+
data = kwargs.fetch(:data)
|
55
|
+
headers = kwargs.fetch(:headers, {}).merge(json_content_type)
|
56
|
+
rest_client.post(resource_description.path_for_resources(namespace: namespace),
|
57
|
+
data: JSON.dump(data), headers: headers)
|
58
|
+
end
|
59
|
+
register_method(method_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def define_delete_resource(api, rest_client, resource_description)
|
64
|
+
method_name = "delete_#{resource_description.singular_name}".to_sym
|
65
|
+
api.instance_eval do
|
66
|
+
define_singleton_method(method_name) do |kwargs = {}|
|
67
|
+
namespace = kwargs.fetch(:namespace) if resource_description.namespaced
|
68
|
+
name = kwargs.fetch(:name)
|
69
|
+
headers = kwargs.fetch(:headers, {})
|
70
|
+
rest_client.delete(resource_description.path_for_resource(namespace: namespace, name: name),
|
71
|
+
headers: headers)
|
72
|
+
end
|
73
|
+
register_method(method_name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def define_get_resource(api, rest_client, resource_description)
|
78
|
+
method_name = "get_#{resource_description.singular_name}".to_sym
|
79
|
+
api.instance_eval do
|
80
|
+
define_singleton_method(method_name) do |kwargs = {}|
|
81
|
+
namespace = kwargs.fetch(:namespace) if resource_description.namespaced
|
82
|
+
name = kwargs.fetch(:name)
|
83
|
+
headers = kwargs.fetch(:headers, {})
|
84
|
+
rest_client.get(resource_description.path_for_resource(namespace: namespace, name: name), headers: headers)
|
85
|
+
end
|
86
|
+
register_method(method_name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def define_get_resources(api, rest_client, resource_description)
|
91
|
+
method_name = "get_#{resource_description.plural_name}".to_sym
|
92
|
+
api.instance_eval do
|
93
|
+
define_singleton_method(method_name) do |kwargs = {}|
|
94
|
+
namespace = kwargs.fetch(:namespace) if resource_description.namespaced
|
95
|
+
headers = kwargs.fetch(:headers, {})
|
96
|
+
rest_client.get(resource_description.path_for_resources(namespace: namespace), headers: headers)
|
97
|
+
end
|
98
|
+
register_method(method_name)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def define_patch_and_apply_resource(api, rest_client, resource_description)
|
103
|
+
define_patch_resource(api, rest_client, resource_description)
|
104
|
+
define_apply_resource(api, rest_client, resource_description)
|
105
|
+
end
|
106
|
+
|
107
|
+
def define_patch_resource(api, rest_client, resource_description)
|
108
|
+
method_name = "patch_#{resource_description.singular_name}".to_sym
|
109
|
+
# Lexically scope this method in the block to make it available inside the closure when invoked by client
|
110
|
+
scoped_content_type_for_patch_strategy = proc { |strategy| content_type_for_patch_strategy(strategy) }
|
111
|
+
api.instance_eval do
|
112
|
+
define_singleton_method(method_name) do |kwargs = {}|
|
113
|
+
namespace = kwargs.fetch(:namespace) if resource_description.namespaced
|
114
|
+
name = kwargs.fetch(:name)
|
115
|
+
data = kwargs.fetch(:data)
|
116
|
+
strategy = kwargs.fetch(:strategy)
|
117
|
+
headers = kwargs.fetch(:headers, {}).merge(scoped_content_type_for_patch_strategy.call(strategy))
|
118
|
+
rest_client.patch(resource_description.path_for_resource(namespace: namespace, name: name),
|
119
|
+
strategy: strategy, data: JSON.dump(data), headers: headers)
|
120
|
+
end
|
121
|
+
register_method(method_name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def define_apply_resource(api, rest_client, resource_description)
|
126
|
+
# method_name = TODO method name
|
127
|
+
# # TODO: apply is really a special-case of patch, but requires a bit more effort to implement...
|
128
|
+
# api.register_method(method_name)
|
129
|
+
end
|
130
|
+
|
131
|
+
def define_update_resource(api, rest_client, resource_description)
|
132
|
+
method_name = "update_#{resource_description.singular_name}".to_sym
|
133
|
+
json_content_type = { "Content-Type": "application/json" }
|
134
|
+
api.instance_eval do
|
135
|
+
api.define_singleton_method(method_name) do |kwargs = {}|
|
136
|
+
namespace = kwargs.fetch(:namespace) if resource_description.namespaced
|
137
|
+
name = kwargs.fetch(:name)
|
138
|
+
data = kwargs.fetch(:data)
|
139
|
+
headers = kwargs.fetch(:headers, {}).merge(json_content_type)
|
140
|
+
rest_client.put(resource_description.path_for_resource(namespace: namespace, name: name),
|
141
|
+
data: JSON.dump(data), headers: headers)
|
142
|
+
end
|
143
|
+
register_method(method_name)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def content_type_for_patch_strategy(strategy)
|
148
|
+
case strategy
|
149
|
+
when :json
|
150
|
+
{ "Content-Type": "application/json-patch+json" }
|
151
|
+
when :merge
|
152
|
+
{ "Content-Type": "application/merge-patch+json" }
|
153
|
+
when :strategic_merge
|
154
|
+
{ "Content-Type": "application/strategic-merge-patch+json" }
|
155
|
+
else
|
156
|
+
raise ArgumentError, "unknown patch strategy: #{strategy}. Acceptable strategies are" \
|
157
|
+
" :json, :merge, or :strategic_merge"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "api"
|
4
|
+
|
5
|
+
module K8y
|
6
|
+
module Client
|
7
|
+
class APIs
|
8
|
+
include Enumerable
|
9
|
+
def initialize(group_versions:)
|
10
|
+
@apis = group_versions.each_with_object({}) do |gv, acc|
|
11
|
+
api = API.new(group_version: gv)
|
12
|
+
acc[gv.to_s] = api
|
13
|
+
define_singleton_method(gv.to_method_name) { api }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
apis.each { |_, api| yield(api) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def apis_for_method(method)
|
22
|
+
select { |api| api.has_api_method?(method) }
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :apis
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "rest_client"
|
4
|
+
require_relative "api_builder"
|
5
|
+
require_relative "apis"
|
6
|
+
|
7
|
+
module K8y
|
8
|
+
module Client
|
9
|
+
class Client
|
10
|
+
ContextNotFoundError = Class.new(Error)
|
11
|
+
APINameConflictError = Class.new(Error)
|
12
|
+
|
13
|
+
attr_reader :config, :context, :apis
|
14
|
+
|
15
|
+
def initialize(config:, context:, group_versions: DEFAULT_GROUP_VERSIONS)
|
16
|
+
@config = config
|
17
|
+
@context = config.context(context)
|
18
|
+
@apis = APIs.new(group_versions: group_versions)
|
19
|
+
end
|
20
|
+
|
21
|
+
def discover!
|
22
|
+
apis.each { |api| APIBuilder.new(api: api, config: config, context: context.name).build! }
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_context(context_name)
|
26
|
+
@context = config.context(context_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(method_name, *args, &block)
|
30
|
+
candidate_apis = apis.apis_for_method(method_name)
|
31
|
+
case candidate_apis.length
|
32
|
+
when 0
|
33
|
+
super
|
34
|
+
when 1
|
35
|
+
candidate_apis.first.public_send(method_name, *args, &block)
|
36
|
+
else
|
37
|
+
raise APINameConflictError, "#{method_name} is defined in multiple group versions: " \
|
38
|
+
"#{candidate_apis.map(&:group_version).join(", ")}. You can access a specific GroupVersion " \
|
39
|
+
"by declaring it explicitly. E.g. client.apis.core_v1.get_pods"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def respond_to_missing?(method_name, include_private = false)
|
44
|
+
apis.apis_for_method(method_name).present? || super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8y
|
4
|
+
module Client
|
5
|
+
class ResourceDescription
|
6
|
+
attr_reader :name, :kind, :namespaced, :verbs
|
7
|
+
alias_method(:plural_name, :name)
|
8
|
+
|
9
|
+
def self.from_hash(hash)
|
10
|
+
new(
|
11
|
+
name: hash.fetch("name"),
|
12
|
+
kind: hash.fetch("kind"),
|
13
|
+
singular_name: hash.fetch("singularName"),
|
14
|
+
namespaced: hash.fetch("namespaced"),
|
15
|
+
verbs: hash.fetch("verbs").map(&:to_sym),
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(name:, kind:, singular_name:, namespaced:, verbs:)
|
20
|
+
@name = name
|
21
|
+
@kind = kind
|
22
|
+
@singular_name = singular_name
|
23
|
+
@namespaced = namespaced
|
24
|
+
@verbs = verbs
|
25
|
+
end
|
26
|
+
|
27
|
+
def singular_name
|
28
|
+
if @singular_name.empty?
|
29
|
+
kind.downcase
|
30
|
+
else
|
31
|
+
@singular_name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def subresource?
|
36
|
+
name.include?("/") # E.g. namespaces/status, deployments/scale, etc.
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_verb?(verb)
|
40
|
+
verbs.include?(verb)
|
41
|
+
end
|
42
|
+
|
43
|
+
def path_for_resources(namespace: nil)
|
44
|
+
if namespace
|
45
|
+
"namespaces/#{namespace}/#{plural_name}"
|
46
|
+
else
|
47
|
+
plural_name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def path_for_resource(namespace: nil, name:)
|
52
|
+
"#{path_for_resources(namespace: namespace)}/#{name}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module K8y
|
3
|
+
module Client
|
4
|
+
class ResponseFormatter
|
5
|
+
UnsupportedResponseTypeError = Class.new(Error)
|
6
|
+
|
7
|
+
attr_reader :response
|
8
|
+
|
9
|
+
def initialize(response)
|
10
|
+
@response = response
|
11
|
+
end
|
12
|
+
|
13
|
+
def format(as: :ros)
|
14
|
+
case as
|
15
|
+
when :ros
|
16
|
+
build_recursive_open_struct_response(response.body)
|
17
|
+
when :raw
|
18
|
+
response
|
19
|
+
else
|
20
|
+
raise UnsupportedResponseTypeError, as.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_recursive_open_struct_response(body)
|
27
|
+
data = JSON.parse(body)
|
28
|
+
if item_list?(data)
|
29
|
+
data.fetch("items").map { |item| RecursiveOpenStruct.new(item, recurse_over_arrays: true) }
|
30
|
+
elsif resources_list?(data)
|
31
|
+
data.fetch("resources").map { |item| RecursiveOpenStruct.new(item, recurse_over_arrays: true) }
|
32
|
+
else
|
33
|
+
RecursiveOpenStruct.new(data, recurse_over_arrays: true)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def item_list?(data)
|
38
|
+
data["kind"] =~ /List$/ && data["items"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def resources_list?(data)
|
42
|
+
data["kind"] =~ /List$/ && data["resources"]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "base64"
|
5
|
+
require "recursive_open_struct"
|
6
|
+
|
7
|
+
require_relative "response_formatter"
|
8
|
+
|
9
|
+
module K8y
|
10
|
+
module Client
|
11
|
+
class RESTClient
|
12
|
+
attr_reader :context, :path
|
13
|
+
|
14
|
+
def initialize(config:, context:, path: "")
|
15
|
+
@config = config
|
16
|
+
@context = context
|
17
|
+
@path = path
|
18
|
+
|
19
|
+
# TODO: generically handle auth depending on provided config
|
20
|
+
hardcoded_auth
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(sub_path = "", headers: {}, as: :ros)
|
24
|
+
response = connection.get(formatted_uri(host, path, sub_path))
|
25
|
+
format_response(response, as: as)
|
26
|
+
end
|
27
|
+
|
28
|
+
def post(sub_path = "", data:, headers: {}, as: :ros)
|
29
|
+
response = connection.post(formatted_uri(host, path, sub_path), data, headers)
|
30
|
+
format_response(response, as: as)
|
31
|
+
end
|
32
|
+
|
33
|
+
def put(sub_path = "", data:, headers: {}, as: :ros)
|
34
|
+
response = connection.put(formatted_uri(host, path, sub_path), data, headers)
|
35
|
+
format_response(response, as: as)
|
36
|
+
end
|
37
|
+
|
38
|
+
def patch(sub_path = "", strategy:, data:, headers: {}, as: :ros)
|
39
|
+
response = connection.patch(formatted_uri(host, path, sub_path), data, headers)
|
40
|
+
format_response(response, as: as)
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(sub_path = "", headers: {}, as: :ros)
|
44
|
+
response = connection.delete(formatted_uri(host, path, sub_path))
|
45
|
+
format_response(response, as: as)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
attr_reader :config, :connection
|
51
|
+
|
52
|
+
def hardcoded_auth
|
53
|
+
cert_store = OpenSSL::X509::Store.new
|
54
|
+
cert_store.add_cert(OpenSSL::X509::Certificate.new(Base64.decode64(cluster.certificate_authority_data)))
|
55
|
+
@connection = Faraday.new(
|
56
|
+
host,
|
57
|
+
ssl: {
|
58
|
+
client_cert: OpenSSL::X509::Certificate.new(Base64.decode64(user.auth_info.client_certificate_data)),
|
59
|
+
client_key: OpenSSL::PKey::RSA.new(Base64.decode64(user.auth_info.client_key_data)),
|
60
|
+
cert_store: cert_store,
|
61
|
+
}
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def host
|
66
|
+
cluster.server
|
67
|
+
end
|
68
|
+
|
69
|
+
def cluster
|
70
|
+
config.cluster_for_context(context)
|
71
|
+
end
|
72
|
+
|
73
|
+
def user
|
74
|
+
config.user_for_context(context)
|
75
|
+
end
|
76
|
+
|
77
|
+
def formatted_uri(host, path, sub_path = "")
|
78
|
+
if !sub_path.empty? && !path.end_with?("/")
|
79
|
+
URI.join(host, "#{path}/", sub_path)
|
80
|
+
else
|
81
|
+
URI.join(host, path, sub_path)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def format_response(response, as: :ros)
|
86
|
+
ResponseFormatter.new(response).format(as: as)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/k8y/client.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "client/client"
|
3
|
+
|
4
|
+
module K8y
|
5
|
+
module Client
|
6
|
+
Error = Class.new(Error)
|
7
|
+
|
8
|
+
DEFAULT_GROUP_VERSIONS = [
|
9
|
+
GroupVersion.new(group: "core", version: "v1"),
|
10
|
+
GroupVersion.new(group: "apps", version: "v1"),
|
11
|
+
]
|
12
|
+
|
13
|
+
def self.from_config(config, context: nil, group_versions: DEFAULT_GROUP_VERSIONS)
|
14
|
+
Client.new(config: config, context: context || config.current_context, group_versions: group_versions)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/k8y/error.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module K8y
|
3
|
+
class GroupVersion
|
4
|
+
attr_reader :group, :version
|
5
|
+
|
6
|
+
def initialize(group:, version:)
|
7
|
+
@group = group
|
8
|
+
@version = version
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#{group}/#{version}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_method_name
|
16
|
+
"#{group}_#{version}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module K8y
|
3
|
+
module Kubeconfig
|
4
|
+
class AuthInfo
|
5
|
+
attr_reader :client_certificate, :client_certificate_data, :client_key, :client_key_data, :token, :token_file,
|
6
|
+
:as, :as_groups, :as_user_extra, :username, :password, :auth_provider, :exec_options, :extensions,
|
7
|
+
|
8
|
+
def self.from_hash(hash)
|
9
|
+
new(
|
10
|
+
client_certificate: hash.fetch("client-certificate", nil),
|
11
|
+
client_certificate_data: hash.fetch("client-certificate-data", nil),
|
12
|
+
client_key: hash.fetch("client-key", nil),
|
13
|
+
client_key_data: hash.fetch("client-key-data", nil),
|
14
|
+
token: hash.fetch("token", nil),
|
15
|
+
token_file: hash.fetch("tokenFile", nil),
|
16
|
+
as: hash.fetch("as", nil),
|
17
|
+
as_groups: hash.fetch("as-groups", nil),
|
18
|
+
as_user_extra: hash.fetch("as-user-extra", nil),
|
19
|
+
username: hash.fetch("username", nil),
|
20
|
+
password: hash.fetch("password", nil),
|
21
|
+
# TODO: this needs something like polymorphic classes, not just raw hash, eventually
|
22
|
+
auth_provider: hash.fetch("auth-provider", nil),
|
23
|
+
# TODO: This will likely benefit from actual domain models eventually, as well.
|
24
|
+
exec_options: hash.fetch("exec", nil),
|
25
|
+
extensions: hash.fetch("extensions", nil)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(client_certificate: nil, client_certificate_data: nil, client_key: nil, client_key_data: nil,
|
30
|
+
token: nil, token_file: nil, as: nil, as_groups: nil, as_user_extra: nil, username: nil, password: nil,
|
31
|
+
auth_provider: nil, exec_options: nil, extensions: nil)
|
32
|
+
@client_certificate = client_certificate
|
33
|
+
@client_certificate_data = client_certificate_data
|
34
|
+
@client_key = client_key
|
35
|
+
@client_key_data = client_key_data
|
36
|
+
@token = token
|
37
|
+
@token_file = token_file
|
38
|
+
@as = as
|
39
|
+
@as_groups = as_groups
|
40
|
+
@as_user_extra = as_user_extra
|
41
|
+
@username = username
|
42
|
+
@password = password
|
43
|
+
@auth_provider = auth_provider
|
44
|
+
@exec_options = exec_options
|
45
|
+
@extensions = extensions
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8y
|
4
|
+
module Kubeconfig
|
5
|
+
class Cluster
|
6
|
+
attr_reader :name, :insecure_skip_tls_verify, :certificate_authority, :certificate_authority_data, :server
|
7
|
+
|
8
|
+
def self.from_hash(hash)
|
9
|
+
cluster = hash.fetch("cluster")
|
10
|
+
new(
|
11
|
+
insecure_skip_tls_verify: cluster.fetch("insecure-skip-tls-verify", false),
|
12
|
+
certificate_authority: cluster.fetch("certificate-authority", nil),
|
13
|
+
certificate_authority_data: cluster.fetch("certificate-authority-data", nil),
|
14
|
+
server: cluster.fetch("server"),
|
15
|
+
name: hash.fetch("name")
|
16
|
+
)
|
17
|
+
rescue Psych::Exception, KeyError => e
|
18
|
+
raise Error, e
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(name:, insecure_skip_tls_verify:, certificate_authority:, certificate_authority_data:, server:)
|
22
|
+
@name = name
|
23
|
+
@insecure_skip_tls_verify = insecure_skip_tls_verify
|
24
|
+
@certificate_authority = certificate_authority
|
25
|
+
@certificate_authority_data = certificate_authority_data
|
26
|
+
@server = URI.parse(server)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require_relative "cluster"
|
5
|
+
require_relative "context"
|
6
|
+
require_relative "user"
|
7
|
+
|
8
|
+
module K8y
|
9
|
+
module Kubeconfig
|
10
|
+
class Config
|
11
|
+
ContextNotFoundError = Class.new(Error)
|
12
|
+
ClusterNotFoundError = Class.new(Error)
|
13
|
+
UserNotFoundError = Class.new(Error)
|
14
|
+
|
15
|
+
attr_reader :api_version, :kind, :preferences, :clusters, :contexts, :users, :current_context
|
16
|
+
|
17
|
+
def self.from_hash(hash)
|
18
|
+
new(
|
19
|
+
api_version: hash.fetch("apiVersion"),
|
20
|
+
kind: hash.fetch("kind"),
|
21
|
+
preferences: hash.fetch("preferences"),
|
22
|
+
clusters: hash.fetch("clusters").map { |cluster| Cluster.from_hash(cluster) },
|
23
|
+
contexts: hash.fetch("contexts").map { |context| Context.from_hash(context) },
|
24
|
+
current_context: hash.fetch("current-context"),
|
25
|
+
users: hash.fetch("users").map { |user| User.from_hash(user) },
|
26
|
+
)
|
27
|
+
rescue Psych::Exception, KeyError => e
|
28
|
+
raise Error, e
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(api_version:, kind:, preferences:, clusters:, contexts:, users:, current_context:)
|
32
|
+
@api_version = api_version
|
33
|
+
@kind = kind
|
34
|
+
@preferences = preferences
|
35
|
+
@clusters = clusters
|
36
|
+
@contexts = contexts
|
37
|
+
@users = users
|
38
|
+
@current_context = current_context
|
39
|
+
end
|
40
|
+
|
41
|
+
def context(name)
|
42
|
+
context = contexts.find { |c| c.name == name }
|
43
|
+
raise ContextNotFoundError, "Could not find context #{name} in config" unless context
|
44
|
+
context
|
45
|
+
end
|
46
|
+
|
47
|
+
def cluster(name)
|
48
|
+
cluster = clusters.find { |c| c.name == name }
|
49
|
+
raise ClusterNotFoundError, "Could not find cluster #{name} in config" unless cluster
|
50
|
+
cluster
|
51
|
+
end
|
52
|
+
|
53
|
+
def user(name)
|
54
|
+
user = users.find { |user| user.name == name }
|
55
|
+
raise UserNotFoundError, "User #{name} not found in config" unless user
|
56
|
+
user
|
57
|
+
end
|
58
|
+
|
59
|
+
def cluster_for_context(context_name)
|
60
|
+
cluster_name = context(context_name).cluster
|
61
|
+
cluster(cluster_name)
|
62
|
+
end
|
63
|
+
|
64
|
+
def user_for_context(context_name)
|
65
|
+
user(context(context_name).user)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8y
|
4
|
+
module Kubeconfig
|
5
|
+
class Context
|
6
|
+
attr_reader :name, :cluster, :namespace, :user
|
7
|
+
|
8
|
+
def self.from_hash(hash)
|
9
|
+
context = hash.fetch("context")
|
10
|
+
new(
|
11
|
+
name: hash.fetch("name"),
|
12
|
+
cluster: context.fetch("cluster"),
|
13
|
+
namespace: context.fetch("namespace", nil),
|
14
|
+
user: context.fetch("user")
|
15
|
+
)
|
16
|
+
rescue Psych::Exception, KeyError => e
|
17
|
+
raise Error, e
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(name:, cluster:, namespace:, user:)
|
21
|
+
@name = name
|
22
|
+
@cluster = cluster
|
23
|
+
@namespace = namespace
|
24
|
+
@user = user
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "auth_info"
|
3
|
+
|
4
|
+
module K8y
|
5
|
+
module Kubeconfig
|
6
|
+
class User
|
7
|
+
attr_reader :name, :auth_info
|
8
|
+
|
9
|
+
def self.from_hash(hash)
|
10
|
+
user = hash.fetch("user")
|
11
|
+
new(
|
12
|
+
name: hash.fetch("name"),
|
13
|
+
auth_info: AuthInfo.from_hash(user)
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(name:, auth_info:)
|
18
|
+
@name = name
|
19
|
+
@auth_info = auth_info
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "kubeconfig/config"
|
4
|
+
|
5
|
+
module K8y
|
6
|
+
module Kubeconfig
|
7
|
+
Error = Class.new(Error)
|
8
|
+
|
9
|
+
KUBECONFIG = ENV["KUBECONFIG"]
|
10
|
+
|
11
|
+
def self.from_file(file = File.open(KUBECONFIG))
|
12
|
+
hash = YAML.safe_load(file.read, permitted_classes: [Date, Time])
|
13
|
+
Config.from_hash(hash)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/k8y/version.rb
ADDED
data/lib/k8y.rb
ADDED
metadata
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: k8y
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Timothy Smith
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: recursive-open-struct
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: railties
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '6.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '6.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '6.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '6.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '11'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '11'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest-reporters
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mocha
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop-shopify
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '2'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '2'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: simplecov
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: webmock
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '3.0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '3.0'
|
181
|
+
description: Ruby client for interacting with kubernetes clusters
|
182
|
+
email:
|
183
|
+
- tsontario@gmail.com
|
184
|
+
executables: []
|
185
|
+
extensions: []
|
186
|
+
extra_rdoc_files: []
|
187
|
+
files:
|
188
|
+
- ".github/workflows/main.yml"
|
189
|
+
- ".gitignore"
|
190
|
+
- ".rubocop.yml"
|
191
|
+
- CHANGELOG.md
|
192
|
+
- Gemfile
|
193
|
+
- LICENSE.txt
|
194
|
+
- README.md
|
195
|
+
- Rakefile
|
196
|
+
- bin/console
|
197
|
+
- bin/setup
|
198
|
+
- k8y.gemspec
|
199
|
+
- lib/k8y.rb
|
200
|
+
- lib/k8y/client.rb
|
201
|
+
- lib/k8y/client/api.rb
|
202
|
+
- lib/k8y/client/api_builder.rb
|
203
|
+
- lib/k8y/client/apis.rb
|
204
|
+
- lib/k8y/client/client.rb
|
205
|
+
- lib/k8y/client/resource_description.rb
|
206
|
+
- lib/k8y/client/response_formatter.rb
|
207
|
+
- lib/k8y/client/rest_client.rb
|
208
|
+
- lib/k8y/error.rb
|
209
|
+
- lib/k8y/group_version.rb
|
210
|
+
- lib/k8y/kubeconfig.rb
|
211
|
+
- lib/k8y/kubeconfig/auth_info.rb
|
212
|
+
- lib/k8y/kubeconfig/cluster.rb
|
213
|
+
- lib/k8y/kubeconfig/config.rb
|
214
|
+
- lib/k8y/kubeconfig/context.rb
|
215
|
+
- lib/k8y/kubeconfig/user.rb
|
216
|
+
- lib/k8y/version.rb
|
217
|
+
homepage: https://github.com/tsontario/k8y
|
218
|
+
licenses: []
|
219
|
+
metadata:
|
220
|
+
homepage_uri: https://github.com/tsontario/k8y
|
221
|
+
source_code_uri: https://github.com/tsontario/k8y
|
222
|
+
changelog_uri: https://github.com/tsontario/k8y
|
223
|
+
post_install_message:
|
224
|
+
rdoc_options: []
|
225
|
+
require_paths:
|
226
|
+
- lib
|
227
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
228
|
+
requirements:
|
229
|
+
- - ">="
|
230
|
+
- !ruby/object:Gem::Version
|
231
|
+
version: 2.7.0
|
232
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
requirements: []
|
238
|
+
rubygems_version: 3.1.4
|
239
|
+
signing_key:
|
240
|
+
specification_version: 4
|
241
|
+
summary: Ruby client for interacting with kubernetes clusters
|
242
|
+
test_files: []
|