kubernetes-deploy 0.21.0 → 0.21.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1155a58879313f8d0466b9d551a5ba9fb4a0ccab
4
- data.tar.gz: a5856d1be46d1aef9a6393e4b5f95715e5832284
3
+ metadata.gz: b7b6b27e2d19b9194f53092f3037a6597d0c098b
4
+ data.tar.gz: 5c1e656293fdb2f0e193bdb90419bd7629f683fd
5
5
  SHA512:
6
- metadata.gz: 9eecadcbaa7709aae55ba3baac6e0afaf120277df59c1dae1a407bd49f6f07755757448de776b7f33ed30298cacad081276ba2afcee4927d2b56ae899ea9a196
7
- data.tar.gz: 75cc0f78449088f47d621da287477c77a01ef33a0d1460fc5739676fee82d5a6652542412c483cd2ad80c708ed72a8350573a57515e848100b5c27cb6b9a2129
6
+ metadata.gz: f6968be66492775b5edac4e2a30fe30c168aafd1db5766f5a97eae84c2fd42dd5603c608960d6b0aadc5dec6f717842c41b9dbbd6cd7f6dad69716b1106b33e3
7
+ data.tar.gz: 751eb781b35bdf1081c41d4f182b84cc97a897b7c199c5020ecd2691c1478fb48174a1527d9ad0e3a58103a4dff734f149b261682d52a923fdb516239db0f8f0
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.required_ruby_version = '>= 2.3.0'
26
26
  spec.add_dependency "activesupport", ">= 5.0"
27
27
  spec.add_dependency "kubeclient", "~> 3.0"
28
- spec.add_dependency "googleauth", "= 0.6.2" # https://github.com/google/google-auth-library-ruby/issues/153
28
+ spec.add_dependency "googleauth", "~> 0.6.6" # https://github.com/google/google-auth-library-ruby/issues/153
29
29
  spec.add_dependency "ejson", "~> 1.0"
30
30
  spec.add_dependency "colorize", "~> 0.8"
31
31
  spec.add_dependency "statsd-instrument", "~> 2.2"
@@ -93,8 +93,6 @@ module KubernetesDeploy
93
93
  kubectl.server_version
94
94
  end
95
95
 
96
- NOT_FOUND_ERROR = 'NotFound'
97
-
98
96
  def initialize(namespace:, context:, current_sha:, template_dir:, logger:, kubectl_instance: nil, bindings: {},
99
97
  max_watch_seconds: nil)
100
98
  @namespace = namespace
@@ -399,7 +397,7 @@ module KubernetesDeploy
399
397
 
400
398
  def apply_all(resources, prune)
401
399
  return unless resources.present?
402
- command = ["apply"]
400
+ command = %w(apply)
403
401
 
404
402
  Dir.mktmpdir do |tmp_dir|
405
403
  resources.each do |r|
@@ -500,7 +498,7 @@ module KubernetesDeploy
500
498
  st, err = nil
501
499
  with_retries(2) do
502
500
  _, err, st = kubectl.run("get", "namespace", @namespace, use_namespace: false, log_failure: true)
503
- st.success? || err.include?(NOT_FOUND_ERROR)
501
+ st.success? || err.include?(KubernetesDeploy::Kubectl::NOT_FOUND_ERROR_TEXT)
504
502
  end
505
503
  raise FatalDeploymentError, "Failed to find namespace. #{err}" unless st.success?
506
504
  @logger.info("Namespace #{@namespace} found")
@@ -3,6 +3,9 @@
3
3
  module KubernetesDeploy
4
4
  class Kubectl
5
5
  DEFAULT_TIMEOUT = 30
6
+ NOT_FOUND_ERROR_TEXT = 'NotFound'
7
+
8
+ class ResourceNotFoundError < StandardError; end
6
9
 
7
10
  def initialize(namespace:, context:, logger:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT,
8
11
  output_is_sensitive: false)
@@ -17,7 +20,7 @@ module KubernetesDeploy
17
20
  raise ArgumentError, "context is required" if context.blank?
18
21
  end
19
22
 
20
- def run(*args, log_failure: nil, use_context: true, use_namespace: true)
23
+ def run(*args, log_failure: nil, use_context: true, use_namespace: true, raise_if_not_found: false)
21
24
  log_failure = @log_failure_by_default if log_failure.nil?
22
25
 
23
26
  args = args.unshift("kubectl")
@@ -29,10 +32,17 @@ module KubernetesDeploy
29
32
  out, err, st = Open3.capture3(*args)
30
33
  @logger.debug(out.shellescape) unless output_is_sensitive?
31
34
 
32
- if !st.success? && log_failure
33
- @logger.warn("The following command failed: #{Shellwords.join(args)}")
34
- @logger.warn(err) unless output_is_sensitive?
35
+ unless st.success?
36
+ if log_failure
37
+ @logger.warn("The following command failed: #{Shellwords.join(args)}")
38
+ @logger.warn(err) unless output_is_sensitive?
39
+ end
40
+
41
+ if raise_if_not_found && err.match(NOT_FOUND_ERROR_TEXT)
42
+ raise ResourceNotFoundError, err
43
+ end
35
44
  end
