krane 1.0.0 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/{ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE.md} +0 -0
  4. data/{pull_request_template.md → .github/pull_request_template.md} +0 -0
  5. data/.gitignore +0 -3
  6. data/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +1020 -0
  7. data/.rubocop.yml +0 -12
  8. data/.shopify-build/{kubernetes-deploy.yml → krane.yml} +16 -1
  9. data/1.0-Upgrade.md +4 -5
  10. data/CHANGELOG.md +55 -1
  11. data/CONTRIBUTING.md +5 -5
  12. data/Gemfile +0 -11
  13. data/README.md +22 -24
  14. data/bin/ci +1 -1
  15. data/bin/test +2 -2
  16. data/dev.yml +4 -4
  17. data/krane.gemspec +22 -5
  18. data/lib/krane/cli/deploy_command.rb +3 -4
  19. data/lib/krane/cli/global_deploy_command.rb +3 -3
  20. data/lib/krane/cli/render_command.rb +3 -3
  21. data/lib/krane/cluster_resource_discovery.rb +9 -6
  22. data/lib/krane/concurrency.rb +2 -2
  23. data/lib/krane/container_logs.rb +1 -1
  24. data/lib/krane/container_overrides.rb +33 -0
  25. data/lib/krane/deploy_task.rb +5 -5
  26. data/lib/krane/ejson_secret_provisioner.rb +7 -4
  27. data/lib/krane/global_deploy_task.rb +3 -2
  28. data/lib/krane/kubectl.rb +11 -1
  29. data/lib/krane/kubernetes_resource.rb +7 -6
  30. data/lib/krane/kubernetes_resource/custom_resource.rb +1 -1
  31. data/lib/krane/kubernetes_resource/custom_resource_definition.rb +1 -1
  32. data/lib/krane/kubernetes_resource/daemon_set.rb +1 -0
  33. data/lib/krane/kubernetes_resource/deployment.rb +3 -2
  34. data/lib/krane/kubernetes_resource/pod.rb +12 -8
  35. data/lib/krane/kubernetes_resource/replica_set.rb +2 -16
  36. data/lib/krane/kubernetes_resource/service.rb +3 -7
  37. data/lib/krane/kubernetes_resource/stateful_set.rb +1 -0
  38. data/lib/krane/render_task.rb +2 -2
  39. data/lib/krane/resource_cache.rb +6 -0
  40. data/lib/krane/resource_watcher.rb +2 -1
  41. data/lib/krane/restart_task.rb +2 -2
  42. data/lib/krane/runner_task.rb +16 -17
  43. data/lib/krane/statsd.rb +2 -2
  44. data/lib/krane/template_sets.rb +1 -1
  45. data/lib/krane/version.rb +1 -1
  46. metadata +168 -20
  47. data/lib/krane/kubernetes_resource/cloudsql.rb +0 -43
  48. data/shipit.yml +0 -4
@@ -8,7 +8,8 @@ module Krane
8
8
  "filenames" => { type: :array, banner: 'config/deploy/production config/deploy/my-extra-resource.yml',
9
9
  aliases: :f, required: false, default: [],
10
10
  desc: "Directories and files that contains the configuration to apply" },
11
- "stdin" => { type: :boolean, default: false, desc: "Read resources from stdin" },
11
+ "stdin" => { type: :boolean, default: false,
12
+ desc: "[DEPRECATED] Read resources from stdin" },
12
13
  "global-timeout" => { type: :string, banner: "duration", default: DEFAULT_DEPLOY_TIMEOUT,
13
14
  desc: "Max duration to monitor workloads correctly deployed" },
