kubernetes-deploy 0.30.0 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +15 -0
  4. data/CONTRIBUTING.md +2 -2
  5. data/README.md +1 -1
  6. data/dev.yml +1 -1
  7. data/dev/flamegraph-from-tests +1 -1
  8. data/exe/kubernetes-deploy +11 -9
  9. data/exe/kubernetes-render +9 -7
  10. data/exe/kubernetes-restart +3 -3
  11. data/exe/kubernetes-run +1 -1
  12. data/kubernetes-deploy.gemspec +3 -3
  13. data/lib/krane.rb +5 -3
  14. data/lib/{kubernetes-deploy → krane}/bindings_parser.rb +1 -1
  15. data/lib/krane/cli/deploy_command.rb +14 -11
  16. data/lib/krane/cli/global_deploy_command.rb +47 -0
  17. data/lib/krane/cli/krane.rb +12 -3
  18. data/lib/krane/cli/render_command.rb +11 -9
  19. data/lib/krane/cli/restart_command.rb +4 -4
  20. data/lib/krane/cli/run_command.rb +3 -3
  21. data/lib/krane/cli/version_command.rb +1 -1
  22. data/lib/krane/cluster_resource_discovery.rb +102 -0
  23. data/lib/{kubernetes-deploy → krane}/common.rb +8 -9
  24. data/lib/krane/concerns/template_reporting.rb +29 -0
  25. data/lib/{kubernetes-deploy → krane}/concurrency.rb +1 -1
  26. data/lib/{kubernetes-deploy → krane}/container_logs.rb +1 -1
  27. data/lib/{kubernetes-deploy → krane}/deferred_summary_logging.rb +2 -2
  28. data/lib/{kubernetes-deploy → krane}/delayed_exceptions.rb +0 -0
  29. data/lib/krane/deploy_task.rb +2 -2
  30. data/lib/{kubernetes-deploy → krane}/deploy_task_config_validator.rb +1 -1
  31. data/lib/krane/deprecated_deploy_task.rb +404 -0
  32. data/lib/{kubernetes-deploy → krane}/duration_parser.rb +1 -1
  33. data/lib/{kubernetes-deploy → krane}/ejson_secret_provisioner.rb +3 -3
  34. data/lib/krane/errors.rb +28 -0
  35. data/lib/{kubernetes-deploy → krane}/formatted_logger.rb +2 -2
  36. data/lib/krane/global_deploy_task.rb +210 -0
  37. data/lib/krane/global_deploy_task_config_validator.rb +12 -0
  38. data/lib/{kubernetes-deploy → krane}/kubeclient_builder.rb +11 -3
  39. data/lib/{kubernetes-deploy → krane}/kubectl.rb +2 -2
  40. data/lib/{kubernetes-deploy → krane}/kubernetes_resource.rb +54 -22
  41. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/cloudsql.rb +1 -1
  42. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/config_map.rb +1 -1
  43. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/cron_job.rb +1 -1
  44. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/custom_resource.rb +2 -2
  45. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/custom_resource_definition.rb +1 -5
  46. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/daemon_set.rb +7 -4
  47. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/deployment.rb +2 -2
  48. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/horizontal_pod_autoscaler.rb +1 -1
  49. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/ingress.rb +1 -1
  50. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/job.rb +1 -1
  51. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/network_policy.rb +1 -1
  52. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/persistent_volume_claim.rb +1 -1
  53. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod.rb +2 -2
  54. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_disruption_budget.rb +2 -2
  55. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_set_base.rb +3 -3
  56. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_template.rb +1 -1
  57. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/replica_set.rb +2 -2
  58. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/resource_quota.rb +1 -1
  59. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/role.rb +1 -1
  60. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/role_binding.rb +1 -1
  61. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/secret.rb +1 -1
  62. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/service.rb +2 -2
  63. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/service_account.rb +1 -1
  64. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/stateful_set.rb +2 -2
  65. data/lib/{kubernetes-deploy → krane}/label_selector.rb +1 -1
  66. data/lib/{kubernetes-deploy → krane}/oj.rb +0 -0
  67. data/lib/{kubernetes-deploy → krane}/options_helper.rb +2 -2
  68. data/lib/{kubernetes-deploy → krane}/remote_logs.rb +2 -2
  69. data/lib/krane/render_task.rb +149 -0
  70. data/lib/{kubernetes-deploy → krane}/renderer.rb +1 -1
  71. data/lib/{kubernetes-deploy → krane}/resource_cache.rb +4 -3
  72. data/lib/krane/resource_deployer.rb +265 -0
  73. data/lib/{kubernetes-deploy → krane}/resource_watcher.rb +6 -6
  74. data/lib/krane/restart_task.rb +224 -0
  75. data/lib/{kubernetes-deploy → krane}/rollout_conditions.rb +1 -1
  76. data/lib/krane/runner_task.rb +212 -0
  77. data/lib/{kubernetes-deploy → krane}/runner_task_config_validator.rb +1 -1
  78. data/lib/{kubernetes-deploy → krane}/statsd.rb +13 -27
  79. data/lib/krane/task_config.rb +19 -0
  80. data/lib/{kubernetes-deploy → krane}/task_config_validator.rb +1 -1
  81. data/lib/{kubernetes-deploy → krane}/template_sets.rb +5 -5
  82. data/lib/krane/version.rb +4 -0
  83. data/lib/kubernetes-deploy/deploy_task.rb +6 -603
  84. data/lib/kubernetes-deploy/errors.rb +1 -26
  85. data/lib/kubernetes-deploy/render_task.rb +5 -139
  86. data/lib/kubernetes-deploy/rescue_krane_exceptions.rb +18 -0
  87. data/lib/kubernetes-deploy/restart_task.rb +6 -215
  88. data/lib/kubernetes-deploy/runner_task.rb +6 -203
  89. metadata +75 -58
  90. data/lib/kubernetes-deploy/cluster_resource_discovery.rb +0 -57
  91. data/lib/kubernetes-deploy/task_config.rb +0 -16
  92. data/lib/kubernetes-deploy/version.rb +0 -4
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'active_support/duration'
4
4
 
