kubernetes-deploy 0.6.6 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/kubernetes-deploy +21 -13
- data/exe/kubernetes-restart +7 -4
- data/exe/kubernetes-run +14 -10
- data/kubernetes-deploy.gemspec +1 -0
- data/lib/kubernetes-deploy.rb +3 -2
- data/lib/kubernetes-deploy/deferred_summary_logging.rb +87 -0
- data/lib/kubernetes-deploy/ejson_secret_provisioner.rb +18 -20
- data/lib/kubernetes-deploy/formatted_logger.rb +42 -0
- data/lib/kubernetes-deploy/kubectl.rb +21 -8
- data/lib/kubernetes-deploy/kubernetes_resource.rb +111 -52
- data/lib/kubernetes-deploy/kubernetes_resource/bugsnag.rb +3 -11
- data/lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb +7 -14
- data/lib/kubernetes-deploy/kubernetes_resource/config_map.rb +5 -9
- data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +31 -14
- data/lib/kubernetes-deploy/kubernetes_resource/ingress.rb +1 -13
- data/lib/kubernetes-deploy/kubernetes_resource/persistent_volume_claim.rb +2 -9
- data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +48 -22
- data/lib/kubernetes-deploy/kubernetes_resource/pod_disruption_budget.rb +5 -9
- data/lib/kubernetes-deploy/kubernetes_resource/pod_template.rb +5 -9
- data/lib/kubernetes-deploy/kubernetes_resource/redis.rb +9 -15
- data/lib/kubernetes-deploy/kubernetes_resource/service.rb +9 -10
- data/lib/kubernetes-deploy/resource_watcher.rb +22 -10
- data/lib/kubernetes-deploy/restart_task.rb +12 -7
- data/lib/kubernetes-deploy/runner.rb +163 -110
- data/lib/kubernetes-deploy/runner_task.rb +22 -19
- data/lib/kubernetes-deploy/version.rb +1 -1
- metadata +18 -4
- data/lib/kubernetes-deploy/logger.rb +0 -45
- data/lib/kubernetes-deploy/ui_helpers.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60da18d26c2e8a36c8761924b17e1b263eacaf70
|
4
|
+
data.tar.gz: 6f05ac075997226ed8c526f6d02cbb4406617b55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da4f1f609882fb90af50e1505a9ca841eb8e6a95de220b3471686425964c953b025858bb86835b9d50f5f6053a77188affaf363d690b2211914a1c3bad4d17e3
|
7
|
+
data.tar.gz: 7fe5caa73866be72e7aa2351a4e0d562d4891eda7fc8261d4d9ceb5b5abc972bc09ca26a67459eec72d2b516bbb77fde10e4d63d78bbe791a93e2b810f5211d1
|
data/exe/kubernetes-deploy
CHANGED
@@ -10,6 +10,7 @@ template_dir = nil
|
|
10
10
|
allow_protected_ns = false
|
11
11
|
prune = true
|
12
12
|
bindings = {}
|
13
|
+
verbose_log_prefix = false
|
13
14
|
|
14
15
|
ARGV.options do |opts|
|
15
16
|
opts.on("--bindings=BINDINGS", Array, "k1=v1,k2=v2") do |binds|
|
@@ -25,6 +26,7 @@ ARGV.options do |opts|
|
|
25
26
|
opts.on("--allow-protected-ns") { allow_protected_ns = true }
|
26
27
|
opts.on("--no-prune") { prune = false }
|
27
28
|
opts.on("--template-dir=DIR") { |v| template_dir = v }
|
29
|
+
opts.on("--verbose-log-prefix") { verbose_log_prefix = true }
|
28
30
|
opts.parse!
|
29
31
|
end
|
30
32
|
|
@@ -43,16 +45,22 @@ revision = ENV.fetch('REVISION') do
|
|
43
45
|
exit 1
|
44
46
|
end
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
48
|
+
namespace = ARGV[0]
|
49
|
+
context = ARGV[1]
|
50
|
+
logger = KubernetesDeploy::FormattedLogger.build(namespace, context, verbose_prefix: verbose_log_prefix)
|
51
|
+
|
52
|
+
runner = KubernetesDeploy::Runner.new(
|
53
|
+
namespace: namespace,
|
54
|
+
context: context,
|
55
|
+
current_sha: revision,
|
56
|
+
template_dir: template_dir,
|
57
|
+
bindings: bindings,
|
58
|
+
logger: logger
|
59
|
+
)
|
60
|
+
|
61
|
+
success = runner.run(
|
62
|
+
verify_result: !skip_wait,
|
63
|
+
allow_protected_ns: allow_protected_ns,
|
64
|
+
prune: prune
|
65
|
+
)
|
66
|
+
exit 1 unless success
|
data/exe/kubernetes-restart
CHANGED
@@ -12,7 +12,10 @@ ARGV.options do |opts|
|
|
12
12
|
opts.parse!
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
namespace = ARGV[0]
|
16
|
+
context = ARGV[1]
|
17
|
+
logger = KubernetesDeploy::FormattedLogger.build(namespace, context)
|
18
|
+
|
19
|
+
restart = KubernetesDeploy::RestartTask.new(namespace: namespace, context: context, logger: logger)
|
20
|
+
success = restart.perform(raw_deployments)
|
21
|
+
exit 1 unless success
|
data/exe/kubernetes-run
CHANGED
@@ -16,16 +16,20 @@ ARGV.options do |opts|
|
|
16
16
|
opts.parse!
|
17
17
|
end
|
18
18
|
|
19
|
+
namespace = ARGV[0]
|
20
|
+
context = ARGV[1]
|
21
|
+
logger = KubernetesDeploy::FormattedLogger.build(namespace, context)
|
22
|
+
|
19
23
|
runner = KubernetesDeploy::RunnerTask.new(
|
20
|
-
namespace:
|
21
|
-
context:
|
24
|
+
namespace: namespace,
|
25
|
+
context: context,
|
26
|
+
logger: logger
|
22
27
|
)
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
29
|
+
success = runner.run(
|
30
|
+
task_template: template,
|
31
|
+
entrypoint: entrypoint,
|
32
|
+
args: ARGV[2..-1],
|
33
|
+
env_vars: env_vars
|
34
|
+
)
|
35
|
+
exit 1 unless success
|
data/kubernetes-deploy.gemspec
CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency "kubeclient", "~> 2.3"
|
25
25
|
spec.add_dependency "googleauth", ">= 0.5"
|
26
26
|
spec.add_dependency "ejson", "1.0.1"
|
27
|
+
spec.add_dependency "colorize", "~> 0.8"
|
27
28
|
|
28
29
|
spec.add_development_dependency "bundler", "~> 1.13"
|
29
30
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/lib/kubernetes-deploy.rb
CHANGED
@@ -5,11 +5,12 @@ require 'active_support/core_ext/numeric/time'
|
|
5
5
|
require 'active_support/core_ext/string/inflections'
|
6
6
|
require 'active_support/core_ext/string/strip'
|
7
7
|
require 'active_support/core_ext/hash/keys'
|
8
|
+
require 'active_support/core_ext/array/conversions'
|
9
|
+
require 'colorized_string'
|
8
10
|
|
9
11
|
require 'kubernetes-deploy/errors'
|
10
|
-
require 'kubernetes-deploy/
|
12
|
+
require 'kubernetes-deploy/formatted_logger'
|
11
13
|
require 'kubernetes-deploy/runner'
|
12
14
|
|
13
15
|
module KubernetesDeploy
|
14
|
-
include Logger
|
15
16
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module KubernetesDeploy
|
2
|
+
# Adds the methods kubernetes-deploy requires to your logger class.
|
3
|
+
# These methods include helpers for logging consistent headings, as well as facilities for
|
4
|
+
# displaying key information later, in a summary section, rather than when it occurred.
|
5
|
+
module DeferredSummaryLogging
|
6
|
+
attr_reader :summary
|
7
|
+
def initialize(*args)
|
8
|
+
reset
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset
|
13
|
+
@summary = DeferredSummary.new
|
14
|
+
@current_phase = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def blank_line(level = :info)
|
18
|
+
public_send(level, "")
|
19
|
+
end
|
20
|
+
|
21
|
+
def phase_heading(phase_name)
|
22
|
+
@current_phase += 1
|
23
|
+
heading("Phase #{@current_phase}: #{phase_name}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def heading(text, secondary_msg = '', secondary_msg_color = :blue)
|
27
|
+
padding = (100.0 - (text.length + secondary_msg.length)) / 2
|
28
|
+
blank_line
|
29
|
+
part1 = ColorizedString.new("#{'-' * padding.floor}#{text}").blue
|
30
|
+
part2 = ColorizedString.new(secondary_msg).colorize(secondary_msg_color)
|
31
|
+
part3 = ColorizedString.new('-' * padding.ceil).blue
|
32
|
+
info(part1 + part2 + part3)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Outputs the deferred summary information saved via @logger.summary.add_action and @logger.summary.add_paragraph
|
36
|
+
def print_summary(success)
|
37
|
+
if success
|
38
|
+
heading("Result: ", "SUCCESS", :green)
|
39
|
+
level = :info
|
40
|
+
else
|
41
|
+
heading("Result: ", "FAILURE", :red)
|
42
|
+
level = :fatal
|
43
|
+
end
|
44
|
+
|
45
|
+
public_send(level, summary.actions_sentence)
|
46
|
+
summary.paragraphs.each do |para|
|
47
|
+
blank_line(level)
|
48
|
+
msg_lines = para.split("\n")
|
49
|
+
msg_lines.each { |line| public_send(level, line) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class DeferredSummary
|
54
|
+
attr_reader :paragraphs
|
55
|
+
|
56
|
+
def initialize
|
57
|
+
@actions_taken = []
|
58
|
+
@paragraphs = []
|
59
|
+
end
|
60
|
+
|
61
|
+
def actions_sentence
|
62
|
+
case @actions_taken.length
|
63
|
+
when 0 then "No actions taken"
|
64
|
+
else
|
65
|
+
@actions_taken.to_sentence.capitalize
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Saves a sentence fragment to be displayed in the first sentence of the summary section
|
70
|
+
#
|
71
|
+
# Example:
|
72
|
+
# # The resulting summary will begin with "Created 3 secrets and failed to deploy 2 resources"
|
73
|
+
# @logger.summary.add_action("created 3 secrets")
|
74
|
+
# @logger.summary.add_cation("failed to deploy 2 resources")
|
75
|
+
def add_action(sentence_fragment)
|
76
|
+
@actions_taken << sentence_fragment
|
77
|
+
end
|
78
|
+
|
79
|
+
# Adds a paragraph to be displayed in the summary section
|
80
|
+
# Paragraphs will be printed in the order they were added, separated by a blank line
|
81
|
+
# This can be used to log a block of data on a particular topic, e.g. debug info for a particular failed resource
|
82
|
+
def add_paragraph(paragraph)
|
83
|
+
paragraphs << paragraph
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
require 'json'
|
3
3
|
require 'base64'
|
4
4
|
require 'open3'
|
5
|
-
require 'kubernetes-deploy/logger'
|
6
5
|
require 'kubernetes-deploy/kubectl'
|
7
6
|
|
8
7
|
module KubernetesDeploy
|
@@ -18,13 +17,12 @@ module KubernetesDeploy
|
|
18
17
|
EJSON_SECRETS_FILE = "secrets.ejson"
|
19
18
|
EJSON_KEYS_SECRET = "ejson-keys"
|
20
19
|
|
21
|
-
def initialize(namespace:, context:, template_dir:)
|
20
|
+
def initialize(namespace:, context:, template_dir:, logger:)
|
22
21
|
@namespace = namespace
|
23
22
|
@context = context
|
24
23
|
@ejson_file = "#{template_dir}/#{EJSON_SECRETS_FILE}"
|
25
|
-
|
26
|
-
|
27
|
-
raise FatalDeploymentError, "Cannot create secrets without a context" if @context.blank?
|
24
|
+
@logger = logger
|
25
|
+
@kubectl = Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: false)
|
28
26
|
end
|
29
27
|
|
30
28
|
def secret_changes_required?
|
@@ -42,7 +40,7 @@ module KubernetesDeploy
|
|
42
40
|
with_decrypted_ejson do |decrypted|
|
43
41
|
secrets = decrypted[MANAGED_SECRET_EJSON_KEY]
|
44
42
|
unless secrets.present?
|
45
|
-
|
43
|
+
@logger.warn("#{EJSON_SECRETS_FILE} does not have key #{MANAGED_SECRET_EJSON_KEY}."\
|
46
44
|
"No secrets will be created.")
|
47
45
|
return
|
48
46
|
end
|
@@ -51,6 +49,7 @@ module KubernetesDeploy
|
|
51
49
|
validate_secret_spec(secret_name, secret_spec)
|
52
50
|
create_or_update_secret(secret_name, secret_spec["_type"], secret_spec["data"])
|
53
51
|
end
|
52
|
+
@logger.summary.add_action("created/updated #{secrets.length} #{'secret'.pluralize(secrets.length)}")
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
@@ -58,16 +57,19 @@ module KubernetesDeploy
|
|
58
57
|
ejson_secret_names = encrypted_ejson.fetch(MANAGED_SECRET_EJSON_KEY, {}).keys
|
59
58
|
live_secrets = run_kubectl_json("get", "secrets")
|
60
59
|
|
60
|
+
prune_count = 0
|
61
61
|
live_secrets.each do |secret|
|
62
62
|
secret_name = secret["metadata"]["name"]
|
63
63
|
next unless secret_managed?(secret)
|
64
64
|
next if ejson_secret_names.include?(secret_name)
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
@logger.info("Pruning secret #{secret_name}")
|
67
|
+
prune_count += 1
|
68
|
+
out, err, st = @kubectl.run("delete", "secret", secret_name)
|
69
|
+
@logger.debug(out)
|
69
70
|
raise EjsonSecretError, err unless st.success?
|
70
71
|
end
|
72
|
+
@logger.summary.add_action("pruned #{prune_count} #{'secret'.pluralize(prune_count)}") if prune_count > 0
|
71
73
|
end
|
72
74
|
|
73
75
|
def managed_secrets_exist?
|
@@ -103,15 +105,15 @@ module KubernetesDeploy
|
|
103
105
|
|
104
106
|
def create_or_update_secret(secret_name, secret_type, data)
|
105
107
|
msg = secret_exists?(secret_name) ? "Updating secret #{secret_name}" : "Creating secret #{secret_name}"
|
106
|
-
|
108
|
+
@logger.info(msg)
|
107
109
|
|
108
110
|
secret_yaml = generate_secret_yaml(secret_name, secret_type, data)
|
109
111
|
file = Tempfile.new(secret_name)
|
110
112
|
file.write(secret_yaml)
|
111
113
|
file.close
|
112
114
|
|
113
|
-
out, err, st =
|
114
|
-
|
115
|
+
out, err, st = @kubectl.run("apply", "--filename=#{file.path}")
|
116
|
+
@logger.debug(out)
|
115
117
|
raise EjsonSecretError, err unless st.success?
|
116
118
|
ensure
|
117
119
|
file.unlink if file
|
@@ -144,7 +146,7 @@ module KubernetesDeploy
|
|
144
146
|
end
|
145
147
|
|
146
148
|
def secret_exists?(secret_name)
|
147
|
-
_out, _err, st =
|
149
|
+
_out, _err, st = @kubectl.run("get", "secret", secret_name)
|
148
150
|
st.success?
|
149
151
|
end
|
150
152
|
|
@@ -166,7 +168,7 @@ module KubernetesDeploy
|
|
166
168
|
end
|
167
169
|
|
168
170
|
def decrypt_ejson(key_dir)
|
169
|
-
|
171
|
+
@logger.info("Decrypting #{EJSON_SECRETS_FILE}")
|
170
172
|
# ejson seems to dump both errors and output to STDOUT
|
171
173
|
out_err, st = Open3.capture2e("EJSON_KEYDIR=#{key_dir} ejson decrypt #{@ejson_file}")
|
172
174
|
raise EjsonSecretError, out_err unless st.success?
|
@@ -176,7 +178,7 @@ module KubernetesDeploy
|
|
176
178
|
end
|
177
179
|
|
178
180
|
def fetch_private_key_from_secret
|
179
|
-
|
181
|
+
@logger.info("Fetching ejson private key from secret #{EJSON_KEYS_SECRET}")
|
180
182
|
|
181
183
|
secret = run_kubectl_json("get", "secret", EJSON_KEYS_SECRET)
|
182
184
|
encoded_private_key = secret["data"][public_key]
|
@@ -189,14 +191,10 @@ module KubernetesDeploy
|
|
189
191
|
|
190
192
|
def run_kubectl_json(*args)
|
191
193
|
args += ["--output=json"]
|
192
|
-
out, err, st =
|
194
|
+
out, err, st = @kubectl.run(*args)
|
193
195
|
raise EjsonSecretError, err unless st.success?
|
194
196
|
result = JSON.parse(out)
|
195
197
|
result.fetch('items', result)
|
196
198
|
end
|
197
|
-
|
198
|
-
def run_kubectl(*args)
|
199
|
-
Kubectl.run_kubectl(*args, namespace: @namespace, context: @context, log_failure: false)
|
200
|
-
end
|
201
199
|
end
|
202
200
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'logger'
|
3
|
+
require 'kubernetes-deploy/deferred_summary_logging'
|
4
|
+
|
5
|
+
module KubernetesDeploy
|
6
|
+
class FormattedLogger < Logger
|
7
|
+
include DeferredSummaryLogging
|
8
|
+
|
9
|
+
def self.build(namespace, context, stream = $stderr, verbose_prefix: false)
|
10
|
+
l = new(stream)
|
11
|
+
l.level = level_from_env
|
12
|
+
|
13
|
+
l.formatter = proc do |severity, datetime, _progname, msg|
|
14
|
+
middle = verbose_prefix ? "[#{context}][#{namespace}]" : ""
|
15
|
+
colorized_line = ColorizedString.new("[#{severity}][#{datetime}]#{middle}\t#{msg}\n")
|
16
|
+
|
17
|
+
case severity
|
18
|
+
when "FATAL"
|
19
|
+
ColorizedString.new("[#{severity}][#{datetime}]#{middle}\t").red + "#{msg}\n"
|
20
|
+
when "ERROR"
|
21
|
+
colorized_line.red
|
22
|
+
when "WARN"
|
23
|
+
colorized_line.yellow
|
24
|
+
else
|
25
|
+
colorized_line
|
26
|
+
end
|
27
|
+
end
|
28
|
+
l
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.level_from_env
|
32
|
+
return ::Logger::DEBUG if ENV["DEBUG"]
|
33
|
+
|
34
|
+
if ENV["LEVEL"]
|
35
|
+
::Logger.const_get(ENV["LEVEL"].upcase)
|
36
|
+
else
|
37
|
+
::Logger::INFO
|
38
|
+
end
|
39
|
+
end
|
40
|
+
private_class_method :level_from_env
|
41
|
+
end
|
42
|
+
end
|
@@ -1,18 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module KubernetesDeploy
|
4
|
-
|
5
|
-
def
|
4
|
+
class Kubectl
|
5
|
+
def initialize(namespace:, context:, logger:, log_failure_by_default:)
|
6
|
+
@namespace = namespace
|
7
|
+
@context = context
|
8
|
+
@logger = logger
|
9
|
+
@log_failure_by_default = log_failure_by_default
|
10
|
+
|
11
|
+
raise ArgumentError, "namespace is required" if namespace.blank?
|
12
|
+
raise ArgumentError, "context is required" if context.blank?
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(*args, log_failure: nil, use_context: true, use_namespace: true)
|
16
|
+
log_failure = @log_failure_by_default if log_failure.nil?
|
17
|
+
|
6
18
|
args = args.unshift("kubectl")
|
7
|
-
args.push("--namespace=#{namespace}") if
|
8
|
-
args.push("--context=#{context}") if
|
19
|
+
args.push("--namespace=#{@namespace}") if use_namespace
|
20
|
+
args.push("--context=#{@context}") if use_context
|
9
21
|
|
10
|
-
|
22
|
+
@logger.debug Shellwords.join(args)
|
11
23
|
out, err, st = Open3.capture3(*args)
|
12
|
-
|
24
|
+
@logger.debug(out.shellescape)
|
25
|
+
|
13
26
|
if !st.success? && log_failure
|
14
|
-
|
15
|
-
|
27
|
+
@logger.warn("The following command failed: #{Shellwords.join(args)}")
|
28
|
+
@logger.warn(err)
|
16
29
|
end
|
17
30
|
[out.chomp, err.chomp, st]
|
18
31
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'json'
|
2
3
|
require 'open3'
|
3
4
|
require 'shellwords'
|
@@ -5,39 +6,42 @@ require 'kubernetes-deploy/kubectl'
|
|
5
6
|
|
6
7
|
module KubernetesDeploy
|
7
8
|
class KubernetesResource
|
8
|
-
def self.logger=(value)
|
9
|
-
@logger = value
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.logger
|
13
|
-
@logger ||= begin
|
14
|
-
l = ::Logger.new($stderr)
|
15
|
-
l.formatter = proc do |_severity, datetime, _progname, msg|
|
16
|
-
"[KUBESTATUS][#{datetime}]\t#{msg}\n"
|
17
|
-
end
|
18
|
-
l
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
9
|
attr_reader :name, :namespace, :file, :context
|
23
10
|
attr_writer :type, :deploy_started
|
24
11
|
|
25
12
|
TIMEOUT = 5.minutes
|
26
13
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
when '
|
40
|
-
|
14
|
+
DEBUG_RESOURCE_NOT_FOUND_MESSAGE = "None found. Please check your usual logging service (e.g. Splunk)."
|
15
|
+
UNUSUAL_FAILURE_MESSAGE = <<-MSG.strip_heredoc
|
16
|
+
It is very unusual for this resource type to fail to deploy. Please try the deploy again.
|
17
|
+
If that new deploy also fails, contact your cluster administrator.
|
18
|
+
MSG
|
19
|
+
STANDARD_TIMEOUT_MESSAGE = <<-MSG.strip_heredoc
|
20
|
+
Kubernetes will continue to attempt to deploy this resource in the cluster, but at this point it is considered unlikely that it will succeed.
|
21
|
+
If you have reason to believe it will succeed, retry the deploy to continue to monitor the rollout.
|
22
|
+
MSG
|
23
|
+
|
24
|
+
def self.for_type(type:, name:, namespace:, context:, file:, logger:)
|
25
|
+
subclass = case type
|
26
|
+
when 'cloudsql' then Cloudsql
|
27
|
+
when 'configmap' then ConfigMap
|
28
|
+
when 'deployment' then Deployment
|
29
|
+
when 'pod' then Pod
|
30
|
+
when 'redis' then Redis
|
31
|
+
when 'bugsnag' then Bugsnag
|
32
|
+
when 'ingress' then Ingress
|
33
|
+
when 'persistentvolumeclaim' then PersistentVolumeClaim
|
34
|
+
when 'service' then Service
|
35
|
+
when 'podtemplate' then PodTemplate
|
36
|
+
when 'poddisruptionbudget' then PodDisruptionBudget
|
37
|
+
end
|
38
|
+
|
39
|
+
opts = { name: name, namespace: namespace, context: context, file: file, logger: logger }
|
40
|
+
if subclass
|
41
|
+
subclass.new(**opts)
|
42
|
+
else
|
43
|
+
inst = new(**opts)
|
44
|
+
inst.tap { |r| r.type = type }
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
@@ -49,9 +53,13 @@ module KubernetesDeploy
|
|
49
53
|
self.class.timeout
|
50
54
|
end
|
51
55
|
|
52
|
-
def initialize(name
|
53
|
-
# subclasses must also set these
|
54
|
-
@name
|
56
|
+
def initialize(name:, namespace:, context:, file:, logger:)
|
57
|
+
# subclasses must also set these if they define their own initializer
|
58
|
+
@name = name
|
59
|
+
@namespace = namespace
|
60
|
+
@context = context
|
61
|
+
@file = file
|
62
|
+
@logger = logger
|
55
63
|
end
|
56
64
|
|
57
65
|
def id
|
@@ -59,7 +67,6 @@ module KubernetesDeploy
|
|
59
67
|
end
|
60
68
|
|
61
69
|
def sync
|
62
|
-
log_status
|
63
70
|
end
|
64
71
|
|
65
72
|
def deploy_failed?
|
@@ -68,7 +75,7 @@ module KubernetesDeploy
|
|
68
75
|
|
69
76
|
def deploy_succeeded?
|
70
77
|
if @deploy_started && !@success_assumption_warning_shown
|
71
|
-
|
78
|
+
@logger.warn("Don't know how to monitor resources of type #{type}. Assuming #{id} deployed successfully.")
|
72
79
|
@success_assumption_warning_shown = true
|
73
80
|
end
|
74
81
|
true
|
@@ -80,7 +87,6 @@ module KubernetesDeploy
|
|
80
87
|
|
81
88
|
def status
|
82
89
|
@status ||= "Unknown"
|
83
|
-
deploy_timed_out? ? "Timed out with status #{@status}" : @status
|
84
90
|
end
|
85
91
|
|
86
92
|
def type
|
@@ -106,31 +112,84 @@ module KubernetesDeploy
|
|
106
112
|
tpr? ? :replace : :apply
|
107
113
|
end
|
108
114
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
}
|
115
|
+
def debug_message
|
116
|
+
helpful_info = []
|
117
|
+
if deploy_failed?
|
118
|
+
helpful_info << ColorizedString.new("#{id}: FAILED").red
|
119
|
+
helpful_info << failure_message if failure_message.present?
|
120
|
+
else
|
121
|
+
helpful_info << ColorizedString.new("#{id}: TIMED OUT").yellow + " (limit: #{timeout}s)"
|
122
|
+
helpful_info << timeout_message if timeout_message.present?
|
123
|
+
end
|
124
|
+
helpful_info << " - Final status: #{status}"
|
125
|
+
|
126
|
+
events = fetch_events
|
127
|
+
if events.present?
|
128
|
+
helpful_info << " - Events:"
|
129
|
+
events.each do |identifier, event_hashes|
|
130
|
+
event_hashes.each { |event| helpful_info << " [#{identifier}]\t#{event}" }
|
131
|
+
end
|
132
|
+
else
|
133
|
+
helpful_info << " - Events: #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
|
134
|
+
end
|
135
|
+
|
136
|
+
if respond_to?(:fetch_logs)
|
137
|
+
container_logs = fetch_logs
|
138
|
+
if container_logs.blank? || container_logs.values.all?(&:blank?)
|
139
|
+
helpful_info << " - Logs: #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
|
140
|
+
else
|
141
|
+
helpful_info << " - Logs:"
|
142
|
+
container_logs.each do |identifier, logs|
|
143
|
+
logs.split("\n").each do |line|
|
144
|
+
helpful_info << " [#{identifier}]\t#{line}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
helpful_info.join("\n")
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns a hash in the following format:
|
154
|
+
# {
|
155
|
+
# "pod/web-1" => [
|
156
|
+
# {"subject_kind" => "Pod", "some" => "stuff"}, # event 1
|
157
|
+
# {"subject_kind" => "Pod", "other" => "stuff"}, # event 2
|
158
|
+
# ]
|
159
|
+
# }
|
160
|
+
def fetch_events
|
161
|
+
return {} unless exists?
|
162
|
+
fields = "{.involvedObject.kind}\t{.count}\t{.message}\t{.reason}"
|
163
|
+
out, _err, st = kubectl.run("get", "events",
|
164
|
+
%(--output=jsonpath={range .items[?(@.involvedObject.name=="#{name}")]}#{fields}\n{end}))
|
165
|
+
return {} unless st.success?
|
166
|
+
|
167
|
+
event_collector = Hash.new { |hash, key| hash[key] = [] }
|
168
|
+
out.split("\n").each_with_object(event_collector) do |event_blob, events|
|
169
|
+
pieces = event_blob.split("\t")
|
170
|
+
subject_kind = pieces[0]
|
171
|
+
# jsonpath can only filter by one thing at a time, and we chose involvedObject.name
|
172
|
+
# This means we still need to filter by involvedObject.kind here to make sure we only retain relevant events
|
173
|
+
next unless subject_kind.downcase == type.downcase
|
174
|
+
events[id] << "#{pieces[3] || 'Unknown'}: #{pieces[2]} (#{pieces[1]} events)" # Reason: Message (X events)
|
175
|
+
end
|
119
176
|
end
|
120
177
|
|
121
|
-
def
|
122
|
-
|
178
|
+
def timeout_message
|
179
|
+
STANDARD_TIMEOUT_MESSAGE
|
123
180
|
end
|
124
181
|
|
125
|
-
def
|
126
|
-
KubernetesResource.logger.info("[#{@context}][#{@namespace}] #{JSON.dump(status_data)}")
|
182
|
+
def failure_message
|
127
183
|
end
|
128
184
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
185
|
+
def pretty_status
|
186
|
+
padding = " " * (50 - id.length)
|
187
|
+
msg = exists? ? status : "not found"
|
188
|
+
"#{id}#{padding}#{msg}"
|
189
|
+
end
|
132
190
|
|
133
|
-
|
191
|
+
def kubectl
|
192
|
+
@kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: false)
|
134
193
|
end
|
135
194
|
end
|
136
195
|
end
|