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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/{ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE.md} +0 -0
- data/{pull_request_template.md → .github/pull_request_template.md} +0 -0
- data/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +24 -30
- data/.rubocop.yml +0 -12
- data/.shopify-build/krane.yml +20 -6
- data/1.0-Upgrade.md +2 -2
- data/CHANGELOG.md +61 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +14 -16
- data/bin/ci +1 -1
- data/bin/test +2 -2
- data/dev.yml +1 -1
- data/krane.gemspec +7 -5
- data/lib/krane/annotation.rb +11 -0
- data/lib/krane/cli/deploy_command.rb +3 -4
- data/lib/krane/cli/global_deploy_command.rb +3 -3
- data/lib/krane/cli/render_command.rb +3 -3
- data/lib/krane/cluster_resource_discovery.rb +10 -6
- data/lib/krane/concerns/template_reporting.rb +0 -6
- data/lib/krane/container_logs.rb +1 -1
- data/lib/krane/container_overrides.rb +33 -0
- data/lib/krane/deploy_task.rb +16 -13
- data/lib/krane/ejson_secret_provisioner.rb +1 -2
- data/lib/krane/global_deploy_task.rb +4 -7
- data/lib/krane/kubectl.rb +11 -1
- data/lib/krane/kubernetes_resource.rb +19 -46
- 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/pod.rb +12 -8
- 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 +7 -2
- data/lib/krane/resource_watcher.rb +1 -1
- data/lib/krane/restart_task.rb +2 -2
- data/lib/krane/runner_task.rb +16 -17
- data/lib/krane/statsd.rb +2 -2
- data/lib/krane/template_sets.rb +1 -1
- data/lib/krane/version.rb +1 -1
- metadata +27 -17
- data/lib/krane/kubernetes_resource/cloudsql.rb +0 -44
data/bin/ci
CHANGED
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 (
|
45
|
-
PARALLELIZE_ME=1
|
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
data/krane.gemspec
CHANGED
@@ -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.
|
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
|
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", "
|
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.
|
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.
|
59
|
+
spec.add_development_dependency("rubocop", "~> 0.88.0")
|
58
60
|
spec.add_development_dependency("codecov")
|
59
61
|
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:
|
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
|
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,
|
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
|
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,
|
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
|
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,
|
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,
|
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",
|
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,
|
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"))
|
data/lib/krane/container_logs.rb
CHANGED
@@ -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
|
data/lib/krane/deploy_task.rb
CHANGED
@@ -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
|
-
|
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(
|
121
|
-
run!(
|
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?
|
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(
|
50
|
-
run!(
|
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)
|
data/lib/krane/kubectl.rb
CHANGED
@@ -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
|
-
|
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)|(
|
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
|
-
|
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(
|
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 --
|
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(
|
499
|
-
timeout_annotation_key =
|
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",
|
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
|