14
15
  "verify-result" => { type: :boolean, default: true,
@@ -28,11 +29,10 @@ module Krane
28
29
 
29
30
  selector = ::Krane::LabelSelector.parse(options[:selector])
30
31
 
31
- # never mutate options directly
32
32
  filenames = options[:filenames].dup
33
33
  filenames << "-" if options[:stdin]
34
34
  if filenames.empty?
35
- raise Thor::RequiredArgumentMissingError, 'At least one of --filenames or --stdin must be set'
35
+ raise(Thor::RequiredArgumentMissingError, '--filenames must be set and not empty')
36
36
  end
37
37
 
38
38
  ::Krane::OptionsHelper.with_processed_template_paths(filenames) do |paths|
@@ -7,7 +7,8 @@ module Krane
7
7
  "bindings" => { type: :array, banner: "foo=bar abc=def", desc: 'Bindings for erb' },
8
8
  "filenames" => { type: :array, banner: 'config/deploy/production config/deploy/my-extra-resource.yml',
9
9
  required: false, default: [], aliases: 'f', desc: 'Directories and files to render' },
10
- "stdin" => { type: :boolean, desc: "Read resources from stdin", default: false },
10
+ "stdin" => { type: :boolean, default: false,
11
+ desc: "[DEPRECATED] Read resources from stdin" },
11
12
  "current-sha" => { type: :string, banner: "SHA", desc: "Expose SHA `current_sha` in ERB bindings",
12
13
  lazy_default: '' },
13
14
  }
@@ -20,11 +21,10 @@ module Krane
20
21
  bindings_parser = ::Krane::BindingsParser.new
21
22
  options[:bindings]&.each { |b| bindings_parser.add(b) }
22
23
 
23
- # never mutate options directly
24
24
  filenames = options[:filenames].dup
25
25
  filenames << "-" if options[:stdin]
26
26
  if filenames.empty?
27
- raise Thor::RequiredArgumentMissingError, 'At least one of --filenames or --stdin must be set'
27
+ raise(Thor::RequiredArgumentMissingError, '--filenames must be set and not empty')
28
28
  end
29
29
 
30
30
  ::Krane::OptionsHelper.with_processed_template_paths(filenames, render_erb: true) do |paths|
@@ -37,7 +37,7 @@ module Krane
37
37
  def fetch_resources(namespaced: false)
38
38
  command = %w(api-resources)
39
39
  command << "--namespaced=#{namespaced}"
40
- raw, _, st = kubectl.run(*command, output: "wide", attempts: 5,
40
+ raw, err, st = kubectl.run(*command, output: "wide", attempts: 5,
41
41
  use_namespace: false)
42
42
  if st.success?
43
43
  rows = raw.split("\n")
@@ -59,7 +59,7 @@ module Krane
59
59
  resource
60
60
  end
61
61
  else
62
- []
62
+ raise FatalKubeAPIError, "Error retrieving api-resources: #{err}"
63
63
  end
64
64
  end
65
65
 
@@ -68,7 +68,7 @@ module Krane
68
68
  # kubectl api-versions returns a list of group/version strings e.g. autoscaling/v2beta2
69
69
  # A kind may not exist in all versions of the group.
70
70
  def fetch_api_versions
71
- raw, _, st = kubectl.run("api-versions", attempts: 5, use_namespace: false)
71
+ raw, err, st = kubectl.run("api-versions", attempts: 5, use_namespace: false)
72
72
  # The "core" group is represented by an empty string
73
73
  versions = { "" => %w(v1) }
74
74
  if st.success?
@@ -78,6 +78,8 @@ module Krane
78
78
  versions[group] ||= []
79
79
  versions[group] << version
80
80
  end
81
+ else
82
+ raise FatalKubeAPIError, "Error retrieving api-versions: #{err}"
81
83
  end
82
84
  versions
83
85
  end
@@ -85,7 +87,8 @@ module Krane
85
87
  def version_for_kind(versions, kind)
86
88
  # Override list for kinds that don't appear in the lastest version of a group
87
89
  version_override = { "CronJob" => "v1beta1", "VolumeAttachment" => "v1beta1",
88
- "CSIDriver" => "v1beta1", "Ingress" => "v1beta1", "CSINode" => "v1beta1" }
90
+ "CSIDriver" => "v1beta1", "Ingress" => "v1beta1",
91
+ "CSINode" => "v1beta1", "Job" => "v1" }
89
92
 
90
93
  pattern = /v(?<major>\d+)(?<pre>alpha|beta)?(?<minor>\d+)?/
91
94
  latest = versions.sort_by do |version|
@@ -97,12 +100,12 @@ module Krane
97
100
  end
98
101
 
99
102
  def fetch_crds