5
- module KubernetesDeploy
5
+ module Krane
6
6
  # This class is a less strict extension of ActiveSupport::Duration::ISO8601Parser.
7
7
  # In addition to full ISO8601 durations, it can parse unprefixed ISO8601 time components (e.g. '1H').
8
8
  # It is also case-insensitive.
@@ -2,9 +2,9 @@
2
2
  require 'json'
3
3
  require 'base64'
4
4
  require 'open3'
5
- require 'kubernetes-deploy/kubectl'
5
+ require 'krane/kubectl'
6
6
 
7
- module KubernetesDeploy
7
+ module Krane
8
8
  class EjsonSecretError < FatalDeploymentError
9
9
  def initialize(msg)
10
10
  super("Generation of Kubernetes secrets from ejson failed: #{msg}")
@@ -111,7 +111,7 @@ module KubernetesDeploy
111
111
  "data" => encoded_data,
112
112
  }
113
113
 
114
- KubernetesDeploy::Secret.build(
114
+ Krane::Secret.build(
115
115
  namespace: namespace, context: context, logger: logger, definition: secret, statsd_tags: @statsd_tags,
116
116
  )
117
117
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Krane
4
+ class FatalDeploymentError < StandardError; end
5
+ class FatalKubeAPIError < FatalDeploymentError; end
6
+ class KubectlError < StandardError; end
7
+ class TaskConfigurationError < FatalDeploymentError; end
8
+
9
+ class InvalidTemplateError < FatalDeploymentError
10
+ attr_reader :content
11
+ attr_accessor :filename
12
+ def initialize(err, filename: nil, content: nil)
13
+ @filename = filename
14
+ @content = content
15
+ super(err)
16
+ end
17
+ end
18
+
19
+ class DeploymentTimeoutError < FatalDeploymentError; end
20
+
21
+ class EjsonPrunableError < FatalDeploymentError
22
+ def initialize
23
+ super("Found #{KubernetesResource::LAST_APPLIED_ANNOTATION} annotation on " \
24
+ "#{EjsonSecretProvisioner::EJSON_KEYS_SECRET} secret. " \
25
+ "krane will not continue since it is extremely unlikely that this secret should be pruned.")
26
+ end
27
+ end
28
+ end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  require 'logger'
3
3
  require 'colorized_string'
4
- require 'kubernetes-deploy/deferred_summary_logging'
4
+ require 'krane/deferred_summary_logging'
5
5
 
6
- module KubernetesDeploy
6
+ module Krane
7
7
  class FormattedLogger < Logger
8
8
  include DeferredSummaryLogging
9
9
 
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+ require 'tempfile'
3
+
4
+ require 'krane/common'
5
+ require 'krane/concurrency'
6
+ require 'krane/resource_cache'
7
+ require 'krane/kubectl'
8
+ require 'krane/kubeclient_builder'
9
+ require 'krane/cluster_resource_discovery'
10
+ require 'krane/template_sets'
11
+ require 'krane/resource_deployer'
12
+ require 'krane/kubernetes_resource'
13
+ require 'krane/global_deploy_task_config_validator'
14
+ require 'krane/concerns/template_reporting'
15
+
16
+ %w(
17
+ custom_resource
18
+ custom_resource_definition
19
+ ).each do |subresource|
20
+ require "krane/kubernetes_resource/#{subresource}"
21
+ end
22
+
23
+ module Krane
24
+ # Ship global resources to a context
25
+ class GlobalDeployTask
26
+ extend Krane::StatsD::MeasureMethods
27
+ include TemplateReporting
28
+ delegate :context, :logger, :global_kinds, to: :@task_config
29
+
30
+ # Initializes the deploy task
31
+ #
32
+ # @param context [String] Kubernetes context
33
+ # @param global_timeout [Integer] Timeout in seconds
34
+ # @param selector [Hash] Selector(s) parsed by Krane::LabelSelector
35
+ # @param template_paths [Array<String>] An array of template paths
36
+ def initialize(context:, global_timeout: nil, selector: nil, filenames: [], logger: nil)
37
+ template_paths = filenames.map { |path| File.expand_path(path) }
38
+
39
+ @task_config = TaskConfig.new(context, nil, logger)
40
+ @template_sets = TemplateSets.from_dirs_and_files(paths: template_paths,
41
+ logger: @task_config.logger)
42
+ @global_timeout = global_timeout
43
+ @selector = selector
44
+ end
45
+
46
+ # Runs the task, returning a boolean representing success or failure
47
+ #
48
+ # @return [Boolean]
49
+ def run(*args)
50
+ run!(*args)
51
+ true
52
+ rescue FatalDeploymentError
53
+ false
54
+ end
55
+
56
+ # Runs the task, raising exceptions in case of issues
57
+ #
58
+ # @param verify_result [Boolean] Wait for completion and verify success
59
+ # @param prune [Boolean] Enable deletion of resources that match the provided
60
+ # selector and do not appear in the template dir
61
+ #
62
+ # @return [nil]
63
+ def run!(verify_result: true, prune: true)
64
+ start = Time.now.utc
65
+ logger.reset
66
+
67
+ logger.phase_heading("Initializing deploy")
68
+ validate_configuration
69
+ resources = discover_resources
70
+ validate_resources(resources)
71
+
72
+ logger.phase_heading("Checking initial resource statuses")
73
+ check_initial_status(resources)
74
+
75
+ logger.phase_heading("Deploying all resources")
76
+ deploy!(resources, verify_result, prune)
77
+
78
+ StatsD.client.event("Deployment succeeded",
79
+ "Successfully deployed all resources to #{context}",
80
+ alert_type: "success", tags: statsd_tags + %w(status:success))
81
+ StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
82
+ tags: statsd_tags << "status:success")
83
+ logger.print_summary(:success)
84
+ rescue Krane::DeploymentTimeoutError
85
+ logger.print_summary(:timed_out)
86
+ StatsD.client.event("Deployment timed out",
87
+ "One or more resources failed to deploy to #{context} in time",
88
+ alert_type: "error", tags: statsd_tags + %w(status:timeout))
89
+ StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
90
+ tags: statsd_tags << "status:timeout")
91
+ raise
92
+ rescue Krane::FatalDeploymentError => error
93
+ logger.summary.add_action(error.message) if error.message != error.class.to_s
94
+ logger.print_summary(:failure)
95
+ StatsD.client.event("Deployment failed",
96
+ "One or more resources failed to deploy to #{context}",
97
+ alert_type: "error", tags: statsd_tags + %w(status:failed))
98
+ StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
99
+ tags: statsd_tags << "status:failed")
100
+ raise
101
+ end
102
+
103
+ private
104
+
105
+ def deploy!(resources, verify_result, prune)
106
+ resource_deployer = ResourceDeployer.new(task_config: @task_config,
107
+ prune_whitelist: prune_whitelist, max_watch_seconds: @global_timeout,
108
+ selector: @selector, statsd_tags: statsd_tags)
109
+ resource_deployer.deploy!(resources, verify_result, prune)
110
+ end
111
+
112
+ def validate_configuration
113
+ task_config_validator = GlobalDeployTaskConfigValidator.new(@task_config,
114
+ kubectl, kubeclient_builder)
115
+ errors = []
116
+ errors += task_config_validator.errors
117
+ errors += @template_sets.validate
118
+ errors << "Selector is required" unless @selector.to_h.present?
119
+ unless errors.empty?
120
+ add_para_from_list(logger: logger, action: "Configuration invalid", enum: errors)
121
+ raise TaskConfigurationError
122
+ end
123
+
124
+ logger.info("Using resource selector #{@selector}")
125
+ logger.info("All required parameters and files are present")
126
+ end
127
+ measure_method(:validate_configuration)
128
+
129
+ def validate_resources(resources)
130
+ validate_globals(resources)
131
+
132
+ Concurrency.split_across_threads(resources) do |r|
133
+ r.validate_definition(@kubectl, selector: @selector)
134
+ end
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
+ failed_resources = resources.select(&:validation_failed?)
142
+ if failed_resources.present?
143
+ failed_resources.each do |r|
144
+ content = File.read(r.file_path) if File.file?(r.file_path) && !r.sensitive_template_content?
145
+ record_invalid_template(logger: logger, err: r.validation_error_msg,
146
+ filename: File.basename(r.file_path), content: content)
147
+ end
148
+ raise FatalDeploymentError, "Template validation failed"
149
+ end
150
+ end
151
+ measure_method(:validate_resources)
152
+
153
+ def validate_globals(resources)
154
+ return unless (namespaced = resources.reject(&:global?).presence)
155
+ namespaced_names = namespaced.map do |resource|
156
+ "#{resource.name} (#{resource.type}) in #{File.basename(resource.file_path)}"
157
+ end
158
+ namespaced_names = FormattedLogger.indent_four(namespaced_names.join("\n"))
159
+
160
+ logger.summary.add_paragraph(ColorizedString.new("Namespaced resources:\n#{namespaced_names}").yellow)
161
+ raise FatalDeploymentError, "This command cannot deploy namespaced resources"
162
+ end
163
+
164
+ def discover_resources
165
+ logger.info("Discovering resources:")
166
+ resources = []
167
+ crds_by_kind = cluster_resource_discoverer.crds.map { |crd| [crd.name, crd] }.to_h
168
+ @template_sets.with_resource_definitions do |r_def|
169
+ crd = crds_by_kind[r_def["kind"]]&.first
170
+ r = KubernetesResource.build(context: context, logger: logger, definition: r_def,
171
+ crd: crd, global_names: global_kinds, statsd_tags: statsd_tags)
172
+ resources << r
173
+ logger.info(" - #{r.id}")
174
+ end
175
+
176
+ resources.sort
177
+ rescue InvalidTemplateError => e
178
+ record_invalid_template(logger: logger, err: e.message, filename: e.filename, content: e.content)
179
+ raise FatalDeploymentError, "Failed to parse template"
180
+ end
181
+ measure_method(:discover_resources)
182
+
183
+ def cluster_resource_discoverer
184
+ @cluster_resource_discoverer ||= ClusterResourceDiscovery.new(task_config: @task_config)
185
+ end
186
+
187
+ def statsd_tags
188
+ %W(context:#{context})
189
+ end
190
+
191
+ def kubectl
192
+ @kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
193
+ end
194
+
195
+ def kubeclient_builder
196
+ @kubeclient_builder ||= KubeclientBuilder.new
197
+ end
198
+
199
+ def prune_whitelist
200
+ cluster_resource_discoverer.prunable_resources(namespaced: false)
201
+ end
202
+
203
+ def check_initial_status(resources)
204
+ cache = ResourceCache.new(@task_config)
205
+ Concurrency.split_across_threads(resources) { |r| r.sync(cache) }
206
+ resources.each { |r| logger.info(r.pretty_status) }
207
+ end
208
+ measure_method(:check_initial_status, "initial_status.duration")
209
+ end
210
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'krane/task_config_validator'
4
+
5
+ module Krane
6
+ class GlobalDeployTaskConfigValidator < Krane::TaskConfigValidator
7
+ def initialize(*arguments)
8
+ super(*arguments)
9
+ @validations -= [:validate_namespace_exists]
10
+ end
11
+ end
12
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'kubeclient'
3
3
 
4
- module KubernetesDeploy
4
+ module Krane
5
5
  class KubeclientBuilder
6
6
  class ContextMissingError < FatalDeploymentError
7
7
  def initialize(context_name, kubeconfig)
@@ -103,6 +103,14 @@ module KubernetesDeploy
103
103
  )
