kubes 0.3.5 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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