kubes 0.3.4 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +6 -5
  4. data/docs/_docs/ci/cloudbuild.md +2 -0
  5. data/docs/_docs/config/args.md +10 -0
  6. data/docs/_docs/config/args/docker.md +19 -0
  7. data/docs/_docs/config/args/kubectl.md +19 -0
  8. data/docs/_docs/config/docker.md +4 -40
  9. data/docs/_docs/config/env.md +1 -1
  10. data/docs/_docs/config/hooks.md +10 -0
  11. data/docs/_docs/config/hooks/docker.md +70 -0
  12. data/docs/_docs/config/hooks/kubectl.md +83 -0
  13. data/docs/_docs/config/hooks/kubes.md +67 -0
  14. data/docs/_docs/config/hooks/ruby.md +76 -0
  15. data/docs/_docs/config/kubectl.md +3 -54
  16. data/docs/_docs/config/reference.md +20 -0
  17. data/docs/_docs/config/skip.md +58 -0
  18. data/docs/_docs/dsl/resources.md +1 -1
  19. data/docs/_docs/dsl/resources/backend_config.md +1 -1
  20. data/docs/_docs/helpers.md +3 -2
  21. data/docs/_docs/intro.md +6 -3
  22. data/docs/_docs/learn/dsl/review-project.md +4 -2
  23. data/docs/_docs/learn/yaml/review-project.md +4 -2
  24. data/docs/_docs/{auto-context.md → misc/auto-context.md} +0 -0
  25. data/docs/_docs/{kustomize.md → misc/kustomize.md} +0 -0
  26. data/docs/_docs/misc/separate-steps.md +21 -0
  27. data/docs/_docs/patterns.md +4 -1
  28. data/docs/_docs/patterns/clock-web-worker.md +2 -0
  29. data/docs/_docs/patterns/migrations.md +123 -0
  30. data/docs/_docs/patterns/secrets.md +82 -0
  31. data/docs/_includes/config/hooks/options.md +20 -0
  32. data/docs/_includes/sidebar.html +35 -11
  33. data/docs/_reference/kubes-exec.md +14 -6
  34. data/docs/_reference/kubes-init.md +1 -0
  35. data/docs/_reference/kubes-logs.md +1 -0
  36. data/docs/_sass/theme.scss +25 -1
  37. data/kubes.gemspec +3 -0
  38. data/lib/kubes.rb +3 -0
  39. data/lib/kubes/cli.rb +20 -5
  40. data/lib/kubes/cli/apply.rb +2 -1
  41. data/lib/kubes/cli/base.rb +11 -0
  42. data/lib/kubes/cli/compile.rb +8 -0
  43. data/lib/kubes/cli/delete.rb +1 -1
  44. data/lib/kubes/cli/exec.rb +37 -6
  45. data/lib/kubes/cli/get.rb +3 -2
  46. data/lib/kubes/cli/init.rb +7 -2
  47. data/lib/kubes/cli/logs.rb +27 -3
  48. data/lib/kubes/cli/prune.rb +95 -0
  49. data/lib/kubes/compiler.rb +11 -7
  50. data/lib/kubes/compiler/decorator/base.rb +7 -1
  51. data/lib/kubes/compiler/decorator/{resources/secret.rb → hashable.rb} +5 -4
  52. data/lib/kubes/compiler/decorator/hashable/field.rb +53 -0
  53. data/lib/kubes/compiler/decorator/hashable/storage.rb +19 -0
  54. data/lib/kubes/compiler/decorator/post.rb +77 -0
  55. data/lib/kubes/compiler/decorator/pre.rb +12 -0
  56. data/lib/kubes/compiler/shared/helpers.rb +7 -2
  57. data/lib/kubes/compiler/strategy.rb +2 -2
  58. data/lib/kubes/compiler/strategy/base.rb +1 -1
  59. data/lib/kubes/compiler/strategy/result.rb +4 -6
  60. data/lib/kubes/config.rb +16 -11
  61. data/lib/kubes/docker/strategy/build/docker.rb +1 -1
  62. data/lib/kubes/docker/strategy/build/gcloud.rb +1 -1
  63. data/lib/kubes/docker/strategy/image_name.rb +1 -1
  64. data/lib/kubes/docker/strategy/push/docker.rb +1 -1
  65. data/lib/kubes/docker/strategy/push/gcloud.rb +1 -1
  66. data/lib/kubes/docker/strategy/utils.rb +1 -1
  67. data/lib/kubes/hooks/builder.rb +29 -15
  68. data/lib/kubes/hooks/concern.rb +10 -0
  69. data/lib/kubes/hooks/dsl.rb +2 -1
  70. data/lib/kubes/hooks/runner.rb +22 -0
  71. data/lib/kubes/kubectl.rb +21 -18
  72. data/lib/kubes/kubectl/batch.rb +8 -5
  73. data/lib/kubes/kubectl/{decider.rb → dispatcher.rb} +1 -1
  74. data/lib/kubes/kubectl/fetch/base.rb +13 -10
  75. data/lib/kubes/kubectl/fetch/deployment.rb +12 -13
  76. data/lib/kubes/kubectl/fetch/pods.rb +4 -15
  77. data/lib/kubes/kubectl/kustomize.rb +1 -1
  78. data/lib/kubes/kubectl/ordering.rb +12 -0
  79. data/lib/kubes/util/consider.rb +2 -1
  80. data/lib/kubes/util/sh.rb +1 -1
  81. data/lib/kubes/version.rb +1 -1
  82. data/lib/templates/dsl/.kubes/resources/web/deployment.rb +1 -1
  83. data/lib/templates/yaml/.kubes/resources/web/deployment.yaml.tt +1 -1
  84. data/spec/fixtures/decorators/deployment/both/envFrom.yaml +31 -0
  85. data/spec/fixtures/decorators/deployment/both/valueFrom.yaml +33 -0
  86. data/spec/fixtures/decorators/deployment/both/volumes.yaml +40 -0
  87. data/spec/fixtures/prune/capture.yaml +57 -0
  88. data/spec/fixtures/prune/fetch_items.yaml +268 -0
  89. data/spec/kubes/cli/prune_spec.rb +38 -0
  90. data/spec/kubes/compiler/decorator/{resources → post}/deployment_spec.rb +52 -6
  91. data/spec/kubes/compiler/decorator/{resources → post}/pod_spec.rb +2 -11
  92. metadata +57 -19
  93. data/lib/kubes/compiler/decorator.rb +0 -17
  94. data/lib/kubes/compiler/decorator/compile.rb +0 -12
  95. data/lib/kubes/compiler/decorator/resources/base.rb +0 -13
  96. data/lib/kubes/compiler/decorator/resources/container.rb +0 -76
  97. data/lib/kubes/compiler/decorator/resources/container/mapping.rb +0 -28
  98. data/lib/kubes/compiler/decorator/resources/deployment.rb +0 -10
  99. data/lib/kubes/compiler/decorator/resources/pod.rb +0 -10
  100. data/lib/kubes/compiler/decorator/write.rb +0 -14
  101. data/lib/kubes/docker/strategy/hooks.rb +0 -9
