kubes 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +6 -5
  4. data/docs/_docs/config/args.md +10 -0
  5. data/docs/_docs/config/args/docker.md +19 -0
  6. data/docs/_docs/config/{kubectl/args.md → args/kubectl.md} +2 -0
  7. data/docs/_docs/config/docker.md +4 -40
  8. data/docs/_docs/config/hooks.md +10 -0
  9. data/docs/_docs/config/hooks/docker.md +70 -0
  10. data/docs/_docs/config/hooks/kubectl.md +83 -0
  11. data/docs/_docs/config/hooks/kubes.md +67 -0
  12. data/docs/_docs/config/hooks/ruby.md +74 -0
  13. data/docs/_docs/config/kubectl.md +2 -2
  14. data/docs/_docs/config/reference.md +20 -0
  15. data/docs/_docs/config/skip.md +58 -0
  16. data/docs/_docs/dsl/resources.md +1 -1
  17. data/docs/_docs/intro.md +3 -1
  18. data/docs/_docs/patterns/migrations.md +121 -0
  19. data/docs/_includes/config/hooks/options.md +20 -0
  20. data/docs/_includes/sidebar.html +25 -12
  21. data/docs/_sass/theme.scss +25 -1
  22. data/lib/kubes/cli.rb +20 -5
  23. data/lib/kubes/cli/apply.rb +2 -1
  24. data/lib/kubes/cli/base.rb +11 -0
  25. data/lib/kubes/cli/delete.rb +1 -1
  26. data/lib/kubes/cli/exec.rb +37 -6
  27. data/lib/kubes/cli/get.rb +1 -1
  28. data/lib/kubes/cli/logs.rb +27 -3
  29. data/lib/kubes/cli/prune.rb +95 -0
  30. data/lib/kubes/compiler.rb +18 -7
  31. data/lib/kubes/compiler/decorator/base.rb +7 -1
  32. data/lib/kubes/compiler/decorator/{resources/secret.rb → hashable.rb} +5 -4
  33. data/lib/kubes/compiler/decorator/hashable/field.rb +53 -0
  34. data/lib/kubes/compiler/decorator/hashable/storage.rb +19 -0
  35. data/lib/kubes/compiler/decorator/post.rb +77 -0
  36. data/lib/kubes/compiler/decorator/pre.rb +12 -0
  37. data/lib/kubes/compiler/strategy.rb +2 -2
  38. data/lib/kubes/compiler/strategy/base.rb +1 -1
  39. data/lib/kubes/compiler/strategy/result.rb +4 -6
  40. data/lib/kubes/config.rb +16 -11
  41. data/lib/kubes/docker/strategy/build/docker.rb +1 -1
  42. data/lib/kubes/docker/strategy/build/gcloud.rb +1 -1
  43. data/lib/kubes/docker/strategy/image_name.rb +1 -1
  44. data/lib/kubes/docker/strategy/push/docker.rb +1 -1
  45. data/lib/kubes/docker/strategy/push/gcloud.rb +1 -1
  46. data/lib/kubes/docker/strategy/utils.rb +1 -1
  47. data/lib/kubes/hooks/builder.rb +29 -15
  48. data/lib/kubes/hooks/concern.rb +10 -0
  49. data/lib/kubes/hooks/dsl.rb +2 -1
  50. data/lib/kubes/hooks/runner.rb +22 -0
  51. data/lib/kubes/kubectl.rb +21 -18
  52. data/lib/kubes/kubectl/batch.rb +8 -5
  53. data/lib/kubes/kubectl/{decider.rb → dispatcher.rb} +1 -1
  54. data/lib/kubes/kubectl/fetch/base.rb +12 -9
  55. data/lib/kubes/kubectl/fetch/deployment.rb +12 -13
  56. data/lib/kubes/kubectl/fetch/pods.rb +4 -15
  57. data/lib/kubes/kubectl/kustomize.rb +1 -1
  58. data/lib/kubes/kubectl/ordering.rb +12 -0
  59. data/lib/kubes/util/consider.rb +2 -1
  60. data/lib/kubes/util/sh.rb +1 -1
  61. data/lib/kubes/version.rb +1 -1
  62. data/spec/fixtures/decorators/deployment/both/envFrom.yaml +31 -0
  63. data/spec/fixtures/prune/capture.yaml +57 -0
  64. data/spec/fixtures/prune/fetch_items.yaml +268 -0
  65. data/spec/kubes/cli/prune_spec.rb +38 -0
  66. data/spec/kubes/compiler/decorator/{resources → post}/deployment_spec.rb +25 -6
  67. data/spec/kubes/compiler/decorator/{resources → post}/pod_spec.rb +2 -11
  68. metadata +35 -19
  69. data/docs/_docs/config/kubectl/hooks.md +0 -39
  70. data/lib/kubes/compiler/decorator.rb +0 -17
  71. data/lib/kubes/compiler/decorator/compile.rb +0 -12
  72. data/lib/kubes/compiler/decorator/resources/base.rb +0 -13
  73. data/lib/kubes/compiler/decorator/resources/container.rb +0 -76
  74. data/lib/kubes/compiler/decorator/resources/container/mapping.rb +0 -28
  75. data/lib/kubes/compiler/decorator/resources/deployment.rb +0 -10
  76. data/lib/kubes/compiler/decorator/resources/pod.rb +0 -10
  77. data/lib/kubes/compiler/decorator/write.rb +0 -14
  78. data/lib/kubes/docker/strategy/hooks.rb +0 -9
