krane 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) 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/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +24 -30
  6. data/.rubocop.yml +0 -12
  7. data/.shopify-build/krane.yml +20 -6
  8. data/1.0-Upgrade.md +2 -2
  9. data/CHANGELOG.md +61 -0
  10. data/CONTRIBUTING.md +2 -2
  11. data/README.md +14 -16
  12. data/bin/ci +1 -1
  13. data/bin/test +2 -2
  14. data/dev.yml +1 -1
  15. data/krane.gemspec +7 -5
  16. data/lib/krane/annotation.rb +11 -0
  17. data/lib/krane/cli/deploy_command.rb +3 -4
  18. data/lib/krane/cli/global_deploy_command.rb +3 -3
  19. data/lib/krane/cli/render_command.rb +3 -3
  20. data/lib/krane/cluster_resource_discovery.rb +10 -6
  21. data/lib/krane/concerns/template_reporting.rb +0 -6
  22. data/lib/krane/container_logs.rb +1 -1
  23. data/lib/krane/container_overrides.rb +33 -0
  24. data/lib/krane/deploy_task.rb +16 -13
  25. data/lib/krane/ejson_secret_provisioner.rb +1 -2
  26. data/lib/krane/global_deploy_task.rb +4 -7
  27. data/lib/krane/kubectl.rb +11 -1
  28. data/lib/krane/kubernetes_resource.rb +19 -46
  29. data/lib/krane/kubernetes_resource/custom_resource.rb +2 -2
  30. data/lib/krane/kubernetes_resource/custom_resource_definition.rb +13 -10
  31. data/lib/krane/kubernetes_resource/deployment.rb +5 -7
  32. data/lib/krane/kubernetes_resource/pod.rb +12 -8
  33. data/lib/krane/psych_k8s_compatibility.rb +36 -0
  34. data/lib/krane/render_task.rb +2 -2
  35. data/lib/krane/renderer.rb +2 -0
  36. data/lib/krane/resource_deployer.rb +7 -2
  37. data/lib/krane/resource_watcher.rb +1 -1
  38. data/lib/krane/restart_task.rb +2 -2
  39. data/lib/krane/runner_task.rb +16 -17
  40. data/lib/krane/statsd.rb +2 -2
  41. data/lib/krane/template_sets.rb +1 -1
  42. data/lib/krane/version.rb +1 -1
  43. metadata +27 -17
  44. data/lib/krane/kubernetes_resource/cloudsql.rb +0 -44
data/bin/ci CHANGED
@@ -17,5 +17,5 @@ docker run --rm \
17
17
  -e VERBOSE=1 \
18
18
  -e PARALLELISM=$PARALLELISM \
19
19
  -w /usr/src/app \
20
- ruby:2.4 \
20
+ ruby:"${RUBY_VERSION:-2.5}" \
21
21
  bin/test
data/bin/test CHANGED
@@ -41,7 +41,7 @@ bundle exec rake unit_test
41
41
  print_header "Run Non-Parallel Integration Tests"
42
42
  bundle exec rake serial_integration_test
43
43
 
44
- print_header "Run Parallel Integration Tests (N=$PARALLELISM)"
45
- PARALLELIZE_ME=1 N=$PARALLELISM bundle exec rake integration_test
44
+ print_header "Run Parallel Integration Tests (MT_CPU=$PARALLELISM)"
45
+ PARALLELIZE_ME=1 MT_CPU=$PARALLELISM bundle exec rake integration_test
46
46
 
47
47
  test $err -eq 0
data/dev.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: krane
3
3
  up:
4
- - ruby: 2.4.6 # Matches gemspec
4
+ - ruby: 2.5.7 # Matches gemspec
5
5
  - bundler
6
6
  - homebrew:
7
7
  - Caskroom/cask/minikube
@@ -23,17 +23,19 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = %w(lib)
25
25
 
