kerbi 0.0.1
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/bin/kerbi +3 -0
- data/boilerplate/Gemfile.erb +6 -0
- data/boilerplate/kerbifile.rb.erb +9 -0
- data/boilerplate/values.yaml.erb +1 -0
- data/lib/cli/base.rb +83 -0
- data/lib/cli/project_handler.rb +17 -0
- data/lib/cli/root_handler.rb +47 -0
- data/lib/cli/values_handler.rb +11 -0
- data/lib/config/cli_opts.rb +50 -0
- data/lib/config/cli_schema.rb +104 -0
- data/lib/config/globals.rb +7 -0
- data/lib/config/manager.rb +36 -0
- data/lib/kerbi.rb +43 -0
- data/lib/main/code_gen.rb +131 -0
- data/lib/main/mixer.rb +235 -0
- data/lib/main/state_manager.rb +47 -0
- data/lib/mixins/mixer.rb +79 -0
- data/lib/utils/cli.rb +59 -0
- data/lib/utils/helm.rb +64 -0
- data/lib/utils/kubectl.rb +58 -0
- data/lib/utils/misc.rb +41 -0
- data/lib/utils/mixing.rb +181 -0
- data/lib/utils/values.rb +133 -0
- data/spec/fixtures/embedding.yaml.erb +3 -0
- data/spec/main/examples_spec.rb +12 -0
- data/spec/main/mixer_spec.rb +126 -0
- data/spec/main/project_code_gen_spec.rb +25 -0
- data/spec/main/state_manager_spec.rb +84 -0
- data/spec/mixins/mixer_mixin_spec.rb +55 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/utils/helm_spec.rb +114 -0
- data/spec/utils/misc_utils_spec.rb +22 -0
- data/spec/utils/mixing_utils_spec.rb +209 -0
- data/spec/utils/state_utils.rb +15 -0
- data/spec/utils/values_utils_spec.rb +146 -0
- metadata +170 -0
data/lib/main/mixer.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
module Kerbi
|
2
|
+
class Mixer
|
3
|
+
include Kerbi::Mixins::Mixer
|
4
|
+
|
5
|
+
##
|
6
|
+
# Values hash available to subclasses
|
7
|
+
# @return [Immutable::Hash] symbol-keyed hash
|
8
|
+
attr_reader :values
|
9
|
+
|
10
|
+
##
|
11
|
+
# Release name available for templating
|
12
|
+
# @return [String] symbol-keyed hash
|
13
|
+
attr_reader :release_name
|
14
|
+
|
15
|
+
##
|
16
|
+
# Array of res-hashes being aggregated
|
17
|
+
# @return [Array<Hash>] list of hashes
|
18
|
+
attr_reader :output
|
19
|
+
|
20
|
+
##
|
21
|
+
# Array of patches to be applied to results
|
22
|
+
# @return [Array<Hash>] list of hashes
|
23
|
+
attr_accessor :patch_stack
|
24
|
+
|
25
|
+
##
|
26
|
+
# Constructor
|
27
|
+
# @param [Hash] values the values tree that will be accessible to the subclass
|
28
|
+
def initialize(values, opts={})
|
29
|
+
@output = []
|
30
|
+
@release_name = opts[:release_name] || "default"
|
31
|
+
@patch_stack = []
|
32
|
+
@values = self.class.compute_own_values_subtree(
|
33
|
+
values,
|
34
|
+
opts[:overwrite_values_root]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Where users should return a hash or
|
40
|
+
# an array of hashes representing Kubernetes resources
|
41
|
+
# @yield [bucket] Exec context in which hashes are collected into one bucket
|
42
|
+
# @yieldparam [Kerbi::ResBucket] g Bucket object with essential methods
|
43
|
+
# @yieldreturn [Array<Hash>] array of hashes representing Kubernetes resources
|
44
|
+
# @return [Array<Hash>] array of hashes representing Kubernetes resources
|
45
|
+
def run
|
46
|
+
begin
|
47
|
+
self.mix
|
48
|
+
rescue Exception => e
|
49
|
+
puts "Exception below caused by mixer #{self.class.name}"
|
50
|
+
raise e
|
51
|
+
end
|
52
|
+
self.output
|
53
|
+
end
|
54
|
+
|
55
|
+
def mix
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Registers a dict or an array of dicts that will part of the
|
60
|
+
# mixers's final output, which is an Array<Hash>.
|
61
|
+
# @param [Hash | Array<Hash>] dict the hash to be added
|
62
|
+
def push(dicts)
|
63
|
+
final_list = Utils::Mixing.sanitize_res_dict_list(dicts)
|
64
|
+
self.output.append(*final_list)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Normalizes, sanitizes and filters a dict or an array of
|
69
|
+
# dicts.
|
70
|
+
# @param [Hash | Array<Hash>] dict the hash to be added
|
71
|
+
def dicts(dict, **opts)
|
72
|
+
output = Utils::Mixing.clean_and_filter_dicts(dict, **opts)
|
73
|
+
should_patch = opts[:no_patch].blank?
|
74
|
+
should_patch ? apply_patch_context(output) : output
|
75
|
+
end
|
76
|
+
alias_method :dict, :dicts
|
77
|
+
|
78
|
+
##
|
79
|
+
# Loads a YAML/JSON/ERB file, parses it, interpolates it,
|
80
|
+
# and returns processed and filtered list of dicts via #dicts.
|
81
|
+
# @param [String] fname with or without extension, relative to self
|
82
|
+
# @param [Hash] opts filtering and other options for #dicts
|
83
|
+
# @return [Array<Hash>] processed dicts read from file
|
84
|
+
def file(fname, **opts)
|
85
|
+
output = Utils::Mixing.yaml_file_to_dicts(
|
86
|
+
self.class.resolve_file_name(fname),
|
87
|
+
**opts.merge({src_binding: binding})
|
88
|
+
)
|
89
|
+
dicts(output)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param [String] fname
|
93
|
+
# @param [Hash] opts filtering and other options for #dicts
|
94
|
+
# @return [Array]
|
95
|
+
def dir(fname, **opts)
|
96
|
+
output = Utils::Mixing.yamls_in_dir_to_dicts(
|
97
|
+
self.class.pwd,
|
98
|
+
resolve_file_name(fname),
|
99
|
+
**opts
|
100
|
+
)
|
101
|
+
dicts(output)
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Run 'helm template' on Helm project, parse the output into dicts,
|
106
|
+
# return processed and filtered list via #dicts.
|
107
|
+
# @param [String] chart_id using format 'jetstack/cert-manager'
|
108
|
+
# @param [Hash] opts filtering and other options for #dicts
|
109
|
+
# @return [Array<Hash>] processed and filtered dicts
|
110
|
+
def chart(chart_id, **opts)
|
111
|
+
release = opts[:release] || release_name
|
112
|
+
helm_output = Utils::Helm.template(release, chart_id, **opts)
|
113
|
+
dicts(helm_output)
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Run another mixer given by klass, return processed and
|
118
|
+
# filtered list via #dicts.
|
119
|
+
# @param [Class<Kerbi::Mixer>] klass other mixer's class
|
120
|
+
# @param [Hash] opts filtering and other options for #dicts
|
121
|
+
# @return [Array<Hash>] processed and filtered dicts
|
122
|
+
def mixer(klass, **opts)
|
123
|
+
force_subtree = opts.delete(:values)
|
124
|
+
mixer_inst = klass.new(
|
125
|
+
force_subtree.nil? ? values : force_subtree,
|
126
|
+
release_name: release_name,
|
127
|
+
overwrite_values_subtree: !force_subtree.nil?
|
128
|
+
)
|
129
|
+
output = mixer_inst.run
|
130
|
+
dicts(output)
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Any x-to-dict statements (e.g #dicts, #dir, #chart) executed
|
135
|
+
# in the &block passed to this method will have their return values
|
136
|
+
# deep merged with the dict(s) passed.
|
137
|
+
# @param [Array<Hash>|Hash] dict
|
138
|
+
# @param [Proc] block
|
139
|
+
# @return [Array<Hash>, Hash]
|
140
|
+
def patched_with(dict, &block)
|
141
|
+
new_patches = extract_patches(dict)
|
142
|
+
patch_stack.push(new_patches)
|
143
|
+
yield(block)
|
144
|
+
patch_stack.pop
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def extract_patches(obj)
|
150
|
+
(obj.is_a?(Hash) ? [obj] : obj).map(&:deep_dup)
|
151
|
+
end
|
152
|
+
|
153
|
+
def apply_patch_context(output)
|
154
|
+
return output if patch_stack.blank?
|
155
|
+
output.map do |res|
|
156
|
+
patch_stack.flatten.inject(res) do |whole, patch|
|
157
|
+
whole.deep_merge(patch)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
##
|
163
|
+
# Coerces filename of unknown format to an absolute path
|
164
|
+
# @param [String] fname simplified or absolute path of file
|
165
|
+
# @return [String] a variation of the filename that exists
|
166
|
+
##
|
167
|
+
# Convenience instance method for accessing class level pwd
|
168
|
+
# @return [String] the subclass' pwd as defined by the user
|
169
|
+
|
170
|
+
class << self
|
171
|
+
|
172
|
+
##
|
173
|
+
# Pass a deep key that will be used to dig into the values
|
174
|
+
# dict the mixer gets upon initialization. For example if
|
175
|
+
# deep_key is "x", then if the mixer is initialized with
|
176
|
+
# values as x: {y: 'z'}, then its final values attribute
|
177
|
+
# will be {y: 'z'}.
|
178
|
+
def values_root(deep_key)
|
179
|
+
@vals_root_deep_key = deep_key
|
180
|
+
end
|
181
|
+
|
182
|
+
def compute_own_values_subtree(values_root, override)
|
183
|
+
self.compute_values_subtree(
|
184
|
+
values_root,
|
185
|
+
override ? nil : @vals_root_deep_key
|
186
|
+
)
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# Given a values_root dict and a deep key (e.g "x.y.z"), outputs
|
191
|
+
# a frozen, deep-cloned, subtree corresponding to the deep
|
192
|
+
# key's position in the values_root.
|
193
|
+
# @param [Hash] values_root dict from which to extract subtree
|
194
|
+
# @param [String] deep_key key in dict in "x.y.z" format
|
195
|
+
# @return [Hash] frozen and deep-cloned subtree
|
196
|
+
def compute_values_subtree(values_root, deep_key)
|
197
|
+
subtree = values_root.deep_dup
|
198
|
+
if deep_key.present?
|
199
|
+
deep_key_parts = deep_key.split(".")
|
200
|
+
subtree = subtree.dig(*deep_key_parts)
|
201
|
+
end
|
202
|
+
subtree.freeze
|
203
|
+
end
|
204
|
+
|
205
|
+
def resolve_file_name(fname)
|
206
|
+
dir = self.pwd
|
207
|
+
Kerbi::Utils::Misc.real_files_for(
|
208
|
+
fname,
|
209
|
+
"#{fname}.yaml",
|
210
|
+
"#{fname}.yaml.erb",
|
211
|
+
"#{dir}/#{fname}",
|
212
|
+
"#{dir}/#{fname}.yaml",
|
213
|
+
"#{dir}/#{fname}.yaml.erb"
|
214
|
+
).first
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Sets the absolute path of the directory where
|
219
|
+
# yamls used by this Gen can be found, usually "__dir__"
|
220
|
+
# @param [String] dirname absolute path of the directory
|
221
|
+
# @return [void]
|
222
|
+
def locate_self(dirname)
|
223
|
+
@dir_location = dirname
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# Returns the value set by locate_self
|
228
|
+
# @return [String] the subclass' pwd as defined by the user
|
229
|
+
def pwd
|
230
|
+
@dir_location
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Kerbi
|
2
|
+
class StateManager
|
3
|
+
def self.patch
|
4
|
+
self.create_configmap_if_missing
|
5
|
+
patch_values = self.compile_patch
|
6
|
+
config_map = utils::State.kubectl_get_cm("state", raise_on_err: false)
|
7
|
+
crt_values = utils::State.read_cm_data(config_map)
|
8
|
+
merged_vars = crt_values.deep_merge(patch_values)
|
9
|
+
new_body = { **config_map, data: { variables: JSON.dump(merged_vars) } }
|
10
|
+
yaml_body = YAML.dump(new_body.deep_stringify_keys)
|
11
|
+
Utils::Kubectl.apply_tmpfile(yaml_body, args_manager.get_kmd_arg_str)
|
12
|
+
end
|
13
|
+
|
14
|
+
def compile_patch
|
15
|
+
values = {}
|
16
|
+
|
17
|
+
args_manager.get_fnames.each do |fname|
|
18
|
+
new_values = YAML.load_file(fname).deep_symbolize_keys
|
19
|
+
values.deep_merge!(new_values)
|
20
|
+
end
|
21
|
+
|
22
|
+
args_manager.get_inlines.each do |assignment_str|
|
23
|
+
assignment = Utils::Utils.str_assign_to_h(assignment_str)
|
24
|
+
values.deep_merge!(assignment)
|
25
|
+
end
|
26
|
+
values
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_crt_vars
|
30
|
+
create_configmap_if_missing
|
31
|
+
get_configmap_values(get_configmap)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_configmap_if_missing
|
35
|
+
unless get_configmap(raise_on_er: false)
|
36
|
+
kmd = "create cm state #{args_manager.get_kmd_arg_str}"
|
37
|
+
Utils::Kubectl.kmd(kmd)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def utils
|
44
|
+
Kerbi::Utils
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/mixins/mixer.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module Mixins
|
3
|
+
module Mixer
|
4
|
+
|
5
|
+
# @param [Hash] dict hash or array
|
6
|
+
# @return [String] encoded string
|
7
|
+
def embed(dict, indent: 25)
|
8
|
+
_dict = dict.is_a?(Array) ? dict.first : dict
|
9
|
+
raw = YAML.dump(_dict).sub("---", "")
|
10
|
+
indented_lines = raw.split("\n").map do |line|
|
11
|
+
line.indent(indent)
|
12
|
+
end
|
13
|
+
"\n#{indented_lines.join("\n")}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Array|Hash] dicts hash or array
|
17
|
+
# @return [String] encoded string
|
18
|
+
def embed_array(dicts, indent: 25)
|
19
|
+
return "[]" unless dicts.present?
|
20
|
+
|
21
|
+
unless dicts.is_a?(Array)
|
22
|
+
raise "embed_array called with non-array #{dicts.class} #{dicts}"
|
23
|
+
end
|
24
|
+
|
25
|
+
raw = YAML.dump(dicts).sub("---", "")
|
26
|
+
indented_lines = raw.split("\n").map do |line|
|
27
|
+
line.indent(indent)
|
28
|
+
end
|
29
|
+
|
30
|
+
"\n#{indented_lines.join("\n")}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [String] string string to be base64 encoded
|
34
|
+
# @return [String] encoded string
|
35
|
+
def b64enc(string)
|
36
|
+
if string
|
37
|
+
Base64.strict_encode64(string)
|
38
|
+
else
|
39
|
+
''
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [String] string string to be base64 encoded
|
44
|
+
# @return [String] encoded string
|
45
|
+
def b64dec(string)
|
46
|
+
if string
|
47
|
+
Base64.decode64(string).strip
|
48
|
+
else
|
49
|
+
''
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param [String] fname absolute path of file to be encoded
|
54
|
+
# @return [String] encoded string
|
55
|
+
def b64enc_file(fname)
|
56
|
+
file_contents = File.read(fname) rescue nil
|
57
|
+
b64enc(file_contents)
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# @param [Hash] opts options
|
62
|
+
# @option opts [String] url full URL to raw yaml file contents on the web
|
63
|
+
# @option opts [String] from one of [github]
|
64
|
+
# @option opts [String] except list of filenames to avoid
|
65
|
+
# @raise [Exception] if project-id/file missing in github hash
|
66
|
+
def http_descriptor_to_url(**opts)
|
67
|
+
return opts[:url] if opts[:url]
|
68
|
+
|
69
|
+
if opts[:from] == 'github'
|
70
|
+
base = "https://raw.githubusercontent.com"
|
71
|
+
branch = opts[:branch] || 'master'
|
72
|
+
project, file = (opts[:project] || opts[:id]), opts[:file]
|
73
|
+
raise "Project and/or file not found" unless project && file
|
74
|
+
"#{base}/#{project}/#{branch}/#{file}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/utils/cli.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module Utils
|
3
|
+
module Cli
|
4
|
+
|
5
|
+
##
|
6
|
+
# Convenience method for running and compiling the output
|
7
|
+
# of several mixers. Returns all result dicts in a flat array
|
8
|
+
# preserving the order they were created in.
|
9
|
+
# @param [Array<Class<Kerbi::Mixer>] mixer_classes mixers to run
|
10
|
+
# @param [Hash] values root values hash to pass to all mixers
|
11
|
+
# @param [Object] release_name helm-like release_name for mixers
|
12
|
+
# @return [List<Hash>] all dicts emitted by mixers
|
13
|
+
def self.run_mixers(mixer_classes, values, release_name)
|
14
|
+
mixer_classes.inject([]) do |whole, gen_class|
|
15
|
+
mixer_instance = gen_class.new(values, release_name: release_name)
|
16
|
+
whole + mixer_instance.run.flatten
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Turns list of key-symbol dicts into their
|
22
|
+
# pretty YAML representation.
|
23
|
+
# @param [Array<Hash>] dicts dicts to YAMLify
|
24
|
+
# @return [String] pretty YAML representation of input
|
25
|
+
def self.dicts_to_yaml(dicts)
|
26
|
+
if dicts.is_a?(Array)
|
27
|
+
dicts.each_with_index.map do |h, i|
|
28
|
+
raw = YAML.dump(h.deep_stringify_keys)
|
29
|
+
raw.gsub("---\n", i.zero? ? '' : "---\n\n")
|
30
|
+
end.join("\n")
|
31
|
+
else
|
32
|
+
as_yaml = YAML.dump(dicts.deep_stringify_keys)
|
33
|
+
as_yaml.gsub("---\n", "")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Turns list of key-symbol dicts into their
|
39
|
+
# pretty JSON representation.
|
40
|
+
# @param [Array<Hash>] dicts dicts to YAMLify
|
41
|
+
# @return [String] pretty JSON representation of input
|
42
|
+
def self.dicts_to_json(dicts)
|
43
|
+
JSON.pretty_generate(dicts)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Searches the expected paths for the kerbifile and ruby-loads it.
|
48
|
+
# @param [String] root directory to search
|
49
|
+
def self.load_kerbifile(fname_expr)
|
50
|
+
fname_expr ||= Dir.pwd
|
51
|
+
abs_path = "#{fname_expr}/kerbifile.rb"
|
52
|
+
if File.exists?(abs_path)
|
53
|
+
#noinspection RubyResolve
|
54
|
+
load(abs_path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/utils/helm.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module Utils
|
3
|
+
module Helm
|
4
|
+
|
5
|
+
def self.config
|
6
|
+
Kerbi::Config::Manager
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# Tests whether Kerbi can invoke Helm commands
|
11
|
+
# @return [Boolean] true if helm commands succeed locally
|
12
|
+
def self.can_exec?
|
13
|
+
!!system(config.helm_exec, out: File::NULL, err: File::NULL)
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Writes a hash of values to a YAML to a temp file
|
18
|
+
# @param [Hash] values a hash of values
|
19
|
+
# @return [String] the path of the file
|
20
|
+
def self.make_tmp_values_file(values)
|
21
|
+
File.open(config.tmp_helm_values_path, 'w') do |f|
|
22
|
+
f.write(YAML.dump((values || {}).deep_stringify_keys))
|
23
|
+
end
|
24
|
+
config.tmp_helm_values_path
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Deletes the temp file
|
29
|
+
# @return [void]
|
30
|
+
def self.del_tmp_values_file
|
31
|
+
if File.exists?(config.tmp_helm_values_path)
|
32
|
+
File.delete(config.tmp_helm_values_path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Joins assignments in flat hash into list of --set flags
|
38
|
+
# @param [Hash] inline_assigns flat Hash of deep_key: val
|
39
|
+
# @return [String] corresponding space-separated --set flags
|
40
|
+
def self.encode_inline_assigns(inline_assigns)
|
41
|
+
(inline_assigns || []).map do |key, value|
|
42
|
+
raise "Assignments must be flat" if value.is_a?(Hash)
|
43
|
+
"--set #{key}=#{value}"
|
44
|
+
end.join(" ")
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Runs the helm template command
|
49
|
+
# @param [String] release release name to pass to Helm
|
50
|
+
# @param [String] project <org>/<chart> string identifying helm chart
|
51
|
+
# @return [Array<Hash>]
|
52
|
+
def self.template(release, project, opts={})
|
53
|
+
raise "Helm executable not working" unless can_exec?
|
54
|
+
tmp_file = make_tmp_values_file(opts[:values])
|
55
|
+
inline_flags = encode_inline_assigns(opts[:inline_assigns])
|
56
|
+
command = "#{config.helm_exec} template #{release} #{project}"
|
57
|
+
command += " -f #{tmp_file} #{inline_flags} #{opts[:cli_args]}"
|
58
|
+
output = `#{command}`
|
59
|
+
del_tmp_values_file
|
60
|
+
YAML.load_stream(output)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module Utils
|
3
|
+
module Kubectl
|
4
|
+
def self.kmd(cmd, options = {})
|
5
|
+
cmd = "kubectl #{cmd}"
|
6
|
+
self.eval_shell_cmd(cmd, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.jkmd(cmd, options={})
|
10
|
+
cmd = "kubectl #{cmd} -o json"
|
11
|
+
begin
|
12
|
+
output = self.eval_shell_cmd(cmd, options)
|
13
|
+
as_hash = JSON.parse(output).deep_symbolize_keys
|
14
|
+
if as_hash.has_key?(:items)
|
15
|
+
as_hash[:items]
|
16
|
+
else
|
17
|
+
as_hash
|
18
|
+
end
|
19
|
+
rescue
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.eval_shell_cmd(cmd, options={})
|
25
|
+
print_err = options[:print_err]
|
26
|
+
raise_on_err = options[:raise_on_err]
|
27
|
+
begin
|
28
|
+
output, status = Open3.capture2e(cmd)
|
29
|
+
if status.success?
|
30
|
+
output = output[0..output.length - 2] if output.end_with?("\n")
|
31
|
+
output
|
32
|
+
else
|
33
|
+
if print_err
|
34
|
+
puts "Command \"#{cmd}\" error status #{status} with message:"
|
35
|
+
puts output
|
36
|
+
puts "---"
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
rescue Exception => e
|
41
|
+
if print_err
|
42
|
+
puts "Command \"#{cmd}\" failed with message:"
|
43
|
+
puts e.message
|
44
|
+
puts "---"
|
45
|
+
end
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.apply_tmpfile(yaml_str, append)
|
51
|
+
tmp_fname = "/tmp/man-#{SecureRandom.hex(32)}.yaml"
|
52
|
+
File.write(tmp_fname, yaml_str)
|
53
|
+
kmd("apply -f #{tmp_fname} #{append}", print_err: true)
|
54
|
+
File.delete(tmp_fname)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/utils/misc.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module Utils
|
3
|
+
module Misc
|
4
|
+
def self.one_to_array(item)
|
5
|
+
return [] if item.nil?
|
6
|
+
if item.is_a?(Array)
|
7
|
+
item
|
8
|
+
else
|
9
|
+
[item]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
## Given a list of filenames, returns the subset that
|
14
|
+
# are real files.
|
15
|
+
# @param [Array] candidates filenames to try
|
16
|
+
# @return [Array] subset of candidate filenames that are real filenames
|
17
|
+
def self.real_files_for(*candidates)
|
18
|
+
candidates.select do |fname|
|
19
|
+
File.exists?(fname)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Turns a nested dict into a deep-keyed dict. For example
|
25
|
+
# {x: {y: 'z'}} becomes {'x.y': 'z'}
|
26
|
+
# @param [Hash] hash input nested dict
|
27
|
+
# @return [Hash] flattened dict
|
28
|
+
def self.flatten_hash(hash)
|
29
|
+
hash.each_with_object({}) do |(k, v), h|
|
30
|
+
if v.is_a? Hash
|
31
|
+
flatten_hash(v).map do |h_k, h_v|
|
32
|
+
h["#{k}.#{h_k}".to_sym] = h_v
|
33
|
+
end
|
34
|
+
else
|
35
|
+
h[k] = v
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|