kubernetes-deploy 0.12.2 → 0.12.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 191575b8707a76646afc97a5857e41aeedfe841a
4
- data.tar.gz: 0fc63d6a819b3aa446e5c9c000837c6207fe3ec8
3
+ metadata.gz: d72e1288dc4248dfcb5a1843a63946c8b9e89c1d
4
+ data.tar.gz: cd7f50c0d7fc5524ce8c9b83113af0e1d8abb01b
5
5
  SHA512:
6
- metadata.gz: 9c6d55d9154882ca8299ad288bea2f28913901b334ca22313e24a133d3d3900ecb2fa3a1690cc053f30608e5118b400099d610245a3791c20ba1c98331f7e644
7
- data.tar.gz: de1465e7bedd7aba0fadcb80e019bd5a2e6d0547e1d9cc452fe4f02815d886931fb3f19fc349a8b56a25040341c22401b1ed6f107d0d34b7e793e0c0e24a2365
6
+ metadata.gz: 8d399ff5fd70beddf51c041ba7f39ba586501c7e9a0728e7fb820523d70b1ebb957cf5df14d3abeba78b197ed1fdcdbf3dfbbf7b489b6164be5725a32237c0ba
7
+ data.tar.gz: 381a1e2bc1828589974638e1facfb3090f31fc6d3e4be024fb3c85072e3ba6cf2108f72cc26071e6668f6b0952407d64eaf41bddeb2e1e668ffc9a83687ce09f
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = '>= 2.3.0'
26
26
  spec.add_dependency "activesupport", ">= 4.2"
27
- spec.add_dependency "kubeclient", "~> 2.3"
27
+ spec.add_dependency "kubeclient", "~> 2.4"
28
28
  spec.add_dependency "googleauth", ">= 0.5"
29
29
  spec.add_dependency "ejson", "1.0.1"
30
30
  spec.add_dependency "colorize", "~> 0.8"
@@ -5,7 +5,7 @@ module KubernetesDeploy
5
5
 
6
6
  class NamespaceNotFoundError < FatalDeploymentError
7
7
  def initialize(name, context)
8
- super("Namespace `#{name}` not found in context `#{context}`. Aborting the task.")
8
+ super("Namespace `#{name}` not found in context `#{context}`")
9
9
  end
10
10
  end
11
11
  end
@@ -6,8 +6,7 @@ module KubernetesDeploy
6
6
  module KubeclientBuilder
7
7
  class ContextMissingError < FatalDeploymentError
8
8
  def initialize(context_name)
9
- super("`#{context_name}` context must be configured in your KUBECONFIG (#{ENV['KUBECONFIG']}). " \
10
- "Please see the README.")
9
+ super("`#{context_name}` context must be configured in your KUBECONFIG (#{ENV['KUBECONFIG']}).")
11
10
  end
12
11
  end
13
12
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class ResourceWatcher
4
- def initialize(resources, logger:, deploy_started_at: Time.now.utc)
4
+ def initialize(resources, logger:, deploy_started_at: Time.now.utc, operation_name: "deploy")
5
5
  unless resources.is_a?(Enumerable)
6
6
  raise ArgumentError, <<~MSG
7
7
  ResourceWatcher expects Enumerable collection, got `#{resources.class}` instead
@@ -10,30 +10,33 @@ module KubernetesDeploy
10
10
  @resources = resources
11
11
  @logger = logger
12
12
  @deploy_started_at = deploy_started_at
13
+ @operation_name = operation_name
13
14
  end
14
15
 
15
- def run(delay_sync: 3.seconds, reminder_interval: 30.seconds)
16
+ def run(delay_sync: 3.seconds, reminder_interval: 30.seconds, record_summary: true)
16
17
  delay_sync_until = last_message_logged_at = Time.now.utc
18
+ remainder = @resources.dup
17
19
 
18
- while @resources.present?
20
+ while remainder.present?
19
21
  if Time.now.utc < delay_sync_until
20
22
  sleep(delay_sync_until - Time.now.utc)
21
23
  end
22
24
  delay_sync_until = Time.now.utc + delay_sync # don't pummel the API if the sync is fast
23
25
 