26
- spec.required_ruby_version = '>= 2.4.0'
26
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
27
+
28
+ spec.required_ruby_version = '>= 2.5.0'
27
29
  spec.add_dependency("activesupport", ">= 5.0")
28
30
  spec.add_dependency("kubeclient", "~> 4.3")
29
- spec.add_dependency("googleauth", "~> 0.8.0")
31
+ spec.add_dependency("googleauth", "~> 0.8")
30
32
  spec.add_dependency("ejson", "~> 1.0")
31
33
  spec.add_dependency("colorize", "~> 0.8")
32
34
  spec.add_dependency("statsd-instrument", ['>= 2.8', "< 3.1"])
33
35
  spec.add_dependency("oj", "~> 3.0")
34
36
  spec.add_dependency("concurrent-ruby", "~> 1.1")
35
37
  spec.add_dependency("jsonpath", "~> 0.9.6")
36
- spec.add_dependency("thor", "~> 0.20.3")
38
+ spec.add_dependency("thor", ">= 1.0", "< 2.0")
37
39
 
38
40
  # Basics
39
41
  spec.add_development_dependency("bundler")
@@ -41,7 +43,7 @@ Gem::Specification.new do |spec|
41
43
  spec.add_development_dependency("yard")
42
44
 
43
45
  # Test framework
44
- spec.add_development_dependency("minitest", "~> 5.0")
46
+ spec.add_development_dependency("minitest", "~> 5.12")
45
47
  spec.add_development_dependency("minitest-stub-const", "~> 0.6")
46
48
  spec.add_development_dependency("minitest-reporters")
47
49
  spec.add_development_dependency("mocha", "~> 1.5")
@@ -54,6 +56,6 @@ Gem::Specification.new do |spec|
54
56
  spec.add_development_dependency("byebug")
55
57
  spec.add_development_dependency("ruby-prof")
56
58
  spec.add_development_dependency("ruby-prof-flamegraph")
57
- spec.add_development_dependency("rubocop", "~> 0.76.0")
59
+ spec.add_development_dependency("rubocop", "~> 0.88.0")
58
60
  spec.add_development_dependency("codecov")
59
61
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Krane
4
+ module Annotation
5
+ class << self
6
+ def for(suffix)
7
+ "krane.shopify.io/#{suffix}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -14,7 +14,7 @@ module Krane
14
14
  aliases: :f, required: false, default: [],
15
15
  desc: "Directories and files that contains the configuration to apply" },
16
16
  "stdin" => { type: :boolean, default: false,
17
- desc: "Read resources from stdin" },
17
+ desc: "[DEPRECATED] Read resources from stdin" },
18
18
  "global-timeout" => { type: :string, banner: "duration", default: DEFAULT_DEPLOY_TIMEOUT,
19
19
  desc: "Max duration to monitor workloads correctly deployed" },
20
20
  "protected-namespaces" => { type: :array, banner: "namespace1 namespace2 namespaceN",
@@ -26,7 +26,7 @@ module Krane
26
26
  "selector" => { type: :string, banner: "'label=value'",
27
27
  desc: "Select workloads by selector(s)" },
28
28
  "verbose-log-prefix" => { type: :boolean, desc: "Add [context][namespace] to the log prefix",
29
- default: true },
29
+ default: false },
30
30
  "verify-result" => { type: :boolean, default: true,
31
31
  desc: "Verify workloads correctly deployed" },
32
32
  }
@@ -46,11 +46,10 @@ module Krane
46
46
  protected_namespaces = []
47
47
  end
48
48
 
49
- # never mutate options directly
50
49
  filenames = options[:filenames].dup
51
50
  filenames << "-" if options[:stdin]
52
51
  if filenames.empty?
53
- raise Thor::RequiredArgumentMissingError, 'At least one of --filenames or --stdin must be set'
52
+ raise(Thor::RequiredArgumentMissingError, '--filenames must be set and not empty')
54
53
  end
