kubernetes-deploy 0.19.0 → 0.20.0

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: 99f56eb5da1d7f17885cff3d19787b45a1484504
4
- data.tar.gz: e2919b899b313e2c4be0af2b7731816f9c8336de
3
+ metadata.gz: a581b73a9b59ce7dbc35a528acf269ba9ee12523
4
+ data.tar.gz: 4d3b6268317bf30b3011d1eb3e9f8bcc9c731c4f
5
5
  SHA512:
6
- metadata.gz: 103546e46a2e476e1cc1ae339fcce8362fd199d144d890d1b895ed46848181ba822dcb713eb672581f625b25e3992c4ea9439e59888c4b3f0b74b44d156bba50
7
- data.tar.gz: 7aeb474292e52f2b3e9ead4f038732804949c253da2119993c89d64e1357d7920c2031bce184821cb711c091ed54f8b6edf181c47d72b9242c3d2e0c972d7355
6
+ metadata.gz: 15197ca3f8d90b28927dc8a5ab365bb7f455434c49af708db82b2f8f5dd7430420a8e4819edbc1d4579cd2291df0a002f11924be3a865631a11e5a1fa3f3b344
7
+ data.tar.gz: bae4f408b4bfad19e61754991f24bf34d48dca2929f1d33dbd592b2fcc009b7135adc3dc92201c1e8d7633d80520a798e03aedec45778d59d81aa0a872ab7426
@@ -1,10 +1,11 @@
1
- - name: 'Run Test Suite (:kubernetes: 1.10-latest)'
2
- command: bin/ci
3
- agents:
4
- queue: minikube-ci
5
- env:
6
- LOGGING_LEVEL: 4
7
- KUBERNETES_VERSION: v1.10-latest
1
+ # Disableed until minikube can run 1.10
2
+ # - name: 'Run Test Suite (:kubernetes: 1.10-latest)'
3
+ # command: bin/ci
4
+ # agents:
5
+ # queue: minikube-ci
6
+ # env:
7
+ # LOGGING_LEVEL: 4
8
+ # KUBERNETES_VERSION: v1.10-latest
8
9
  - name: 'Run Test Suite (:kubernetes: 1.9-latest)'
9
10
  command: bin/ci
10
11
  agents:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ### Master