104
104
  end
105
105
 
106
+ def build_scheduling_v1beta1_kubeclient(context)
107
+ build_kubeclient(
108
+ api_version: "v1beta1",
109
+ context: context,
110
+ endpoint_path: "/apis/scheduling.k8s.io"
111
+ )
112
+ end
113
+
106
114
  def validate_config_files
107
115
  errors = []
108
116
  if @kubeconfig_files.empty?
@@ -137,8 +145,8 @@ module KubernetesDeploy
137
145
  ssl_options: kube_context.ssl_options,
138
146
  auth_options: kube_context.auth_options,
139
147
  timeouts: {
140
- open: KubernetesDeploy::Kubectl::DEFAULT_TIMEOUT,
141
- read: KubernetesDeploy::Kubectl::DEFAULT_TIMEOUT,
148
+ open: Krane::Kubectl::DEFAULT_TIMEOUT,
149
+ read: Krane::Kubectl::DEFAULT_TIMEOUT,
142
150
  }
143
151
  )
144
152
  client.discover
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'open3'
3
3
 
4
- module KubernetesDeploy
4
+ module Krane
5
5
  class Kubectl
6
6
  ERROR_MATCHERS = {
7
7
  not_found: /NotFound/,
@@ -52,7 +52,7 @@ module KubernetesDeploy
52
52
  else
53
53
  logger.debug("Kubectl err: #{output_is_sensitive ? '<suppressed sensitive output>' : err}")
54
54
  end
55
- StatsD.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1] })
55
+ StatsD.client.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1] })
56
56
 