100
- raw_json, _, st = kubectl.run("get", "CustomResourceDefinition", output: "json", attempts: 5,
103
+ raw_json, err, st = kubectl.run("get", "CustomResourceDefinition", output: "json", attempts: 5,
101
104
  use_namespace: false)
102
105
  if st.success?
103
106
  JSON.parse(raw_json)["items"]
104
107
  else
105
- []
108
+ raise FatalKubeAPIError, "Error retrieving CustomResourceDefinition: #{err}"
106
109
  end
107
110
  end
108
111
 
@@ -3,11 +3,11 @@ module Krane
3
3
  module Concurrency
4
4
  MAX_THREADS = 8
5
5
 
6
- def self.split_across_threads(all_work, &block)
6
+ def self.split_across_threads(all_work, max_threads: MAX_THREADS, &block)
7
7
  return if all_work.empty?
8
8
  raise ArgumentError, "Block of work is required" unless block_given?
9
9
 
10
- slice_size = ((all_work.length + MAX_THREADS - 1) / MAX_THREADS)
10
+ slice_size = ((all_work.length + max_threads - 1) / max_threads)
11
11
  threads = []
12
12
  all_work.each_slice(slice_size) do |work_group|
13
13
  threads << Thread.new { work_group.each(&block) }
@@ -3,7 +3,7 @@ module Krane
3
3
  class ContainerLogs
4
4
  attr_reader :lines, :container_name
5
5
 
6
- DEFAULT_LINE_LIMIT = 250
6
+ DEFAULT_LINE_LIMIT = 25
7
7
 
8
8
  def initialize(parent_id:, container_name:, namespace:, context:, logger:)
9
9
  @parent_id = parent_id
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ module Krane
3
+ class ContainerOverrides
4
+ attr_reader :command, :arguments, :env_vars, :image_tag
5
+
6
+ def initialize(command: nil, arguments: nil, env_vars: [], image_tag: nil)
7
+ @command = command
8
+ @arguments = arguments
9
+ @env_vars = env_vars
10
+ @image_tag = image_tag
11
+ end
12
+
13
+ def apply!(container)
14
+ container.command = command if command
15
+ container.args = arguments if arguments
16
+
17
+ if image_tag
18
+ image = container.image
19
+ base_image, _old_tag = image.split(':')
20
+ new_image = "#{base_image}:#{image_tag}"
21
+
22
+ container.image = new_image
23
+ end
24
+
25
+ env_args = env_vars.map do |env|
26
+ key, value = env.split('=', 2)
27
+ { name: key, value: value }
28
+ end
29
+ container.env ||= []
30
+ container.env = container.env.map(&:to_h) + env_args
31
+ end
32
+ end
33
+ end
@@ -10,7 +10,6 @@ require 'krane/resource_cache'
10
10
  require 'krane/kubernetes_resource'
11
11
  %w(
12
12
  custom_resource
13
- cloudsql
14
13
  config_map
15
14
  deployment
16
15
  ingress
@@ -60,14 +59,14 @@ module Krane
60
59
  before_crs = %w(
61
60
  ResourceQuota
62
61
  NetworkPolicy
63
- )
64
- after_crs = %w(
65
62
  ConfigMap
66
63
  PersistentVolumeClaim
67
64
  ServiceAccount
68
65
  Role
69
66
  RoleBinding
70
67
  Secret
68
+ )
69
+ after_crs = %w(
71
70
  Pod
72
71
  )
73
72
 
@@ -117,8 +116,8 @@ module Krane
117
116
  # Runs the task, returning a boolean representing success or failure
118
117
  #
119
118
  # @return [Boolean]
120
- def run(*args)
121
- run!(*args)
119
+ def run(**args)
120
+ run!(**args)
122
121
  true
123
122
  rescue FatalDeploymentError
124
123
  false
@@ -216,6 +215,7 @@ module Krane
216
215
 
217
216
  def check_initial_status(resources)
218
217
  cache = ResourceCache.new(@task_config)
218
+ cache.prewarm(resources)
219
219
  Krane::Concurrency.split_across_threads(resources) { |r| r.sync(cache) }
220
220
  resources.each { |r| @logger.info(r.pretty_status) }
221
221
  end
@@ -134,10 +134,13 @@ module Krane
134
134
  end
135
135
 
136
136
  def decrypt_ejson(key_dir)
137
- # ejson seems to dump both errors and output to STDOUT
138
- out_err, st = Open3.capture2e("EJSON_KEYDIR=#{key_dir} ejson decrypt #{@ejson_file}")
139
- raise EjsonSecretError, out_err unless st.success?
140
- JSON.parse(out_err)
137
+ out, err, st = Open3.capture3("EJSON_KEYDIR=#{key_dir} ejson decrypt #{@ejson_file}")
138
+ unless st.success?
139
+ # older ejson versions dump some errors to STDOUT
140
+ msg = err.presence || out
141
+ raise EjsonSecretError, msg
142
+ end
143
+ JSON.parse(out)
141
144
  rescue JSON::ParserError
142
145
  raise EjsonSecretError, "Failed to parse decrypted ejson"
143
146
  end
@@ -46,8 +46,8 @@ module Krane
46
46
  # Runs the task, returning a boolean representing success or failure
47
47
  #
48
48
  # @return [Boolean]
49
- def run(*args)
50
- run!(*args)
49
+ def run(**args)
50
+ run!(**args)
51
51
  true
52
52
  rescue FatalDeploymentError
53
53
  false
@@ -202,6 +202,7 @@ module Krane
202
202
 
203
203
  def check_initial_status(resources)
204
204
  cache = ResourceCache.new(@task_config)
205
+ cache.prewarm(resources)
205
206
  Concurrency.split_across_threads(resources) { |r| r.sync(cache) }
206
207
  resources.each { |r| logger.info(r.pretty_status) }
207
208
  end
@@ -6,6 +6,7 @@ module Krane
6
6
  ERROR_MATCHERS = {
7
7
  not_found: /NotFound/,
8
8
  client_timeout: /Client\.Timeout exceeded while awaiting headers/,
9
+ empty: /\A\z/,
9
10
  }
10
11
  DEFAULT_TIMEOUT = 15
11
12
  MAX_RETRY_DELAY = 16
@@ -34,7 +35,16 @@ module Krane
34
35
  (1..attempts).to_a.each do |current_attempt|
35
36
  logger.debug("Running command (attempt #{current_attempt}): #{cmd.join(' ')}")
36
37
  out, err, st = Open3.capture3(*cmd)
37
- logger.debug("Kubectl out: " + out.gsub(/\s+/, ' ')) unless output_is_sensitive
38
+
39
+ # https://github.com/Shopify/krane/issues/395
40
+ unless out.valid_encoding?
41
+ out = out.dup.force_encoding(Encoding::UTF_8)
42
+ end
43
+
44
+ if logger.debug? && !output_is_sensitive
45
+ # don't do the gsub unless we're going to print this
46
+ logger.debug("Kubectl out: " + out.gsub(/\s+/, ' '))
47
+ end
38
48
 
39
49
  break if st.success?
40
50
  raise(ResourceNotFoundError, err) if err.match(ERROR_MATCHERS[:not_found]) && raise_if_not_found
@@ -16,7 +16,7 @@ module Krane
16
16
  TIMEOUT = 5.minutes
17
17
  LOG_LINE_COUNT = 250
18
18
  SERVER_DRY_RUN_DISABLED_ERROR =
19
- /(unknown flag: --server-dry-run)|(doesn't support dry-run)|(dryRun alpha feature is disabled)/
19
+ /(unknown flag: --server-dry-run)|(does[\s\']n[o|']t support dry[-\s]run)|(dryRun alpha feature is disabled)/
20
20
 
21
21
  DISABLE_FETCHING_LOG_INFO = 'DISABLE_FETCHING_LOG_INFO'
22
22
  DISABLE_FETCHING_EVENT_INFO = 'DISABLE_FETCHING_EVENT_INFO'
@@ -38,6 +38,7 @@ module Krane
38
38
  LAST_APPLIED_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
39
39
  SENSITIVE_TEMPLATE_CONTENT = false
40
40
  SERVER_DRY_RUNNABLE = false
41
+ SYNC_DEPENDENCIES = []
41
42
 
42
43
  class << self
43
44
  def build(namespace: nil, context:, definition:, logger:, statsd_tags:, crd: nil, global_names: [])
@@ -59,8 +60,8 @@ module Krane
59
60
  end
60
61
 
61
62
  def class_for_kind(kind)
62
- if Krane.const_defined?(kind)
63
- Krane.const_get(kind)
63
+ if Krane.const_defined?(kind, false)
64
+ Krane.const_get(kind, false)
64
65
  end
65
66
  rescue NameError
66
67
  nil
@@ -255,7 +256,7 @@ module Krane
255
256
  if cause == :gave_up
256
257
  debug_heading = ColorizedString.new("#{id}: GLOBAL WATCH TIMEOUT (#{info_hash[:timeout]} seconds)").yellow
257
258
  helpful_info << "If you expected it to take longer than #{info_hash[:timeout]} seconds for your deploy"\
258
- " to roll out, increase --max-watch-seconds."
259
+ " to roll out, increase --global-timeout."
259
260
  elsif deploy_failed?
260
261
  debug_heading = ColorizedString.new("#{id}: FAILED").red
261
262
  helpful_info << failure_message if failure_message.present?
@@ -571,7 +572,7 @@ module Krane
571
572
  def validate_with_server_side_dry_run(kubectl)
572
573
  command = ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
573
574
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
574
- retry_whitelist: [:client_timeout], attempts: 3)
575
+ retry_whitelist: [:client_timeout, :empty], attempts: 3)
575
576
  end
576
577
 
577
578
  # Local dry run is supported on only create and apply
@@ -581,7 +582,7 @@ module Krane
581
582
  verb = deploy_method == :apply ? "apply" : "create"
582
583
  command = [verb, "-f", file_path, "--dry-run", "--output=name"]
583
584
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
584
- retry_whitelist: [:client_timeout], attempts: 3, use_namespace: !global?)
585
+ retry_whitelist: [:client_timeout, :empty], attempts: 3, use_namespace: !global?)
585
586
  end