55
54
 
56
55
  ::Krane::OptionsHelper.with_processed_template_paths(filenames) do |paths|
@@ -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,9 @@ 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",
92
+ "IngressClass" => "v1beta1" }
89
93
 
90
94
  pattern = /v(?<major>\d+)(?<pre>alpha|beta)?(?<minor>\d+)?/
91
95
  latest = versions.sort_by do |version|
@@ -97,12 +101,12 @@ module Krane
97
101
  end
98
102
 
99
103
  def fetch_crds
100
- raw_json, _, st = kubectl.run("get", "CustomResourceDefinition", output: "json", attempts: 5,
104
+ raw_json, err, st = kubectl.run("get", "CustomResourceDefinition", output: "json", attempts: 5,
101
105
  use_namespace: false)
102
106
  if st.success?
103
107
  JSON.parse(raw_json)["items"]
104
108
  else
105
- []
109
+ raise FatalKubeAPIError, "Error retrieving CustomResourceDefinition: #{err}"
106
110
  end
107
111
  end
108
112
 
@@ -15,12 +15,6 @@ module Krane
15
15
  logger.summary.add_paragraph(debug_msg)
16
16
  end
17
17
 
18
- def record_warnings(logger:, warning:, filename:)
19
- warn_msg = "Template warning: #{filename}\n"
20
- warn_msg += "> Warning message:\n#{Krane::FormattedLogger.indent_four(warning)}"
21
- logger.summary.add_paragraph(ColorizedString.new(warn_msg).yellow)
22
- end
23
-
24
18
  def add_para_from_list(logger:, action:, enum:)
25
19
  logger.summary.add_action(action)
26
20
  logger.summary.add_paragraph(enum.map { |e| "- #{e}" }.join("\n"))
@@ -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
@@ -4,13 +4,13 @@ require 'shellwords'
4
4
  require 'tempfile'
5
5
  require 'fileutils'
6
6
 
7
+ require 'krane/annotation'
7
8
  require 'krane/common'
8
9
  require 'krane/concurrency'
9
10
  require 'krane/resource_cache'
10
11
  require 'krane/kubernetes_resource'
11
12
  %w(
12
13
  custom_resource
13
- cloudsql
14
14
  config_map
15
15
  deployment
16
16
  ingress
@@ -57,21 +57,24 @@ module Krane
57
57
  )
58
58
 
59
59
  def predeploy_sequence
60
+ default_group = { group: nil }
60
61
  before_crs = %w(
61
62
  ResourceQuota
62
63
  NetworkPolicy
63
- )
64
- after_crs = %w(
65
64
  ConfigMap
66
65
  PersistentVolumeClaim
67
66
  ServiceAccount
68
67
  Role
69
68
  RoleBinding
70
69
  Secret
70
+ ).map { |r| [r, default_group] }
71
+
72
+ after_crs = %w(
71
73
  Pod
72
- )
74
+ ).map { |r| [r, default_group] }
73
75
 
74
- before_crs + cluster_resource_discoverer.crds.select(&:predeployed?).map(&:kind) + after_crs
76
+ crs = cluster_resource_discoverer.crds.select(&:predeployed?).map { |cr| [cr.kind, { group: cr.group }] }
77
+ Hash[before_crs + crs + after_crs]
75
78
  end
76
79
 
77
80
  def prune_whitelist
@@ -117,8 +120,8 @@ module Krane
117
120
  # Runs the task, returning a boolean representing success or failure
118
121
  #
119
122
  # @return [Boolean]
120
- def run(*args)
121
- run!(*args)
123
+ def run(**args)
124
+ run!(**args)
122
125
  true
123
126
  rescue FatalDeploymentError
124
127
  false
@@ -211,7 +214,10 @@ module Krane
211
214
  end
212
215
 
213
216
  def deploy_has_priority_resources?(resources)