24
- KubernetesDeploy::Concurrency.split_across_threads(@resources, &:sync)
25
- newly_finished_resources, @resources = @resources.partition(&:deploy_finished?)
26
+ KubernetesDeploy::Concurrency.split_across_threads(remainder, &:sync)
27
+ newly_finished_resources, remainder = remainder.partition(&:deploy_finished?)
26
28
 
27
29
  if newly_finished_resources.present?
28
30
  watch_time = (Time.now.utc - @deploy_started_at).round(1)
29
31
  report_what_just_happened(newly_finished_resources, watch_time)
30
- report_what_is_left(reminder: false)
32
+ report_what_is_left(remainder, reminder: false)
31
33
  last_message_logged_at = Time.now.utc
32
34
  elsif due_for_reminder?(last_message_logged_at, reminder_interval)
33
- report_what_is_left(reminder: true)
35
+ report_what_is_left(remainder, reminder: true)
34
36
  last_message_logged_at = Time.now.utc
35
37
  end
36
38
  end
39
+ record_statuses_for_summary(@resources) if record_summary
37
40
  end
38
41
 
39
42
  private
@@ -44,25 +47,43 @@ module KubernetesDeploy
44
47
  new_successes, new_failures = resources.partition(&:deploy_succeeded?)
45
48
  new_failures.each do |resource|
46
49
  if resource.deploy_failed?
47
- @logger.error("#{resource.id} failed to deploy after #{watch_time}s")
50
+ @logger.error("#{resource.id} failed to #{@operation_name} after #{watch_time}s")
48
51
  else
49
- @logger.error("#{resource.id} deployment timed out")
52
+ @logger.error("#{resource.id} rollout timed out")
50
53
  end
51
54
  end
52
55
 
53
56
  if new_successes.present?
54
- success_string = ColorizedString.new("Successfully deployed in #{watch_time}s:").green
57
+ success_string = ColorizedString.new("Successfully #{@operation_name}ed in #{watch_time}s:").green
55
58
  @logger.info("#{success_string} #{new_successes.map(&:id).join(', ')}")
56
59
  end
57
60
  end
58
61
 
59
- def report_what_is_left(reminder:)
60
- return unless @resources.present?
61
- resource_list = @resources.map(&:id).join(', ')
62
+ def report_what_is_left(resources, reminder:)
63
+ return unless resources.present?
64
+ resource_list = resources.map(&:id).join(', ')
62
65
  msg = reminder ? "Still waiting for: #{resource_list}" : "Continuing to wait for: #{resource_list}"
63
66
  @logger.info(msg)
64
67
  end
65
68
 
69
+ def record_statuses_for_summary(resources)
70
+ successful_resources, failed_resources = resources.partition(&:deploy_succeeded?)
71
+ fail_count = failed_resources.length
72
+ success_count = successful_resources.length
73
+
74
+ if success_count > 0
75
+ @logger.summary.add_action("successfully #{@operation_name}ed #{success_count} "\
76
+ "#{'resource'.pluralize(success_count)}")
77
+ final_statuses = successful_resources.map(&:pretty_status).join("\n")
78
+ @logger.summary.add_paragraph("#{ColorizedString.new('Successful resources').green}\n#{final_statuses}")
79
+ end
80
+
81
+ if fail_count > 0
82
+ @logger.summary.add_action("failed to #{@operation_name} #{fail_count} #{'resource'.pluralize(fail_count)}")
83
+ failed_resources.each { |r| @logger.summary.add_paragraph(r.debug_message) }
84
+ end
85
+ end
86
+
66
87
  def due_for_reminder?(last_message_logged_at, reminder_interval)
67
88
  (last_message_logged_at.to_f + reminder_interval.to_f) <= Time.now.utc.to_f
68
89
  end
@@ -6,13 +6,9 @@ module KubernetesDeploy
6
6
  class RestartTask
7
7
  include KubernetesDeploy::KubeclientBuilder
8
8
 
9
- class DeploymentNotFoundError < FatalDeploymentError
10
- def initialize(name, namespace)
11
- super("Deployment `#{name}` not found in namespace `#{namespace}`. Aborting the task.")
12
- end
13
- end
9
+ class FatalRestartError < FatalDeploymentError; end
14
10
 
15
- class RestartError < FatalDeploymentError
11
+ class RestartAPIError < FatalRestartError
16
12
  def initialize(deployment_name, response)