57
57
  break unless retriable_err?(err, retry_whitelist) && current_attempt < attempts
58
58
  sleep(retry_delay(current_attempt))
@@ -2,12 +2,12 @@
2
2
  require 'json'
3
3
  require 'shellwords'
4
4
 
5
- require 'kubernetes-deploy/remote_logs'
6
- require 'kubernetes-deploy/duration_parser'
7
- require 'kubernetes-deploy/label_selector'
8
- require 'kubernetes-deploy/rollout_conditions'
5
+ require 'krane/remote_logs'
6
+ require 'krane/duration_parser'
7
+ require 'krane/label_selector'
8
+ require 'krane/rollout_conditions'
9
9
 
10
- module KubernetesDeploy
10
+ module Krane
11
11
  class KubernetesResource
12
12
  attr_reader :name, :namespace, :context
13
13
  attr_writer :type, :deploy_started_at, :global
@@ -40,7 +40,7 @@ module KubernetesDeploy
40
40
  SERVER_DRY_RUNNABLE = false
41
41
 
42
42
  class << self
43
- def build(namespace:, context:, definition:, logger:, statsd_tags:, crd: nil, global_names: [])
43
+ def build(namespace: nil, context:, definition:, logger:, statsd_tags:, crd: nil, global_names: [])
44
44
  validate_definition_essentials(definition)