586
587
 
587
588
  def labels
@@ -62,7 +62,7 @@ module Krane
62
62
  kind
63
63
  end
64
64
 
65
- def validate_definition(*)
65
+ def validate_definition(*, **)
66
66
  super
67
67
 
68
68
  @crd.validate_rollout_conditions
@@ -66,7 +66,7 @@ module Krane
66
66
  @rollout_conditions = nil
67
67
  end
68
68
 
69
- def validate_definition(*)
69
+ def validate_definition(*, **)
70
70
  super
71
71
 
72
72
  validate_rollout_conditions
@@ -3,6 +3,7 @@ require "krane/kubernetes_resource/pod_set_base"
3
3
  module Krane
4
4
  class DaemonSet < PodSetBase
5
5
  TIMEOUT = 5.minutes
6
+ SYNC_DEPENDENCIES = %w(Pod)
6
7
  attr_reader :pods
7
8
 
8
9
  def sync(cache)
@@ -4,6 +4,7 @@ require 'krane/kubernetes_resource/replica_set'
4
4
  module Krane
5
5
  class Deployment < KubernetesResource
6
6
  TIMEOUT = 7.minutes
7
+ SYNC_DEPENDENCIES = %w(Pod ReplicaSet)
7
8
  REQUIRED_ROLLOUT_ANNOTATION_SUFFIX = "required-rollout"
