kubes 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +6 -5
- data/docs/_docs/config/args.md +10 -0
- data/docs/_docs/config/args/docker.md +19 -0
- data/docs/_docs/config/{kubectl/args.md → args/kubectl.md} +2 -0
- data/docs/_docs/config/docker.md +4 -40
- data/docs/_docs/config/hooks.md +10 -0
- data/docs/_docs/config/hooks/docker.md +70 -0
- data/docs/_docs/config/hooks/kubectl.md +83 -0
- data/docs/_docs/config/hooks/kubes.md +67 -0
- data/docs/_docs/config/hooks/ruby.md +74 -0
- data/docs/_docs/config/kubectl.md +2 -2
- data/docs/_docs/config/reference.md +20 -0
- data/docs/_docs/config/skip.md +58 -0
- data/docs/_docs/dsl/resources.md +1 -1
- data/docs/_docs/intro.md +3 -1
- data/docs/_docs/patterns/migrations.md +121 -0
- data/docs/_includes/config/hooks/options.md +20 -0
- data/docs/_includes/sidebar.html +25 -12
- data/docs/_sass/theme.scss +25 -1
- data/lib/kubes/cli.rb +20 -5
- data/lib/kubes/cli/apply.rb +2 -1
- data/lib/kubes/cli/base.rb +11 -0
- data/lib/kubes/cli/delete.rb +1 -1
- data/lib/kubes/cli/exec.rb +37 -6
- data/lib/kubes/cli/get.rb +1 -1
- data/lib/kubes/cli/logs.rb +27 -3
- data/lib/kubes/cli/prune.rb +95 -0
- data/lib/kubes/compiler.rb +18 -7
- data/lib/kubes/compiler/decorator/base.rb +7 -1
- data/lib/kubes/compiler/decorator/{resources/secret.rb → hashable.rb} +5 -4
- data/lib/kubes/compiler/decorator/hashable/field.rb +53 -0
- data/lib/kubes/compiler/decorator/hashable/storage.rb +19 -0
- data/lib/kubes/compiler/decorator/post.rb +77 -0
- data/lib/kubes/compiler/decorator/pre.rb +12 -0
- data/lib/kubes/compiler/strategy.rb +2 -2
- data/lib/kubes/compiler/strategy/base.rb +1 -1
- data/lib/kubes/compiler/strategy/result.rb +4 -6
- data/lib/kubes/config.rb +16 -11
- data/lib/kubes/docker/strategy/build/docker.rb +1 -1
- data/lib/kubes/docker/strategy/build/gcloud.rb +1 -1
- data/lib/kubes/docker/strategy/image_name.rb +1 -1
- data/lib/kubes/docker/strategy/push/docker.rb +1 -1
- data/lib/kubes/docker/strategy/push/gcloud.rb +1 -1
- data/lib/kubes/docker/strategy/utils.rb +1 -1
- data/lib/kubes/hooks/builder.rb +29 -15
- data/lib/kubes/hooks/concern.rb +10 -0
- data/lib/kubes/hooks/dsl.rb +2 -1
- data/lib/kubes/hooks/runner.rb +22 -0
- data/lib/kubes/kubectl.rb +21 -18
- data/lib/kubes/kubectl/batch.rb +8 -5
- data/lib/kubes/kubectl/{decider.rb → dispatcher.rb} +1 -1
- data/lib/kubes/kubectl/fetch/base.rb +12 -9
- data/lib/kubes/kubectl/fetch/deployment.rb +12 -13
- data/lib/kubes/kubectl/fetch/pods.rb +4 -15
- data/lib/kubes/kubectl/kustomize.rb +1 -1
- data/lib/kubes/kubectl/ordering.rb +12 -0
- data/lib/kubes/util/consider.rb +2 -1
- data/lib/kubes/util/sh.rb +1 -1
- data/lib/kubes/version.rb +1 -1
- data/spec/fixtures/decorators/deployment/both/envFrom.yaml +31 -0
- data/spec/fixtures/prune/capture.yaml +57 -0
- data/spec/fixtures/prune/fetch_items.yaml +268 -0
- data/spec/kubes/cli/prune_spec.rb +38 -0
- data/spec/kubes/compiler/decorator/{resources → post}/deployment_spec.rb +25 -6
- data/spec/kubes/compiler/decorator/{resources → post}/pod_spec.rb +2 -11
- metadata +35 -19
- data/docs/_docs/config/kubectl/hooks.md +0 -39
- data/lib/kubes/compiler/decorator.rb +0 -17
- data/lib/kubes/compiler/decorator/compile.rb +0 -12
- data/lib/kubes/compiler/decorator/resources/base.rb +0 -13
- data/lib/kubes/compiler/decorator/resources/container.rb +0 -76
- data/lib/kubes/compiler/decorator/resources/container/mapping.rb +0 -28
- data/lib/kubes/compiler/decorator/resources/deployment.rb +0 -10
- data/lib/kubes/compiler/decorator/resources/pod.rb +0 -10
- data/lib/kubes/compiler/decorator/write.rb +0 -14
- data/lib/kubes/docker/strategy/hooks.rb +0 -9
data/lib/kubes/cli/exec.rb
CHANGED
@@ -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
|
-
|
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 =
|
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
|
-
|
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
|
data/lib/kubes/cli/get.rb
CHANGED
data/lib/kubes/cli/logs.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/kubes/compiler.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
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.
|
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
|
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
|
4
|
-
class
|
3
|
+
module Kubes::Compiler::Decorator
|
4
|
+
class Hashable < Base
|
5
5
|
include Kubes::Compiler::Util::YamlDump
|
6
6
|
|
7
|
-
def
|
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
|
-
|
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
|