@@ -1,15 +1,49 @@
1
1
  class Kubes::CLI
2
2
  class Exec < Base
3
+ extend Memoist
4
+ include Kubes::Logging
3
5
  include Kubes::Util::Sh
4
6
 
5
7
  def run
6
8
  compile
7
- metadata = Kubes::Kubectl::Fetch::Deployment.new(@options).metadata
9
+ pod = find_pod
8
10
 
11
+ unless pod
12
+ logger.info <<~EOL
13
+ Unable to find a pod to exec into. This means there was no deployment found.
14
+ You can also try using the -p option and specifying enough of the pod name. Example:
15
+
16
+ kubes exec -p web
17
+
18
+ EOL
19
+ exit 1
20
+ end
21
+
22
+ container = " -c #{@options[:container]}" unless @options[:container].nil?
23
+ cmd = @options[:cmd].empty? ? "bash" : @options[:cmd].join(' ')
24
+ sh("kubectl exec #{ns} -ti #{pod}#{container} -- #{cmd}")
25
+ end
26
+
27
+ def find_pod
28
+ pod_name || deployment_pod
29
+ end
30
+
31
+ def ns
32
+ "-n #{metadata['namespace']}" if metadata
33
+ end
34
+
35
+ def metadata
36
+ deployment = Kubes::Kubectl::Fetch::Deployment.new(@options)
37
+ deployment.metadata if deployment.found
38
+ end
39
+ memoize :metadata
40
+
41
+ def deployment_pod
42
+ return unless metadata
9
43
  labels = metadata['labels'].map { |k,v| "#{k}=#{v}" }.join(',')
10
44
  ns = metadata['namespace']
11
45
 
12
- resp = capture("kubectl get pod -l #{labels} -n #{ns} -o json")
46
+ resp = sh_capture("kubectl get pod -l #{labels} -n #{ns} -o json")
13
47
  data = JSON.load(resp)
14
48
  pod = latest_pod(data['items'])
15
49
 
@@ -18,10 +52,7 @@ class Kubes::CLI
18
52
  exit 1
19
53
  end
20
54
 
21
- name = pod['metadata']['name']
22
- container = " -c #{@options[:container]}" unless @options[:container].nil?
23
- cmd = @options[:cmd].empty? ? "bash" : @options[:cmd].join(' ')
24
- sh("kubectl exec -n #{ns} -ti #{name}#{container} -- #{cmd}")
55
+ pod['metadata']['name']
25
56
  end
26
57
 
27
58
  # get latest running pod
@@ -2,7 +2,7 @@ class Kubes::CLI
2
2
  class Get < Base
3
3
  def run
4
4
  compile
5
- Kubes::Kubectl.run(:get, @options)
5
+ Kubes::Kubectl.run(:get, @options.merge(exit_on_fail: false))
6
6
  return unless @options[:show_pods]
7
7
  pods = Kubes::Kubectl::Fetch::Pods.new(@options)
8
8
  pods.show
@@ -1,21 +1,45 @@
1
1
  class Kubes::CLI