8
9
  REQUIRED_ROLLOUT_ANNOTATION_DEPRECATED = "kubernetes-deploy.shopify.io/#{REQUIRED_ROLLOUT_ANNOTATION_SUFFIX}"
9
10
  REQUIRED_ROLLOUT_ANNOTATION = "krane.shopify.io/#{REQUIRED_ROLLOUT_ANNOTATION_SUFFIX}"
@@ -96,7 +97,7 @@ module Krane
96
97
  progress_condition.present? ? deploy_failing_to_progress? : super
97
98
  end
98
99
 
99
- def validate_definition(*)
100
+ def validate_definition(*, **)
100
101
  super
101
102
 
102
103
  unless REQUIRED_ROLLOUT_TYPES.include?(required_rollout) || percent?(required_rollout)
@@ -190,7 +191,7 @@ module Krane
190
191
  def min_available_replicas
191
192
  if percent?(required_rollout)
192
193
  (desired_replicas * required_rollout.to_i / 100.0).ceil
193
- elsif max_unavailable =~ /%/
194
+ elsif max_unavailable.is_a?(String) && max_unavailable =~ /%/
194
195
  (desired_replicas * (100 - max_unavailable.to_i) / 100.0).ceil
195
196
  else
196
197
  desired_replicas - max_unavailable.to_i
@@ -219,14 +219,7 @@ module Krane
219
219
  limbo_reason = @status.dig("state", "waiting", "reason")