45
45
  opts = { namespace: namespace, context: context, definition: definition, logger: logger,
46
46
  statsd_tags: statsd_tags }
@@ -59,8 +59,8 @@ module KubernetesDeploy
59
59
  end
60
60
 
61
61
  def class_for_kind(kind)
62
- if KubernetesDeploy.const_defined?(kind)
63
- KubernetesDeploy.const_get(kind)
62
+ if Krane.const_defined?(kind)
63
+ Krane.const_get(kind)
64
64
  end
65
65
  rescue NameError
66
66
  nil
@@ -87,8 +87,9 @@ module KubernetesDeploy
87
87
  raise InvalidTemplateError.new("Template is missing required field 'kind'", content: debug_content)
88
88
  end
89
89
 
90
- if definition.dig('metadata', 'name').blank?
91
- raise InvalidTemplateError.new("Template is missing required field 'metadata.name'", content: debug_content)
90
+ if definition.dig('metadata', 'name').blank? && definition.dig('metadata', 'generateName').blank?
91
+ raise InvalidTemplateError.new("Template must specify one of 'metadata.name' or 'metadata.generateName'",
92
+ content: debug_content)
92
93
  end
93
94
  end
94
95
  end
@@ -112,7 +113,7 @@ module KubernetesDeploy
112
113
 