17
13
  super("Failed to restart #{deployment_name}. " \
18
14
  "API returned non-200 response code (#{response.code})\n" \
@@ -27,58 +23,71 @@ module KubernetesDeploy
27
23
  @context = context
28
24
  @namespace = namespace
29
25
  @logger = logger
30
- @kubeclient = build_v1_kubeclient(context)
31
- @v1beta1_kubeclient = build_v1beta1_kubeclient(context)
32
- @policy_v1beta1_kubeclient = build_policy_v1beta1_kubeclient(context)
33
26
  end
34
27
 
35
28
  def perform(deployments_names = nil)
29
+ start = Time.now.utc
36
30
  @logger.reset
31
+
32
+ @logger.phase_heading("Initializing restart")
37
33
  verify_namespace
34
+ deployments = identify_target_deployments(deployments_names)
35
+
36
+ @logger.phase_heading("Triggering restart by touching ENV[RESTARTED_AT]")
37
+ patch_kubeclient_deployments(deployments)
38
+
39
+ @logger.phase_heading("Waiting for rollout")
40
+ resources = build_watchables(deployments, start)
41
+ ResourceWatcher.new(resources, logger: @logger, operation_name: "restart").run
42
+ success = resources.all?(&:deploy_succeeded?)
43
+ rescue FatalDeploymentError => error
44
+ @logger.summary.add_action(error.message)
45
+ success = false
46
+ ensure
47
+ @logger.print_summary(success)
48
+ status = success ? "success" : "failed"
49
+ tags = %W(namespace:#{@namespace} context:#{@context} status:#{status} deployments:#{deployments.to_a.length}})
50
+ ::StatsD.measure('restart.duration', StatsD.duration(start), tags: tags)
51
+ end
38
52
 
39
- if deployments_names
40
- deployments = fetch_deployments(deployments_names.uniq)
53
+ private
54
+
55
+ def identify_target_deployments(deployment_names)
56
+ if deployment_names.nil?
57
+ @logger.info("Configured to restart all deployments with the `#{ANNOTATION}` annotation")
58
+ deployments = v1beta1_kubeclient.get_deployments(namespace: @namespace)
59
+ .select { |d| d.metadata.annotations[ANNOTATION] }
41
60
 
42
61
  if deployments.none?
43
- raise ArgumentError, "no deployments with names #{deployments_names} found in namespace #{@namespace}"
62
+ raise FatalRestartError, "no deployments with the `#{ANNOTATION}` annotation found in namespace #{@namespace}"
44
63
  end
64
+ elsif deployment_names.empty?
65
+ raise FatalRestartError, "Configured to restart deployments by name, but list of names was blank"
45
66
  else
46
- deployments = @v1beta1_kubeclient
47
- .get_deployments(namespace: @namespace)
48
- .select { |d| d.metadata.annotations[ANNOTATION] }
67
+ deployment_names = deployment_names.uniq
68
+ list = deployment_names.join(', ')
69
+ @logger.info("Configured to restart deployments by name: #{list}")
49
70
 
71
+ deployments = fetch_deployments(deployment_names)
50
72
  if deployments.none?
51
- raise ArgumentError, "no deployments found in namespace #{@namespace} with #{ANNOTATION} annotation available"
73
+ raise FatalRestartError, "no deployments with names #{list} found in namespace #{@namespace}"
52
74
  end
53
75
  end
54
-
55
- @logger.phase_heading("Triggering restart by touching ENV[RESTARTED_AT]")
56
- patch_kubeclient_deployments(deployments)
57
-
58
- @logger.phase_heading("Waiting for rollout")
59
- wait_for_rollout(deployments)
60
-
61
- names = deployments.map { |d| "`#{d.metadata.name}`" }
62
- @logger.info "Restart of #{names.sort.join(', ')} deployments succeeded"
63
- true
64
- rescue FatalDeploymentError => error
65
- @logger.fatal "#{error.class}: #{error.message}"
66
- false
76
+ deployments
67
77
  end
68
78
 
69
- private
70
-
71
- def wait_for_rollout(kubeclient_resources)
72
- resources = kubeclient_resources.map do |d|
79
+ def build_watchables(kubeclient_resources, started)
80
+ kubeclient_resources.map do |d|
73
81
  definition = d.to_h.deep_stringify_keys
74
- Deployment.new(namespace: @namespace, context: @context, definition: definition, logger: @logger)
82
+ r = Deployment.new(namespace: @namespace, context: @context, definition: definition, logger: @logger)
83
+ r.deploy_started = started # we don't care what happened to the resource before the restart cmd ran
84
+ r
75
85
  end
76
- watcher = ResourceWatcher.new(resources, logger: @logger)
77
- watcher.run
78
86
  end
79
87
 
80
88
  def verify_namespace
81
- @kubeclient.get_namespace(@namespace)
89
+ kubeclient.get_namespace(@namespace)
90
+ @logger.info("Namespace #{@namespace} found in context #{@context}")
82
91
  rescue KubeException => error
83
92
  if error.error_code == 404
84
93
  raise NamespaceNotFoundError.new(@namespace, @context)
@@ -88,7 +97,7 @@ module KubernetesDeploy
88
97
  end
89
98
 
90
99
  def patch_deployment_with_restart(record)
91
- @v1beta1_kubeclient.patch_deployment(
100
+ v1beta1_kubeclient.patch_deployment(
92
101
  record.metadata.name,
93
102
  build_patch_payload(record),
94
103
  @namespace
@@ -101,7 +110,7 @@ module KubernetesDeploy
101
110
  if HTTP_OK_RANGE.cover?(response.code)
102
111
  @logger.info "Triggered `#{record.metadata.name}` restart"
103
112
  else
104
- raise RestartError.new(record.metadata.name, response)
113
+ raise RestartAPIError.new(record.metadata.name, response)
105
114
  end
106
115
  end
107
116
  end
@@ -110,10 +119,10 @@ module KubernetesDeploy
110
119
  list.map do |name|
111
120
  record = nil
112
121
  begin
113
- record = @v1beta1_kubeclient.get_deployment(name, @namespace)
122
+ record = v1beta1_kubeclient.get_deployment(name, @namespace)
114
123
  rescue KubeException => error
115
124
  if error.error_code == 404
116
- raise DeploymentNotFoundError.new(name, @namespace)
125
+ raise FatalRestartError, "Deployment `#{name}` not found in namespace `#{@namespace}`"
117
126
  else
118
127
  raise
119
128
  end
@@ -139,5 +148,13 @@ module KubernetesDeploy
139
148
  }
140
149
  }
141
150
  end
151
+
152
+ def kubeclient
153
+ @kubeclient ||= build_v1_kubeclient(@context)
154
+ end
155
+
156
+ def v1beta1_kubeclient
157
+ @v1beta1_kubeclient ||= build_v1beta1_kubeclient(@context)
158
+ end
142
159
  end
143
160
  end
@@ -109,7 +109,7 @@ module KubernetesDeploy
109
109
  @logger.phase_heading("Predeploying priority resources")
110
110
  start_priority_resource = Time.now.utc
111
111
  predeploy_priority_resources(resources)
112
- ::StatsD.measure('priority_resources.duration', statsd_duration(start_priority_resource), tags: statsd_tags)
112
+ ::StatsD.measure('priority_resources.duration', StatsD.duration(start_priority_resource), tags: statsd_tags)
113
113
  end
114
114
 
115
115
  @logger.phase_heading("Deploying all resources")
@@ -120,8 +120,7 @@ module KubernetesDeploy
120
120
  if verify_result
121
121
  start_normal_resource = Time.now.utc
122
122
  deploy_resources(resources, prune: prune, verify: true)
123
- ::StatsD.measure('normal_resources.duration', statsd_duration(start_normal_resource), tags: statsd_tags)
124
- record_statuses(resources)
123
+ ::StatsD.measure('normal_resources.duration', StatsD.duration(start_normal_resource), tags: statsd_tags)
125
124
  success = resources.all?(&:deploy_succeeded?)
126
125
  else
127
126
  deploy_resources(resources, prune: prune, verify: false)
@@ -139,7 +138,7 @@ module KubernetesDeploy
139
138
  ensure
140
139
  @logger.print_summary(success)
141
140
  status = success ? "success" : "failed"
142
- ::StatsD.measure('all_resources.duration', statsd_duration(start), tags: statsd_tags << "status:#{status}")
141
+ ::StatsD.measure('all_resources.duration', StatsD.duration(start), tags: statsd_tags << "status:#{status}")
143
142
  success
144
143
  end
145
144
 
@@ -152,23 +151,6 @@ module KubernetesDeploy
152
151
 
153
152
  private
154
153
 
155
- def record_statuses(resources)
156
- successful_resources, failed_resources = resources.partition(&:deploy_succeeded?)
157
- fail_count = failed_resources.length
158
- success_count = successful_resources.length
159
-
160
- if success_count > 0
161
- @logger.summary.add_action("successfully deployed #{success_count} #{'resource'.pluralize(success_count)}")
162
- final_statuses = successful_resources.map(&:pretty_status).join("\n")
163
- @logger.summary.add_paragraph("#{ColorizedString.new('Successful resources').green}\n#{final_statuses}")
164
- end
165
-
166
- if fail_count > 0
167
- @logger.summary.add_action("failed to deploy #{fail_count} #{'resource'.pluralize(fail_count)}")
168
- failed_resources.each { |r| @logger.summary.add_paragraph(r.debug_message) }
169
- end
170
- end
171
-
172
154
  # Inspect the file referenced in the kubectl stderr
173
155
  # to make it easier for developer to understand what's going on
174
156
  def find_bad_files_from_kubectl_output(stderr)
@@ -186,7 +168,7 @@ module KubernetesDeploy
186
168
  PREDEPLOY_SEQUENCE.each do |resource_type|
187
169
  matching_resources = resource_list.select { |r| r.type == resource_type }
188
170
  next if matching_resources.empty?
189
- deploy_resources(matching_resources, verify: true)
171
+ deploy_resources(matching_resources, verify: true, record_summary: false)
190
172
 
191
173
  failed_resources = matching_resources.reject(&:deploy_succeeded?)
192
174
  fail_count = failed_resources.length
@@ -264,11 +246,6 @@ module KubernetesDeploy
264
246
  " " + str.gsub("\n", "\n ")
265
247
  end
266
248
 
267
- def wait_for_completion(watched_resources, started_at)
268
- watcher = ResourceWatcher.new(watched_resources, logger: @logger, deploy_started_at: started_at)
269
- watcher.run
270
- end
271
-
272
249
  def render_template(filename, raw_template)
273
250
  return raw_template unless File.extname(filename) == ".erb"
274
251
 
@@ -326,7 +303,7 @@ module KubernetesDeploy
326
303
  @logger.info("All required parameters and files are present")
327
304
  end
328
305
 
329
- def deploy_resources(resources, prune: false, verify:)
306
+ def deploy_resources(resources, prune: false, verify:, record_summary: true)
330
307
  return if resources.empty?
331
308
  deploy_started_at = Time.now.utc
332
309
 
@@ -365,7 +342,11 @@ module KubernetesDeploy
365
342
  end
366
343
 
367
344
  apply_all(applyables, prune)
368
- wait_for_completion(resources, deploy_started_at) if verify
345
+
346
+ if verify
347
+ watcher = ResourceWatcher.new(resources, logger: @logger, deploy_started_at: deploy_started_at)
348
+ watcher.run(record_summary: record_summary)
349
+ end
369
350
  end
370
351
 
371
352
  def apply_all(resources, prune)
@@ -429,9 +410,5 @@ module KubernetesDeploy
429
410
  def statsd_tags
430
411
  %W(namespace:#{@namespace} sha:#{@current_sha} context:#{@context})
431
412
  end
432
-
433
- def statsd_duration(start_time)
434
- (Time.now.utc - start_time).round(1)
435
- end
436
413
  end
437
414
  end
@@ -4,6 +4,10 @@ require 'logger'
4
4
 
5
5
  module KubernetesDeploy
6
6
  class StatsD
7
+ def self.duration(start_time)
8
+ (Time.now.utc - start_time).round(1)
9
+ end
10
+
7
11
  def self.build
8
12
  ::StatsD.default_sample_rate = 1.0
9
13
  ::StatsD.prefix = "KubernetesDeploy"
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.12.2"
3
+ VERSION = "0.12.3"
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.12.2
4
+ version: 0.12.3
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: 2017-09-14 00:00:00.000000000 Z
12
+ date: 2017-09-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '2.3'
34
+ version: '2.4'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '2.3'
41
+ version: '2.4'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: googleauth
44
44
  requirement: !ruby/object:Gem::Requirement