@@ -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
 
@@ -8,13 +9,16 @@ module Kubes
8
9
  end
9
10
 
10
11
  def run
11
- results = resources.map do |path|
12
- strategy = Strategy.new(@options.merge(path: path))
13
- strategy.compile
14
- end.compact
12
+ Kubes.config # trigger config load. So can set ENV['VAR'] in config/envs/dev.rb etc
13
+ run_hooks("kubes.rb", name: "compile") do
14
+ results = resources.map do |path|
15
+ strategy = Strategy.new(@options.merge(path: path))
16
+ strategy.compile
17
+ end.compact
15
18
 
16
- results.each do |result|
17
- write(result)
19
+ results.each do |result|
20
+ write(result)
21
+ end
18
22
  end
19
23
 
20
24
  puts "Compiled .kubes/resources files to .kubes/output" if show_compiled_message?
@@ -49,7 +53,7 @@ module Kubes
49
53
  end
50
54
 
51
55
  def write(result)
52
- result.write_decorate!
56
+ result.decorate!(:post)
53
57
  filename, content = result.filename, result.content
54
58
  dest = "#{Kubes.root}/.kubes/output/#{filename}"
55
59
 
@@ -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
@@ -0,0 +1,12 @@
1
+ module Kubes::Compiler::Decorator
2
+ class Pre < Base
3
+ def process
4
+ case @data['kind']
5
+ when "ConfigMap", "Secret"
6
+ Hashable.new(@data).store
7
+ else
8
+ @data # passthrough
9
+ end
10
+ end
11
+ end
12
+ end
@@ -24,8 +24,13 @@ module Kubes::Compiler::Shared
24
24
  extra&.strip&.empty? ? nil : extra # if blank string then also return nil
25
25
  end
26
26
 
27
- def base64(v)
28
- Base64.encode64(v).strip
27
+ def encode64(v)
28
+ Base64.strict_encode64(v).strip
29
+ end
30
+ alias_method :base64, :encode64
31
+
32
+ def decode64(v)
33
+ Base64.strict_decode64(v)
29
34
  end