214
- resources.any? { |r| predeploy_sequence.include?(r.type) }
217
+ resources.any? do |r|
218
+ next unless (pr = predeploy_sequence[r.type])
219
+ !pr[:group] || pr[:group] == r.group
220
+ end
215
221
  end
216
222
 
217
223
  def check_initial_status(resources)
@@ -243,6 +249,8 @@ module Krane
243
249
  @logger.info(" - #{secret.id} (from ejson)")
244
250
  end
245
251
 
252
+ StatsD.client.gauge('discover_resources.count', resources.size, tags: statsd_tags)
253
+
246
254
  resources.sort
247
255
  rescue InvalidTemplateError => e
248
256
  record_invalid_template(logger: @logger, err: e.message, filename: e.filename,
@@ -275,11 +283,6 @@ module Krane
275
283
  r.validate_definition(kubectl, selector: @selector)
276
284
  end
277
285
 
278
- resources.select(&:has_warnings?).each do |resource|
279
- record_warnings(logger: @logger, warning: resource.validation_warning_msg,
280
- filename: File.basename(resource.file_path))
281
- end
282
-
283
286
  failed_resources = resources.select(&:validation_failed?)
284
287
  if failed_resources.present?
285
288
 
@@ -12,10 +12,10 @@ module Krane
12
12
  end
13
13
 
14
14
  class EjsonSecretProvisioner
15
- EJSON_SECRET_ANNOTATION = "kubernetes-deploy.shopify.io/ejson-secret"
16
15
  EJSON_SECRET_KEY = "kubernetes_secrets"
17
16
  EJSON_SECRETS_FILE = "secrets.ejson"
18
17
  EJSON_KEYS_SECRET = "ejson-keys"
18
+
19
19
  delegate :namespace, :context, :logger, to: :@task_config
20
20
 
21
21
  def initialize(task_config:, ejson_keys_secret:, ejson_file:, statsd_tags:, selector: nil)
@@ -106,7 +106,6 @@ module Krane
106
106
  "name" => secret_name,
107
107
  "labels" => labels,
108
108
  "namespace" => namespace,
109
- "annotations" => { EJSON_SECRET_ANNOTATION => "true" },
110
109
  },
111
110
  "data" => encoded_data,
112
111
  }
@@ -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
@@ -133,11 +133,6 @@ module Krane
133
133
  r.validate_definition(@kubectl, selector: @selector)
134
134
  end
135
135
 
136
- resources.select(&:has_warnings?).each do |resource|
137
- record_warnings(logger: logger, warning: resource.validation_warning_msg,
138
- filename: File.basename(resource.file_path))
139
- end
140
-
141
136
  failed_resources = resources.select(&:validation_failed?)
142
137
  if failed_resources.present?
143
138
  failed_resources.each do |r|
@@ -173,6 +168,8 @@ module Krane
173
168
  logger.info(" - #{r.id}")
174
169
  end
175
170
 
171
+ StatsD.client.gauge('discover_resources.count', resources.size, tags: statsd_tags)
172
+
176
173
  resources.sort
177
174
  rescue InvalidTemplateError => e
178
175
  record_invalid_template(logger: logger, err: e.message, filename: e.filename, content: e.content)
@@ -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
@@ -6,9 +6,12 @@ require 'krane/remote_logs'
6
6
  require 'krane/duration_parser'
7
7
  require 'krane/label_selector'
8
8
  require 'krane/rollout_conditions'
9
+ require 'krane/psych_k8s_compatibility'
9
10
 
10
11
  module Krane
11
12
  class KubernetesResource
13
+ using PsychK8sCompatibility
14
+
12
15
  attr_reader :name, :namespace, :context
13
16
  attr_writer :type, :deploy_started_at, :global
14
17
 
@@ -16,7 +19,7 @@ module Krane
16
19
  TIMEOUT = 5.minutes
17
20
  LOG_LINE_COUNT = 250