113
114
  def initialize(namespace:, context:, definition:, logger:, statsd_tags: [])
114
115
  # subclasses must also set these if they define their own initializer
115
- @name = definition.dig("metadata", "name").to_s
116
+ @name = (definition.dig("metadata", "name") || definition.dig("metadata", "generateName")).to_s
116
117
  @optional_statsd_tags = statsd_tags
117
118
  @namespace = namespace
118
119
  @context = context
@@ -170,7 +171,7 @@ module KubernetesDeploy
170
171
 
171
172
  def sync(cache)
172
173
  @instance_data = cache.get_instance(kubectl_resource_type, name, raise_if_not_found: true)
173
- rescue KubernetesDeploy::Kubectl::ResourceNotFoundError
174
+ rescue Krane::Kubectl::ResourceNotFoundError
174
175
  @disappeared = true if deploy_started?
175
176
  @instance_data = {}
176
177
  end
@@ -235,9 +236,13 @@ module KubernetesDeploy
235
236
  !deploy_succeeded? && !deploy_failed? && (Time.now.utc - @deploy_started_at > timeout)
236
237
  end
237
238
 
238
- # Expected values: :apply, :replace, :replace_force
239
+ # Expected values: :apply, :create, :replace, :replace_force
239
240
  def deploy_method
240
- :apply
241
+ if @definition.dig("metadata", "name").blank? && uses_generate_name?
242
+ :create
243
+ else
244
+ :apply
245
+ end
241
246
  end
242
247
 
243
248
  def sync_debug_info(kubectl)
@@ -317,7 +322,7 @@ module KubernetesDeploy
317
322
  def fetch_events(kubectl)
318
323
  return {} unless exists?
319
324
  out, _err, st = kubectl.run("get", "events", "--output=go-template=#{Event.go_template_for(type, name)}",
320
- log_failure: false)
325
+ log_failure: false, use_namespace: !global?)
321
326
  return {} unless st.success?