45
+
36
46
  [out.chomp, err.chomp, st]
37
47
  end
38
48
 
@@ -12,6 +12,7 @@ module KubernetesDeploy
12
12
 
13
13
  def deploy_failed?
14
14
  return false unless deploy_started?
15
+ return true if failed_status_condition
15
16
  return false unless @instance_data.dig("spec", "backoffLimit").present?
16
17
  (@instance_data.dig("status", "failed") || 0) >= @instance_data.dig("spec", "backoffLimit")
17
18
  end
@@ -30,8 +31,20 @@ module KubernetesDeploy
30
31
  end
31
32
  end
32
33
 
34
+ def failure_message
35
+ if (condition = failed_status_condition.presence)
36
+ "#{condition['reason']} (#{condition['message']})"
37
+ end
38
+ end
39
+
33
40
  private
34
41
 
42
+ def failed_status_condition
43
+ @instance_data.dig("status", "conditions")&.detect do |condition|
44
+ condition["type"] == 'Failed' && condition['status'] == "True"
45
+ end
46
+ end
47
+
35
48
  def done?
36
49
  (@instance_data.dig("status", "succeeded") || 0) == @instance_data.dig("spec", "completions")
37
50
  end
@@ -66,14 +66,10 @@ module KubernetesDeploy
66
66
  return STANDARD_TIMEOUT_MESSAGE unless readiness_probe_failure?
67
67
  probe_failure_msgs = @containers.map(&:readiness_fail_reason).compact
68
68
  header = "The following containers have not passed their readiness probes on at least one pod:\n"
69
- header + probe_failure_msgs.join("\n") + "\n"
69
+ header + probe_failure_msgs.join("\n")
70
70
  end
71
71
 
72
72
  def failure_message
73
- if phase == FAILED_PHASE_NAME && !TRANSIENT_FAILURE_REASONS.include?(reason)
74
- phase_problem = "Pod status: #{status}. "
75
- end
76
-
77
73
  doomed_containers = @containers.select(&:doomed?)
78
74
  if doomed_containers.present?
79
75
  container_problems = if unmanaged?
@@ -86,7 +82,7 @@ module KubernetesDeploy
86
82
  container_problems += "> #{red_name}: #{c.doom_reason}\n"
87
83
  end
88
84
  end
89
- "#{phase_problem}#{container_problems}".presence
85
+ "#{phase_failure_message} #{container_problems}".strip.presence
90
86
  end
91
87
 
92
88
  def fetch_debug_logs(kubectl)
@@ -100,6 +96,29 @@ module KubernetesDeploy
100
96
 
101
97
  private
102
98
 
99
+ def failed_phase?
100
+ phase == FAILED_PHASE_NAME
101
+ end
102
+
103
+ def transient_failure_reason?
104
+ return false if unmanaged?
105
+ TRANSIENT_FAILURE_REASONS.include?(reason)
106
+ end
107
+
108
+ def phase_failure_message
109
+ if failed_phase? && !transient_failure_reason?
110
+ return "Pod status: #{status}."
111
+ end
112
+
113
+ return unless unmanaged?
114
+
115
+ if terminating?
116
+ "Pod status: Terminating."
117
+ elsif disappeared?
118
+ "Pod status: Disappeared."
119
+ end
120
+ end
121
+
103
122
  def logs
