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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/lib/cli/base_handler.rb +194 -0
  3. data/lib/cli/base_serializer.rb +120 -0
  4. data/lib/cli/config_handler.rb +51 -0
  5. data/lib/cli/entry_serializers.rb +99 -0
  6. data/lib/cli/project_handler.rb +2 -2
  7. data/lib/cli/release_handler.rb +41 -0
  8. data/lib/cli/release_serializer.rb +46 -0
  9. data/lib/cli/root_handler.rb +34 -13
  10. data/lib/cli/state_handler.rb +88 -0
  11. data/lib/cli/values_handler.rb +4 -3
  12. data/{boilerplate → lib/code-gen/new-project}/Gemfile.erb +0 -0
  13. data/lib/code-gen/new-project/kerbifile.rb.erb +9 -0
  14. data/lib/code-gen/new-project/values.yaml.erb +1 -0
  15. data/lib/config/cli_schema.rb +343 -28
  16. data/lib/config/config_file.rb +60 -0
  17. data/lib/config/globals.rb +4 -0
  18. data/lib/config/run_opts.rb +162 -0
  19. data/lib/config/state_consts.rb +11 -0
  20. data/lib/kerbi.rb +35 -10
  21. data/lib/main/code_gen.rb +1 -1
  22. data/lib/main/errors.rb +115 -0
  23. data/lib/main/mixer.rb +20 -10
  24. data/lib/mixins/cli_state_helpers.rb +108 -0
  25. data/lib/mixins/cm_backend_testing.rb +109 -0
  26. data/lib/mixins/entry_tag_logic.rb +183 -0
  27. data/lib/state/base_backend.rb +93 -0
  28. data/lib/state/config_map_backend.rb +173 -0
  29. data/lib/state/entry.rb +173 -0
  30. data/lib/state/entry_set.rb +137 -0
  31. data/lib/state/metadata.yaml.erb +11 -0
  32. data/lib/state/mixers.rb +23 -0
  33. data/lib/state/resources.yaml.erb +17 -0
  34. data/lib/utils/cli.rb +108 -9
  35. data/lib/utils/helm.rb +10 -12
  36. data/lib/utils/k8s_auth.rb +87 -0
  37. data/lib/utils/misc.rb +36 -1
  38. data/lib/utils/mixing.rb +1 -1
  39. data/lib/utils/values.rb +13 -22
  40. data/spec/cli/config_handler_spec.rb +38 -0
  41. data/spec/cli/release_handler_spec.rb +127 -0
  42. data/spec/cli/root_handler_spec.rb +100 -0
  43. data/spec/cli/state_handler_spec.rb +108 -0
  44. data/spec/cli/values_handler_spec.rb +17 -0
  45. data/spec/fixtures/expectations/common/bad-tag.txt +1 -0
  46. data/spec/fixtures/expectations/config/bad-set.txt +1 -0
  47. data/spec/fixtures/expectations/config/set.txt +1 -0
  48. data/spec/fixtures/expectations/config/show-default.yaml +6 -0
  49. data/spec/fixtures/expectations/release/delete.txt +1 -0
  50. data/spec/fixtures/expectations/release/init-already-existed.txt +2 -0
  51. data/spec/fixtures/expectations/release/init-both-created.txt +2 -0
  52. data/spec/fixtures/expectations/release/list.txt +5 -0
  53. data/spec/fixtures/expectations/release/status-all-working.txt +5 -0
  54. data/spec/fixtures/expectations/release/status-data-unreadable.txt +5 -0
  55. data/spec/fixtures/expectations/release/status-not-provisioned.txt +5 -0
  56. data/spec/fixtures/expectations/root/template-inlines.yaml +31 -0
  57. data/spec/fixtures/expectations/root/template-production.yaml +31 -0
  58. data/spec/fixtures/expectations/root/template-read-inlines.yaml +31 -0
  59. data/spec/fixtures/expectations/root/template-read.yaml +31 -0
  60. data/spec/fixtures/expectations/root/template-write.yaml +31 -0
  61. data/spec/fixtures/expectations/root/template.yaml +31 -0
  62. data/spec/fixtures/expectations/root/values.json +28 -0
  63. data/spec/fixtures/expectations/state/delete.txt +1 -0
  64. data/spec/fixtures/expectations/state/demote.txt +1 -0
  65. data/spec/fixtures/expectations/state/list.json +51 -0
  66. data/spec/fixtures/expectations/state/list.txt +6 -0
  67. data/spec/fixtures/expectations/state/list.yaml +35 -0
  68. data/spec/fixtures/expectations/state/promote.txt +1 -0
  69. data/spec/fixtures/expectations/state/prune-candidates.txt +1 -0
  70. data/spec/fixtures/expectations/state/retag.txt +1 -0
  71. data/spec/fixtures/expectations/state/set.txt +1 -0
  72. data/spec/fixtures/expectations/state/show.json +13 -0
  73. data/spec/fixtures/expectations/state/show.txt +13 -0
  74. data/spec/fixtures/expectations/state/show.yaml +8 -0
  75. data/spec/fixtures/expectations/values/order-of-precedence.yaml +4 -0
  76. data/spec/main/configmap_backend_spec.rb +110 -0
  77. data/spec/main/project_code_gen_spec.rb +8 -2
  78. data/spec/main/state_entry_set_spec.rb +112 -0
  79. data/spec/main/state_entry_spec.rb +109 -0
  80. data/spec/mini-projects/hello-kerbi/common/metadata.yaml.erb +5 -0
  81. data/spec/mini-projects/hello-kerbi/consts.rb +5 -0
  82. data/spec/mini-projects/hello-kerbi/helpers.rb +8 -0
  83. data/spec/mini-projects/hello-kerbi/kerbifile.rb +18 -0
  84. data/spec/mini-projects/hello-kerbi/pod-and-service.yaml.erb +23 -0
  85. data/spec/mini-projects/hello-kerbi/values/production.yaml +2 -0
  86. data/spec/mini-projects/hello-kerbi/values/v2.yaml +2 -0
  87. data/spec/mini-projects/hello-kerbi/values/values.yaml +4 -0
  88. data/spec/spec_helper.rb +143 -1
  89. data/spec/utils/helm_spec.rb +89 -109
  90. data/spec/utils/k8s_auth_spec.rb +32 -0
  91. data/spec/utils/misc_utils_spec.rb +9 -0
  92. data/spec/utils/values_utils_spec.rb +12 -19
  93. metadata +143 -16
  94. data/boilerplate/kerbifile.rb.erb +0 -9
  95. data/boilerplate/values.yaml.erb +0 -1
  96. data/lib/cli/base.rb +0 -83
  97. data/lib/config/cli_opts.rb +0 -50
  98. data/lib/config/manager.rb +0 -36
  99. data/lib/main/state_manager.rb +0 -47
  100. data/lib/utils/kubectl.rb +0 -58
  101. data/spec/main/examples_spec.rb +0 -12
  102. data/spec/main/state_manager_spec.rb +0 -84
  103. 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
+ }) %>
@@ -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>] dicts dicts to YAMLify
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>] dicts dicts to YAMLify
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(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)
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
- def self.config
6
- Kerbi::Config::Manager
7
- end
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(config.helm_exec, out: File::NULL, err: File::NULL)
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
- 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
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?(config.tmp_helm_values_path)
32
- File.delete(config.tmp_helm_values_path)
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 = "#{config.helm_exec} template #{release} #{project}"
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
@@ -24,7 +24,7 @@ module Kerbi
24
24
  contents = File.read(fname)
25
25
  begin
26
26
  self.yaml_str_to_dicts(contents, **opts)
27
- rescue Exception => e
27
+ rescue Error => e
28
28
  STDERR.puts "Exception below from file #{fname}"
29
29
  raise e
30
30
  end
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 = [self::DEFAULT_VALUE_PATH, *fname_exprs].uniq
22
+ final_exprs = fname_exprs.uniq
25
23
  final_exprs.map do |fname_expr|
26
- path = resolve_fname_expr(fname_expr, **opts)
27
- if fname_expr != 'values' && !path
28
- raise "Could not resolve file '#{fname_expr}'"
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