220
220
  limbo_message = @status.dig("state", "waiting", "message")
221
221
 
222
- if @status.dig("lastState", "terminated", "reason") == "ContainerCannotRun"
223
- # ref: https://github.com/kubernetes/kubernetes/blob/562e721ece8a16e05c7e7d6bdd6334c910733ab2/pkg/kubelet/dockershim/docker_container.go#L353
224
- exit_code = @status.dig('lastState', 'terminated', 'exitCode')
225
- "Failed to start (exit #{exit_code}): #{@status.dig('lastState', 'terminated', 'message')}"
226
- elsif @status.dig("state", "terminated", "reason") == "ContainerCannotRun"
227
- exit_code = @status.dig('state', 'terminated', 'exitCode')
228
- "Failed to start (exit #{exit_code}): #{@status.dig('state', 'terminated', 'message')}"
229
- elsif limbo_reason == "CrashLoopBackOff"
222
+ if limbo_reason == "CrashLoopBackOff"
230
223
  exit_code = @status.dig('lastState', 'terminated', 'exitCode')
231
224
  "Crashing repeatedly (exit #{exit_code}). See logs for more information."
232
225
  elsif limbo_reason == "ErrImagePull" && limbo_message.match(/not found/i)
@@ -234,6 +227,17 @@ module Krane
234
227
  "Did you wait for it to be built and pushed to the registry before deploying?"
235
228
  elsif limbo_reason == "CreateContainerConfigError"
236
229
  "Failed to generate container configuration: #{limbo_message}"
230
+ elsif @status.dig("lastState", "terminated", "reason") == "ContainerCannotRun"
231
+ # ref: https://github.com/kubernetes/kubernetes/blob/562e721ece8a16e05c7e7d6bdd6334c910733ab2/pkg/kubelet/dockershim/docker_container.go#L353
232
+ exit_code = @status.dig('lastState', 'terminated', 'exitCode')
233
+ # We've observed failures here that are actually issues with the node or kube infra, and not with the
234
+ # container. These issues have been transient and result in a 128 exit code, so do not treat these as fatal.
235
+ return if exit_code == 128
236
+ "Failed to start (exit #{exit_code}): #{@status.dig('lastState', 'terminated', 'message')}"
237
+ elsif @status.dig("state", "terminated", "reason") == "ContainerCannotRun"
238
+ exit_code = @status.dig('state', 'terminated', 'exitCode')
239
+ return if exit_code == 128
240
+ "Failed to start (exit #{exit_code}): #{@status.dig('state', 'terminated', 'message')}"
237
241
  end
238
242
  end
239
243
 
@@ -4,6 +4,7 @@ require 'krane/kubernetes_resource/pod_set_base'
4
4
  module Krane
5
5
  class ReplicaSet < PodSetBase
6
6
  TIMEOUT = 5.minutes
7
+ SYNC_DEPENDENCIES = %w(Pod)
7
8
  attr_reader :pods
8
9
 
9
10
  def initialize(namespace:, context:, definition:, logger:, statsd_tags: nil,
@@ -17,7 +18,7 @@ module Krane
17
18
 
18
19
  def sync(cache)
19
20
  super
20
- @pods = fetch_pods_if_needed(cache) || []
21
+ @pods = exists? ? find_pods(cache) : []
21
22
  end
22
23
 
23
24
  def status
@@ -58,21 +59,6 @@ module Krane
58
59
  observed_generation != current_generation
59
60
  end
60
61
 
61
- def fetch_pods_if_needed(cache)
62
- # If the ReplicaSet doesn't exist, its pods won't either
63
- return unless exists?
64
- # If the status hasn't been updated yet, we're not going to make a determination anyway
65
- return if stale_status?
66
- # If we don't want any pods at all, we don't need to look for them
67
- return if desired_replicas == 0
68
- # We only need to fetch pods so that deploy_failed? can check that they aren't ALL bad.
69
- # If we can already tell some pods are ok from the RS data, don't bother fetching them (which can be expensive)
70
- # Lower numbers here make us more susceptible to being fooled by replicas without probes briefly appearing ready
71
- return if ready_replicas > 1
72
-
73
- find_pods(cache)
74
- end
75
-
76
62
  def rollout_data
77
63
  return { "replicas" => 0 } unless exists?
78
64
  { "replicas" => 0 }.merge(