2
2
  class Logs < Base
3
+ include Kubes::Logging
3
4
  include Kubes::Util::Sh
4
5
 
5
6
  def run
6
7
  compile
8
+ sh("kubectl logs #{args}")
9
+ end
10
+
11
+ private
12
+ def args
13
+ args = pod_name || deployment_args
14
+
15
+ unless args
16
+ logger.info <<~EOL
17
+ Unable to find a pod to show logs for. This means there was no deployment found.
18
+ You can also try using the -p option and specifying enough of the pod name. Example:
19
+
20
+ kubes logs -p web
21
+
22
+ EOL
23
+ exit 1
24
+ end
25
+
26
+ follow = " -f" if @options[:follow]
27
+ "#{args}#{follow}"
28
+ end
29
+
30
+ def deployment_args
7
31
  deployment = Kubes::Kubectl::Fetch::Deployment.new(@options)
8
32
  metadata = deployment.metadata
33
+ return unless metadata
34
+
9
35
  name = metadata['name']
10
36
  ns = metadata['namespace']
11
37
 
12
- follow = " -f" if @options[:follow]
13
38
  container = container(deployment)
14
39
  c = " -c #{container}" if container
15
- sh("kubectl logs deployment/#{name}#{follow} -n #{ns}#{c}")
40
+ "deployment/#{name} -n #{ns}#{c}"
16
41
  end
17
42
 
18
- private
19
43
  def container(deployment)
20
44
  container = @options[:container]
21
45
  return container if container
@@ -0,0 +1,95 @@
1
+ class Kubes::CLI
2
+ class Prune < Base
3
+ KINDS = %w[ConfigMap Secret]
4
+ extend Memoist
5
+ include Kubes::Util::Sure
6
+
7
+ def run
8
+ return unless anything_to_prune?
9
+ logger.info "Pruning old resources: #{KINDS.join(', ')}"
10
+
11
+ perform(preview: true) unless @options[:yes]
12
+ sure?("This will prune/delete resources. Are you sure?")
13
+ perform(preview: false)
14
+ end
15
+
16
+ def fetcher
17
+ Kubes::Kubectl::Fetch::Base.new(@options)
18
+ end
19
+ memoize :fetcher
20
+
21
+ def namespace
22
+ fetcher.namespace
23
+ end
24
+
25
+ def anything_to_prune?
26
+ items = []
27
+ return unless namespace
28
+
29
+ with_old_items { |i| items << i }
30
+ if items.empty?
31
+ logger.info("There are no old resources that need pruning.") unless @options[:quiet]
32
+ end
33
+ !items.empty?
34
+ end
35
+
36
+ def perform(preview:)
37
+ with_old_items do |item|
38
+ prune(item, preview)
39
+ end
40
+ end
41
+
42
+ def with_old_items
43
+ items = get_all_items
44
+ items.each do |i|
45
+ next unless old?(i)
46
+ yield(i)
47
+ end
48
+ end
49
+
50
+ def old?(item)
51
+ name = item['metadata']['name']
52
+ kind = item['kind']
53
+ built = built_kinds[kind] || [] # IE: {"demo-secret"=>"ebd93b58dd"}
54
+ built.each do |original_name,hash|
55
+ return false unless name.include?(original_name)
56
+ current = "#{original_name}-#{hash}"
57
+ return false if name == current
58
+ # Spec cover the tricky regexp logic
59
+ regexp = Regexp.new("#{original_name}-\\w{10}$") # IE: # demo-secret-\w{10}$
60
+ return true if name.match(regexp)
61
+ end
62
+
63
+ false
64
+ end
65
+
66
+ def get_all_items
67
+ secrets = capture_items('secret')
68
+ config_maps = capture_items('configmap')
69
+ secrets + config_maps
70
+ end
71
+
72
+ def capture_items(kind)
73
+ data = Kubes::Kubectl.capture("get #{kind} -o json -n #{namespace}")
74
+ data['items'] || []
75
+ end
76
+
77
+ # IE: {"Secret"=>{"demo-secret"=>"ebd93b58dd"}}
78
+ def built_kinds
79
+ compile # compile so built kinds are in memory
80
+ Kubes::Compiler::Decorator::Hashable::Storage.md5s
81
+ end
82
+ memoize :built_kinds
83
+
84
+ def prune(item, preview=false)
85
+ kind = item['kind']
86
+ name = item['metadata']['name']
87
+ args = "delete #{kind} #{name} -n #{namespace}"
88
+ if preview
89
+ logger.info " kubectl #{args}"
90
+ else
91
+ Kubes::Kubectl.execute(args)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,5 +1,6 @@
1
1
  module Kubes
