krane 2.4.6 → 3.6.2
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 -1
- data/.github/dependabot.yml +22 -0
- data/.github/workflows/add-to-project.yml +18 -0
- data/.github/workflows/ci.yml +35 -36
- data/.github/workflows/cla.yml +22 -0
- data/.gitignore +0 -1
- data/.ruby-version +1 -0
- data/1.0-Upgrade.md +1 -1
- data/CHANGELOG.md +98 -1
- data/CONTRIBUTING.md +6 -8
- data/Gemfile +2 -0
- data/README.md +39 -26
- data/bin/ci +1 -1
- data/bin/setup +2 -2
- data/bin/test +16 -9
- data/dev.yml +14 -15
- data/krane.gemspec +7 -7
- data/lib/krane/bindings_parser.rb +4 -3
- data/lib/krane/cli/krane.rb +6 -6
- data/lib/krane/cluster_resource_discovery.rb +4 -17
- data/lib/krane/common.rb +1 -1
- data/lib/krane/deploy_task.rb +5 -25
- data/lib/krane/ejson_secret_provisioner.rb +4 -4
- data/lib/krane/global_deploy_task.rb +2 -2
- data/lib/krane/kubeclient_builder.rb +2 -2
- data/lib/krane/kubectl.rb +21 -13
- data/lib/krane/kubernetes_resource/horizontal_pod_autoscaler.rb +1 -1
- data/lib/krane/kubernetes_resource/pod.rb +1 -0
- data/lib/krane/kubernetes_resource/service.rb +8 -0
- data/lib/krane/kubernetes_resource/stateful_set.rb +15 -11
- data/lib/krane/kubernetes_resource.rb +3 -18
- data/lib/krane/resource_cache.rb +1 -1
- data/lib/krane/resource_deployer.rb +7 -13
- data/lib/krane/restart_task.rb +2 -3
- data/lib/krane/rollout_conditions.rb +2 -2
- data/lib/krane/version.rb +1 -1
- metadata +26 -25
- data/.github/probots.yml +0 -2
- data/lib/krane/kubernetes_resource/mutating_webhook_configuration.rb +0 -87
- data/lib/krane/oj.rb +0 -4
data/krane.gemspec
CHANGED
|
@@ -25,14 +25,14 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
|
|
26
26
|
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
|
27
27
|
|
|
28
|
-
spec.required_ruby_version = '>= 2.6
|
|
28
|
+
spec.required_ruby_version = '>= 2.7.6'
|
|
29
29
|
spec.add_dependency("activesupport", ">= 5.0")
|
|
30
30
|
spec.add_dependency("kubeclient", "~> 4.9")
|
|
31
|
-
spec.add_dependency("googleauth", "~>
|
|
31
|
+
spec.add_dependency("googleauth", "~> 1.2")
|
|
32
32
|
spec.add_dependency("ejson", "~> 1.0")
|
|
33
33
|
spec.add_dependency("colorize", "~> 0.8")
|
|
34
|
-
spec.add_dependency("statsd-instrument", ['>= 2.8', "<
|
|
35
|
-
spec.add_dependency("
|
|
34
|
+
spec.add_dependency("statsd-instrument", ['>= 2.8', "< 3.9"])
|
|
35
|
+
spec.add_dependency("multi_json")
|
|
36
36
|
spec.add_dependency("concurrent-ruby", "~> 1.1")
|
|
37
37
|
spec.add_dependency("jsonpath", "~> 1.0")
|
|
38
38
|
spec.add_dependency("thor", ">= 1.0", "< 2.0")
|
|
@@ -43,11 +43,11 @@ Gem::Specification.new do |spec|
|
|
|
43
43
|
spec.add_development_dependency("yard")
|
|
44
44
|
|
|
45
45
|
# Test framework
|
|
46
|
-
spec.add_development_dependency("minitest", "~> 5.
|
|
46
|
+
spec.add_development_dependency("minitest", "~> 5.19")
|
|
47
47
|
spec.add_development_dependency("minitest-stub-const", "~> 0.6")
|
|
48
48
|
spec.add_development_dependency("minitest-reporters")
|
|
49
|
-
spec.add_development_dependency("mocha", "~> 1
|
|
50
|
-
spec.add_development_dependency("webmock", "~> 3.
|
|
49
|
+
spec.add_development_dependency("mocha", "~> 2.1")
|
|
50
|
+
spec.add_development_dependency("webmock", "~> 3.18")
|
|
51
51
|
spec.add_development_dependency("timecop")
|
|
52
52
|
|
|
53
53
|
# Debugging and analysis
|
|
@@ -41,7 +41,8 @@ module Krane
|
|
|
41
41
|
when '.json'
|
|
42
42
|
bindings = parse_json(File.read(file_path))
|
|
43
43
|
when '.yaml', '.yml'
|
|
44
|
-
bindings = YAML.safe_load(File.read(file_path), [], [],
|
|
44
|
+
bindings = YAML.safe_load(File.read(file_path), permitted_classes: [], permitted_symbols: [],
|
|
45
|
+
aliases: true, filename: file_path)
|
|
45
46
|
else
|
|
46
47
|
raise ArgumentError, "Supplied file does not appear to be JSON or YAML"
|
|
47
48
|
end
|
|
@@ -53,14 +54,14 @@ module Krane
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def parse_json(string)
|
|
56
|
-
bindings =
|
|
57
|
+
bindings = MultiJson.load(string)
|
|
57
58
|
|
|
58
59
|
unless bindings.is_a?(Hash)
|
|
59
60
|
raise ArgumentError, "Expected JSON data to be a hash."
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
bindings
|
|
63
|
-
rescue
|
|
64
|
+
rescue MultiJson::ParseError
|
|
64
65
|
nil
|
|
65
66
|
end
|
|
66
67
|
|
data/lib/krane/cli/krane.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'krane'
|
|
3
|
-
require 'krane/oj'
|
|
4
|
-
require 'thor'
|
|
5
|
-
require 'krane/cli/version_command'
|
|
6
|
-
require 'krane/cli/restart_command'
|
|
7
|
-
require 'krane/cli/run_command'
|
|
8
|
-
require 'krane/cli/render_command'
|
|
9
3
|
require 'krane/cli/deploy_command'
|
|
10
4
|
require 'krane/cli/global_deploy_command'
|
|
5
|
+
require 'krane/cli/render_command'
|
|
6
|
+
require 'krane/cli/restart_command'
|
|
7
|
+
require 'krane/cli/run_command'
|
|
8
|
+
require 'krane/cli/version_command'
|
|
9
|
+
require 'multi_json'
|
|
10
|
+
require 'thor'
|
|
11
11
|
|
|
12
12
|
module Krane
|
|
13
13
|
module CLI
|
|
@@ -19,7 +19,7 @@ module Krane
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def prunable_resources(namespaced:)
|
|
22
|
-
black_list = %w(Namespace Node ControllerRevision)
|
|
22
|
+
black_list = %w(Namespace Node ControllerRevision Event)
|
|
23
23
|
fetch_resources(namespaced: namespaced).map do |resource|
|
|
24
24
|
next unless resource["verbs"].one? { |v| v == "delete" }
|
|
25
25
|
next if black_list.include?(resource["kind"])
|
|
@@ -37,19 +37,6 @@ module Krane
|
|
|
37
37
|
end.compact.uniq { |r| "#{r['apigroup']}/#{r['kind']}" }
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
def fetch_mutating_webhook_configurations
|
|
41
|
-
command = %w(get mutatingwebhookconfigurations)
|
|
42
|
-
raw_json, err, st = kubectl.run(*command, output: "json", attempts: 5, use_namespace: false)
|
|
43
|
-
if st.success?
|
|
44
|
-
JSON.parse(raw_json)["items"].map do |definition|
|
|
45
|
-
Krane::MutatingWebhookConfiguration.new(namespace: namespace, context: context, logger: logger,
|
|
46
|
-
definition: definition, statsd_tags: @namespace_tags)
|
|
47
|
-
end
|
|
48
|
-
else
|
|
49
|
-
raise FatalKubeAPIError, "Error retrieving mutatingwebhookconfigurations: #{err}"
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
40
|
private
|
|
54
41
|
|
|
55
42
|
# During discovery, the api paths may not actually be at the root, so we must programatically find it.
|
|
@@ -67,7 +54,7 @@ module Krane
|
|
|
67
54
|
@api_path_cache["/"] ||= begin
|
|
68
55
|
raw_json, err, st = kubectl.run("get", "--raw", base_api_path, attempts: 5, use_namespace: false)
|
|
69
56
|
paths = if st.success?
|
|
70
|
-
|
|
57
|
+
MultiJson.load(raw_json)["paths"]
|
|
71
58
|
else
|
|
72
59
|
raise FatalKubeAPIError, "Error retrieving raw path /: #{err}"
|
|
73
60
|
end
|
|
@@ -79,7 +66,7 @@ module Krane
|
|
|
79
66
|
@api_path_cache[path] ||= begin
|
|
80
67
|
raw_json, err, st = kubectl.run("get", "--raw", path, attempts: 2, use_namespace: false)
|
|
81
68
|
if st.success?
|
|
82
|
-
|
|
69
|
+
MultiJson.load(raw_json)
|
|
83
70
|
else
|
|
84
71
|
logger.warn("Error retrieving api path: #{err}")
|
|
85
72
|
{}
|
|
@@ -105,7 +92,7 @@ module Krane
|
|
|
105
92
|
raw_json, err, st = kubectl.run("get", "CustomResourceDefinition", output: "json", attempts: 5,
|
|
106
93
|
use_namespace: false)
|
|
107
94
|
if st.success?
|
|
108
|
-
|
|
95
|
+
MultiJson.load(raw_json)["items"]
|
|
109
96
|
else
|
|
110
97
|
raise FatalKubeAPIError, "Error retrieving CustomResourceDefinition: #{err}"
|
|
111
98
|
end
|
data/lib/krane/common.rb
CHANGED
data/lib/krane/deploy_task.rb
CHANGED
|
@@ -30,7 +30,6 @@ require 'krane/kubernetes_resource'
|
|
|
30
30
|
custom_resource_definition
|
|
31
31
|
horizontal_pod_autoscaler
|
|
32
32
|
secret
|
|
33
|
-
mutating_webhook_configuration
|
|
34
33
|
).each do |subresource|
|
|
35
34
|
require "krane/kubernetes_resource/#{subresource}"
|
|
36
35
|
end
|
|
@@ -78,7 +77,7 @@ module Krane
|
|
|
78
77
|
Hash[before_crs + crs + after_crs]
|
|
79
78
|
end
|
|
80
79
|
|
|
81
|
-
def
|
|
80
|
+
def prune_allowlist
|
|
82
81
|
cluster_resource_discoverer.prunable_resources(namespaced: true)
|
|
83
82
|
end
|
|
84
83
|
|
|
@@ -193,7 +192,7 @@ module Krane
|
|
|
193
192
|
|
|
194
193
|
def resource_deployer
|
|
195
194
|
@resource_deployer ||= Krane::ResourceDeployer.new(task_config: @task_config,
|
|
196
|
-
|
|
195
|
+
prune_allowlist: prune_allowlist, global_timeout: @global_timeout,
|
|
197
196
|
selector: @selector, statsd_tags: statsd_tags, current_sha: @current_sha)
|
|
198
197
|
end
|
|
199
198
|
|
|
@@ -285,26 +284,11 @@ module Krane
|
|
|
285
284
|
end
|
|
286
285
|
measure_method(:validate_configuration)
|
|
287
286
|
|
|
288
|
-
def partition_dry_run_resources(resources)
|
|
289
|
-
individuals = []
|
|
290
|
-
mutating_webhook_configurations = cluster_resource_discoverer.fetch_mutating_webhook_configurations
|
|
291
|
-
mutating_webhook_configurations.each do |mutating_webhook_configuration|
|
|
292
|
-
mutating_webhook_configuration.webhooks.each do |webhook|
|
|
293
|
-
individuals = (individuals + resources.select { |resource| webhook.matches_resource?(resource) }).uniq
|
|
294
|
-
resources -= individuals
|
|
295
|
-
end
|
|
296
|
-
end
|
|
297
|
-
[resources, individuals]
|
|
298
|
-
end
|
|
299
|
-
|
|
300
287
|
def validate_resources(resources)
|
|
301
288
|
validate_globals(resources)
|
|
302
|
-
batchable_resources, individuals = partition_dry_run_resources(resources.dup)
|
|
303
|
-
batch_dry_run_success = kubectl.server_dry_run_enabled? && validate_dry_run(batchable_resources)
|
|
304
|
-
individuals += batchable_resources unless batch_dry_run_success
|
|
305
289
|
resources.select! { |r| r.selected?(@selector) } if @selector_as_filter
|
|
306
290
|
Krane::Concurrency.split_across_threads(resources) do |r|
|
|
307
|
-
r.validate_definition(kubectl: kubectl, selector: @selector, dry_run:
|
|
291
|
+
r.validate_definition(kubectl: kubectl, selector: @selector, dry_run: true)
|
|
308
292
|
end
|
|
309
293
|
failed_resources = resources.select(&:validation_failed?)
|
|
310
294
|
if failed_resources.present?
|
|
@@ -330,15 +314,11 @@ module Krane
|
|
|
330
314
|
"Use GlobalDeployTask instead."
|
|
331
315
|
end
|
|
332
316
|
|
|
333
|
-
def validate_dry_run(resources)
|
|
334
|
-
resource_deployer.dry_run(resources)
|
|
335
|
-
end
|
|
336
|
-
|
|
337
317
|
def namespace_definition
|
|
338
318
|
@namespace_definition ||= begin
|
|
339
319
|
definition, _err, st = kubectl.run("get", "namespace", @namespace, use_namespace: false,
|
|
340
320
|
log_failure: true, raise_if_not_found: true, attempts: 3, output: 'json')
|
|
341
|
-
st.success? ?
|
|
321
|
+
st.success? ? MultiJson.load(definition, symbolize_names: true) : nil
|
|
342
322
|
end
|
|
343
323
|
rescue Kubectl::ResourceNotFoundError
|
|
344
324
|
nil
|
|
@@ -372,7 +352,7 @@ module Krane
|
|
|
372
352
|
unless st.success?
|
|
373
353
|
raise EjsonSecretError, "Error retrieving Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET}: #{err}"
|
|
374
354
|
end
|
|
375
|
-
|
|
355
|
+
MultiJson.load(out)
|
|
376
356
|
end
|
|
377
357
|
end
|
|
378
358
|
|
|
@@ -117,8 +117,8 @@ module Krane
|
|
|
117
117
|
|
|
118
118
|
def load_ejson_from_file
|
|
119
119
|
return {} unless File.exist?(@ejson_file)
|
|
120
|
-
|
|
121
|
-
rescue
|
|
120
|
+
MultiJson.load(File.read(@ejson_file))
|
|
121
|
+
rescue MultiJson::ParseError => e
|
|
122
122
|
raise EjsonSecretError, "Failed to parse encrypted ejson:\n #{e}"
|
|
123
123
|
end
|
|
124
124
|
|
|
@@ -139,8 +139,8 @@ module Krane
|
|
|
139
139
|
msg = err.presence || out
|
|
140
140
|
raise EjsonSecretError, msg
|
|
141
141
|
end
|
|
142
|
-
|
|
143
|
-
rescue
|
|
142
|
+
MultiJson.load(out)
|
|
143
|
+
rescue MultiJson::ParseError
|
|
144
144
|
raise EjsonSecretError, "Failed to parse decrypted ejson"
|
|
145
145
|
end
|
|
146
146
|
|
|
@@ -108,7 +108,7 @@ module Krane
|
|
|
108
108
|
|
|
109
109
|
def deploy!(resources, verify_result, prune)
|
|
110
110
|
resource_deployer = ResourceDeployer.new(task_config: @task_config,
|
|
111
|
-
|
|
111
|
+
prune_allowlist: prune_allowlist, global_timeout: @global_timeout,
|
|
112
112
|
selector: @selector, statsd_tags: statsd_tags)
|
|
113
113
|
resource_deployer.deploy!(resources, verify_result, prune)
|
|
114
114
|
end
|
|
@@ -194,7 +194,7 @@ module Krane
|
|
|
194
194
|
@kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
|
|
195
195
|
end
|
|
196
196
|
|
|
197
|
-
def
|
|
197
|
+
def prune_allowlist
|
|
198
198
|
cluster_resource_discoverer.prunable_resources(namespaced: false)
|
|
199
199
|
end
|
|
200
200
|
|
data/lib/krane/kubectl.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Krane
|
|
|
12
12
|
DEFAULT_TIMEOUT = 15
|
|
13
13
|
MAX_RETRY_DELAY = 16
|
|
14
14
|
SERVER_DRY_RUN_MIN_VERSION = "1.13"
|
|
15
|
+
ALLOW_LIST_MIN_VERSION = "1.26"
|
|
15
16
|
|
|
16
17
|
class ResourceNotFoundError < StandardError; end
|
|
17
18
|
|
|
@@ -82,9 +83,17 @@ module Krane
|
|
|
82
83
|
def version_info
|
|
83
84
|
@version_info ||=
|
|
84
85
|
begin
|
|
85
|
-
response, _, status = run("version", use_namespace: false, log_failure: true, attempts: 2)
|
|
86
|
+
response, _, status = run("version", output: "json", use_namespace: false, log_failure: true, attempts: 2)
|
|
86
87
|
raise KubectlError, "Could not retrieve kubectl version info" unless status.success?
|
|
87
|
-
|
|
88
|
+
|
|
89
|
+
version_data = MultiJson.load(response)
|
|
90
|
+
client_version = platform_agnostic_version(version_data.dig("clientVersion", "gitVersion").to_s)
|
|
91
|
+
server_version = platform_agnostic_version(version_data.dig("serverVersion", "gitVersion").to_s)
|
|
92
|
+
unless client_version && server_version
|
|
93
|
+
raise KubectlError, "Received invalid kubectl version data: #{version_data}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
{ client: client_version, server: server_version }
|
|
88
97
|
end
|
|
89
98
|
end
|
|
90
99
|
|
|
@@ -101,10 +110,14 @@ module Krane
|
|
|
101
110
|
end
|
|
102
111
|
|
|
103
112
|
def dry_run_flag
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
"--dry-run=server"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def allowlist_flag
|
|
117
|
+
if client_version >= Gem::Version.new(ALLOW_LIST_MIN_VERSION)
|
|
118
|
+
"--prune-allowlist"
|
|
106
119
|
else
|
|
107
|
-
"--
|
|
120
|
+
"--prune-whitelist"
|
|
108
121
|
end
|
|
109
122
|
end
|
|
110
123
|
|
|
@@ -127,15 +140,10 @@ module Krane
|
|
|
127
140
|
end
|
|
128
141
|
end
|
|
129
142
|
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
match = l.match(/^(?<kind>Client|Server).* GitVersion:"v(?<version>\d+\.\d+\.\d+)/)
|
|
134
|
-
if match
|
|
135
|
-
info[match[:kind].downcase.to_sym] = Gem::Version.new(match[:version])
|
|
136
|
-
end
|
|
143
|
+
def platform_agnostic_version(version_string)
|
|
144
|
+
if match = version_string.match(/v(?<version>\d+\.\d+\.\d+)/)
|
|
145
|
+
Gem::Version.new(match[:version])
|
|
137
146
|
end
|
|
138
|
-
info
|
|
139
147
|
end
|
|
140
148
|
end
|
|
141
149
|
end
|
|
@@ -5,6 +5,7 @@ module Krane
|
|
|
5
5
|
class Service < KubernetesResource
|
|
6
6
|
TIMEOUT = 7.minutes
|
|
7
7
|
SYNC_DEPENDENCIES = %w(Pod Deployment StatefulSet)
|
|
8
|
+
SKIP_ENDPOINT_VALIDATION_ANNOTATION = 'skip-endpoint-validation'
|
|
8
9
|
|
|
9
10
|
def sync(cache)
|
|
10
11
|
super
|
|
@@ -59,6 +60,9 @@ module Krane
|
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
def requires_endpoints?
|
|
63
|
+
# skip validation if the annotation is present
|
|
64
|
+
return false if skip_endpoint_validation
|
|
65
|
+
|
|
62
66
|
# services of type External don't have endpoints
|
|
63
67
|
return false if external_name_svc?
|
|
64
68
|
|
|
@@ -96,5 +100,9 @@ module Krane
|
|
|
96
100
|
def published?
|
|
97
101
|
@instance_data.dig('status', 'loadBalancer', 'ingress').present?
|
|
98
102
|
end
|
|
103
|
+
|
|
104
|
+
def skip_endpoint_validation
|
|
105
|
+
krane_annotation_value(SKIP_ENDPOINT_VALIDATION_ANNOTATION) == 'true'
|
|
106
|
+
end
|
|
99
107
|
end
|
|
100
108
|
end
|
|
@@ -5,6 +5,7 @@ module Krane
|
|
|
5
5
|
TIMEOUT = 10.minutes
|
|
6
6
|
ONDELETE = 'OnDelete'
|
|
7
7
|
SYNC_DEPENDENCIES = %w(Pod)
|
|
8
|
+
REQUIRED_ROLLOUT_TYPES = %w(full).freeze
|
|
8
9
|
attr_reader :pods
|
|
9
10
|
|
|
10
11
|
def sync(cache)
|
|
@@ -19,25 +20,24 @@ module Krane
|
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def deploy_succeeded?
|
|
22
|
-
success = observed_generation == current_generation
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if update_strategy == ONDELETE
|
|
26
|
-
# Gem cannot monitor update since it doesn't occur until delete
|
|
23
|
+
success = observed_generation == current_generation
|
|
24
|
+
|
|
25
|
+
if update_strategy == ONDELETE && required_rollout != "full"
|
|
27
26
|
unless @success_assumption_warning_shown
|
|
28
|
-
@logger.warn("WARNING: Your StatefulSet's updateStrategy is set to
|
|
29
|
-
"which means updates will not be applied until its pods are deleted.
|
|
30
|
-
"Consider switching to rollingUpdate.")
|
|
27
|
+
@logger.warn("WARNING: Your StatefulSet's updateStrategy is set to #{update_strategy}, "\
|
|
28
|
+
"which means updates will not be applied until its pods are deleted.")
|
|
31
29
|
@success_assumption_warning_shown = true
|
|
32
30
|
end
|
|
33
31
|
else
|
|
34
|
-
success &= desired_replicas == status_data['
|
|
32
|
+
success &= desired_replicas == status_data['readyReplicas'].to_i
|
|
33
|
+
success &= desired_replicas == status_data['updatedReplicas'].to_i
|
|
35
34
|
end
|
|
35
|
+
|
|
36
36
|
success
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def deploy_failed?
|
|
40
|
-
return false if update_strategy == ONDELETE
|
|
40
|
+
return false if update_strategy == ONDELETE && required_rollout != 'full'
|
|
41
41
|
pods.present? && pods.any?(&:deploy_failed?) &&
|
|
42
42
|
observed_generation == current_generation
|
|
43
43
|
end
|
|
@@ -65,7 +65,11 @@ module Krane
|
|
|
65
65
|
def parent_of_pod?(pod_data)
|
|
66
66
|
return false unless pod_data.dig("metadata", "ownerReferences")
|
|
67
67
|
pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == @instance_data["metadata"]["uid"] } &&
|
|
68
|
-
@instance_data["status"]["
|
|
68
|
+
@instance_data["status"]["updateRevision"] == pod_data["metadata"]["labels"]["controller-revision-hash"]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def required_rollout
|
|
72
|
+
krane_annotation_value("required-rollout") || nil
|
|
69
73
|
end
|
|
70
74
|
end
|
|
71
75
|
end
|
|
@@ -226,11 +226,6 @@ module Krane
|
|
|
226
226
|
version ? grouping : "core"
|
|
227
227
|
end
|
|
228
228
|
|
|
229
|
-
def version
|
|
230
|
-
prefix, version = @definition.dig("apiVersion").split("/")
|
|
231
|
-
version || prefix
|
|
232
|
-
end
|
|
233
|
-
|
|
234
229
|
def kubectl_resource_type
|
|
235
230
|
type
|
|
236
231
|
end
|
|
@@ -250,7 +245,7 @@ module Krane
|
|
|
250
245
|
end
|
|
251
246
|
|
|
252
247
|
def deploy_method_override
|
|
253
|
-
krane_annotation_value(DEPLOY_METHOD_OVERRIDE_ANNOTATION)&.to_sym
|
|
248
|
+
krane_annotation_value(DEPLOY_METHOD_OVERRIDE_ANNOTATION)&.gsub("-", "_")&.to_sym
|
|
254
249
|
end
|
|
255
250
|
|
|
256
251
|
def sync_debug_info(kubectl)
|
|
@@ -558,7 +553,6 @@ module Krane
|
|
|
558
553
|
if err.empty? || err.match(SERVER_DRY_RUN_DISABLED_ERROR)
|
|
559
554
|
_, err, st = validate_with_local_dry_run(kubectl)
|
|
560
555
|
end
|
|
561
|
-
|
|
562
556
|
return true if st.success?
|
|
563
557
|
@validation_errors << if sensitive_template_content?
|
|
564
558
|
"Validation for #{id} failed. Detailed information is unavailable as the raw error may contain sensitive data."
|
|
@@ -569,11 +563,7 @@ module Krane
|
|
|
569
563
|
|
|
570
564
|
# Server side dry run is only supported on apply
|
|
571
565
|
def validate_with_server_side_dry_run(kubectl)
|
|
572
|
-
command =
|
|
573
|
-
["apply", "-f", file_path, "--dry-run=server", "--output=name"]
|
|
574
|
-
else
|
|
575
|
-
["apply", "-f", file_path, "--server-dry-run", "--output=name"]
|
|
576
|
-
end
|
|
566
|
+
command = ["apply", "-f", file_path, "--dry-run=server", "--output=name"]
|
|
577
567
|
|
|
578
568
|
kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
|
|
579
569
|
retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3)
|
|
@@ -584,12 +574,7 @@ module Krane
|
|
|
584
574
|
# If the resource template uses generateName, validating with apply will fail
|
|
585
575
|
def validate_with_local_dry_run(kubectl)
|
|
586
576
|
verb = deploy_method == :apply ? "apply" : "create"
|
|
587
|
-
command =
|
|
588
|
-
[verb, "-f", file_path, "--dry-run=client", "--output=name"]
|
|
589
|
-
else
|
|
590
|
-
[verb, "-f", file_path, "--dry-run", "--output=name"]
|
|
591
|
-
end
|
|
592
|
-
|
|
577
|
+
command = [verb, "-f", file_path, "--dry-run=client", "--output=name"]
|
|
593
578
|
kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
|
|
594
579
|
retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3, use_namespace: !global?)
|
|
595
580
|
end
|
data/lib/krane/resource_cache.rb
CHANGED
|
@@ -64,7 +64,7 @@ module Krane
|
|
|
64
64
|
raise KubectlError unless st.success?
|
|
65
65
|
|
|
66
66
|
instances = {}
|
|
67
|
-
|
|
67
|
+
MultiJson.load(raw_json)["items"].each do |resource|
|
|
68
68
|
resource_name = resource.dig("metadata", "name")
|
|
69
69
|
instances[resource_name] = resource
|
|
70
70
|
end
|
|
@@ -11,22 +11,15 @@ module Krane
|
|
|
11
11
|
delegate :logger, to: :@task_config
|
|
12
12
|
attr_reader :statsd_tags
|
|
13
13
|
|
|
14
|
-
def initialize(task_config:,
|
|
14
|
+
def initialize(task_config:, prune_allowlist:, global_timeout:, current_sha: nil, selector:, statsd_tags:)
|
|
15
15
|
@task_config = task_config
|
|
16
|
-
@
|
|
16
|
+
@prune_allowlist = prune_allowlist
|
|
17
17
|
@global_timeout = global_timeout
|
|
18
18
|
@current_sha = current_sha
|
|
19
19
|
@selector = selector
|
|
20
20
|
@statsd_tags = statsd_tags
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def dry_run(resources)
|
|
24
|
-
apply_all(resources, true, dry_run: true)
|
|
25
|
-
true
|
|
26
|
-
rescue FatalDeploymentError
|
|
27
|
-
false
|
|
28
|
-
end
|
|
29
|
-
|
|
30
23
|
def deploy!(resources, verify_result, prune)
|
|
31
24
|
if verify_result
|
|
32
25
|
deploy_all_resources(resources, prune: prune, verify: true)
|
|
@@ -102,7 +95,7 @@ module Krane
|
|
|
102
95
|
# Apply can be done in one large batch, the rest have to be done individually
|
|
103
96
|
applyables, individuals = resources.partition { |r| r.deploy_method == :apply }
|
|
104
97
|
# Prunable resources should also applied so that they can be pruned
|
|
105
|
-
pruneable_types = @
|
|
98
|
+
pruneable_types = @prune_allowlist.map { |t| t.split("/").last }
|
|
106
99
|
applyables += individuals.select { |r| pruneable_types.include?(r.type) && !r.deploy_method_override }
|
|
107
100
|
|
|
108
101
|
individuals.each do |individual_resource|
|
|
@@ -147,14 +140,15 @@ module Krane
|
|
|
147
140
|
r.deploy_started_at = Time.now.utc unless dry_run
|
|
148
141
|
end
|
|
149
142
|
command.push("-f", tmp_dir)
|
|
150
|
-
if prune && @
|
|
143
|
+
if prune && @prune_allowlist.present?
|
|
151
144
|
command.push("--prune")
|
|
152
145
|
if @selector
|
|
153
146
|
command.push("--selector", @selector.to_s)
|
|
154
147
|
else
|
|
155
148
|
command.push("--all")
|
|
156
149
|
end
|
|
157
|
-
|
|
150
|
+
allow_list_flag = kubectl.allowlist_flag
|
|
151
|
+
@prune_allowlist.each { |type| command.push("#{allow_list_flag}=#{type}") }
|
|
158
152
|
end
|
|
159
153
|
|
|
160
154
|
command.push(kubectl.dry_run_flag) if dry_run
|
|
@@ -252,7 +246,7 @@ module Krane
|
|
|
252
246
|
# For resources that rely on a generateName attribute, we get the `name` from the result of the call to `create`
|
|
253
247
|
# We must explicitly set this name value so that the `apply` step for pruning can run successfully
|
|
254
248
|
if status.success? && resource.uses_generate_name?
|
|
255
|
-
resource.use_generated_name(
|
|
249
|
+
resource.use_generated_name(MultiJson.load(out))
|
|
256
250
|
end
|
|
257
251
|
|
|
258
252
|
[err, status]
|
data/lib/krane/restart_task.rb
CHANGED
|
@@ -12,10 +12,9 @@ module Krane
|
|
|
12
12
|
class FatalRestartError < FatalDeploymentError; end
|
|
13
13
|
|
|
14
14
|
class RestartAPIError < FatalRestartError
|
|
15
|
-
def initialize(deployment_name,
|
|
15
|
+
def initialize(deployment_name, message)
|
|
16
16
|
super("Failed to restart #{deployment_name}. " \
|
|
17
|
-
"
|
|
18
|
-
"Response:\n#{response.body}")
|
|
17
|
+
"Error:\n#{message}")
|
|
19
18
|
end
|
|
20
19
|
end
|
|
21
20
|
|
|
@@ -11,7 +11,7 @@ module Krane
|
|
|
11
11
|
def from_annotation(conditions_string)
|
|
12
12
|
return new(default_conditions) if conditions_string.downcase.strip == "true"
|
|
13
13
|
|
|
14
|
-
conditions =
|
|
14
|
+
conditions = MultiJson.load(conditions_string).slice('success_conditions', 'failure_conditions')
|
|
15
15
|
conditions.deep_symbolize_keys!
|
|
16
16
|
|
|
17
17
|
# Create JsonPath objects
|
|
@@ -26,7 +26,7 @@ module Krane
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
new(conditions)
|
|
29
|
-
rescue
|
|
29
|
+
rescue MultiJson::ParseError => e
|
|
30
30
|
raise RolloutConditionsError, "Rollout conditions are not valid JSON: #{e}"
|
|
31
31
|
rescue StandardError => e
|
|
32
32
|
raise RolloutConditionsError,
|
data/lib/krane/version.rb
CHANGED