kerbi 0.0.1 → 0.0.5
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/lib/cli/base_handler.rb +194 -0
- data/lib/cli/base_serializer.rb +120 -0
- data/lib/cli/config_handler.rb +51 -0
- data/lib/cli/entry_serializers.rb +99 -0
- data/lib/cli/project_handler.rb +2 -2
- data/lib/cli/release_handler.rb +41 -0
- data/lib/cli/release_serializer.rb +46 -0
- data/lib/cli/root_handler.rb +34 -13
- data/lib/cli/state_handler.rb +88 -0
- data/lib/cli/values_handler.rb +4 -3
- data/{boilerplate → lib/code-gen/new-project}/Gemfile.erb +0 -0
- data/lib/code-gen/new-project/kerbifile.rb.erb +9 -0
- data/lib/code-gen/new-project/values.yaml.erb +1 -0
- data/lib/config/cli_schema.rb +343 -28
- data/lib/config/config_file.rb +60 -0
- data/lib/config/globals.rb +4 -0
- data/lib/config/run_opts.rb +162 -0
- data/lib/config/state_consts.rb +11 -0
- data/lib/kerbi.rb +35 -10
- data/lib/main/code_gen.rb +1 -1
- data/lib/main/errors.rb +115 -0
- data/lib/main/mixer.rb +20 -10
- data/lib/mixins/cli_state_helpers.rb +108 -0
- data/lib/mixins/cm_backend_testing.rb +109 -0
- data/lib/mixins/entry_tag_logic.rb +183 -0
- data/lib/state/base_backend.rb +93 -0
- data/lib/state/config_map_backend.rb +173 -0
- data/lib/state/entry.rb +173 -0
- data/lib/state/entry_set.rb +137 -0
- data/lib/state/metadata.yaml.erb +11 -0
- data/lib/state/mixers.rb +23 -0
- data/lib/state/resources.yaml.erb +17 -0
- data/lib/utils/cli.rb +108 -9
- data/lib/utils/helm.rb +10 -12
- data/lib/utils/k8s_auth.rb +87 -0
- data/lib/utils/misc.rb +36 -1
- data/lib/utils/mixing.rb +1 -1
- data/lib/utils/values.rb +13 -22
- data/spec/cli/config_handler_spec.rb +38 -0
- data/spec/cli/release_handler_spec.rb +127 -0
- data/spec/cli/root_handler_spec.rb +100 -0
- data/spec/cli/state_handler_spec.rb +108 -0
- data/spec/cli/values_handler_spec.rb +17 -0
- data/spec/fixtures/expectations/common/bad-tag.txt +1 -0
- data/spec/fixtures/expectations/config/bad-set.txt +1 -0
- data/spec/fixtures/expectations/config/set.txt +1 -0
- data/spec/fixtures/expectations/config/show-default.yaml +6 -0
- data/spec/fixtures/expectations/release/delete.txt +1 -0
- data/spec/fixtures/expectations/release/init-already-existed.txt +2 -0
- data/spec/fixtures/expectations/release/init-both-created.txt +2 -0
- data/spec/fixtures/expectations/release/list.txt +5 -0
- data/spec/fixtures/expectations/release/status-all-working.txt +5 -0
- data/spec/fixtures/expectations/release/status-data-unreadable.txt +5 -0
- data/spec/fixtures/expectations/release/status-not-provisioned.txt +5 -0
- data/spec/fixtures/expectations/root/template-inlines.yaml +31 -0
- data/spec/fixtures/expectations/root/template-production.yaml +31 -0
- data/spec/fixtures/expectations/root/template-read-inlines.yaml +31 -0
- data/spec/fixtures/expectations/root/template-read.yaml +31 -0
- data/spec/fixtures/expectations/root/template-write.yaml +31 -0
- data/spec/fixtures/expectations/root/template.yaml +31 -0
- data/spec/fixtures/expectations/root/values.json +28 -0
- data/spec/fixtures/expectations/state/delete.txt +1 -0
- data/spec/fixtures/expectations/state/demote.txt +1 -0
- data/spec/fixtures/expectations/state/list.json +51 -0
- data/spec/fixtures/expectations/state/list.txt +6 -0
- data/spec/fixtures/expectations/state/list.yaml +35 -0
- data/spec/fixtures/expectations/state/promote.txt +1 -0
- data/spec/fixtures/expectations/state/prune-candidates.txt +1 -0
- data/spec/fixtures/expectations/state/retag.txt +1 -0
- data/spec/fixtures/expectations/state/set.txt +1 -0
- data/spec/fixtures/expectations/state/show.json +13 -0
- data/spec/fixtures/expectations/state/show.txt +13 -0
- data/spec/fixtures/expectations/state/show.yaml +8 -0
- data/spec/fixtures/expectations/values/order-of-precedence.yaml +4 -0
- data/spec/main/configmap_backend_spec.rb +110 -0
- data/spec/main/project_code_gen_spec.rb +8 -2
- data/spec/main/state_entry_set_spec.rb +112 -0
- data/spec/main/state_entry_spec.rb +109 -0
- data/spec/mini-projects/hello-kerbi/common/metadata.yaml.erb +5 -0
- data/spec/mini-projects/hello-kerbi/consts.rb +5 -0
- data/spec/mini-projects/hello-kerbi/helpers.rb +8 -0
- data/spec/mini-projects/hello-kerbi/kerbifile.rb +18 -0
- data/spec/mini-projects/hello-kerbi/pod-and-service.yaml.erb +23 -0
- data/spec/mini-projects/hello-kerbi/values/production.yaml +2 -0
- data/spec/mini-projects/hello-kerbi/values/v2.yaml +2 -0
- data/spec/mini-projects/hello-kerbi/values/values.yaml +4 -0
- data/spec/spec_helper.rb +143 -1
- data/spec/utils/helm_spec.rb +89 -109
- data/spec/utils/k8s_auth_spec.rb +32 -0
- data/spec/utils/misc_utils_spec.rb +9 -0
- data/spec/utils/values_utils_spec.rb +12 -19
- metadata +143 -16
- data/boilerplate/kerbifile.rb.erb +0 -9
- data/boilerplate/values.yaml.erb +0 -1
- data/lib/cli/base.rb +0 -83
- data/lib/config/cli_opts.rb +0 -50
- data/lib/config/manager.rb +0 -36
- data/lib/main/state_manager.rb +0 -47
- data/lib/utils/kubectl.rb +0 -58
- data/spec/main/examples_spec.rb +0 -12
- data/spec/main/state_manager_spec.rb +0 -84
- data/spec/utils/state_utils.rb +0 -15
@@ -0,0 +1,137 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module State
|
3
|
+
|
4
|
+
##
|
5
|
+
# Baby version of ActiveRecord::Relation. Holds an array of
|
6
|
+
# entries (i.e Kerbi::State::Entry) and exposes useful group-level
|
7
|
+
# operations, like sorting, find maximums, etc...
|
8
|
+
class EntrySet
|
9
|
+
|
10
|
+
include Kerbi::Mixins::EntryTagLogic
|
11
|
+
|
12
|
+
attr_reader :entries
|
13
|
+
|
14
|
+
# @param [Array<Hash>] dicts
|
15
|
+
def initialize(dicts)
|
16
|
+
@entries = dicts.map { |h| Entry.from_dict(self, h) }
|
17
|
+
sort_by_created_at
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate!
|
21
|
+
entries.each(&:validate)
|
22
|
+
if (bad_entries = entries.reject(&:valid?)).any?
|
23
|
+
errors = Hash[bad_entries.map do |entry|
|
24
|
+
[entry.tag, entry.validation_errors.deep_dup]
|
25
|
+
end]
|
26
|
+
raise Kerbi::EntryValidationError.new(errors)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Filters entries by candidate status, returning only the
|
32
|
+
# ones that are NOT candidates.
|
33
|
+
# @return [Array<Kerbi::State::Entry>]
|
34
|
+
def committed
|
35
|
+
entries.select(&:committed?)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Filters entries by candidate status, returning only the
|
40
|
+
# ones that ARE candidates.
|
41
|
+
# @return [Array<Kerbi::State::Entry>]
|
42
|
+
def candidates
|
43
|
+
entries.reject(&:committed?)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Finds the most recently created/updated entry in the list
|
48
|
+
# that is not a candidate.
|
49
|
+
# @return [?Kerbi::State::Entry]
|
50
|
+
def latest
|
51
|
+
committed.first
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Finds the least recently created/updated entry in the list
|
56
|
+
# that is not a candidate.
|
57
|
+
# @return [?Kerbi::State::Entry]
|
58
|
+
def oldest
|
59
|
+
committed.last
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Finds the most recently created/updated entry in the list
|
64
|
+
# that is a candidate.
|
65
|
+
# @return [?Kerbi::State::Entry]
|
66
|
+
def latest_candidate
|
67
|
+
candidates.first
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Given a target entry tag expression, searches underlying array
|
72
|
+
# for the corresponding entry.
|
73
|
+
#
|
74
|
+
# Assumes the tag expression contains special interpolatable words,
|
75
|
+
# and thus resolves the tag expression into a literal tag first.
|
76
|
+
#
|
77
|
+
# Invokes tag resolution logic specific to reading entries, which
|
78
|
+
# is different than for writing entries (see #resolve_read_tag_expr).
|
79
|
+
# @param [String] tag_expr
|
80
|
+
# @return [Kerbi::State::Entry]
|
81
|
+
def find_entry_for_read(tag_expr)
|
82
|
+
resolved_tag = resolve_read_tag_expr(tag_expr)
|
83
|
+
entry = find_by_literal_tag(resolved_tag)
|
84
|
+
raise Kerbi::StateNotFoundError.new(tag_expr) unless entry
|
85
|
+
entry
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Given a target entry tag expression, searches underlying array
|
90
|
+
# for the corresponding entry.
|
91
|
+
#
|
92
|
+
# Assumes the tag expression contains special interpolatable words,
|
93
|
+
# and thus resolves the tag expression into a literal tag first.
|
94
|
+
#
|
95
|
+
# Invokes tag resolution logic specific to writing entries, which
|
96
|
+
# is different than for reading entries (see #resolve_write_tag_expr).
|
97
|
+
#
|
98
|
+
# If an entry is not found, initializes a new empty entry with the
|
99
|
+
# given resolved tag, and adds it to the set's underlying array.
|
100
|
+
# @param [String] tag_expr
|
101
|
+
# @return [?Kerbi::State::Entry]
|
102
|
+
def find_or_init_entry_for_write(tag_expr)
|
103
|
+
resolved_tag = resolve_write_tag_expr(tag_expr)
|
104
|
+
if(existing_entry = find_by_literal_tag(resolved_tag))
|
105
|
+
existing_entry
|
106
|
+
else
|
107
|
+
entry = Kerbi::State::Entry.new(self, tag: resolved_tag)
|
108
|
+
entries.unshift(entry)
|
109
|
+
entry
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Performs simple linear search for an entry whose tags matches
|
115
|
+
# exactly tag_expr.
|
116
|
+
# @param [String] tag_expr
|
117
|
+
# @return [?Kerbi::State::Entry]
|
118
|
+
def find_by_literal_tag(tag_expr)
|
119
|
+
return nil unless tag_expr.present?
|
120
|
+
entries.find { |e| e.tag == tag_expr }
|
121
|
+
end
|
122
|
+
alias_method :get, :find_by_literal_tag
|
123
|
+
|
124
|
+
|
125
|
+
def prune_candidates
|
126
|
+
entries.select!(&:committed?)
|
127
|
+
end
|
128
|
+
|
129
|
+
def sort_by_created_at
|
130
|
+
entries.sort! do |a, b|
|
131
|
+
both_defined = a.created_at && b.created_at
|
132
|
+
both_defined ? b.created_at <=> a.created_at : 0
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% consts = Kerbi::State::Consts %>
|
2
|
+
|
3
|
+
metadata:
|
4
|
+
labels: <%= embed({
|
5
|
+
consts::CREATOR_ATTR => consts::CREATOR_VAL
|
6
|
+
}) %>
|
7
|
+
|
8
|
+
annotations: <%= embed({
|
9
|
+
consts::CREATOR_ATTR => consts::CREATOR_VAL,
|
10
|
+
description: "used by kerbi to keep records of previously applied values"
|
11
|
+
}) %>
|
data/lib/state/mixers.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module State
|
3
|
+
class ConfigMapMixer < Kerbi::Mixer
|
4
|
+
locate_self __dir__
|
5
|
+
|
6
|
+
def mix
|
7
|
+
patched_with file("metadata") do
|
8
|
+
push file("resources", only: [{kind: "ConfigMap"}])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class NamespaceMixer < Kerbi::Mixer
|
14
|
+
locate_self __dir__
|
15
|
+
|
16
|
+
def mix
|
17
|
+
patched_with file("metadata") do
|
18
|
+
push file("resources", only: [{kind: "Namespace"}])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% consts = Kerbi::State::Consts %>
|
2
|
+
|
3
|
+
<% entries_json = JSON.dump(values[consts::ENTRIES_ATTR]) %>
|
4
|
+
|
5
|
+
apiVersion: v1
|
6
|
+
kind: ConfigMap
|
7
|
+
metadata:
|
8
|
+
name: <%= values[:cm_name] %>
|
9
|
+
namespace: <%= values[:namespace] %>
|
10
|
+
data: <%= embed({consts::ENTRIES_ATTR => entries_json}) %>
|
11
|
+
|
12
|
+
---
|
13
|
+
|
14
|
+
apiVersion: v1
|
15
|
+
kind: Namespace
|
16
|
+
metadata:
|
17
|
+
name: <%= values[:namespace] %>
|
data/lib/utils/cli.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module Kerbi
|
2
2
|
module Utils
|
3
3
|
module Cli
|
4
|
-
|
5
4
|
##
|
6
5
|
# Convenience method for running and compiling the output
|
7
6
|
# of several mixers. Returns all result dicts in a flat array
|
@@ -17,10 +16,24 @@ module Kerbi
|
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
19
|
+
def self.coerce_hash_or_array(actual, **options)
|
20
|
+
if (type = options[:coerce_type]).present?
|
21
|
+
if type == 'Array'
|
22
|
+
actual.is_a?(Array) ? actual : [actual]
|
23
|
+
elsif type == 'Hash'
|
24
|
+
actual.is_a?(Array) ? actual[0].to_h : actual.to_h
|
25
|
+
else
|
26
|
+
raise "Unrecognized type coercion #{type}"
|
27
|
+
end
|
28
|
+
else
|
29
|
+
actual
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
20
33
|
##
|
21
34
|
# Turns list of key-symbol dicts into their
|
22
35
|
# pretty YAML representation.
|
23
|
-
# @param [Array<Hash
|
36
|
+
# @param [Array<Hash>|Hash] dicts dicts to YAMLify
|
24
37
|
# @return [String] pretty YAML representation of input
|
25
38
|
def self.dicts_to_yaml(dicts)
|
26
39
|
if dicts.is_a?(Array)
|
@@ -29,15 +42,52 @@ module Kerbi
|
|
29
42
|
raw.gsub("---\n", i.zero? ? '' : "---\n\n")
|
30
43
|
end.join("\n")
|
31
44
|
else
|
45
|
+
return "{}" if dicts.empty?
|
32
46
|
as_yaml = YAML.dump(dicts.deep_stringify_keys)
|
33
47
|
as_yaml.gsub("---\n", "")
|
34
48
|
end
|
35
49
|
end
|
36
50
|
|
51
|
+
# @param [Array<Object>] entries
|
52
|
+
def self.list_to_table(entries, serializer_cls)
|
53
|
+
if entries.is_a?(Array)
|
54
|
+
table = Terminal::Table.new(
|
55
|
+
headings: serializer_cls.header_titles,
|
56
|
+
rows: entries.map(&:values)
|
57
|
+
)
|
58
|
+
table.style = LIST_TABLE_STYLE
|
59
|
+
table.to_s
|
60
|
+
else
|
61
|
+
table = Terminal::Table.new do |t|
|
62
|
+
#noinspection RubyResolve
|
63
|
+
entries.each do |key, value|
|
64
|
+
new_key = key.upcase.to_s.bold
|
65
|
+
new_value = fmt_table_value(value)
|
66
|
+
t.add_row [new_key, new_value]
|
67
|
+
end
|
68
|
+
t.style = DESCRIBE_TABLE_STYLE
|
69
|
+
|
70
|
+
end
|
71
|
+
table.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.fmt_table_value(value)
|
76
|
+
if value.is_a?(Hash)
|
77
|
+
flattened = Kerbi::Utils::Misc.flatten_hash(value)
|
78
|
+
stringified = flattened.deep_stringify_keys
|
79
|
+
dicts_to_yaml(stringified)
|
80
|
+
elsif value.is_a?(Array)
|
81
|
+
value.join(",")
|
82
|
+
else
|
83
|
+
value.to_s
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
37
87
|
##
|
38
88
|
# Turns list of key-symbol dicts into their
|
39
89
|
# pretty JSON representation.
|
40
|
-
# @param [Array<Hash
|
90
|
+
# @param [Array<Hash>|Hash] dicts dicts to YAMLify
|
41
91
|
# @return [String] pretty JSON representation of input
|
42
92
|
def self.dicts_to_json(dicts)
|
43
93
|
JSON.pretty_generate(dicts)
|
@@ -46,14 +96,63 @@ module Kerbi
|
|
46
96
|
##
|
47
97
|
# Searches the expected paths for the kerbifile and ruby-loads it.
|
48
98
|
# @param [String] root directory to search
|
49
|
-
def self.load_kerbifile(
|
50
|
-
|
51
|
-
abs_path = "#{
|
52
|
-
|
53
|
-
|
54
|
-
|
99
|
+
def self.load_kerbifile(root_dir)
|
100
|
+
root_dir ||= Dir.pwd
|
101
|
+
abs_path = "#{root_dir}/kerbifile.rb"
|
102
|
+
exists = File.exists?(abs_path)
|
103
|
+
raise Kerbi::KerbifileNotFoundError.new(root: root_dir) unless exists
|
104
|
+
#noinspection RubyResolve
|
105
|
+
load(abs_path)
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Given the various Kubernetes authentication options and
|
110
|
+
# configs, generates a Hash with the necessary data/schema
|
111
|
+
# to pass onto the internal k8s authentication logic.
|
112
|
+
#
|
113
|
+
# This method only delegates. Actual work done is done here at:
|
114
|
+
# Kerbi::Utils::K8sAuth.
|
115
|
+
# @return [Hash] auth bundle for the k8s authentication logic.
|
116
|
+
# @param [Kerbi::RunOpts] run_opts
|
117
|
+
def self.make_k8s_auth_bundle(run_opts)
|
118
|
+
case run_opts.k8s_auth_type
|
119
|
+
when "kube-config"
|
120
|
+
Kerbi::Utils::K8sAuth.kube_config_bundle(
|
121
|
+
path: run_opts.kube_config_path,
|
122
|
+
name: run_opts.kube_context_name
|
123
|
+
)
|
124
|
+
when "basic"
|
125
|
+
Kerbi::Utils::K8sAuth.basic_auth_bundle(
|
126
|
+
username: run_opts.k8s_auth_username,
|
127
|
+
password: run_opts.k8s_auth_password
|
128
|
+
)
|
129
|
+
when "token"
|
130
|
+
Kerbi::Utils::K8sAuth.token_auth_bundle(
|
131
|
+
bearer_token: run_opts.k8s_auth_token,
|
132
|
+
)
|
133
|
+
when "in-cluster"
|
134
|
+
Kerbi::Utils::K8sAuth.in_cluster_auth_bundle
|
135
|
+
else
|
136
|
+
raise "Bad k8s connect type '#{run_opts.k8s_auth_type}'"
|
55
137
|
end
|
56
138
|
end
|
139
|
+
|
140
|
+
LIST_TABLE_STYLE = {
|
141
|
+
border_left: false,
|
142
|
+
border_right: false,
|
143
|
+
border_top: false,
|
144
|
+
border_x: "",
|
145
|
+
border_y: "",
|
146
|
+
border_i: ""
|
147
|
+
}.freeze
|
148
|
+
|
149
|
+
DESCRIBE_TABLE_STYLE = {
|
150
|
+
all_separators: true,
|
151
|
+
border_x: "-",
|
152
|
+
border_y: "",
|
153
|
+
border_i: ""
|
154
|
+
}.freeze
|
155
|
+
|
57
156
|
end
|
58
157
|
end
|
59
158
|
end
|
data/lib/utils/helm.rb
CHANGED
@@ -2,15 +2,14 @@ module Kerbi
|
|
2
2
|
module Utils
|
3
3
|
module Helm
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
HELM_EXEC = "helm"
|
6
|
+
TMP_VALUES_PATH = "/tmp/kerbi-helm-tmp.yaml"
|
7
|
+
|
9
8
|
##
|
10
9
|
# Tests whether Kerbi can invoke Helm commands
|
11
10
|
# @return [Boolean] true if helm commands succeed locally
|
12
11
|
def self.can_exec?
|
13
|
-
!!system(
|
12
|
+
!!system(HELM_EXEC, out: File::NULL, err: File::NULL)
|
14
13
|
end
|
15
14
|
|
16
15
|
##
|
@@ -18,18 +17,17 @@ module Kerbi
|
|
18
17
|
# @param [Hash] values a hash of values
|
19
18
|
# @return [String] the path of the file
|
20
19
|
def self.make_tmp_values_file(values)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
config.tmp_helm_values_path
|
20
|
+
content = YAML.dump((values || {}).deep_stringify_keys)
|
21
|
+
File.write(TMP_VALUES_PATH, content)
|
22
|
+
TMP_VALUES_PATH
|
25
23
|
end
|
26
24
|
|
27
25
|
##
|
28
26
|
# Deletes the temp file
|
29
27
|
# @return [void]
|
30
28
|
def self.del_tmp_values_file
|
31
|
-
if File.exists?(
|
32
|
-
File.delete(
|
29
|
+
if File.exists?(TMP_VALUES_PATH)
|
30
|
+
File.delete(TMP_VALUES_PATH)
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
@@ -53,7 +51,7 @@ module Kerbi
|
|
53
51
|
raise "Helm executable not working" unless can_exec?
|
54
52
|
tmp_file = make_tmp_values_file(opts[:values])
|
55
53
|
inline_flags = encode_inline_assigns(opts[:inline_assigns])
|
56
|
-
command = "#{
|
54
|
+
command = "#{HELM_EXEC} template #{release} #{project}"
|
57
55
|
command += " -f #{tmp_file} #{inline_flags} #{opts[:cli_args]}"
|
58
56
|
output = `#{command}`
|
59
57
|
del_tmp_values_file
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
##
|
5
|
+
# ALl *_bundle methods return a custom-schema hash
|
6
|
+
# that is to be used to create a Kubeclient::Client instance.
|
7
|
+
# See its constructor docs to understand.
|
8
|
+
# Underlying lib credit: https://github.com/ManageIQ/kubeclient
|
9
|
+
module K8sAuth
|
10
|
+
|
11
|
+
##
|
12
|
+
# Auth using config/credentials from a local kube context
|
13
|
+
# entry.
|
14
|
+
# See https://github.com/ManageIQ/kubeclient#kubeclientconfig
|
15
|
+
def self.kube_config_bundle(path: nil, name: nil)
|
16
|
+
path = path || default_kube_config_path
|
17
|
+
config = Kubeclient::Config.read(path)
|
18
|
+
context = config.context(name)
|
19
|
+
|
20
|
+
{
|
21
|
+
endpoint: context.api_endpoint,
|
22
|
+
options: {
|
23
|
+
ssl_options: context.ssl_options,
|
24
|
+
auth_options: context.auth_options
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Basic username password auth.
|
31
|
+
# See https://github.com/ManageIQ/kubeclient#authentication
|
32
|
+
def self.basic_auth_bundle(username:, password:)
|
33
|
+
{
|
34
|
+
endpoint: "https://localhost:8443/api",
|
35
|
+
options: {
|
36
|
+
auth_options: {
|
37
|
+
username: username,
|
38
|
+
password: password
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Auth using explicit bearer token for each request.
|
46
|
+
# See https://github.com/ManageIQ/kubeclient#authentication
|
47
|
+
def self.token_auth_bundle(bearer_token:)
|
48
|
+
{
|
49
|
+
endpoint: "https://localhost:8443/api",
|
50
|
+
options: {
|
51
|
+
auth_options: {
|
52
|
+
bearer_token: bearer_token
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Auth if kerbi is inside a Kubernetes cluster. Uses default
|
60
|
+
# credentials in pod's filesystem. Likely requires extra
|
61
|
+
# RBAC resources for that service account to exist, e.g
|
62
|
+
# a few Roles/ClusterRoles and RoleBinding/ClusterRoleBindings
|
63
|
+
# in order for CRUD methods to actually work.
|
64
|
+
# See https://github.com/ManageIQ/kubeclient#middleware
|
65
|
+
def self.in_cluster_auth_bundle
|
66
|
+
token_path = '/var/run/secrets/kubernetes.io/serviceaccount/token'
|
67
|
+
ca_crt_path = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
68
|
+
auth_options = { bearer_token_file: token_path }
|
69
|
+
|
70
|
+
ssl_options = {}
|
71
|
+
ssl_options[:ca_file] = ca_crt_path if File.exist?(ca_crt_path)
|
72
|
+
|
73
|
+
{
|
74
|
+
endpoint: "https://kubernetes.default.svc",
|
75
|
+
options: {
|
76
|
+
auth_options: auth_options,
|
77
|
+
ssl_options: ssl_options
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.default_kube_config_path
|
83
|
+
ENV['KUBECONFIG'] || "#{Dir.home}/.kube/config"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/utils/misc.rb
CHANGED
@@ -16,7 +16,7 @@ module Kerbi
|
|
16
16
|
# @return [Array] subset of candidate filenames that are real filenames
|
17
17
|
def self.real_files_for(*candidates)
|
18
18
|
candidates.select do |fname|
|
19
|
-
File.exists?(fname)
|
19
|
+
File.exists?(fname) && !Dir.exists?(fname)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -36,6 +36,41 @@ module Kerbi
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Credit: https://gist.github.com/henrik/146844
|
42
|
+
# @param [Hash] hash_a
|
43
|
+
# @param [Hash] hash_b
|
44
|
+
def self.deep_hash_diff(hash_a, hash_b)
|
45
|
+
(hash_a.keys | hash_b.keys).inject({}) do |diff, k|
|
46
|
+
if hash_a[k] != hash_b[k]
|
47
|
+
if hash_a[k].is_a?(Hash) && hash_b[k].is_a?(Hash)
|
48
|
+
diff[k] = deep_hash_diff(hash_a[k], hash_b[k])
|
49
|
+
else
|
50
|
+
diff[k] = [hash_a[k], hash_b[k]]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
diff
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Credit: https://stackoverflow.com/a/195894/11913562
|
59
|
+
# @param [Time] other
|
60
|
+
# @return [String]
|
61
|
+
def self.pretty_time_elapsed(other)
|
62
|
+
a = (Time.now - other).to_i
|
63
|
+
case a
|
64
|
+
when 0 then 'just now'
|
65
|
+
when 1 then 'a second ago'
|
66
|
+
when 2..59 then a.to_s+' seconds ago'
|
67
|
+
when 60..119 then 'a minute ago' #120 = 2 minutes
|
68
|
+
when 120..3540 then (a/60).to_i.to_s+' minutes ago'
|
69
|
+
when 3541..7100 then 'an hour ago' # 3600 = 1 hour
|
70
|
+
when 7101..82800 then ((a+99)/3600).to_i.to_s+' hours ago'
|
71
|
+
else other.to_s
|
72
|
+
end
|
73
|
+
end
|
39
74
|
end
|
40
75
|
end
|
41
76
|
end
|
data/lib/utils/mixing.rb
CHANGED
data/lib/utils/values.rb
CHANGED
@@ -4,8 +4,6 @@ module Kerbi
|
|
4
4
|
# Utilities module for all value loading functionality.
|
5
5
|
module Values
|
6
6
|
|
7
|
-
DEFAULT_VALUE_PATH = "values"
|
8
|
-
|
9
7
|
def self.from_files(fname_exprs, **opts)
|
10
8
|
final_paths = resolve_fname_exprs(fname_exprs, **opts)
|
11
9
|
load_yaml_files(final_paths)
|
@@ -21,16 +19,24 @@ module Kerbi
|
|
21
19
|
# @param [Hash] opts downstream options for file-loading methods
|
22
20
|
# @return [Array<String>] list of unique absolute filenames
|
23
21
|
def self.resolve_fname_exprs(fname_exprs, **opts)
|
24
|
-
final_exprs =
|
22
|
+
final_exprs = fname_exprs.uniq
|
25
23
|
final_exprs.map do |fname_expr|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
24
|
+
candidate_paths = values_paths(fname_expr, **opts)
|
25
|
+
path = Kerbi::Utils::Misc.real_files_for(*candidate_paths)[0]
|
26
|
+
raise_if_file_not_found(fname_expr, path, **opts)
|
30
27
|
path.presence
|
31
28
|
end.compact.uniq
|
32
29
|
end
|
33
30
|
|
31
|
+
def self.raise_if_file_not_found(fname_expr, path, **opts)
|
32
|
+
if fname_expr != 'values' && !path
|
33
|
+
raise Kerbi::ValuesFileNotFoundError.new(
|
34
|
+
fname_expr: fname_expr,
|
35
|
+
root: opts[:root]
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
34
40
|
##
|
35
41
|
# Loads the dicts from files pointed to by final_file_paths into
|
36
42
|
# memory, and returns the deep-merged hash in the order of the files.
|
@@ -54,21 +60,6 @@ module Kerbi
|
|
54
60
|
end
|
55
61
|
end
|
56
62
|
|
57
|
-
##
|
58
|
-
# Turns a user supplied values filename into a final, usable
|
59
|
-
# absolute filesystem path. This method calls the values_paths method,
|
60
|
-
# which assumes a kerbi directory structure.
|
61
|
-
# @param [String] expr
|
62
|
-
# @param [Hash] opts
|
63
|
-
# @return [?String] the absolute filename or nil if it does not exist
|
64
|
-
def self.resolve_fname_expr(expr, **opts)
|
65
|
-
candidate_paths = self.values_paths(expr, **opts)
|
66
|
-
candidate_paths.find do |candidate_path|
|
67
|
-
File.exists?(candidate_path) && \
|
68
|
-
!File.directory?(candidate_path)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
63
|
##
|
73
64
|
# Parses a single cli-level key-value assignment with the form
|
74
65
|
# foo.bar=baz. Raises an exception if the expression is malformed.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative './../spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe "$ kerbi config [COMMAND]" do
|
4
|
+
|
5
|
+
# before(:each) { Kerbi::ConfigFile.reset }
|
6
|
+
#
|
7
|
+
# describe "$ kerbi config show" do
|
8
|
+
# it "outputs the expected text" do
|
9
|
+
# exp_cli_eq_file("config show", "config", "show-default", "yaml")
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# describe "$ kerbi config set [ATTR] [VALUE]" do
|
14
|
+
# let(:cmd) { "config set namespace kerbi-spec" }
|
15
|
+
# context "for a legal assignment" do
|
16
|
+
# it "outputs the expected text and changes the value" do
|
17
|
+
# exp_cli_eq_file(cmd, "config", "set", "txt")
|
18
|
+
# new_value = Kerbi::ConfigFile.read["namespace"]
|
19
|
+
# expect(new_value).to eq("kerbi-spec")
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# context "for an illegal assignment" do
|
23
|
+
# let(:cmd){ "config set bad-key fail" }
|
24
|
+
# it "outputs the expected error message" do
|
25
|
+
# exp_cli_eq_file(cmd, "config", "bad-set", "txt")
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# describe "$ kerbi config get [ATTR]" do
|
31
|
+
# let(:cmd) { "config get namespace" }
|
32
|
+
# it "outputs the expected text and changes the value" do
|
33
|
+
# cli("config set namespace kerbi-spec")
|
34
|
+
# expect(cli(cmd).gsub(/\s+/, "")).to eq("kerbi-spec")
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
|
38
|
+
end
|