104
123
  @logs ||= KubernetesDeploy::RemoteLogs.new(
105
124
  logger: @logger,
@@ -85,6 +85,7 @@ module KubernetesDeploy
85
85
  @logger = logger
86
86
  @definition = definition
87
87
  @statsd_report_done = false
88
+ @disappeared = false
88
89
  @validation_errors = []
89
90
  @instance_data = {}
90
91
  end
@@ -121,12 +122,23 @@ module KubernetesDeploy
121
122
  end
122
123
 
123
124
  def sync(mediator)
124
- @instance_data = mediator.get_instance(kubectl_resource_type, name)
125
+ @instance_data = mediator.get_instance(kubectl_resource_type, name, raise_if_not_found: true)
126
+ rescue KubernetesDeploy::Kubectl::ResourceNotFoundError
127
+ @disappeared = true if deploy_started?
128
+ @instance_data = {}
125
129
  end
126
130
 
127
131
  def after_sync
128
132
  end
129
133
 
134
+ def terminating?
135
+ @instance_data.dig('metadata', 'deletionTimestamp').present?
136
+ end
137
+
138
+ def disappeared?
139
+ @disappeared
140
+ end
141
+
130
142
  def deploy_failed?
131
143
  false
132
144
  end
@@ -189,22 +201,26 @@ module KubernetesDeploy
189
201
  def debug_message(cause = nil, info_hash = {})
190
202
  helpful_info = []
191
203
  if cause == :gave_up
192
- helpful_info << ColorizedString.new("#{id}: GLOBAL WATCH TIMEOUT (#{info_hash[:timeout]} seconds)").yellow
204
+ debug_heading = ColorizedString.new("#{id}: GLOBAL WATCH TIMEOUT (#{info_hash[:timeout]} seconds)").yellow
193
205
  helpful_info << "If you expected it to take longer than #{info_hash[:timeout]} seconds for your deploy"\
194
206
  " to roll out, increase --max-watch-seconds."
195
207
  elsif deploy_failed?
196
- helpful_info << ColorizedString.new("#{id}: FAILED").red
208
+ debug_heading = ColorizedString.new("#{id}: FAILED").red
197
209
  helpful_info << failure_message if failure_message.present?
198
210
  elsif deploy_timed_out?
199
- helpful_info << ColorizedString.new("#{id}: TIMED OUT (#{pretty_timeout_type})").yellow
211
+ debug_heading = ColorizedString.new("#{id}: TIMED OUT (#{pretty_timeout_type})").yellow
200
212
  helpful_info << timeout_message if timeout_message.present?
201
213
  else
202
214
  # Arriving in debug_message when we neither failed nor timed out is very unexpected. Dump all available info.
203
- helpful_info << ColorizedString.new("#{id}: MONITORING ERROR").red
215
+ debug_heading = ColorizedString.new("#{id}: MONITORING ERROR").red
204
216
  helpful_info << failure_message if failure_message.present?
205
217
  helpful_info << timeout_message if timeout_message.present? && timeout_message != STANDARD_TIMEOUT_MESSAGE
206
218
  end
207
- helpful_info << " - Final status: #{status}"
219
+
220
+ final_status = " - Final status: #{status}"
221
+ final_status = "\n#{final_status}" if helpful_info.present? && !helpful_info.last.end_with?("\n")
222
+ helpful_info.prepend(debug_heading)
223
+ helpful_info << final_status
208
224
 
209
225
  if @debug_events.present?
210
226
  helpful_info << " - Events (common success events excluded):"
@@ -10,12 +10,16 @@ module KubernetesDeploy
10
10
  clear_cache
11
11
  end
12
12
 
13
- def get_instance(kind, resource_name)
14
- if @cache.key?(kind)
15
- @cache.dig(kind, resource_name) || {}
16
- else
17
- request_instance(kind, resource_name)
13
+ def get_instance(kind, resource_name, raise_if_not_found: false)
14
+ unless @cache.key?(kind)
15
+ return request_instance(kind, resource_name, raise_if_not_found: raise_if_not_found)
18
16
  end
17
+
18
+ cached_instance = @cache[kind].fetch(resource_name, {})
19
+ if cached_instance.blank? && raise_if_not_found
20
+ raise KubernetesDeploy::Kubectl::ResourceNotFoundError, "Resource does not exist (used cache for kind #{kind})"
21
+ end
22
+ cached_instance
19
23
  end
20
24
 
21
25
  def get_all(kind, selector = nil)
@@ -55,17 +59,22 @@ module KubernetesDeploy
55
59
  @cache = {}
56
60
  end
57
61
 
58
- def request_instance(kind, iname)
59
- raw_json, _, st = kubectl.run("get", kind, iname, "-a", "--output=json")
62
+ def request_instance(kind, iname, raise_if_not_found:)
63
+ raw_json, _err, st = kubectl.run("get", kind, iname, "-a", "--output=json",
64
+ raise_if_not_found: raise_if_not_found)
60
65
  st.success? ? JSON.parse(raw_json) : {}
61
66
  end
62
67
 
63
68
  def fetch_by_kind(kind)
64
69
  raw_json, _, st = kubectl.run("get", kind, "-a", "--output=json")
65
70
  return unless st.success?
66
- @cache[kind] = JSON.parse(raw_json)["items"].each_with_object({}) do |r, instances|
67
- instances[r.dig("metadata", "name")] = r
71
+
72
+ instances = {}
73
+ JSON.parse(raw_json)["items"].each do |resource|
74
+ resource_name = resource.dig("metadata", "name")
75
+ instances[resource_name] = resource
68
76
  end
77
+ @cache[kind] = instances
69
78
  end
70
79
  end
71
80
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.21.0"
3
+ VERSION = "0.21.1"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubernetes-deploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.21.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katrina Verey
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2018-10-16 00:00:00.000000000 Z
12
+ date: 2018-10-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -43,16 +43,16 @@ dependencies:
43
43
  name: googleauth
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - '='
46
+ - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: 0.6.2
48
+ version: 0.6.6
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - '='
53
+ - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: 0.6.2
55
+ version: 0.6.6
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: ejson
58
58
  requirement: !ruby/object:Gem::Requirement