kubes 0.3.5 → 0.4.4

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -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 +76 -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/helpers.md +3 -2
  18. data/docs/_docs/intro.md +3 -1
  19. data/docs/_docs/learn/dsl/review-project.md +4 -2
  20. data/docs/_docs/learn/yaml/review-project.md +4 -2
  21. data/docs/_docs/patterns.md +4 -1
  22. data/docs/_docs/patterns/clock-web-worker.md +2 -0
  23. data/docs/_docs/patterns/migrations.md +123 -0
  24. data/docs/_docs/patterns/secrets.md +82 -0
  25. data/docs/_includes/config/hooks/options.md +20 -0
  26. data/docs/_includes/layering/layers.md +1 -1
  27. data/docs/_includes/sidebar.html +28 -13
  28. data/docs/_sass/theme.scss +25 -1
  29. data/kubes.gemspec +3 -0
  30. data/lib/kubes.rb +4 -1
  31. data/lib/kubes/cli.rb +20 -5
  32. data/lib/kubes/cli/apply.rb +2 -1
  33. data/lib/kubes/cli/base.rb +11 -0
  34. data/lib/kubes/cli/compile.rb +8 -0
  35. data/lib/kubes/cli/delete.rb +1 -1
  36. data/lib/kubes/cli/exec.rb +37 -6
  37. data/lib/kubes/cli/get.rb +1 -1
  38. data/lib/kubes/cli/init.rb +7 -2
  39. data/lib/kubes/cli/logs.rb +27 -3
  40. data/lib/kubes/cli/prune.rb +95 -0
  41. data/lib/kubes/compiler.rb +11 -7
  42. data/lib/kubes/compiler/decorator/base.rb +7 -1
  43. data/lib/kubes/compiler/decorator/{resources/secret.rb → hashable.rb} +5 -4
  44. data/lib/kubes/compiler/decorator/hashable/field.rb +53 -0
  45. data/lib/kubes/compiler/decorator/hashable/storage.rb +19 -0
  46. data/lib/kubes/compiler/decorator/post.rb +77 -0
  47. data/lib/kubes/compiler/decorator/pre.rb +12 -0
  48. data/lib/kubes/compiler/shared/helpers.rb +7 -2
  49. data/lib/kubes/compiler/strategy.rb +2 -2
  50. data/lib/kubes/compiler/strategy/base.rb +2 -3
  51. data/lib/kubes/compiler/strategy/dsl.rb +2 -2
  52. data/lib/kubes/compiler/strategy/erb.rb +8 -1
  53. data/lib/kubes/compiler/strategy/erb/yaml_error.rb +60 -0
  54. data/lib/kubes/compiler/strategy/result.rb +4 -6
  55. data/lib/kubes/compiler/util/normalize.rb +1 -1
  56. data/lib/kubes/compiler/util/save_file.rb +8 -0
  57. data/lib/kubes/config.rb +16 -11
  58. data/lib/kubes/docker/strategy/build/docker.rb +1 -1
  59. data/lib/kubes/docker/strategy/build/gcloud.rb +1 -1
  60. data/lib/kubes/docker/strategy/image_name.rb +1 -1
  61. data/lib/kubes/docker/strategy/push/docker.rb +1 -1
  62. data/lib/kubes/docker/strategy/push/gcloud.rb +1 -1
  63. data/lib/kubes/docker/strategy/utils.rb +1 -1
  64. data/lib/kubes/hooks/builder.rb +29 -15
  65. data/lib/kubes/hooks/concern.rb +10 -0
  66. data/lib/kubes/hooks/dsl.rb +2 -1
  67. data/lib/kubes/hooks/runner.rb +22 -0
  68. data/lib/kubes/kubectl.rb +21 -18
  69. data/lib/kubes/kubectl/batch.rb +8 -5
  70. data/lib/kubes/kubectl/{decider.rb → dispatcher.rb} +1 -1
  71. data/lib/kubes/kubectl/fetch/base.rb +12 -9
  72. data/lib/kubes/kubectl/fetch/deployment.rb +12 -13
  73. data/lib/kubes/kubectl/fetch/pods.rb +4 -15
  74. data/lib/kubes/kubectl/kustomize.rb +1 -1
  75. data/lib/kubes/kubectl/ordering.rb +12 -0
  76. data/lib/kubes/util/consider.rb +2 -1
  77. data/lib/kubes/util/sh.rb +1 -1
  78. data/lib/kubes/version.rb +1 -1
  79. data/spec/fixtures/decorators/deployment/both/envFrom.yaml +31 -0
  80. data/spec/fixtures/decorators/deployment/both/valueFrom.yaml +33 -0
  81. data/spec/fixtures/decorators/deployment/both/volumes.yaml +40 -0
  82. data/spec/fixtures/prune/capture.yaml +57 -0
  83. data/spec/fixtures/prune/fetch_items.yaml +268 -0
  84. data/spec/kubes/cli/prune_spec.rb +38 -0
  85. data/spec/kubes/compiler/decorator/{resources → post}/deployment_spec.rb +52 -6
  86. data/spec/kubes/compiler/decorator/{resources → post}/pod_spec.rb +2 -11
  87. metadata +56 -19
  88. data/docs/_docs/config/kubectl/hooks.md +0 -39
  89. data/lib/kubes/compiler/decorator.rb +0 -17
  90. data/lib/kubes/compiler/decorator/compile.rb +0 -12
  91. data/lib/kubes/compiler/decorator/resources/base.rb +0 -13
  92. data/lib/kubes/compiler/decorator/resources/container.rb +0 -76
  93. data/lib/kubes/compiler/decorator/resources/container/mapping.rb +0 -28
  94. data/lib/kubes/compiler/decorator/resources/deployment.rb +0 -10
  95. data/lib/kubes/compiler/decorator/resources/pod.rb +0 -10
  96. data/lib/kubes/compiler/decorator/write.rb +0 -14
  97. data/lib/kubes/docker/strategy/hooks.rb +0 -9