2
2
  class Compiler
3
+ include Kubes::Hooks::Concern
3
4
  include Kubes::Logging
4
5
  include Kubes::Util::Consider
5
6
 
@@ -7,17 +8,27 @@ module Kubes
7
8
  @options = options
8
9
  end
9
10
 
11
+ # Separate command like prune can call compile. Apply also calls Prune.
12
+ # Instead of moving Compile out of Prune, will use this class variable.
13
+ # In case we have other cases where compile is called in another area.
14
+ # We only want compiled to be called once so hooks only fire once.
15
+ @@compiled = false
10
16
  def run
11
- results = resources.map do |path|
12
- strategy = Strategy.new(@options.merge(path: path))
13
- strategy.compile
14
- end.compact
17
+ return if @@compiled
18
+ Kubes.config # trigger config load. So can set ENV['VAR'] in config/envs/dev.rb etc
19
+ run_hooks("kubes.rb", name: "compile") do
20
+ results = resources.map do |path|
21
+ strategy = Strategy.new(@options.merge(path: path))
22
+ strategy.compile
23
+ end.compact
15
24
 
16
- results.each do |result|
17
- write(result)
25
+ results.each do |result|
26
+ write(result)
27
+ end
18
28
  end
19
29
 
20
30
  puts "Compiled .kubes/resources files to .kubes/output" if show_compiled_message?
31
+ @@compiled = true
21
32
  end
22
33
 
23
34
  def resources
@@ -49,7 +60,7 @@ module Kubes
49
60
  end
50
61
 
51
62
  def write(result)
52
- result.write_decorate!
63
+ result.decorate!(:post)
53
64
  filename, content = result.filename, result.content
54
65
  dest = "#{Kubes.root}/.kubes/output/#{filename}"
55
66
 
@@ -1,14 +1,20 @@
1
1
  module Kubes::Compiler::Decorator
2
2
  class Base
3
+ attr_reader :data
3
4
  def initialize(data)
4
5
  @data = data
5
6
  end
6
7
 
8
+ def run
9
+ return @data unless Kubes.config.suffix_hash
10
+ process
11
+ end
12
+
7
13
  def result
8
14
  if @data.is_a?(Kubes::Compiler::Dsl::Core::Blocks)
9
15
  @data.results.each { |k,v| process(v) } # returns nil
10
16
  else
11
- process(@data) # returns Hash
17
+ process # processes and returns @data
12
18
  end
13
19
  @data # important to return @data so we keep the original @data structure: Blocks or Hash
14
20
  end
@@ -1,17 +1,18 @@
1
1
  require 'digest'
2
2
 
3
- module Kubes::Compiler::Decorator::Resources
4
- class Secret < Base
3
+ module Kubes::Compiler::Decorator
4
+ class Hashable < Base
5
5
  include Kubes::Compiler::Util::YamlDump
6
6
 
7
- def perform
7
+ def store
8
8
  # even though name is required, will allow logic to get the kubectl apply and kubectl to surface the required name error
9
9
  name = @data.dig('metadata','name')
10
10
  return @data unless name
11
11
 
12
+ # puts "name #{name}" # TODO: scope Kind so Secret and ConfigMap can have the same name...
12
13
  md5 = md5(@data)
13
14
  @data['metadata']['name'] = "#{name}-#{md5}"
14
- Kubes::Compiler::Decorator.store(name, md5)
15
+ Storage.store(@data['kind'], name, md5)
15
16
  @data
16
17
  end
17
18
 