2
+
3
+ *Features*
4
+
5
+ *Bug Fixes*
6
+
7
+ *Enhancements*
8
+
9
+ ### 0.20.0
10
+
11
+ *Features*
12
+ - Automatically add all Kubernetes namespace labels to StatsD tags ([#278](https://github.com/Shopify/kubernetes-deploy/pull/278))
13
+
14
+ *Bug Fixes*
15
+ - Prevent calling sleep with a negative value ([#273](https://github.com/Shopify/kubernetes-deploy/pull/273))
16
+ - Prevent no-op redeploys of bad code from hanging forever ([#262](https://github.com/Shopify/kubernetes-deploy/pull/262))
17
+
18
+ *Enhancements*
19
+ - Improve output for rendering errors ([#253](https://github.com/Shopify/kubernetes-deploy/pull/253))
20
+
1
21
  ### 0.19.0
2
22
  *Features*
3
23
  - Added `--max-watch-seconds=seconds` to kubernetes-restart and kubernetes-deploy. When set
data/dev.yml CHANGED
@@ -8,7 +8,7 @@ up:
8
8
  - custom:
9
9
  name: Minikube Cluster
10
10
  met?: test $(minikube status | grep Running | wc -l) -eq 2 && $(minikube status | grep -q 'Correctly Configured')
11
- meet: minikube start --vm-driver=xhyve --kubernetes-version=v1.7.5
11
+ meet: minikube start --kubernetes-version=v1.9.4 --vm-driver=hyperkit
12
12
  down: minikube stop
13
13
  commands:
14
14
  reset-minikube: minikube delete && rm -rf ~/.minikube
@@ -36,5 +36,5 @@ Gem::Specification.new do |spec|
36
36
  spec.add_development_dependency "minitest", "~> 5.0"
37
37
  spec.add_development_dependency "minitest-stub-const", "~> 0.6"
38
38
  spec.add_development_dependency "webmock", "~> 3.0"
39
- spec.add_development_dependency "mocha", "~> 1.1"
39
+ spec.add_development_dependency "mocha", "~> 1.5"
40
40
  end
@@ -92,6 +92,7 @@ module KubernetesDeploy
92
92
  def initialize(namespace:, context:, current_sha:, template_dir:, logger:, kubectl_instance: nil, bindings: {},
93
93
  max_watch_seconds: nil)
94
94
  @namespace = namespace
95
+ @namespace_tags = []
95
96
  @context = context
96
97
  @current_sha = current_sha
97
98
  @template_dir = File.expand_path(template_dir)
@@ -122,6 +123,7 @@ module KubernetesDeploy
122
123
  validate_configuration(allow_protected_ns: allow_protected_ns, prune: prune)
123
124
  confirm_context_exists
124
125
  confirm_namespace_exists
126
+ @namespace_tags |= tags_from_namespace_labels
125
127
  resources = discover_resources
126
128
  validate_definitions(resources)
127
129
 
@@ -187,15 +189,6 @@ module KubernetesDeploy
187
189
 
188
190
  private
189
191
 
190
- # Inspect the file referenced in the kubectl stderr
191
- # to make it easier for developer to understand what's going on
192
- def find_bad_files_from_kubectl_output(stderr)
193
- # stderr often contains one or more lines like the following, from which we can extract the file path(s):
194
- # Error from server (TypeOfError): error when creating "/path/to/service-gqq5oh.yml": Service "web" is invalid:
195
- matches = stderr.scan(%r{"(/\S+\.ya?ml\S*)"})
196
- matches&.flatten
197
- end
198
-
199
192
  def deploy_has_priority_resources?(resources)
200
193
  resources.any? { |r| PREDEPLOY_SEQUENCE.include?(r.type) }
201
194
  end
@@ -225,7 +218,8 @@ module KubernetesDeploy
225
218
  return unless failed_resources.present?
226
219
 
227
220
  failed_resources.each do |r|
228
- record_invalid_template(r.validation_error_msg, file_paths: [r.file_path])
221
+ content = File.read(r.file_path) if File.file?(r.file_path)
222
+ record_invalid_template(err: r.validation_error_msg, filename: File.basename(r.file_path), content: content)
229
223
  end
230
224
  raise FatalDeploymentError, "Template validation failed"
231
225
  end
@@ -238,7 +232,8 @@ module KubernetesDeploy
238
232
  next unless filename.end_with?(".yml.erb", ".yml", ".yaml", ".yaml.erb")
239
233
 
240
234
  split_templates(filename) do |r_def|
241
- r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger, definition: r_def)
235
+ r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger,
236
+ definition: r_def, statsd_tags: @namespace_tags)
242
237
  resources << r
243
238
  @logger.info " - #{r.id}"
244
239
  end
@@ -250,34 +245,25 @@ module KubernetesDeploy
250
245
  file_content = File.read(File.join(@template_dir, filename))
251
246
  rendered_content = @renderer.render_template(filename, file_content)
252
247
  YAML.load_stream(rendered_content) do |doc|
253
- yield doc unless doc.blank?
248
+ next if doc.blank?
249
+ unless doc.is_a?(Hash)
250
+ raise InvalidTemplateError.new("Template is not a valid Kubernetes manifest",
251
+ filename: filename, content: doc)
252
+ end
253
+ yield doc
254
254
  end
255
+ rescue InvalidTemplateError => e
256
+ record_invalid_template(err: e.message, filename: e.filename, content: e.content)
257
+ raise FatalDeploymentError, "Failed to render and parse template"
255
258
  rescue Psych::SyntaxError => e
256
- debug_msg = <<~INFO
257
- Error message: #{e}
258
-
259
- Template content:
260
- ---
261
- INFO
262
- debug_msg += rendered_content
263
- @logger.summary.add_paragraph(debug_msg)
264
- raise FatalDeploymentError, "Template '#{filename}' cannot be parsed"
259
+ record_invalid_template(err: e.message, filename: filename, content: rendered_content)
260
+ raise FatalDeploymentError, "Failed to render and parse template"
265
261
  end
266
262
 
267
- def record_invalid_template(err, file_paths:, original_filenames: nil)
268
- template_names = Array(original_filenames)
269
- file_content = Array(file_paths).each_with_object([]) do |file_path, contents|
270
- next unless File.file?(file_path)
271
- contents << File.read(file_path)
272
- template_names << File.basename(file_path) unless original_filenames
273
- end.join("\n")
274
- template_list = template_names.compact.join(", ").presence || "See error message"
275
-
276
- debug_msg = ColorizedString.new("Invalid #{'template'.pluralize(template_names.length)}: #{template_list}\n").red
277
- debug_msg += "> Error from kubectl:\n#{indent_four(err)}"
278
- if file_content.present?
279
- debug_msg += "\n> Rendered template content:\n#{indent_four(file_content)}"
280
- end
263
+ def record_invalid_template(err:, filename:, content:)
264
+ debug_msg = ColorizedString.new("Invalid template: #{filename}\n").red
265
+ debug_msg += "> Error message:\n#{indent_four(err)}"
266
+ debug_msg += "\n> Template content:\n#{indent_four(content)}"
281
267
  @logger.summary.add_paragraph(debug_msg)
282
268
  end
283
269
 
@@ -406,11 +392,7 @@ module KubernetesDeploy
406
392
  if st.success?
407
393
  log_pruning(out) if prune
408
394
  else
409
- file_paths = find_bad_files_from_kubectl_output(err)
410
- warn_msg = "WARNING: Any resources not mentioned in the error below were likely created/updated. " \
411
- "You may wish to roll back this deploy."
412
- @logger.summary.add_paragraph(ColorizedString.new(warn_msg).yellow)
413
- record_invalid_template(err, file_paths: file_paths)
395
+ record_apply_failure(err)
414
396
  raise FatalDeploymentError, "Command failed: #{Shellwords.join(command)}"
415
397
  end
416
398
  end
@@ -424,6 +406,41 @@ module KubernetesDeploy
424
406
  @logger.summary.add_action("pruned #{pruned.length} #{'resource'.pluralize(pruned.length)}")
425
407
  end
426
408
 
409
+ def record_apply_failure(err)
410
+ warn_msg = "WARNING: Any resources not mentioned in the error(s) below were likely created/updated. " \
411
+ "You may wish to roll back this deploy."
412
+ @logger.summary.add_paragraph(ColorizedString.new(warn_msg).yellow)
413
+
414
+ unidentified_errors = []
415
+ err.each_line do |line|
416
+ bad_files = find_bad_files_from_kubectl_output(line)
417
+ if bad_files.present?
418
+ bad_files.each { |f| record_invalid_template(err: f[:err], filename: f[:filename], content: f[:content]) }
419
+ else
420
+ unidentified_errors << line
421
+ end
422
+ end
423
+
424
+ if unidentified_errors.present?
425
+ msg = "#{ColorizedString.new('Unidentified error(s):').red}\n#{indent_four(unidentified_errors.join)}"
426
+ @logger.summary.add_paragraph(msg)
427
+ end
428
+ end
429
+
430
+ # Inspect the file referenced in the kubectl stderr
431
+ # to make it easier for developer to understand what's going on
432
+ def find_bad_files_from_kubectl_output(line)
433
+ # stderr often contains one or more lines like the following, from which we can extract the file path(s):
434
+ # Error from server (TypeOfError): error when creating "/path/to/service-gqq5oh.yml": Service "web" is invalid:
435
+
436
+ line.scan(%r{"(/\S+\.ya?ml\S*)"}).each_with_object([]) do |matches, bad_files|
437
+ matches.each do |path|
438
+ content = File.read(path) if File.file?(path)
439
+ bad_files << { filename: File.basename(path), err: line, content: content }
440
+ end
441
+ end
442
+ end
443
+
427
444
  def confirm_context_exists
428
445
  out, err, st = kubectl.run("config", "get-contexts", "-o", "name",
429
446
  use_namespace: false, use_context: false, log_failure: false)
@@ -462,12 +479,24 @@ module KubernetesDeploy
462
479
  @logger.info("Namespace #{@namespace} found")
463
480
  end
464
481
 
482
+ def tags_from_namespace_labels
483
+ namespace_info = nil
484
+ with_retries(2) do
485
+ namespace_info, _, st = kubectl.run("get", "namespace", @namespace, "-o", "json", use_namespace: false,
486
+ log_failure: true)
487
+ st.success?
488
+ end
489
+ return [] if namespace_info.blank?
490
+ namespace_labels = JSON.parse(namespace_info, symbolize_names: true).fetch(:metadata, {}).fetch(:labels, {})
491
+ namespace_labels.map { |key, value| "#{key}:#{value}" }
492
+ end
493
+
465
494
  def kubectl
466
495
  @kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: true)
467
496
  end
468
497
 
469
498
  def statsd_tags
470
- %W(namespace:#{@namespace} sha:#{@current_sha} context:#{@context})
499
+ %W(namespace:#{@namespace} sha:#{@current_sha} context:#{@context}) | @namespace_tags
471
500
  end
472
501
 
473
502
  def with_retries(limit)
@@ -3,6 +3,15 @@ module KubernetesDeploy
3
3
  class FatalDeploymentError < StandardError; end
4
4
  class KubectlError < StandardError; end
5
5
 
6
+ class InvalidTemplateError < FatalDeploymentError
7
+ attr_reader :filename, :content
8
+ def initialize(err, filename:, content: nil)
9
+ @filename = filename
10
+ @content = content
11
+ super(err)
12
+ end
13
+ end
14
+
6
15
  class NamespaceNotFoundError < FatalDeploymentError
7
16
  def initialize(name, context)
8
17
  super("Namespace `#{name}` not found in context `#{context}`")
@@ -28,8 +28,9 @@ module KubernetesDeploy
28
28
  TIMEOUT_OVERRIDE_ANNOTATION = "kubernetes-deploy.shopify.io/timeout-override"
29
29
 
30
30
  class << self
31
- def build(namespace:, context:, definition:, logger:)
32
- opts = { namespace: namespace, context: context, definition: definition, logger: logger }
31
+ def build(namespace:, context:, definition:, logger:, statsd_tags:)
32
+ opts = { namespace: namespace, context: context, definition: definition, logger: logger,
33
+ statsd_tags: statsd_tags }
33
34
  if KubernetesDeploy.const_defined?(definition["kind"])
34
35
  klass = KubernetesDeploy.const_get(definition["kind"])
35
36
  klass.new(**opts)
@@ -65,7 +66,7 @@ module KubernetesDeploy
65
66
  "timeout: #{timeout}s"
66
67
  end
67
68
 
68
- def initialize(namespace:, context:, definition:, logger:)
69
+ def initialize(namespace:, context:, definition:, logger:, statsd_tags: [])
69
70
  # subclasses must also set these if they define their own initializer
70
71
  @name = definition.dig("metadata", "name")
71
72
  unless @name.present?
@@ -73,6 +74,7 @@ module KubernetesDeploy
73
74
  raise FatalDeploymentError, "Template is missing required field metadata.name"
74
75
  end
75
76
 
77
+ @optional_statsd_tags = statsd_tags
76
78
  @namespace = namespace
77
79
  @context = context
78
80
  @logger = logger
@@ -356,7 +358,9 @@ module KubernetesDeploy
356
358
  else
357
359
  "unknown"
358
360
  end
359
- %W(context:#{context} namespace:#{namespace} resource:#{id} type:#{type} sha:#{ENV['REVISION']} status:#{status})
361
+ tags = %W(context:#{context} namespace:#{namespace} resource:#{id}
362
+ type:#{type} sha:#{ENV['REVISION']} status:#{status})
363
+ tags | @optional_statsd_tags
360
364
  end
361
365
  end
362
366
  end
@@ -96,6 +96,16 @@ module KubernetesDeploy
96
96
 
97
97
  private
98
98
 
99
+ def current_generation
100
+ return -2 unless exists? # different default than observed
101
+ @instance_data.dig('metadata', 'generation')
102
+ end
103
+
104
+ def observed_generation
105
+ return -1 unless exists? # different default than current
106
+ @instance_data.dig('status', 'observedGeneration')
107
+ end
108
+
99
109
  def desired_replicas
100
110
  return -1 unless exists?
101
111
  @instance_data["spec"]["replicas"].to_i
@@ -133,12 +143,13 @@ module KubernetesDeploy
133
143
  # Deployments were being updated prematurely with incorrect progress information
134
144
  # https://github.com/kubernetes/kubernetes/issues/49637
135
145
  return false unless Time.now.utc - @deploy_started_at >= progress_deadline.to_i
136
- else
137
- return false unless deploy_started?
138
146
  end
139
147
 
140
- progress_condition["status"] == 'False' &&
141
- Time.parse(progress_condition["lastUpdateTime"]).to_i >= (@deploy_started_at - 5.seconds).to_i
148
+ # This assumes that when the controller bumps the observed generation, it also updates/clears all the status
149
+ # conditions. Specifically, it assumes the progress condition is immediately set to True if a rollout is starting.
150
+ deploy_started? &&
151
+ current_generation == observed_generation &&
152
+ progress_condition["status"] == 'False'
142
153
  end
143
154
 
144
155
  def find_latest_rs(mediator)
@@ -5,7 +5,8 @@ module KubernetesDeploy
5
5
 
6
6
  FAILED_PHASE_NAME = "Failed"
7
7
 
8
- def initialize(namespace:, context:, definition:, logger:, parent: nil, deploy_started_at: nil)
8
+ def initialize(namespace:, context:, definition:, logger:,
9
+ statsd_tags: nil, parent: nil, deploy_started_at: nil)
9
10
  @parent = parent
10
11
  @deploy_started_at = deploy_started_at
11
12
  @containers = definition.fetch("spec", {}).fetch("containers", []).map { |c| Container.new(c) }
@@ -14,7 +15,8 @@ module KubernetesDeploy
14
15
  raise FatalDeploymentError, "Template is missing required field spec.containers"
15
16
  end
16
17
  @containers += definition["spec"].fetch("initContainers", []).map { |c| Container.new(c, init_container: true) }
17
- super(namespace: namespace, context: context, definition: definition, logger: logger)
18
+ super(namespace: namespace, context: context, definition: definition,
19
+ logger: logger, statsd_tags: statsd_tags)
18
20
  end
19
21
 
20
22
  def sync(mediator)
@@ -5,11 +5,13 @@ module KubernetesDeploy
5
5
  TIMEOUT = 5.minutes
6
6
  attr_reader :pods
7
7
 
8
- def initialize(namespace:, context:, definition:, logger:, parent: nil, deploy_started_at: nil)
8
+ def initialize(namespace:, context:, definition:, logger:, statsd_tags: nil,
9
+ parent: nil, deploy_started_at: nil)
9
10
  @parent = parent
10
11
  @deploy_started_at = deploy_started_at
11
12
  @pods = []
12
- super(namespace: namespace, context: context, definition: definition, logger: logger)
13
+ super(namespace: namespace, context: context, definition: definition,
14
+ logger: logger, statsd_tags: statsd_tags)
13
15
  end
14
16
 
15
17
  SYNC_DEPENDENCIES = %w(Pod)
@@ -7,14 +7,14 @@ require 'json'
7
7
 
8
8
  module KubernetesDeploy
9
9
  class Renderer
10
- class InvalidPartialError < FatalDeploymentError
11
- attr_reader :parents
12
- def initialize(msg, parents = [])
10
+ class InvalidPartialError < InvalidTemplateError
11
+ attr_accessor :parents, :content, :filename
12
+ def initialize(msg, parents: [], content: nil, filename:)
13
13
  @parents = parents
14
- super(msg)
14
+ super(msg, content: content, filename: filename)
15
15
  end
16
16
  end
17
- class PartialNotFound < InvalidPartialError; end
17
+ class PartialNotFound < InvalidTemplateError; end
18
18
 
19
19
  def initialize(current_sha:, template_dir:, logger:, bindings: {})
20
20
  @current_sha = current_sha
@@ -35,11 +35,11 @@ module KubernetesDeploy
35
35
 
36
36
  ERB.new(raw_template, nil, '-').result(erb_binding)
37
37
  rescue InvalidPartialError => err
38
- all_parents = err.parents.dup.unshift(filename)
39
- raise FatalDeploymentError, "#{err.message} (included from: #{all_parents.join(' -> ')})"
38
+ err.parents = err.parents.dup.unshift(filename)
39
+ err.filename = "#{err.filename} (partial included from: #{err.parents.join(' -> ')})"
40
+ raise err
40
41
  rescue StandardError => err
41
- report_template_invalid(err.message, raw_template)
42
- raise FatalDeploymentError, "Template '#{filename}' cannot be rendered"
42
+ raise InvalidTemplateError.new(err.message, filename: filename, content: raw_template)
43
43
  end
44
44
 
45
45
  def render_partial(partial, locals)
@@ -60,12 +60,14 @@ module KubernetesDeploy
60
60
  # Note that JSON is a subset of YAML.
61
61
  JSON.generate(docs.children.first.to_ruby)
62
62
  rescue PartialNotFound => err
63
- raise InvalidPartialError, err.message
63
+ # get the filename from the first parent, not the missing partial itself
64
+ raise err if err.filename == partial
65
+ raise InvalidPartialError.new(err.message, filename: partial, content: expanded_template || template)
64
66
  rescue InvalidPartialError => err
65
- raise InvalidPartialError.new(err.message, err.parents.dup.unshift(File.basename(partial_path)))
67
+ err.parents = err.parents.dup.unshift(File.basename(partial_path))
68
+ raise err
66
69
  rescue StandardError => err
67
- report_template_invalid(err.message, expanded_template)
68
- raise InvalidPartialError, "Template '#{partial_path}' cannot be rendered"
70
+ raise InvalidPartialError.new(err.message, filename: partial_path, content: expanded_template || template)
69
71
  end
70
72
 
71
73
  private
@@ -91,12 +93,8 @@ module KubernetesDeploy
91
93
  return partial_path if File.exist?(partial_path)
92
94
  end
93
95
  end
94
- raise PartialNotFound, "Could not find partial '#{name}' in any of #{@partials_dirs.join(':')}"
95
- end
96
-
97
- def report_template_invalid(message, content)
98
- @logger.summary.add_paragraph("Error from renderer:\n #{message.tr("\n", ' ')}")
99
- @logger.summary.add_paragraph("Rendered template content:\n#{content}")
96
+ raise PartialNotFound.new("Could not find partial '#{name}' in any of #{@partials_dirs.join(':')}",
97
+ filename: name)
100
98
  end
101
99
 
102
100
  class TemplateContext
@@ -24,8 +24,8 @@ module KubernetesDeploy
24
24
  if @timeout && (Time.now.utc - monitoring_started > @timeout)
25
25
  report_and_give_up(remainder)
26
26
  end
27
- if Time.now.utc < delay_sync_until
28
- sleep(delay_sync_until - Time.now.utc)
27
+ if (sleep_duration = delay_sync_until - Time.now.utc) > 0
28
+ sleep(sleep_duration)
29
29
  end
30
30
  delay_sync_until = Time.now.utc + delay_sync # don't pummel the API if the sync is fast
31
31
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.19.0"
3
+ VERSION = "0.20.0"
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.19.0
4
+ version: 0.20.0
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-04-09 00:00:00.000000000 Z
12
+ date: 2018-04-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -185,14 +185,14 @@ dependencies:
185
185
  requirements:
186
186
  - - "~>"
187
187
  - !ruby/object:Gem::Version
188
- version: '1.1'
188
+ version: '1.5'
189
189
  type: :development
190
190
  prerelease: false
191
191
  version_requirements: !ruby/object:Gem::Requirement
192
192
  requirements:
193
193
  - - "~>"
194
194
  - !ruby/object:Gem::Version
195
- version: '1.1'
195
+ version: '1.5'
196
196
  description: Kubernetes deploy scripts
197
197
  email:
198
198
  - ops-accounts+shipit@shopify.com