@@ -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
@@ -1,13 +1,12 @@
1
1
  class Kubes::Compiler::Strategy
2
2
  class Base
3
3
  include Kubes::Logging
4
+ include Kubes::Compiler::Util::SaveFile
4
5
 
5
6
  def initialize(options={})
6
7
  @options = options
7
8
  @path = options[:path]
8
-
9
- @filename = @path.sub(%r{.*\.kubes/resources/},'') # IE: web/deployment.rb or web/deployment.yaml
10
- @save_file = @filename.sub('.rb','.yaml')
9
+ @save_file = save_file(@path)
11
10
  end
12
11
  end
13
12
  end
@@ -17,7 +17,7 @@ class Kubes::Compiler::Strategy
17
17
  end
18
18
 
19
19
  def syntax_class
20
- klass_name = normalize_kind(@filename)
20
+ klass_name = normalize_kind(@save_file)
21
21
  "Kubes::Compiler::Dsl::Syntax::#{klass_name}".constantize
22
22
  rescue NameError
23
23
  logger.debug "Using default resource for: #{klass_name}"
@@ -25,7 +25,7 @@ class Kubes::Compiler::Strategy
25
25
  end
26
26
 
27
27
  def block_form?
28
- type = extract_type(@filename)
28
+ type = extract_type(@save_file)
29
29
  type.pluralize == type
30
30
  end
31
31
  end
@@ -32,11 +32,18 @@ class Kubes::Compiler::Strategy
32
32
  def render_result(path)
33
33
  if File.exist?(path)
34
34
  yaml = RenderMePretty.result(path, context: self)
35
- result = YAML.load(yaml)
35
+ result = yaml_load(path, yaml)
36
36
  result.is_a?(Hash) ? result : {} # in case of blank yaml doc a Boolean false is returned
37
37
  else
38
38
  {}
39
39
  end
40
40
  end
41
+
42
+ def yaml_load(path, yaml)
43
+ YAML.load(yaml)
44
+ rescue Psych::SyntaxError
45
+ YamlError.new(path, yaml).show
46
+ exit 1
47
+ end
41
48
  end
42
49
  end
@@ -0,0 +1,60 @@
1
+ class Kubes::Compiler::Strategy::Erb
2
+ class YamlError
3
+ include Kubes::Compiler::Util::SaveFile
4
+
5
+ def initialize(path, rendered_yaml)
6
+ @path, @rendered_yaml = path, rendered_yaml
7
+ end
8
+
9
+ def show
10
+ FileUtils.mkdir_p(File.dirname(dest))
11
+ IO.write(dest, @rendered_yaml)
12
+ show_error(dest)
13
+ end
14
+
15
+ def dest
16
+ save_file = save_file(@path)
17
+ "#{Kubes.root}/.kubes/output/#{save_file}"
18
+ end
19
+
20
+ def show_error(path)
21
+ text = IO.read(path)
22
+ begin
23
+ YAML.load(text)
24
+ rescue Psych::SyntaxError => e
25
+ handle_yaml_syntax_error(e, path)
26
+ end
27
+ end
28
+
29
+ def handle_yaml_syntax_error(e, path)
30
+ io = StringIO.new
31
+ io.puts "Invalid yaml. Output written for debugging: #{path}".color(:red)
32
+ io.puts "ERROR: #{e.message}".color(:red)
33
+
34
+ # Grab line info. Example error:
35
+ # ERROR: (<unknown>): could not find expected ':' while scanning a simple key at line 2 column 1
36
+ md = e.message.match(/at line (\d+) column (\d+)/)
37
+ line = md[1].to_i
38
+
39
+ lines = IO.read(path).split("\n")
40
+ context = 5 # lines of context
41
+ top, bottom = [line-context-1, 0].max, line+context-1
42
+ spacing = lines.size.to_s.size
43
+ lines[top..bottom].each_with_index do |line_content, index|
44
+ line_number = top+index+1
45
+ if line_number == line
46
+ io.printf("%#{spacing}d %s\n".color(:red), line_number, line_content)
47
+ else
48
+ io.printf("%#{spacing}d %s\n", line_number, line_content)
49
+ end
50
+ end
51
+
52
+ if ENV['KUBES_TEST']
53
+ io.string
54
+ else
55
+ puts io.string
56
+ exit 1
57
+ end
58
+ end
59
+ end
60
+ 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
@@ -5,7 +5,7 @@ module Kubes::Compiler::Util
5
5
  end
6
6
 
7
7
  def extract_type(path)
8
- File.basename(path).sub('.rb','').sub(/-.*/,'')
8
+ File.basename(path).sub('.yaml','').sub('.yml','').sub('.rb','').sub(/-.*/,'')
9
9
  end
10
10
  end
11
11
  end
@@ -0,0 +1,8 @@
1
+ module Kubes::Compiler::Util
2
+ module SaveFile
3
+ def save_file(path)
4
+ filename = path.sub(%r{.*\.kubes/resources/},'') # IE: web/deployment.rb or web/deployment.yaml
5
+ filename.sub('.yml','.yaml').sub('.rb','.yaml')
6
+ end
7
+ end
8
+ end
@@ -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