krane 1.1.0 → 2.0.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.
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