30
35
  end
31
36
  end
@@ -11,7 +11,7 @@ class Kubes::Compiler
11
11
 
12
12
  strategy = klass.new(@options.merge(path: @path)) # Dsl or Erb
13
13
  result = strategy.run
14
- result.compile_decorate! # compile phase decoration
14
+ result.decorate!(:pre) # compile pre phase decoration
15
15
  result
16
16
  end
17
17
 
@@ -19,7 +19,7 @@ class Kubes::Compiler
19
19
  ext = File.extname(@path)
20
20
  case ext
21
21
  when '.rb' then Dsl
22
- when '.yaml' then Erb
22
+ when '.yaml','.yml' then Erb
23
23
  else Pass
24
24
  end
25
25
  end
@@ -7,7 +7,7 @@ class Kubes::Compiler::Strategy
7
7
  @path = options[:path]
8
8
 
9
9
  @filename = @path.sub(%r{.*\.kubes/resources/},'') # IE: web/deployment.rb or web/deployment.yaml
10
- @save_file = @filename.sub('.rb','.yaml')
10
+ @save_file = @filename.sub('.yml','.yaml').sub('.rb','.yaml')
11
11
  end
12
12
  end
13
13
  end
@@ -11,12 +11,10 @@ class Kubes::Compiler::Strategy
11
11
  @data.respond_to?(:read)
12
12
  end
13
13
 
14
- def compile_decorate!
15
- @data = Kubes::Compiler::Decorator::Compile.new(@data).result
16
- end
17
-
18
- def write_decorate!
19
- @data = Kubes::Compiler::Decorator::Write.new(@data).result
14
+ # decorate(:pre) or decorate(:post)
15
+ def decorate!(phase)
16
+ klass = "Kubes::Compiler::Decorator::#{phase.to_s.camelize}".constantize
17
+ @data = klass.new(@data).result
20
18
  end
21
19
 
22
20
  def content
@@ -11,31 +11,36 @@ module Kubes
11
11
  def defaults
12
12
  config = ActiveSupport::OrderedOptions.new
13
13
 
14
- config.state = ActiveSupport::OrderedOptions.new
15
- config.state.docker_image_path = "#{Kubes.root}/.kubes/state/docker_image.txt"
14
+ config.auto_prune = true
16
15
 
17
- config.logger = Logger.new($stdout)
18
- config.logger.level = ENV['KUBES_LOG_LEVEL'] || :info
16
+ config.builder = "docker" # IE: docker or gcloud
19
17
 
20
18
  # Auto-switching options
21
19
  config.kubectl = ActiveSupport::OrderedOptions.new
22
20
  config.kubectl.context = nil
23
21
  config.kubectl.context_keep = true # after switching context keep it
24
- config.kubectl.exit_on_fail = nil # whether or not continue if the kubectl command fails
25
22
 
26
- config.kubectl.exit_on_fail_for_apply = true # whether or not continue if the kubectl apply command fails
27
- config.kubectl.exit_on_fail_for_delete = false # whether or not continue if the kubectl delete command fails
28
- # Note: not using config.kubectl.delete.exit_on_fail because delete a method internal to ActiveSupport::OrderedOptions
23
+ # whether or not continue if the kubectl command fails
24
+ config.kubectl.exit_on_fail = ActiveSupport::OrderedOptions.new
25
+ config.kubectl.exit_on_fail.apply = true # whether or not continue if the kubectl apply command fails
26
+ config.kubectl.exit_on_fail.delete = false # whether or not continue if the kubectl delete command fails
27
+ # Note: delete is a internal method to ActiveSupport::OrderedOptions so will have to access it with ['...']
29
28
 
30
29
  config.kubectl.order = ActiveSupport::OrderedOptions.new
31
30
  config.kubectl.order.roles = role_order
32
31
  config.kubectl.order.kinds = kind_order
33
32
 
34
- config.suffix_hash = true # append suffix has to ConfigMap and Secret
35
-
36
33
  config.repo = nil # expected to be set by .kubes/config.rb
37
34
 
38
- config.builder = "docker" # IE: docker or gcloud
35
+ config.logger = Logger.new($stdout)
36
+ config.logger.level = ENV['KUBES_LOG_LEVEL'] || :info
37
+
38
+ config.skip = []
39
+
40
+ config.state = ActiveSupport::OrderedOptions.new
41
+ config.state.docker_image_path = "#{Kubes.root}/.kubes/state/docker_image.txt"
42
+
43
+ config.suffix_hash = true # append suffix hash to ConfigMap and Secret
39
44
 
40
45
  config
41
46
  end