kerbi 0.0.1 → 0.0.2
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 +180 -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/root_handler.rb +32 -13
- data/lib/cli/state_handler.rb +95 -0
- data/lib/cli/values_handler.rb +4 -3
- data/lib/config/cli_schema.rb +299 -27
- data/lib/config/config_file.rb +60 -0
- data/lib/config/globals.rb +4 -0
- data/lib/config/run_opts.rb +150 -0
- data/lib/config/state_consts.rb +10 -0
- data/lib/kerbi.rb +31 -9
- data/lib/main/errors.rb +109 -0
- data/lib/main/mixer.rb +12 -8
- data/lib/mixins/cli_state_helpers.rb +136 -0
- data/lib/mixins/cm_backend_testing.rb +95 -0
- data/lib/mixins/entry_tag_logic.rb +183 -0
- data/lib/state/base_backend.rb +59 -0
- data/lib/state/config_map_backend.rb +119 -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 +77 -10
- 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/root_handler_spec.rb +99 -0
- data/spec/cli/state_handler_spec.rb +139 -0
- data/spec/cli/values_handler_spec.rb +17 -0
- data/spec/expectations/common/bad-tag.txt +1 -0
- data/spec/expectations/config/bad-set.txt +1 -0
- data/spec/expectations/config/set.txt +1 -0
- data/spec/expectations/config/show-default.yaml +6 -0
- data/spec/expectations/root/template-inlines.yaml +31 -0
- data/spec/expectations/root/template-production.yaml +31 -0
- data/spec/expectations/root/template-read-inlines.yaml +31 -0
- data/spec/expectations/root/template-read.yaml +31 -0
- data/spec/expectations/root/template-write.yaml +31 -0
- data/spec/expectations/root/template.yaml +31 -0
- data/spec/expectations/root/values.json +28 -0
- data/spec/expectations/state/delete.txt +1 -0
- data/spec/expectations/state/demote.txt +1 -0
- data/spec/expectations/state/init-already-existed.txt +2 -0
- data/spec/expectations/state/init-both-created.txt +2 -0
- data/spec/expectations/state/list.json +51 -0
- data/spec/expectations/state/list.txt +6 -0
- data/spec/expectations/state/list.yaml +35 -0
- data/spec/expectations/state/promote.txt +1 -0
- data/spec/expectations/state/prune-candidates.txt +1 -0
- data/spec/expectations/state/retag.txt +1 -0
- data/spec/expectations/state/set.txt +1 -0
- data/spec/expectations/state/show.json +13 -0
- data/spec/expectations/state/show.txt +13 -0
- data/spec/expectations/state/show.yaml +8 -0
- data/spec/expectations/state/status-all-working.txt +4 -0
- data/spec/expectations/state/status-not-provisioned.txt +4 -0
- data/spec/expectations/values/order-of-precedence.yaml +4 -0
- data/spec/main/configmap_backend_spec.rb +109 -0
- data/spec/main/state_entry_set_spec.rb +112 -0
- data/spec/main/state_entry_spec.rb +109 -0
- data/spec/spec_helper.rb +87 -1
- data/spec/utils/helm_spec.rb +1 -21
- 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 +114 -13
- 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,119 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module State
|
3
|
+
|
4
|
+
##
|
5
|
+
# Treats a Kubernetes configmap in a namespace as a
|
6
|
+
# persistent store for state entries. Reads and writes
|
7
|
+
# to the configmap.
|
8
|
+
class ConfigMapBackend < Kerbi::State::BaseBackend
|
9
|
+
include Kerbi::Mixins::CmBackendTesting
|
10
|
+
|
11
|
+
attr_reader :auth_bundle
|
12
|
+
attr_reader :namespace
|
13
|
+
|
14
|
+
# @param [Hash] auth_bundle generated by Kerbi::Utils::K8sAuth
|
15
|
+
# @param [String] namespace Kubernetes namespace where configmap lives
|
16
|
+
def initialize(auth_bundle, namespace)
|
17
|
+
@auth_bundle = auth_bundle.freeze
|
18
|
+
@namespace = namespace.freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Checks for the namespace and configmap, creating along
|
23
|
+
# the way if missing. Does not raise if already exists.
|
24
|
+
# @param [Hash] opts for things like verbose
|
25
|
+
def provision_missing_resources(**opts)
|
26
|
+
create_namespace unless (ns_existed = namespace_exists?)
|
27
|
+
echo_init("namespaces/#{namespace}", ns_existed, opts)
|
28
|
+
|
29
|
+
create_resource unless (cm_existed = resource_exists?)
|
30
|
+
echo_init("#{namespace}/configmaps/#{cm_name}", cm_existed, opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Creates the configmap with 0 entries.
|
35
|
+
def create_resource
|
36
|
+
apply_resource(template_resource([]))
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Creates the configmap given an exact dict representation
|
41
|
+
# of its contents. This method doesn't actually get used outside
|
42
|
+
# of rspec, but it's super useful there so keeping for time being.
|
43
|
+
# @param [Hash] resource_desc
|
44
|
+
def apply_resource(resource_desc)
|
45
|
+
#noinspection RubyResolve
|
46
|
+
client("v1").create_config_map(resource_desc)
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Outputs the dict representation of the configmap, templated
|
51
|
+
# with the given entries.
|
52
|
+
# @param [Array<Kerbi::State::Entry>] entries
|
53
|
+
# @return [Hash]
|
54
|
+
def template_resource(entries)
|
55
|
+
values = { consts::ENTRIES_ATTR => entries.map(&:to_h) }
|
56
|
+
opts = { release_name: namespace }
|
57
|
+
Kerbi::State::ConfigMapMixer.new(values, **opts).run.first
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Creates the required namespace resource for this configmap
|
62
|
+
# in the cluster.
|
63
|
+
def create_namespace
|
64
|
+
opts = { release_name: namespace }
|
65
|
+
dict = Kerbi::State::NamespaceMixer.new({}, **opts).run.first
|
66
|
+
#noinspection RubyResolve
|
67
|
+
client("v1").create_namespace(dict)
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
##
|
73
|
+
# Reads the configmap from Kubernetes, returns its dict representation.
|
74
|
+
def load_resource
|
75
|
+
#noinspection RubyResolve
|
76
|
+
client("v1").get_config_map(cm_name, namespace).to_h
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Templates the updated version of the configmap given the entries
|
81
|
+
# in memory, and uses the new dict to overwrite the last configmap
|
82
|
+
# in the cluster.
|
83
|
+
def update_resource
|
84
|
+
new_resource = template_resource(entries)
|
85
|
+
#noinspection RubyResolve
|
86
|
+
client("v1").update_config_map(new_resource)
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Deserializes the list of entries in the configmap. Calls
|
91
|
+
# #resources, which is memoized, so may trigger a cluster read.
|
92
|
+
# @return [Array<Hash>] entries
|
93
|
+
def read_entries
|
94
|
+
str_entries = resource[:data][:entries]
|
95
|
+
JSON.parse(str_entries)
|
96
|
+
end
|
97
|
+
|
98
|
+
## Creates an instance of Kubeclient::Client given
|
99
|
+
# the auth_bundle in the state, and a Kubernetes API name
|
100
|
+
# like appsV1 (defaults to "v1" if not passed).
|
101
|
+
# @return [Kubeclient::Client]
|
102
|
+
def client(api_name="v1")
|
103
|
+
Kubeclient::Client.new(
|
104
|
+
auth_bundle[:endpoint],
|
105
|
+
api_name,
|
106
|
+
**auth_bundle[:options]
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def consts
|
111
|
+
Kerbi::State::Consts
|
112
|
+
end
|
113
|
+
|
114
|
+
def cm_name
|
115
|
+
consts::RESOURCE_NAME
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/state/entry.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
module Kerbi
|
2
|
+
module State
|
3
|
+
|
4
|
+
##
|
5
|
+
# Represents a single Kerbi state entry.
|
6
|
+
class Entry
|
7
|
+
|
8
|
+
CANDIDATE_PREFIX = "[cand]-"
|
9
|
+
|
10
|
+
ATTRS = %i[tag message values default_values created_at]
|
11
|
+
SETTABLE_ATTRS = %i[message created_at]
|
12
|
+
|
13
|
+
attr_accessor :set
|
14
|
+
|
15
|
+
attr_accessor :tag
|
16
|
+
attr_accessor :message
|
17
|
+
attr_accessor :default_values
|
18
|
+
attr_accessor :values
|
19
|
+
attr_accessor :created_at
|
20
|
+
|
21
|
+
attr_reader :validation_errors
|
22
|
+
|
23
|
+
def initialize(set, dict)
|
24
|
+
@set = set
|
25
|
+
ATTRS.each do |attr|
|
26
|
+
instance_variable_set("@#{attr}", dict[attr].freeze)
|
27
|
+
end
|
28
|
+
@_was_validated = false
|
29
|
+
@validation_errors = []
|
30
|
+
end
|
31
|
+
|
32
|
+
## A state entry is a 'candidate' if its tag has the
|
33
|
+
# candidate signature - that is it starts with [cand]-.
|
34
|
+
# @return [TrueClass, FalseClass]
|
35
|
+
def candidate?
|
36
|
+
tag.start_with?(CANDIDATE_PREFIX)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Convenience method that returns the negation of #candidate?
|
41
|
+
# @return [TrueClass, FalseClass]
|
42
|
+
def committed?
|
43
|
+
!candidate?
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Ghetto attribute validation. Pushes a attr => msg hash to the
|
48
|
+
# @validation_errors for every problem found. Does not raise on
|
49
|
+
# problems.
|
50
|
+
# @return [NilClass]
|
51
|
+
def validate
|
52
|
+
@validation_errors.push(
|
53
|
+
attr: 'tag',
|
54
|
+
msg: "Cannot be empty",
|
55
|
+
value: tag
|
56
|
+
) unless tag.present?
|
57
|
+
|
58
|
+
@_was_validated = true
|
59
|
+
end
|
60
|
+
|
61
|
+
def valid?
|
62
|
+
raise "valid? called before #validate" unless @_was_validated
|
63
|
+
validation_errors.empty?
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Computes a delta between this state's values and its
|
68
|
+
# default values.
|
69
|
+
# @return [Hash]
|
70
|
+
def overrides_delta
|
71
|
+
if values.is_a?(Hash) & default_values.is_a?(Hash)
|
72
|
+
Kerbi::Utils::Misc.deep_hash_diff(default_values, values)
|
73
|
+
else
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Dynamically assign as a user.
|
80
|
+
# @param [String|Symbol] attr_name
|
81
|
+
# @param [Object] new_value
|
82
|
+
# @return [String] the old value, for convenience
|
83
|
+
def assign_attr(attr_name, new_value)
|
84
|
+
if SETTABLE_ATTRS.include?(attr_name.to_sym)
|
85
|
+
old_value = send(attr_name)
|
86
|
+
send("#{attr_name}=", new_value)
|
87
|
+
old_value
|
88
|
+
else
|
89
|
+
raise Kerbi::NoSuchStateAttrName
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Replace current tag with a new one, where the new
|
95
|
+
# one can contain special interpolatable words like
|
96
|
+
# @candidate.
|
97
|
+
# @param [String] new_tag_expr
|
98
|
+
# @return [String] the old tag, for convenience
|
99
|
+
def retag(new_tag_expr)
|
100
|
+
old_tag = tag
|
101
|
+
self.tag = set.resolve_write_tag_expr(new_tag_expr)
|
102
|
+
old_tag
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Removes the [cand]- part of the tag, making this
|
107
|
+
# entry lose its candidate status.
|
108
|
+
#
|
109
|
+
# Raises an exception if this entry was not a candidate.
|
110
|
+
# @return [String] the old tag, for convenience
|
111
|
+
def promote
|
112
|
+
raise Kerbi::StateNotPromotable unless candidate?
|
113
|
+
old_tag = tag
|
114
|
+
self.tag = tag[CANDIDATE_PREFIX.length..]
|
115
|
+
old_tag
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Adds the [cand]- flag to this entry's tag, making this
|
120
|
+
# entry gain candidate status.
|
121
|
+
#
|
122
|
+
# Raises an exception if this entry was already a candidate.
|
123
|
+
# @return [String] the old tag, for convenience
|
124
|
+
def demote
|
125
|
+
raise Kerbi::StateNotDemotable unless committed?
|
126
|
+
old_tag = tag
|
127
|
+
self.tag = "#{CANDIDATE_PREFIX}#{tag}"
|
128
|
+
old_tag
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Convenience method to get all overridden keys between
|
133
|
+
# the values and default_values dicts.
|
134
|
+
# @return [Array<String>]
|
135
|
+
def overridden_keys
|
136
|
+
(delta = overrides_delta) ? delta.keys.map(&:to_s) : []
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_h
|
140
|
+
special_ser = {
|
141
|
+
values: values || {},
|
142
|
+
default_values: default_values || {},
|
143
|
+
created_at: created_at.to_s
|
144
|
+
}
|
145
|
+
Hash[ATTRS.map{|k|[k, send(k)]}].merge(special_ser)
|
146
|
+
end
|
147
|
+
alias_method :serialize, :to_h
|
148
|
+
|
149
|
+
def to_json
|
150
|
+
JSON.dump(serialize)
|
151
|
+
end
|
152
|
+
|
153
|
+
# @param [Hash] dict
|
154
|
+
# @return [Kerbi::State::Entry]
|
155
|
+
def self.from_dict(set, dict={})
|
156
|
+
dict.deep_symbolize_keys!
|
157
|
+
dict.slice!(*ATTRS)
|
158
|
+
|
159
|
+
self.new(
|
160
|
+
set,
|
161
|
+
**dict,
|
162
|
+
values: dict[:values] || {},
|
163
|
+
default_values: dict[:default_values] || {},
|
164
|
+
created_at: (Time.parse(dict[:created_at]) rescue nil)
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.versioned?(expr)
|
169
|
+
Gem::Version.correct?(expr)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -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: <%= consts::RESOURCE_NAME %>
|
9
|
+
namespace: <%= release_name %>
|
10
|
+
data: <%= embed({consts::ENTRIES_ATTR => entries_json}) %>
|
11
|
+
|
12
|
+
---
|
13
|
+
|
14
|
+
apiVersion: v1
|
15
|
+
kind: Namespace
|
16
|
+
metadata:
|
17
|
+
name: <%= release_name %>
|
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,31 @@ 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
|
-
|
55
|
-
|
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)
|
56
106
|
end
|
107
|
+
|
108
|
+
LIST_TABLE_STYLE = {
|
109
|
+
border_left: false,
|
110
|
+
border_right: false,
|
111
|
+
border_top: false,
|
112
|
+
border_x: "",
|
113
|
+
border_y: "",
|
114
|
+
border_i: ""
|
115
|
+
}.freeze
|
116
|
+
|
117
|
+
DESCRIBE_TABLE_STYLE = {
|
118
|
+
all_separators: true,
|
119
|
+
border_x: "-",
|
120
|
+
border_y: "",
|
121
|
+
border_i: ""
|
122
|
+
}.freeze
|
123
|
+
|
57
124
|
end
|
58
125
|
end
|
59
126
|
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
|