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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +15 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +1 -1
- data/dev.yml +1 -1
- data/dev/flamegraph-from-tests +1 -1
- data/exe/kubernetes-deploy +11 -9
- data/exe/kubernetes-render +9 -7
- data/exe/kubernetes-restart +3 -3
- data/exe/kubernetes-run +1 -1
- data/kubernetes-deploy.gemspec +3 -3
- data/lib/krane.rb +5 -3
- data/lib/{kubernetes-deploy → krane}/bindings_parser.rb +1 -1
- data/lib/krane/cli/deploy_command.rb +14 -11
- data/lib/krane/cli/global_deploy_command.rb +47 -0
- data/lib/krane/cli/krane.rb +12 -3
- data/lib/krane/cli/render_command.rb +11 -9
- data/lib/krane/cli/restart_command.rb +4 -4
- data/lib/krane/cli/run_command.rb +3 -3
- data/lib/krane/cli/version_command.rb +1 -1
- data/lib/krane/cluster_resource_discovery.rb +102 -0
- data/lib/{kubernetes-deploy → krane}/common.rb +8 -9
- data/lib/krane/concerns/template_reporting.rb +29 -0
- data/lib/{kubernetes-deploy → krane}/concurrency.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/container_logs.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/deferred_summary_logging.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/delayed_exceptions.rb +0 -0
- data/lib/krane/deploy_task.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/deploy_task_config_validator.rb +1 -1
- data/lib/krane/deprecated_deploy_task.rb +404 -0
- data/lib/{kubernetes-deploy → krane}/duration_parser.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/ejson_secret_provisioner.rb +3 -3
- data/lib/krane/errors.rb +28 -0
- data/lib/{kubernetes-deploy → krane}/formatted_logger.rb +2 -2
- data/lib/krane/global_deploy_task.rb +210 -0
- data/lib/krane/global_deploy_task_config_validator.rb +12 -0
- data/lib/{kubernetes-deploy → krane}/kubeclient_builder.rb +11 -3
- data/lib/{kubernetes-deploy → krane}/kubectl.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource.rb +54 -22
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/cloudsql.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/config_map.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/cron_job.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/custom_resource.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/custom_resource_definition.rb +1 -5
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/daemon_set.rb +7 -4
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/deployment.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/horizontal_pod_autoscaler.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/ingress.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/job.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/network_policy.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/persistent_volume_claim.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_disruption_budget.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_set_base.rb +3 -3
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_template.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/replica_set.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/resource_quota.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/role.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/role_binding.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/secret.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/service.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/service_account.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/stateful_set.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/label_selector.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/oj.rb +0 -0
- data/lib/{kubernetes-deploy → krane}/options_helper.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/remote_logs.rb +2 -2
- data/lib/krane/render_task.rb +149 -0
- data/lib/{kubernetes-deploy → krane}/renderer.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/resource_cache.rb +4 -3
- data/lib/krane/resource_deployer.rb +265 -0
- data/lib/{kubernetes-deploy → krane}/resource_watcher.rb +6 -6
- data/lib/krane/restart_task.rb +224 -0
- data/lib/{kubernetes-deploy → krane}/rollout_conditions.rb +1 -1
- data/lib/krane/runner_task.rb +212 -0
- data/lib/{kubernetes-deploy → krane}/runner_task_config_validator.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/statsd.rb +13 -27
- data/lib/krane/task_config.rb +19 -0
- data/lib/{kubernetes-deploy → krane}/task_config_validator.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/template_sets.rb +5 -5
- data/lib/krane/version.rb +4 -0
- data/lib/kubernetes-deploy/deploy_task.rb +6 -603
- data/lib/kubernetes-deploy/errors.rb +1 -26
- data/lib/kubernetes-deploy/render_task.rb +5 -139
- data/lib/kubernetes-deploy/rescue_krane_exceptions.rb +18 -0
- data/lib/kubernetes-deploy/restart_task.rb +6 -215
- data/lib/kubernetes-deploy/runner_task.rb +6 -203
- metadata +75 -58
- data/lib/kubernetes-deploy/cluster_resource_discovery.rb +0 -57
- data/lib/kubernetes-deploy/task_config.rb +0 -16
- data/lib/kubernetes-deploy/version.rb +0 -4
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'active_support/duration'
|
4
4
|
|
5
|
-
module
|
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 '
|
5
|
+
require 'krane/kubectl'
|
6
6
|
|
7
|
-
module
|
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
|
-
|
114
|
+
Krane::Secret.build(
|
115
115
|
namespace: namespace, context: context, logger: logger, definition: secret, statsd_tags: @statsd_tags,
|
116
116
|
)
|
117
117
|
end
|
data/lib/krane/errors.rb
ADDED
@@ -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 '
|
4
|
+
require 'krane/deferred_summary_logging'
|
5
5
|
|
6
|
-
module
|
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
|
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:
|
141
|
-
read:
|
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
|
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 '
|
6
|
-
require '
|
7
|
-
require '
|
8
|
-
require '
|
5
|
+
require 'krane/remote_logs'
|
6
|
+
require 'krane/duration_parser'
|
7
|
+
require 'krane/label_selector'
|
8
|
+
require 'krane/rollout_conditions'
|
9
9
|
|
10
|
-
module
|
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
|
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
|
63
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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 =
|
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 =
|
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
|
-
|
501
|
-
|
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
|
-
|
535
|
+
retry_whitelist: [:client_timeout], attempts: 3, use_namespace: !global?)
|
504
536
|
end
|
505
537
|
|
506
538
|
def labels
|