18
21
  SERVER_DRY_RUN_DISABLED_ERROR =
19
- /(unknown flag: --server-dry-run)|(doesn't support dry-run)|(dryRun alpha feature is disabled)/
22
+ /(unknown flag: --server-dry-run)|(does[\s\']n[o|']t support dry[-\s]run)|(dryRun alpha feature is disabled)/
20
23
 
21
24
  DISABLE_FETCHING_LOG_INFO = 'DISABLE_FETCHING_LOG_INFO'
22
25
  DISABLE_FETCHING_EVENT_INFO = 'DISABLE_FETCHING_EVENT_INFO'
@@ -32,9 +35,7 @@ module Krane
32
35
  If you have reason to believe it will succeed, retry the deploy to continue to monitor the rollout.
33
36
  MSG
34
37
 
35
- TIMEOUT_OVERRIDE_ANNOTATION_SUFFIX = "timeout-override"
36
- TIMEOUT_OVERRIDE_ANNOTATION_DEPRECATED = "kubernetes-deploy.shopify.io/#{TIMEOUT_OVERRIDE_ANNOTATION_SUFFIX}"
37
- TIMEOUT_OVERRIDE_ANNOTATION = "krane.shopify.io/#{TIMEOUT_OVERRIDE_ANNOTATION_SUFFIX}"
38
+ TIMEOUT_OVERRIDE_ANNOTATION = "timeout-override"
38
39
  LAST_APPLIED_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
39
40
  SENSITIVE_TEMPLATE_CONTENT = false
40
41
  SERVER_DRY_RUNNABLE = false
@@ -60,8 +61,8 @@ module Krane
60
61
  end
61
62
 
62
63
  def class_for_kind(kind)
63
- if Krane.const_defined?(kind)
64
- Krane.const_get(kind)
64
+ if Krane.const_defined?(kind, false)
65
+ Krane.const_get(kind, false)
65
66
  end
66
67
  rescue NameError
67
68
  nil
@@ -103,7 +104,7 @@ module Krane
103
104
  def timeout_override
104
105
  return @timeout_override if defined?(@timeout_override)
105
106
 
106
- @timeout_override = DurationParser.new(krane_annotation_value(TIMEOUT_OVERRIDE_ANNOTATION_SUFFIX)).parse!.to_i
107
+ @timeout_override = DurationParser.new(krane_annotation_value(TIMEOUT_OVERRIDE_ANNOTATION)).parse!.to_i
107
108
  rescue DurationParser::ParsingError
108
109
  @timeout_override = nil
109
110
  end
@@ -123,7 +124,6 @@ module Krane
123
124
  @statsd_report_done = false
124
125
  @disappeared = false
125
126
  @validation_errors = []
126
- @validation_warnings = []
127
127
  @instance_data = {}
128
128
  @server_dry_run_validated = false
129
129
  end
@@ -134,22 +134,12 @@ module Krane
134
134
 
135
135
  def validate_definition(kubectl, selector: nil)
136
136
  @validation_errors = []
137
- @validation_warnings = []
138
137
  validate_selector(selector) if selector
139
138
  validate_timeout_annotation
140
- validate_annotation_version
141
139
  validate_spec_with_kubectl(kubectl)
142
140
  @validation_errors.present?
143
141
  end
144
142
 
145
- def validation_warning_msg
146
- @validation_warnings.join("\n")
147
- end
148
-
149
- def has_warnings?
150
- @validation_warnings.present?
151
- end
152
-
153
143
  def validation_error_msg
154
144
  @validation_errors.join("\n")
155
145
  end
@@ -228,6 +218,11 @@ module Krane
228
218
  @type || self.class.kind
229
219
  end
230
220
 
221
+ def group
222
+ grouping, version = @definition.dig("apiVersion").split("/")
223
+ version ? grouping : "core"
224
+ end
225
+
231
226
  def kubectl_resource_type
232
227
  type
233
228
  end
@@ -256,7 +251,7 @@ module Krane
256
251
  if cause == :gave_up
257
252
  debug_heading = ColorizedString.new("#{id}: GLOBAL WATCH TIMEOUT (#{info_hash[:timeout]} seconds)").yellow
258
253
  helpful_info << "If you expected it to take longer than #{info_hash[:timeout]} seconds for your deploy"\
259
- " to roll out, increase --max-watch-seconds."
254
+ " to roll out, increase --global-timeout."
260
255
  elsif deploy_failed?
261
256
  debug_heading = ColorizedString.new("#{id}: FAILED").red
262
257
  helpful_info << failure_message if failure_message.present?
@@ -495,8 +490,8 @@ module Krane
495
490
  private
496
491
 
497
492
  def validate_timeout_annotation
498
- timeout_override_value = krane_annotation_value(TIMEOUT_OVERRIDE_ANNOTATION_SUFFIX)
499
- timeout_annotation_key = krane_annotation_key(TIMEOUT_OVERRIDE_ANNOTATION_SUFFIX)
493
+ timeout_override_value = krane_annotation_value(TIMEOUT_OVERRIDE_ANNOTATION)
494
+ timeout_annotation_key = Annotation.for(TIMEOUT_OVERRIDE_ANNOTATION)
500
495
  return if timeout_override_value.nil?
501
496
 
502
497
  override = DurationParser.new(timeout_override_value).parse!
@@ -509,30 +504,8 @@ module Krane
509
504
  @validation_errors << "#{timeout_annotation_key} annotation is invalid: #{e}"
510
505
  end
511
506
 
512
- def validate_annotation_version
513
- return if validation_warning_msg.include?("annotations is deprecated")
514
- annotation_keys = @definition.dig("metadata", "annotations")&.keys
515
- annotation_keys&.each do |annotation|
516
- if annotation.include?("kubernetes-deploy.shopify.io")
517
- annotation_prefix = annotation.split('/').first
518
- @validation_warnings << "#{annotation_prefix} as a prefix for annotations is deprecated: "\
519
- "Use the 'krane.shopify.io' annotation prefix instead"
520
- return
521
- end
522
- end
523
- end
524
-
525
507
  def krane_annotation_value(suffix)
526
- @definition.dig("metadata", "annotations", "kubernetes-deploy.shopify.io/#{suffix}") ||
527
- @definition.dig("metadata", "annotations", "krane.shopify.io/#{suffix}")
528
- end
529
-
530
- def krane_annotation_key(suffix)
531
- if @definition.dig("metadata", "annotations", "kubernetes-deploy.shopify.io/#{suffix}")
532
- "kubernetes-deploy.shopify.io/#{suffix}"
533
- elsif @definition.dig("metadata", "annotations", "krane.shopify.io/#{suffix}")
534
- "krane.shopify.io/#{suffix}"
535
- end
508
+ @definition.dig("metadata", "annotations", Annotation.for(suffix))
536
509
  end
537
510
 
538
511
  def validate_selector(selector)
@@ -572,7 +545,7 @@ module Krane
572
545
  def validate_with_server_side_dry_run(kubectl)
573
546
  command = ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
574
547
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
575
- retry_whitelist: [:client_timeout], attempts: 3)
548
+ retry_whitelist: [:client_timeout, :empty], attempts: 3)
576
549
  end
577
550
 
578
551
  # Local dry run is supported on only create and apply
@@ -582,7 +555,7 @@ module Krane
582
555
  verb = deploy_method == :apply ? "apply" : "create"
583
556
  command = [verb, "-f", file_path, "--dry-run", "--output=name"]
584
557
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
585
- retry_whitelist: [:client_timeout], attempts: 3, use_namespace: !global?)
558
+ retry_whitelist: [:client_timeout, :empty], attempts: 3, use_namespace: !global?)
586
559
  end
587
560
 
588
561
  def labels