322
327
 
323
328
  event_collector = Hash.new { |hash, key| hash[key] = [] }
@@ -340,7 +345,7 @@ module KubernetesDeploy
340
345
 
341
346
  def report_status_to_statsd(watch_time)
342
347
  unless @statsd_report_done
343
- StatsD.distribution('resource.duration', watch_time, tags: statsd_tags)
348
+ StatsD.client.distribution('resource.duration', watch_time, tags: statsd_tags)
344
349
  @statsd_report_done = true
345
350
  end
346
351
  end
@@ -350,13 +355,29 @@ module KubernetesDeploy
350
355
  end
351
356
 
352
357
  def server_dry_runnable_resource?
353
- self.class::SERVER_DRY_RUNNABLE
358
+ # generateName and server-side dry run are incompatible because the former only works with `create`
359
+ # and the latter only works with `apply`
360
+ self.class::SERVER_DRY_RUNNABLE && !uses_generate_name?
361
+ end
362
+
363
+ def uses_generate_name?
364
+ @definition.dig('metadata', 'generateName').present?
354
365
  end
355
366
 
356
367
  def server_dry_run_validated?
357
368
  @server_dry_run_validated
358
369
  end
359
370
 
371
+ # If a resource uses generateName, we don't know the full name of the resource until it's deployed to the cluster.
372
+ # In this case, we need to update our local definition with the realized name in order to accurately track the
373
+ # resource during deploy
374
+ def use_generated_name(instance_data)
375
+ @name = instance_data.dig('metadata', 'name')
376
+ @definition['metadata']['name'] = @name
377
+ @definition['metadata'].delete('generateName')
378
+ @file = create_definition_tempfile
379
+ end
380
+
360
381
  class Event
361
382
  EVENT_SEPARATOR = "ENDEVENT--BEGINEVENT"
362
383
  FIELD_SEPARATOR = "ENDFIELD--BEGINFIELD"
@@ -480,13 +501,13 @@ module KubernetesDeploy
480
501
  def validate_spec_with_kubectl(kubectl)
481
502
  err = ""
482
503
  if kubectl.server_dry_run_enabled? && server_dry_runnable_resource?
483
- _, err, st = validate_with_dry_run_option(kubectl, "--server-dry-run")
504
+ _, err, st = validate_with_server_side_dry_run(kubectl)
484
505
  @server_dry_run_validated = st.success?
485
506
  return true if st.success?
486
507
  end
487
508
 
488
509
  if err.empty? || err.match(SERVER_DRY_RUN_DISABLED_ERROR)
489
- _, err, st = validate_with_dry_run_option(kubectl, "--dry-run")
510
+ _, err, st = validate_with_local_dry_run(kubectl)
490
511
  end
491
512
 
492
513
  return true if st.success?
@@ -497,10 +518,21 @@ module KubernetesDeploy
497
518
  end
498
519
  end
499
520
 
500
- def validate_with_dry_run_option(kubectl, dry_run_option)
501
- command = ["apply", "-f", file_path, dry_run_option, "--output=name"]
521
+ # Server side dry run is only supported on apply
522
+ def validate_with_server_side_dry_run(kubectl)
523
+ command = ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
524
+ kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
525
+ retry_whitelist: [:client_timeout], attempts: 3)
526
+ end
527
+
528
+ # Local dry run is supported on only create and apply
529
+ # If the deploy method is create, validating with apply will fail
530
+ # If the resource template uses generateName, validating with apply will fail
531
+ def validate_with_local_dry_run(kubectl)
532
+ verb = deploy_method == :apply ? "apply" : "create"
533
+ command = [verb, "-f", file_path, "--dry-run", "--output=name"]
502
534
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
503
- retry_whitelist: [:client_timeout], attempts: 3)
535
+ retry_whitelist: [:client_timeout], attempts: 3, use_namespace: !global?)
504
536
  end
505
537
 
506
538
  def labels