kubernetes-deploy 0.12.2 → 0.12.3

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: 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