@@ -0,0 +1,53 @@
1
+ class Kubes::Compiler::Decorator::Hashable
2
+ class Field
3
+ # item is full wrapper structure
4
+ #
5
+ # secretRef: <--- wrapper
6
+ # name: demo-secret
7
+ #
8
+ def initialize(item)
9
+ @item = item
10
+ end
11
+
12
+ def hashable?
13
+ x = @item.keys & map.keys
14
+ !x.empty?
15
+ end
16
+
17
+ def kind
18
+ wrapper =~ /configMap/ ? "ConfigMap" : "Secret"
19
+ end
20
+
21
+ # The key of the hashable value.
22
+ #
23
+ # envFrom:
24
+ # - secretRef:
25
+ # name: demo-secret <--- wrapper is 'name'
26
+ #
27
+ def key
28
+ map[wrapper]
29
+ end
30
+
31
+ # The wrapper field is nested right above the item with the hashable value.
32
+ #
33
+ # envFrom:
34
+ # - secretRef: <--- wrapper
35
+ # name: demo-secret
36
+ #
37
+ def wrapper
38
+ @item.keys.first
39
+ end
40
+
41
+ # wrapper element to key that stores the hashable value
42
+ def map
43
+ {
44
+ 'configMapRef' => 'name',
45
+ 'configMapKeyRef' => 'name',
46
+ 'configMap' => 'name',
47
+ 'secretRef' => 'name',
48
+ 'secretKeyRef' => 'name',
49
+ 'secret' => 'secretName',
50
+ }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ class Kubes::Compiler::Decorator::Hashable
2
+ module Storage
3
+ @@md5s = {}
4
+ def store(kind, name, md5)
5
+ @@md5s[kind] ||= {}
6
+ @@md5s[kind][name] = md5
7
+ end
8
+
9
+ def fetch(kind, name)
10
+ @@md5s[kind] ||= {}
11
+ @@md5s[kind][name]
12
+ end
13
+
14
+ def md5s
15
+ @@md5s
16
+ end
17
+ extend self
18
+ end
19
+ end
@@ -0,0 +1,77 @@
1
+ module Kubes::Compiler::Decorator
2
+ class Post < Base
3
+ def process
4
+ add_hash(@data)
5
+ clean_namespace
6
+ @data
7
+ end
8
+
9
+ def clean_namespace
10
+ return unless @data['kind'] == 'Namespace'
11
+ @data['metadata'].delete('namespace')
12
+ @data
13
+ end
14
+
15
+ def add_hash(item, options={})
16
+ # hashable set from previous stack call
17
+ if options[:hashable_field] && item.is_a?(Hash)
18
+ field = options[:hashable_field]
19
+ value_without_md5 = item[field.key]
20
+ @reset_hashable_field = true unless value_without_md5
21
+ if field.hashable? && value_without_md5
22
+ md5 = Hashable::Storage.fetch(field.kind, value_without_md5)
23
+ v = [value_without_md5, md5].compact.join('-')
24
+ item[field.key] = v
25
+ end
26
+ end
27
+
28
+ options[:hashable_field] ||= hashable_field(item) # set for next stack call
29
+ # Pretty tricky case. Given:
30
+ #
31
+ # envFrom:
32
+ # - secretRef:
33
+ # name: demo-secret
34
+ # - configMapRef:
35
+ # name: demo-config-map
36
+ #
37
+ # Need to reset the stored hashable_field in the call stack.
38
+ # Else the field.kind is cached and the md5 look is incorrect
39
+ # The spec/fixtures/decorators/deployment/both/envFrom.yaml fixture covers this.
40
+ if @reset_hashable_field
41
+ options[:hashable_field] = hashable_field(item)
42
+ @reset_hashable_field = false
43
+ end
44
+ case item
45
+ when Array, Hash
46
+ item.each { |i| add_hash(i, options) }
47
+ end
48
+ item
49
+ end
50
+
51
+ # Returns the nested key name that will be hashable. Examples:
52
+ #
53
+ # 1. envFrom example
54
+ # envFrom:
55
+ # - secretRef:
56
+ # name: demo-secret
57
+ #
58
+ # 2. valueFrom example
59
+ # valueFrom:
60
+ # secretKeyRef:
61
+ # name: demo-secret
62
+ # key: password
63
+ #
64
+ # 3. volumes example
65
+ # volumes:
66
+ # - secret:
67
+ # secretName: demo-secret
68
+ #
69
+ # This is useful to capture for the next level of the stack call
70
+ #
71
+ def hashable_field(item)
72
+ return false unless item.is_a?(Hash)
73
+ field = Hashable::Field.new(item)
74
+ field if field.hashable?
75
+ end
76
+ end
77
+ end