krane 1.1.2 → 2.1.1
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.
- checksums.yaml +4 -4
- data/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +11 -19
- data/.shopify-build/krane.yml +20 -6
- data/1.0-Upgrade.md +1 -1
- data/CHANGELOG.md +48 -1
- data/CONTRIBUTING.md +1 -1
- data/README.md +8 -5
- data/bin/ci +1 -1
- data/dev.yml +3 -2
- data/krane.gemspec +4 -2
- data/lib/krane/annotation.rb +11 -0
- data/lib/krane/cluster_resource_discovery.rb +9 -6
- data/lib/krane/concerns/template_reporting.rb +0 -6
- data/lib/krane/container_overrides.rb +33 -0
- data/lib/krane/deploy_task.rb +21 -17
- data/lib/krane/ejson_secret_provisioner.rb +2 -3
- data/lib/krane/global_deploy_task.rb +8 -14
- data/lib/krane/kubeclient_builder.rb +4 -2
- data/lib/krane/kubectl.rb +8 -4
- data/lib/krane/kubernetes_resource.rb +32 -42
- data/lib/krane/kubernetes_resource/custom_resource.rb +2 -2
- data/lib/krane/kubernetes_resource/custom_resource_definition.rb +13 -10
- data/lib/krane/kubernetes_resource/deployment.rb +5 -7
- data/lib/krane/kubernetes_resource/persistent_volume_claim.rb +1 -0
- data/lib/krane/psych_k8s_compatibility.rb +36 -0
- data/lib/krane/render_task.rb +2 -2
- data/lib/krane/renderer.rb +2 -0
- data/lib/krane/resource_deployer.rb +9 -5
- data/lib/krane/restart_task.rb +8 -8
- data/lib/krane/runner_task.rb +21 -24
- data/lib/krane/statsd.rb +2 -2
- data/lib/krane/task_config.rb +7 -2
- data/lib/krane/task_config_validator.rb +3 -3
- data/lib/krane/version.rb +1 -1
- metadata +10 -6
@@ -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
|
}
|
@@ -134,7 +133,7 @@ module Krane
|
|
134
133
|
end
|
135
134
|
|
136
135
|
def decrypt_ejson(key_dir)
|
137
|
-
out, err, st = Open3.capture3(
|
136
|
+
out, err, st = Open3.capture3({ 'EJSON_KEYDIR' => key_dir.to_s }, 'ejson', 'decrypt', @ejson_file.to_s)
|
138
137
|
unless st.success?
|
139
138
|
# older ejson versions dump some errors to STDOUT
|
140
139
|
msg = err.presence || out
|
@@ -25,7 +25,8 @@ module Krane
|
|
25
25
|
class GlobalDeployTask
|
26
26
|
extend Krane::StatsD::MeasureMethods
|
27
27
|
include TemplateReporting
|
28
|
-
delegate :context, :logger, :global_kinds, to: :@task_config
|
28
|
+
delegate :context, :logger, :global_kinds, :kubeclient_builder, to: :@task_config
|
29
|
+
attr_reader :task_config
|
29
30
|
|
30
31
|
# Initializes the deploy task
|
31
32
|
#
|
@@ -33,10 +34,10 @@ module Krane
|
|
33
34
|
# @param global_timeout [Integer] Timeout in seconds
|
34
35
|
# @param selector [Hash] Selector(s) parsed by Krane::LabelSelector (*required*)
|
35
36
|
# @param filenames [Array<String>] An array of filenames and/or directories containing templates (*required*)
|
36
|
-
def initialize(context:, global_timeout: nil, selector: nil, filenames: [], logger: nil)
|
37
|
+
def initialize(context:, global_timeout: nil, selector: nil, filenames: [], logger: nil, kubeconfig: nil)
|
37
38
|
template_paths = filenames.map { |path| File.expand_path(path) }
|
38
39
|
|
39
|
-
@task_config = TaskConfig.new(context, nil, logger)
|
40
|
+
@task_config = TaskConfig.new(context, nil, logger, kubeconfig)
|
40
41
|
@template_sets = TemplateSets.from_dirs_and_files(paths: template_paths,
|
41
42
|
logger: @task_config.logger, render_erb: false)
|
42
43
|
@global_timeout = global_timeout
|
@@ -46,8 +47,8 @@ module Krane
|
|
46
47
|
# Runs the task, returning a boolean representing success or failure
|
47
48
|
#
|
48
49
|
# @return [Boolean]
|
49
|
-
def run(
|
50
|
-
run!(
|
50
|
+
def run(**args)
|
51
|
+
run!(**args)
|
51
52
|
true
|
52
53
|
rescue FatalDeploymentError
|
53
54
|
false
|
@@ -133,11 +134,6 @@ module Krane
|
|
133
134
|
r.validate_definition(@kubectl, selector: @selector)
|
134
135
|
end
|
135
136
|
|
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
137
|
failed_resources = resources.select(&:validation_failed?)
|
142
138
|
if failed_resources.present?
|
143
139
|
failed_resources.each do |r|
|
@@ -173,6 +169,8 @@ module Krane
|
|
173
169
|
logger.info(" - #{r.id}")
|
174
170
|
end
|
175
171
|
|
172
|
+
StatsD.client.gauge('discover_resources.count', resources.size, tags: statsd_tags)
|
173
|
+
|
176
174
|
resources.sort
|
177
175
|
rescue InvalidTemplateError => e
|
178
176
|
record_invalid_template(logger: logger, err: e.message, filename: e.filename, content: e.content)
|
@@ -192,10 +190,6 @@ module Krane
|
|
192
190
|
@kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
|
193
191
|
end
|
194
192
|
|
195
|
-
def kubeclient_builder
|
196
|
-
@kubeclient_builder ||= KubeclientBuilder.new
|
197
|
-
end
|
198
|
-
|
199
193
|
def prune_whitelist
|
200
194
|
cluster_resource_discoverer.prunable_resources(namespaced: false)
|
201
195
|
end
|
@@ -10,8 +10,10 @@ module Krane
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
attr_reader :kubeconfig_files
|
14
|
+
|
15
|
+
def initialize(kubeconfig: nil)
|
16
|
+
files = kubeconfig || ENV["KUBECONFIG"] || "#{Dir.home}/.kube/config"
|
15
17
|
# Split the list by colon for Linux and Mac, and semicolon for Windows.
|
16
18
|
@kubeconfig_files = files.split(/[:;]/).map!(&:strip).reject(&:empty?)
|
17
19
|
end
|
data/lib/krane/kubectl.rb
CHANGED
@@ -6,6 +6,8 @@ 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/,
|
10
|
+
context_deadline: /context deadline exceeded/,
|
9
11
|
}
|
10
12
|
DEFAULT_TIMEOUT = 15
|
11
13
|
MAX_RETRY_DELAY = 16
|
@@ -13,7 +15,7 @@ module Krane
|
|
13
15
|
|
14
16
|
class ResourceNotFoundError < StandardError; end
|
15
17
|
|
16
|
-
delegate :namespace, :context, :logger, to: :@task_config
|
18
|
+
delegate :namespace, :context, :logger, :kubeconfig, to: :@task_config
|
17
19
|
|
18
20
|
def initialize(task_config:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT,
|
19
21
|
output_is_sensitive_default: false)
|
@@ -33,7 +35,8 @@ module Krane
|
|
33
35
|
|
34
36
|
(1..attempts).to_a.each do |current_attempt|
|
35
37
|
logger.debug("Running command (attempt #{current_attempt}): #{cmd.join(' ')}")
|
36
|
-
|
38
|
+
env = { 'KUBECONFIG' => kubeconfig }
|
39
|
+
out, err, st = Open3.capture3(env, *cmd)
|
37
40
|
|
38
41
|
# https://github.com/Shopify/krane/issues/395
|
39
42
|
unless out.valid_encoding?
|
@@ -61,7 +64,8 @@ module Krane
|
|
61
64
|
else
|
62
65
|
logger.debug("Kubectl err: #{output_is_sensitive ? '<suppressed sensitive output>' : err}")
|
63
66
|
end
|
64
|
-
StatsD.client.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1]
|
67
|
+
StatsD.client.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1],
|
68
|
+
max_attempt: attempts, current_attempt: current_attempt })
|
65
69
|
|
66
70
|
break unless retriable_err?(err, retry_whitelist) && current_attempt < attempts
|
67
71
|
sleep(retry_delay(current_attempt))
|
@@ -78,7 +82,7 @@ module Krane
|
|
78
82
|
def version_info
|
79
83
|
@version_info ||=
|
80
84
|
begin
|
81
|
-
response, _, status = run("version", use_namespace: false, log_failure: true)
|
85
|
+
response, _, status = run("version", use_namespace: false, log_failure: true, attempts: 2)
|
82
86
|
raise KubectlError, "Could not retrieve kubectl version info" unless status.success?
|
83
87
|
extract_version_info_from_kubectl_response(response)
|
84
88
|
end
|
@@ -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
|
|
@@ -32,9 +35,9 @@ 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
|
-
|
36
|
-
|
37
|
-
TIMEOUT_OVERRIDE_ANNOTATION = "
|
38
|
+
ALLOWED_DEPLOY_METHOD_OVERRIDES = %w(create replace replace-force)
|
39
|
+
DEPLOY_METHOD_OVERRIDE_ANNOTATION = "deploy-method-override"
|
40
|
+
TIMEOUT_OVERRIDE_ANNOTATION = "timeout-override"
|
38
41
|
LAST_APPLIED_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
|
39
42
|
SENSITIVE_TEMPLATE_CONTENT = false
|
40
43
|
SERVER_DRY_RUNNABLE = false
|
@@ -60,8 +63,8 @@ module Krane
|
|
60
63
|
end
|
61
64
|
|
62
65
|
def class_for_kind(kind)
|
63
|
-
if Krane.const_defined?(kind)
|
64
|
-
Krane.const_get(kind)
|
66
|
+
if Krane.const_defined?(kind, false)
|
67
|
+
Krane.const_get(kind, false)
|
65
68
|
end
|
66
69
|
rescue NameError
|
67
70
|
nil
|
@@ -103,7 +106,7 @@ module Krane
|
|
103
106
|
def timeout_override
|
104
107
|
return @timeout_override if defined?(@timeout_override)
|
105
108
|
|
106
|
-
@timeout_override = DurationParser.new(krane_annotation_value(
|
109
|
+
@timeout_override = DurationParser.new(krane_annotation_value(TIMEOUT_OVERRIDE_ANNOTATION)).parse!.to_i
|
107
110
|
rescue DurationParser::ParsingError
|
108
111
|
@timeout_override = nil
|
109
112
|
end
|
@@ -123,7 +126,6 @@ module Krane
|
|
123
126
|
@statsd_report_done = false
|
124
127
|
@disappeared = false
|
125
128
|
@validation_errors = []
|
126
|
-
@validation_warnings = []
|
127
129
|
@instance_data = {}
|
128
130
|
@server_dry_run_validated = false
|
129
131
|
end
|
@@ -134,22 +136,13 @@ module Krane
|
|
134
136
|
|
135
137
|
def validate_definition(kubectl, selector: nil)
|
136
138
|
@validation_errors = []
|
137
|
-
@validation_warnings = []
|
138
139
|
validate_selector(selector) if selector
|
139
140
|
validate_timeout_annotation
|
140
|
-
|
141
|
+
validate_deploy_method_override_annotation
|
141
142
|
validate_spec_with_kubectl(kubectl)
|
142
143
|
@validation_errors.present?
|
143
144
|
end
|
144
145
|
|
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
146
|
def validation_error_msg
|
154
147
|
@validation_errors.join("\n")
|
155
148
|
end
|
@@ -228,6 +221,11 @@ module Krane
|
|
228
221
|
@type || self.class.kind
|
229
222
|
end
|
230
223
|
|
224
|
+
def group
|
225
|
+
grouping, version = @definition.dig("apiVersion").split("/")
|
226
|
+
version ? grouping : "core"
|
227
|
+
end
|
228
|
+
|
231
229
|
def kubectl_resource_type
|
232
230
|
type
|
233
231
|
end
|
@@ -242,10 +240,14 @@ module Krane
|
|
242
240
|
if @definition.dig("metadata", "name").blank? && uses_generate_name?
|
243
241
|
:create
|
244
242
|
else
|
245
|
-
:apply
|
243
|
+
deploy_method_override || :apply
|
246
244
|
end
|
247
245
|
end
|
248
246
|
|
247
|
+
def deploy_method_override
|
248
|
+
krane_annotation_value(DEPLOY_METHOD_OVERRIDE_ANNOTATION)&.to_sym
|
249
|
+
end
|
250
|
+
|
249
251
|
def sync_debug_info(kubectl)
|
250
252
|
@debug_events = fetch_events(kubectl) unless ENV[DISABLE_FETCHING_EVENT_INFO]
|
251
253
|
@debug_logs = fetch_debug_logs if print_debug_logs? && !ENV[DISABLE_FETCHING_LOG_INFO]
|
@@ -495,8 +497,8 @@ module Krane
|
|
495
497
|
private
|
496
498
|
|
497
499
|
def validate_timeout_annotation
|
498
|
-
timeout_override_value = krane_annotation_value(
|
499
|
-
timeout_annotation_key =
|
500
|
+
timeout_override_value = krane_annotation_value(TIMEOUT_OVERRIDE_ANNOTATION)
|
501
|
+
timeout_annotation_key = Annotation.for(TIMEOUT_OVERRIDE_ANNOTATION)
|
500
502
|
return if timeout_override_value.nil?
|
501
503
|
|
502
504
|
override = DurationParser.new(timeout_override_value).parse!
|
@@ -509,30 +511,18 @@ module Krane
|
|
509
511
|
@validation_errors << "#{timeout_annotation_key} annotation is invalid: #{e}"
|
510
512
|
end
|
511
513
|
|
512
|
-
def
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
"Use the 'krane.shopify.io' annotation prefix instead"
|
520
|
-
return
|
521
|
-
end
|
514
|
+
def validate_deploy_method_override_annotation
|
515
|
+
deploy_method_override_value = krane_annotation_value(DEPLOY_METHOD_OVERRIDE_ANNOTATION)
|
516
|
+
deploy_method_override_annotation_key = Annotation.for(DEPLOY_METHOD_OVERRIDE_ANNOTATION)
|
517
|
+
return unless deploy_method_override_value
|
518
|
+
unless ALLOWED_DEPLOY_METHOD_OVERRIDES.include?(deploy_method_override_value)
|
519
|
+
@validation_errors << "#{deploy_method_override_annotation_key} is invalid: Accepted values are: " \
|
520
|
+
"#{ALLOWED_DEPLOY_METHOD_OVERRIDES.join(', ')} but got #{deploy_method_override_value}"
|
522
521
|
end
|
523
522
|
end
|
524
523
|
|
525
524
|
def krane_annotation_value(suffix)
|
526
|
-
@definition.dig("metadata", "annotations",
|
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
|
525
|
+
@definition.dig("metadata", "annotations", Annotation.for(suffix))
|
536
526
|
end
|
537
527
|
|
538
528
|
def validate_selector(selector)
|
@@ -572,7 +562,7 @@ module Krane
|
|
572
562
|
def validate_with_server_side_dry_run(kubectl)
|
573
563
|
command = ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
|
574
564
|
kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
|
575
|
-
retry_whitelist: [:client_timeout], attempts: 3)
|
565
|
+
retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3)
|
576
566
|
end
|
577
567
|
|
578
568
|
# Local dry run is supported on only create and apply
|
@@ -582,7 +572,7 @@ module Krane
|
|
582
572
|
verb = deploy_method == :apply ? "apply" : "create"
|
583
573
|
command = [verb, "-f", file_path, "--dry-run", "--output=name"]
|
584
574
|
kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
|
585
|
-
retry_whitelist: [:client_timeout], attempts: 3, use_namespace: !global?)
|
575
|
+
retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3, use_namespace: !global?)
|
586
576
|
end
|
587
577
|
|
588
578
|
def labels
|
@@ -62,7 +62,7 @@ module Krane
|
|
62
62
|
kind
|
63
63
|
end
|
64
64
|
|
65
|
-
def validate_definition(
|
65
|
+
def validate_definition(*, **)
|
66
66
|
super
|
67
67
|
|
68
68
|
@crd.validate_rollout_conditions
|
@@ -70,7 +70,7 @@ module Krane
|
|
70
70
|
@validation_errors << "The CRD that specifies this resource is using invalid rollout conditions. " \
|
71
71
|
"Krane will not be able to continue until those rollout conditions are fixed.\n" \
|
72
72
|
"Rollout conditions can be found on the CRD that defines this resource (#{@crd.name}), " \
|
73
|
-
"under the annotation #{CustomResourceDefinition::ROLLOUT_CONDITIONS_ANNOTATION}.\n" \
|
73
|
+
"under the annotation #{Annotation.for(CustomResourceDefinition::ROLLOUT_CONDITIONS_ANNOTATION)}.\n" \
|
74
74
|
"Validation failed with: #{e}"
|
75
75
|
end
|
76
76
|
|
@@ -2,9 +2,8 @@
|
|
2
2
|
module Krane
|
3
3
|
class CustomResourceDefinition < KubernetesResource
|
4
4
|
TIMEOUT = 2.minutes
|
5
|
-
|
6
|
-
|
7
|
-
TIMEOUT_FOR_INSTANCE_ANNOTATION = "krane.shopify.io/instance-timeout"
|
5
|
+
ROLLOUT_CONDITIONS_ANNOTATION = "instance-rollout-conditions"
|
6
|
+
TIMEOUT_FOR_INSTANCE_ANNOTATION = "instance-timeout"
|
8
7
|
GLOBAL = true
|
9
8
|
|
10
9
|
def deploy_succeeded?
|
@@ -20,7 +19,7 @@ module Krane
|
|
20
19
|
end
|
21
20
|
|
22
21
|
def timeout_for_instance
|
23
|
-
timeout = krane_annotation_value(
|
22
|
+
timeout = krane_annotation_value(TIMEOUT_FOR_INSTANCE_ANNOTATION)
|
24
23
|
DurationParser.new(timeout).parse!.to_i
|
25
24
|
rescue DurationParser::ParsingError
|
26
25
|
nil
|
@@ -46,6 +45,10 @@ module Krane
|
|
46
45
|
@definition.dig("spec", "names", "kind")
|
47
46
|
end
|
48
47
|
|
48
|
+
def group
|
49
|
+
@definition.dig("spec", "group")
|
50
|
+
end
|
51
|
+
|
49
52
|
def prunable?
|
50
53
|
prunable = krane_annotation_value("prunable")
|
51
54
|
prunable == "true"
|
@@ -59,25 +62,25 @@ module Krane
|
|
59
62
|
def rollout_conditions
|
60
63
|
return @rollout_conditions if defined?(@rollout_conditions)
|
61
64
|
|
62
|
-
@rollout_conditions = if krane_annotation_value(
|
63
|
-
RolloutConditions.from_annotation(krane_annotation_value(
|
65
|
+
@rollout_conditions = if krane_annotation_value(ROLLOUT_CONDITIONS_ANNOTATION)
|
66
|
+
RolloutConditions.from_annotation(krane_annotation_value(ROLLOUT_CONDITIONS_ANNOTATION))
|
64
67
|
end
|
65
68
|
rescue RolloutConditionsError
|
66
69
|
@rollout_conditions = nil
|
67
70
|
end
|
68
71
|
|
69
|
-
def validate_definition(
|
72
|
+
def validate_definition(*, **)
|
70
73
|
super
|
71
74
|
|
72
75
|
validate_rollout_conditions
|
73
76
|
rescue RolloutConditionsError => e
|
74
|
-
@validation_errors << "Annotation #{
|
77
|
+
@validation_errors << "Annotation #{Annotation.for(ROLLOUT_CONDITIONS_ANNOTATION)} " \
|
75
78
|
"on #{name} is invalid: #{e}"
|
76
79
|
end
|
77
80
|
|
78
81
|
def validate_rollout_conditions
|
79
|
-
if krane_annotation_value(
|
80
|
-
conditions = RolloutConditions.from_annotation(krane_annotation_value(
|
82
|
+
if krane_annotation_value(ROLLOUT_CONDITIONS_ANNOTATION) && @rollout_conditions_validated.nil?
|
83
|
+
conditions = RolloutConditions.from_annotation(krane_annotation_value(ROLLOUT_CONDITIONS_ANNOTATION))
|
81
84
|
conditions.validate!
|
82
85
|
end
|
83
86
|
|
@@ -5,9 +5,7 @@ module Krane
|
|
5
5
|
class Deployment < KubernetesResource
|
6
6
|
TIMEOUT = 7.minutes
|
7
7
|
SYNC_DEPENDENCIES = %w(Pod ReplicaSet)
|
8
|
-
|
9
|
-
REQUIRED_ROLLOUT_ANNOTATION_DEPRECATED = "kubernetes-deploy.shopify.io/#{REQUIRED_ROLLOUT_ANNOTATION_SUFFIX}"
|
10
|
-
REQUIRED_ROLLOUT_ANNOTATION = "krane.shopify.io/#{REQUIRED_ROLLOUT_ANNOTATION_SUFFIX}"
|
8
|
+
REQUIRED_ROLLOUT_ANNOTATION = "required-rollout"
|
11
9
|
REQUIRED_ROLLOUT_TYPES = %w(maxUnavailable full none).freeze
|
12
10
|
DEFAULT_REQUIRED_ROLLOUT = 'full'
|
13
11
|
|
@@ -97,7 +95,7 @@ module Krane
|
|
97
95
|
progress_condition.present? ? deploy_failing_to_progress? : super
|
98
96
|
end
|
99
97
|
|
100
|
-
def validate_definition(
|
98
|
+
def validate_definition(*, **)
|
101
99
|
super
|
102
100
|
|
103
101
|
unless REQUIRED_ROLLOUT_TYPES.include?(required_rollout) || percent?(required_rollout)
|
@@ -106,7 +104,7 @@ module Krane
|
|
106
104
|
|
107
105
|
strategy = @definition.dig('spec', 'strategy', 'type').to_s
|
108
106
|
if required_rollout.downcase == 'maxunavailable' && strategy.present? && strategy.downcase != 'rollingupdate'
|
109
|
-
@validation_errors << "'#{
|
107
|
+
@validation_errors << "'#{Annotation.for(REQUIRED_ROLLOUT_ANNOTATION)}: #{required_rollout}' " \
|
110
108
|
"is incompatible with strategy '#{strategy}'"
|
111
109
|
end
|
112
110
|
|
@@ -151,7 +149,7 @@ module Krane
|
|
151
149
|
end
|
152
150
|
|
153
151
|
def rollout_annotation_err_msg
|
154
|
-
"'#{
|
152
|
+
"'#{Annotation.for(REQUIRED_ROLLOUT_ANNOTATION)}: #{required_rollout}' is invalid. " \
|
155
153
|
"Acceptable values: #{REQUIRED_ROLLOUT_TYPES.join(', ')}"
|
156
154
|
end
|
157
155
|
|
@@ -191,7 +189,7 @@ module Krane
|
|
191
189
|
def min_available_replicas
|
192
190
|
if percent?(required_rollout)
|
193
191
|
(desired_replicas * required_rollout.to_i / 100.0).ceil
|
194
|
-
elsif max_unavailable =~ /%/
|
192
|
+
elsif max_unavailable.is_a?(String) && max_unavailable =~ /%/
|
195
193
|
(desired_replicas * (100 - max_unavailable.to_i) / 100.0).ceil
|
196
194
|
else
|
197
195
|
desired_replicas - max_unavailable.to_i
|