krane 2.1.3 → 2.1.8
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 +1 -0
- data/.shopify-build/krane.yml +20 -6
- data/CHANGELOG.md +36 -0
- data/README.md +2 -2
- data/bin/ci +1 -2
- data/dev.yml +1 -1
- data/krane.gemspec +3 -3
- data/lib/krane/cli/krane.rb +1 -1
- data/lib/krane/cluster_resource_discovery.rb +53 -64
- data/lib/krane/common.rb +0 -1
- data/lib/krane/container_logs.rb +1 -1
- data/lib/krane/deploy_task.rb +21 -3
- data/lib/krane/ejson_secret_provisioner.rb +1 -1
- data/lib/krane/global_deploy_task.rb +1 -1
- data/lib/krane/kubectl.rb +8 -0
- data/lib/krane/kubernetes_resource.rb +13 -3
- data/lib/krane/kubernetes_resource/deployment.rb +2 -0
- data/lib/krane/kubernetes_resource/mutating_webhook_configuration.rb +86 -0
- data/lib/krane/resource_deployer.rb +15 -6
- data/lib/krane/runner_task.rb +1 -1
- data/lib/krane/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f0acd44dff6399afd220b377ae825e964d07da8a0574525ff7b3f966857bf80
|
|
4
|
+
data.tar.gz: 31c0f004b0f4f5db39e73b5fbde06b54bfce2bfbf393676f44b036397e303515
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 240a8713adc34a340ff3d127828c3a1a740d3543656b6b0c730a26db295b7b6f9c306d9fa53cf10ad425a00116d99895b2feec207aae94a1f3b3b5b571bef006
|
|
7
|
+
data.tar.gz: dd13e23ab5b37b152665ce4bd29a10cb77a30115938df6a50ef394b3bccf84ea51fcf12ba7590bd899c071fb083df70eeb94095b1a72086932a9e0122db1cdb3
|
data/.shopify-build/krane.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
containers:
|
|
2
2
|
default:
|
|
3
|
-
docker: circleci/ruby:2.
|
|
3
|
+
docker: circleci/ruby:2.6.6
|
|
4
4
|
|
|
5
5
|
steps:
|
|
6
6
|
- label: Lint
|
|
@@ -8,14 +8,29 @@ steps:
|
|
|
8
8
|
run:
|
|
9
9
|
- bundle: ~
|
|
10
10
|
- bundle exec rubocop
|
|
11
|
-
- label: 'Run Test Suite (:kubernetes: 1.
|
|
11
|
+
- label: 'Run Test Suite (:kubernetes: 1.20-latest :ruby: 3.0)'
|
|
12
12
|
command: bin/ci
|
|
13
13
|
agents:
|
|
14
14
|
queue: k8s-ci
|
|
15
15
|
env:
|
|
16
16
|
LOGGING_LEVEL: "4"
|
|
17
|
-
KUBERNETES_VERSION: v1.
|
|
17
|
+
KUBERNETES_VERSION: v1.20-latest
|
|
18
|
+
RUBY_VERSION: "3.0"
|
|
19
|
+
- label: 'Run Test Suite (:kubernetes: 1.19-latest :ruby: 2.7)'
|
|
20
|
+
command: bin/ci
|
|
21
|
+
agents:
|
|
22
|
+
queue: k8s-ci
|
|
23
|
+
env:
|
|
24
|
+
LOGGING_LEVEL: "4"
|
|
25
|
+
KUBERNETES_VERSION: v1.19-latest
|
|
18
26
|
RUBY_VERSION: "2.7"
|
|
27
|
+
- label: 'Run Test Suite (:kubernetes: 1.18-latest)'
|
|
28
|
+
command: bin/ci
|
|
29
|
+
agents:
|
|
30
|
+
queue: k8s-ci
|
|
31
|
+
env:
|
|
32
|
+
LOGGING_LEVEL: "4"
|
|
33
|
+
KUBERNETES_VERSION: v1.18-latest
|
|
19
34
|
- label: 'Run Test Suite (:kubernetes: 1.17-latest)'
|
|
20
35
|
command: bin/ci
|
|
21
36
|
agents:
|
|
@@ -23,14 +38,13 @@ steps:
|
|
|
23
38
|
env:
|
|
24
39
|
LOGGING_LEVEL: "4"
|
|
25
40
|
KUBERNETES_VERSION: v1.17-latest
|
|
26
|
-
- label: 'Run Test Suite (:kubernetes: 1.16
|
|
41
|
+
- label: 'Run Test Suite (:kubernetes: 1.16-latest)'
|
|
27
42
|
command: bin/ci
|
|
28
43
|
agents:
|
|
29
44
|
queue: k8s-ci
|
|
30
45
|
env:
|
|
31
46
|
LOGGING_LEVEL: "4"
|
|
32
|
-
|
|
33
|
-
KUBERNETES_VERSION: v1.16.12
|
|
47
|
+
KUBERNETES_VERSION: v1.16-latest
|
|
34
48
|
- label: 'Run Test Suite (:kubernetes: 1.15-latest)'
|
|
35
49
|
command: bin/ci
|
|
36
50
|
agents:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
## next
|
|
2
2
|
|
|
3
|
+
## 2.1.8
|
|
4
|
+
|
|
5
|
+
*Other*
|
|
6
|
+
|
|
7
|
+
- Change `statsd-instrument` dependency constraint to `< 4` [#815](https://github.com/Shopify/krane/pull/815)
|
|
8
|
+
|
|
9
|
+
## 2.1.7
|
|
10
|
+
|
|
11
|
+
*Enhancements*
|
|
12
|
+
- ENV["KRANE_LOG_LINE_LIMIT"] allows the number of container logs printed for failures to be configurable from the 25 line default [#803](https://github.com/Shopify/krane/pull/803).
|
|
13
|
+
|
|
14
|
+
*Other*
|
|
15
|
+
- Remove the overly tight timeout on cluster resource discovery, which was causing too many timeouts in high latency environments [#813](https://github.com/Shopify/krane/pull/813)
|
|
16
|
+
|
|
17
|
+
## 2.1.6
|
|
18
|
+
|
|
19
|
+
*Enhancements*
|
|
20
|
+
- Remove the need for a hard coded GVK overide list via improvements to cluster discovery [#778](https://github.com/Shopify/krane/pull/778)
|
|
21
|
+
|
|
22
|
+
*Bug Fixes*
|
|
23
|
+
- Remove resources that are targeted by side-effect-inducing mutating admission webhooks from the serverside dry run batch [#798](https://github.com/Shopify/krane/pull/798)
|
|
24
|
+
|
|
25
|
+
## 2.1.5
|
|
26
|
+
|
|
27
|
+
- Fix bug where the wrong dry-run flag is used for kubectl if client version is below 1.18 AND server version is 1.18+ [#793](https://github.com/Shopify/krane/pull/793).
|
|
28
|
+
|
|
29
|
+
## 2.1.4
|
|
30
|
+
|
|
31
|
+
*Enhancements*
|
|
32
|
+
- Attempt to batch run server-side apply in validation phase instead of dry-running each resource individually [#781](https://github.com/Shopify/krane/pull/781).
|
|
33
|
+
- Evaluate progress condition only after progress deadline seconds have passed since deploy invocation [#765](https://github.com/Shopify/krane/pull/765).
|
|
34
|
+
|
|
35
|
+
*Other*
|
|
36
|
+
- Dropped support for Ruby 2.5 due to EoL. [#782](https://github.com/Shopify/krane/pull/782).
|
|
37
|
+
- Only patch JSON when run as CLI, not as library [#779](https://github.com/Shopify/krane/pull/779).
|
|
38
|
+
|
|
3
39
|
## 2.1.3
|
|
4
40
|
|
|
5
41
|
- Add version exception for ServiceNetworkEndpointGroup (GKE resource) [#768](https://github.com/Shopify/krane/pull/768)
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# krane [](https://buildkite.com/shopify/krane)
|
|
1
|
+
# krane [](https://buildkite.com/shopify/krane)
|
|
2
2
|
|
|
3
3
|
> This project used to be called `kubernetes-deploy`. Check out our [migration guide](https://github.com/Shopify/krane/blob/master/1.0-Upgrade.md) for more information including details about breaking changes.
|
|
4
4
|
|
|
@@ -73,7 +73,7 @@ If you need the ability to render dynamic values in templates before deploying,
|
|
|
73
73
|
|
|
74
74
|
## Prerequisites
|
|
75
75
|
|
|
76
|
-
* Ruby 2.
|
|
76
|
+
* Ruby 2.6+
|
|
77
77
|
* Your cluster must be running Kubernetes v1.15.0 or higher<sup>1</sup>
|
|
78
78
|
|
|
79
79
|
<sup>1</sup> We run integration tests against these Kubernetes versions. You can find our
|
data/bin/ci
CHANGED
|
@@ -12,10 +12,9 @@ docker run --rm \
|
|
|
12
12
|
-v "$PWD":/usr/src/app \
|
|
13
13
|
-v "/usr/bin/kubectl":"/usr/bin/kubectl" \
|
|
14
14
|
-e CI=1 \
|
|
15
|
-
-e CODECOV_TOKEN=$CODECOV_TOKEN \
|
|
16
15
|
-e COVERAGE=1 \
|
|
17
16
|
-e VERBOSE=1 \
|
|
18
17
|
-e PARALLELISM=$PARALLELISM \
|
|
19
18
|
-w /usr/src/app \
|
|
20
|
-
ruby:"${RUBY_VERSION:-2.
|
|
19
|
+
ruby:"${RUBY_VERSION:-2.6.6}" \
|
|
21
20
|
bin/test
|
data/dev.yml
CHANGED
data/krane.gemspec
CHANGED
|
@@ -25,13 +25,13 @@ 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.
|
|
28
|
+
spec.required_ruby_version = '>= 2.6.0'
|
|
29
29
|
spec.add_dependency("activesupport", ">= 5.0")
|
|
30
30
|
spec.add_dependency("kubeclient", "~> 4.3")
|
|
31
31
|
spec.add_dependency("googleauth", "~> 0.8")
|
|
32
32
|
spec.add_dependency("ejson", "~> 1.0")
|
|
33
33
|
spec.add_dependency("colorize", "~> 0.8")
|
|
34
|
-
spec.add_dependency("statsd-instrument", ['>= 2.8', "<
|
|
34
|
+
spec.add_dependency("statsd-instrument", ['>= 2.8', "< 4"])
|
|
35
35
|
spec.add_dependency("oj", "~> 3.0")
|
|
36
36
|
spec.add_dependency("concurrent-ruby", "~> 1.1")
|
|
37
37
|
spec.add_dependency("jsonpath", "~> 0.9.6")
|
|
@@ -57,5 +57,5 @@ Gem::Specification.new do |spec|
|
|
|
57
57
|
spec.add_development_dependency("ruby-prof")
|
|
58
58
|
spec.add_development_dependency("ruby-prof-flamegraph")
|
|
59
59
|
spec.add_development_dependency("rubocop", "~> 0.89.1")
|
|
60
|
-
spec.add_development_dependency("
|
|
60
|
+
spec.add_development_dependency("simplecov")
|
|
61
61
|
end
|
data/lib/krane/cli/krane.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require 'concurrent'
|
|
2
3
|
|
|
3
4
|
module Krane
|
|
4
5
|
class ClusterResourceDiscovery
|
|
@@ -7,6 +8,7 @@ module Krane
|
|
|
7
8
|
def initialize(task_config:, namespace_tags: [])
|
|
8
9
|
@task_config = task_config
|
|
9
10
|
@namespace_tags = namespace_tags
|
|
11
|
+
@api_path_cache = {}
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def crds
|
|
@@ -18,87 +20,74 @@ module Krane
|
|
|
18
20
|
|
|
19
21
|
def prunable_resources(namespaced:)
|
|
20
22
|
black_list = %w(Namespace Node ControllerRevision)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
next if black_list.include?(resource['kind'])
|
|
26
|
-
group_versions = api_versions[resource['apigroup'].to_s]
|
|
27
|
-
version = version_for_kind(group_versions, resource['kind'])
|
|
28
|
-
[resource['apigroup'], version, resource['kind']].compact.join("/")
|
|
23
|
+
fetch_resources(namespaced: namespaced).map do |resource|
|
|
24
|
+
next unless resource["verbs"].one? { |v| v == "delete" }
|
|
25
|
+
next if black_list.include?(resource["kind"])
|
|
26
|
+
[resource["apigroup"], resource["version"], resource["kind"]].compact.join("/")
|
|
29
27
|
end.compact
|
|
30
28
|
end
|
|
31
29
|
|
|
32
|
-
# kubectl api-resources -o wide returns 5 columns
|
|
33
|
-
# NAME SHORTNAMES APIGROUP NAMESPACED KIND VERBS
|
|
34
|
-
# SHORTNAMES and APIGROUP may be blank
|
|
35
|
-
# VERBS is an array
|
|
36
|
-
# serviceaccounts sa <blank> true ServiceAccount [create delete deletecollection get list patch update watch]
|
|
37
30
|
def fetch_resources(namespaced: false)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
responses = Concurrent::Hash.new
|
|
32
|
+
Krane::Concurrency.split_across_threads(api_paths) do |path|
|
|
33
|
+
responses[path] = fetch_api_path(path)["resources"] || []
|
|
34
|
+
end
|
|
35
|
+
responses.flat_map do |path, resources|
|
|
36
|
+
resources.map { |r| resource_hash(path, namespaced, r) }
|
|
37
|
+
end.compact.uniq { |r| r["kind"] }
|
|
38
|
+
end
|
|
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)
|
|
42
43
|
if st.success?
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
full_width_field_names = header.downcase.scan(/[a-z]+[\W]*/)
|
|
47
|
-
cursor = 0
|
|
48
|
-
fields = full_width_field_names.each_with_object({}) do |name, hash|
|
|
49
|
-
start = cursor
|
|
50
|
-
cursor = start + name.length
|
|
51
|
-
# Last field should consume the remainder of the line
|
|
52
|
-
cursor = 0 if full_width_field_names.last == name.strip
|
|
53
|
-
hash[name.strip] = [start, cursor - 1]
|
|
54
|
-
end
|
|
55
|
-
resources.map do |resource|
|
|
56
|
-
resource = fields.map { |k, (s, e)| [k.strip, resource[s..e].strip] }.to_h
|
|
57
|
-
# Manually parse verbs: "[get list]" into %w(get list)
|
|
58
|
-
resource["verbs"] = resource["verbs"][1..-2].split
|
|
59
|
-
resource
|
|
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)
|
|
60
47
|
end
|
|
61
48
|
else
|
|
62
|
-
raise FatalKubeAPIError, "Error retrieving
|
|
49
|
+
raise FatalKubeAPIError, "Error retrieving mutatingwebhookconfigurations: #{err}"
|
|
63
50
|
end
|
|
64
51
|
end
|
|
65
52
|
|
|
66
53
|
private
|
|
67
54
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
rows = raw.split("\n")
|
|
76
|
-
rows.each do |group_version|
|
|
77
|
-
group, version = group_version.split("/")
|
|
78
|
-
versions[group] ||= []
|
|
79
|
-
versions[group] << version
|
|
55
|
+
def api_paths
|
|
56
|
+
@api_path_cache["/"] ||= begin
|
|
57
|
+
raw_json, err, st = kubectl.run("get", "--raw", "/", attempts: 5, use_namespace: false)
|
|
58
|
+
paths = if st.success?
|
|
59
|
+
JSON.parse(raw_json)["paths"]
|
|
60
|
+
else
|
|
61
|
+
raise FatalKubeAPIError, "Error retrieving raw path /: #{err}"
|
|
80
62
|
end
|
|
81
|
-
|
|
82
|
-
raise FatalKubeAPIError, "Error retrieving api-versions: #{err}"
|
|
63
|
+
paths.select { |path| %r{^\/api.*\/v.*$}.match(path) }
|
|
83
64
|
end
|
|
84
|
-
versions
|
|
85
65
|
end
|
|
86
66
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
67
|
+
def fetch_api_path(path)
|
|
68
|
+
@api_path_cache[path] ||= begin
|
|
69
|
+
raw_json, err, st = kubectl.run("get", "--raw", path, attempts: 2, use_namespace: false)
|
|
70
|
+
if st.success?
|
|
71
|
+
JSON.parse(raw_json)
|
|
72
|
+
else
|
|
73
|
+
logger.warn("Error retrieving api path: #{err}")
|
|
74
|
+
{}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
94
78
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
79
|
+
def resource_hash(path, namespaced, blob)
|
|
80
|
+
return unless blob["namespaced"] == namespaced
|
|
81
|
+
# skip sub-resources
|
|
82
|
+
return if blob["name"].include?("/")
|
|
83
|
+
path_regex = %r{(/apis?/)(?<group>[^/]*)/?(?<version>v.+)}
|
|
84
|
+
match = path.match(path_regex)
|
|
85
|
+
{
|
|
86
|
+
"verbs" => blob["verbs"],
|
|
87
|
+
"kind" => blob["kind"],
|
|
88
|
+
"apigroup" => match[:group],
|
|
89
|
+
"version" => match[:version],
|
|
90
|
+
}
|
|
102
91
|
end
|
|
103
92
|
|
|
104
93
|
def fetch_crds
|
data/lib/krane/common.rb
CHANGED
data/lib/krane/container_logs.rb
CHANGED
|
@@ -3,7 +3,7 @@ module Krane
|
|
|
3
3
|
class ContainerLogs
|
|
4
4
|
attr_reader :lines, :container_name
|
|
5
5
|
|
|
6
|
-
DEFAULT_LINE_LIMIT = 25
|
|
6
|
+
DEFAULT_LINE_LIMIT = Integer(ENV.fetch('KRANE_LOG_LINE_LIMIT', 25))
|
|
7
7
|
|
|
8
8
|
def initialize(parent_id:, container_name:, namespace:, context:, logger:)
|
|
9
9
|
@parent_id = parent_id
|
data/lib/krane/deploy_task.rb
CHANGED
|
@@ -30,6 +30,7 @@ require 'krane/kubernetes_resource'
|
|
|
30
30
|
custom_resource_definition
|
|
31
31
|
horizontal_pod_autoscaler
|
|
32
32
|
secret
|
|
33
|
+
mutating_webhook_configuration
|
|
33
34
|
).each do |subresource|
|
|
34
35
|
require "krane/kubernetes_resource/#{subresource}"
|
|
35
36
|
end
|
|
@@ -277,15 +278,28 @@ module Krane
|
|
|
277
278
|
end
|
|
278
279
|
measure_method(:validate_configuration)
|
|
279
280
|
|
|
281
|
+
def partition_dry_run_resources(resources)
|
|
282
|
+
individuals = []
|
|
283
|
+
mutating_webhook_configurations = cluster_resource_discoverer.fetch_mutating_webhook_configurations
|
|
284
|
+
mutating_webhook_configurations.each do |mutating_webhook_configuration|
|
|
285
|
+
mutating_webhook_configuration.webhooks.each do |webhook|
|
|
286
|
+
individuals = (individuals + resources.select { |resource| webhook.matches_resource?(resource) }).uniq
|
|
287
|
+
resources -= individuals
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
[resources, individuals]
|
|
291
|
+
end
|
|
292
|
+
|
|
280
293
|
def validate_resources(resources)
|
|
281
294
|
validate_globals(resources)
|
|
295
|
+
batchable_resources, individuals = partition_dry_run_resources(resources.dup)
|
|
296
|
+
batch_dry_run_success = kubectl.server_dry_run_enabled? && validate_dry_run(batchable_resources)
|
|
297
|
+
individuals += batchable_resources unless batch_dry_run_success
|
|
282
298
|
Krane::Concurrency.split_across_threads(resources) do |r|
|
|
283
|
-
r.validate_definition(kubectl, selector: @selector)
|
|
299
|
+
r.validate_definition(kubectl: kubectl, selector: @selector, dry_run: individuals.include?(r))
|
|
284
300
|
end
|
|
285
|
-
|
|
286
301
|
failed_resources = resources.select(&:validation_failed?)
|
|
287
302
|
if failed_resources.present?
|
|
288
|
-
|
|
289
303
|
failed_resources.each do |r|
|
|
290
304
|
content = File.read(r.file_path) if File.file?(r.file_path) && !r.sensitive_template_content?
|
|
291
305
|
record_invalid_template(logger: @logger, err: r.validation_error_msg,
|
|
@@ -308,6 +322,10 @@ module Krane
|
|
|
308
322
|
"Use GlobalDeployTask instead."
|
|
309
323
|
end
|
|
310
324
|
|
|
325
|
+
def validate_dry_run(resources)
|
|
326
|
+
resource_deployer.dry_run(resources)
|
|
327
|
+
end
|
|
328
|
+
|
|
311
329
|
def namespace_definition
|
|
312
330
|
@namespace_definition ||= begin
|
|
313
331
|
definition, _err, st = kubectl.run("get", "namespace", @namespace, use_namespace: false,
|
|
@@ -53,7 +53,7 @@ module Krane
|
|
|
53
53
|
secrets.map do |secret_name, secret_spec|
|
|
54
54
|
validate_secret_spec(secret_name, secret_spec)
|
|
55
55
|
resource = generate_secret_resource(secret_name, secret_spec["_type"], secret_spec["data"])
|
|
56
|
-
resource.validate_definition(@kubectl)
|
|
56
|
+
resource.validate_definition(kubectl: @kubectl)
|
|
57
57
|
if resource.validation_failed?
|
|
58
58
|
raise EjsonSecretError, "Resulting resource Secret/#{secret_name} failed validation"
|
|
59
59
|
end
|
|
@@ -131,7 +131,7 @@ module Krane
|
|
|
131
131
|
validate_globals(resources)
|
|
132
132
|
|
|
133
133
|
Concurrency.split_across_threads(resources) do |r|
|
|
134
|
-
r.validate_definition(@kubectl, selector: @selector)
|
|
134
|
+
r.validate_definition(kubectl: @kubectl, selector: @selector)
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
failed_resources = resources.select(&:validation_failed?)
|
data/lib/krane/kubectl.rb
CHANGED
|
@@ -100,6 +100,14 @@ module Krane
|
|
|
100
100
|
server_version >= Gem::Version.new(SERVER_DRY_RUN_MIN_VERSION)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
def dry_run_flag
|
|
104
|
+
if client_version >= Gem::Version.new("1.18")
|
|
105
|
+
"--dry-run=server"
|
|
106
|
+
else
|
|
107
|
+
"--server-dry-run"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
103
111
|
private
|
|
104
112
|
|
|
105
113
|
def build_command_from_options(args, use_namespace, use_context, output)
|
|
@@ -134,12 +134,12 @@ module Krane
|
|
|
134
134
|
Kubeclient::Resource.new(@definition)
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
def validate_definition(kubectl
|
|
137
|
+
def validate_definition(kubectl:, selector: nil, dry_run: true)
|
|
138
138
|
@validation_errors = []
|
|
139
139
|
validate_selector(selector) if selector
|
|
140
140
|
validate_timeout_annotation
|
|
141
141
|
validate_deploy_method_override_annotation
|
|
142
|
-
validate_spec_with_kubectl(kubectl)
|
|
142
|
+
validate_spec_with_kubectl(kubectl) if dry_run
|
|
143
143
|
@validation_errors.present?
|
|
144
144
|
end
|
|
145
145
|
|
|
@@ -226,6 +226,11 @@ 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
|
+
|
|
229
234
|
def kubectl_resource_type
|
|
230
235
|
type
|
|
231
236
|
end
|
|
@@ -560,7 +565,12 @@ module Krane
|
|
|
560
565
|
|
|
561
566
|
# Server side dry run is only supported on apply
|
|
562
567
|
def validate_with_server_side_dry_run(kubectl)
|
|
563
|
-
command =
|
|
568
|
+
command = if kubectl.client_version >= Gem::Version.new('1.18')
|
|
569
|
+
["apply", "-f", file_path, "--dry-run=server", "--output=name"]
|
|
570
|
+
else
|
|
571
|
+
["apply", "-f", file_path, "--server-dry-run", "--output=name"]
|
|
572
|
+
end
|
|
573
|
+
|
|
564
574
|
kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
|
|
565
575
|
retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3)
|
|
566
576
|
end
|
|
@@ -155,6 +155,8 @@ module Krane
|
|
|
155
155
|
|
|
156
156
|
def deploy_failing_to_progress?
|
|
157
157
|
return false unless progress_condition.present?
|
|
158
|
+
# Ensure at least progress_deadline wall clock time has passed before before examining progress_condition
|
|
159
|
+
return false if deploy_started? && Time.now.utc - @deploy_started_at < progress_deadline
|
|
158
160
|
|
|
159
161
|
# This assumes that when the controller bumps the observed generation, it also updates/clears all the status
|
|
160
162
|
# conditions. Specifically, it assumes the progress condition is immediately set to True if a rollout is starting.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Krane
|
|
4
|
+
class MutatingWebhookConfiguration < KubernetesResource
|
|
5
|
+
GLOBAL = true
|
|
6
|
+
|
|
7
|
+
class Webhook
|
|
8
|
+
EQUIVALENT = 'Equivalent'
|
|
9
|
+
EXACT = 'Exact'
|
|
10
|
+
|
|
11
|
+
class Rule
|
|
12
|
+
def initialize(definition)
|
|
13
|
+
@definition = definition
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def matches_resource?(resource, accept_equivalent:)
|
|
17
|
+
groups.each do |group|
|
|
18
|
+
versions.each do |version|
|
|
19
|
+
resources.each do |kind|
|
|
20
|
+
return true if (resource.group == group || group == '*' || accept_equivalent) &&
|
|
21
|
+
(resource.version == version || version == '*' || accept_equivalent) &&
|
|
22
|
+
(resource.type.downcase == kind.downcase.singularize || kind == "*")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def groups
|
|
30
|
+
@definition.dig('apiGroups')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def versions
|
|
34
|
+
@definition.dig('apiVersions')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def resources
|
|
38
|
+
@definition.dig('resources')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def initialize(definition)
|
|
43
|
+
@definition = definition
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def side_effects
|
|
47
|
+
@definition.dig('sideEffects')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def has_side_effects?
|
|
51
|
+
!%w(None NoneOnDryRun).include?(side_effects)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def match_policy
|
|
55
|
+
@definition.dig('matchPolicy')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def matches_resource?(resource, skip_rule_if_side_effect_none: true)
|
|
59
|
+
return false if skip_rule_if_side_effect_none && !has_side_effects?
|
|
60
|
+
rules.any? do |rule|
|
|
61
|
+
rule.matches_resource?(resource, accept_equivalent: match_policy == EQUIVALENT)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def rules
|
|
66
|
+
@definition.fetch('rules', []).map { |rule| Rule.new(rule) }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def initialize(namespace:, context:, definition:, logger:, statsd_tags:)
|
|
71
|
+
@webhooks = (definition.dig('webhooks') || []).map { |hook| Webhook.new(hook) }
|
|
72
|
+
super(namespace: namespace, context: context, definition: definition,
|
|
73
|
+
logger: logger, statsd_tags: statsd_tags)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
TIMEOUT = 30.seconds
|
|
77
|
+
|
|
78
|
+
def deploy_succeeded?
|
|
79
|
+
exists?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def webhooks
|
|
83
|
+
@definition.fetch('webhooks', []).map { |webhook| Webhook.new(webhook) }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -20,6 +20,13 @@ module Krane
|
|
|
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
|
+
|
|
23
30
|
def deploy!(resources, verify_result, prune)
|
|
24
31
|
if verify_result
|
|
25
32
|
deploy_all_resources(resources, prune: prune, verify: true)
|
|
@@ -128,17 +135,17 @@ module Krane
|
|
|
128
135
|
end
|
|
129
136
|
end
|
|
130
137
|
|
|
131
|
-
def apply_all(resources, prune)
|
|
138
|
+
def apply_all(resources, prune, dry_run: false)
|
|
132
139
|
return unless resources.present?
|
|
133
|
-
|
|
140
|
+
start = Time.now.utc
|
|
134
141
|
|
|
142
|
+
command = %w(apply)
|
|
135
143
|
Dir.mktmpdir do |tmp_dir|
|
|
136
144
|
resources.each do |r|
|
|
137
145
|
FileUtils.symlink(r.file_path, tmp_dir)
|
|
138
|
-
r.deploy_started_at = Time.now.utc
|
|
146
|
+
r.deploy_started_at = Time.now.utc unless dry_run
|
|
139
147
|
end
|
|
140
148
|
command.push("-f", tmp_dir)
|
|
141
|
-
|
|
142
149
|
if prune && @prune_whitelist.present?
|
|
143
150
|
command.push("--prune")
|
|
144
151
|
if @selector
|
|
@@ -149,20 +156,22 @@ module Krane
|
|
|
149
156
|
@prune_whitelist.each { |type| command.push("--prune-whitelist=#{type}") }
|
|
150
157
|
end
|
|
151
158
|
|
|
159
|
+
command.push(kubectl.dry_run_flag) if dry_run
|
|
152
160
|
output_is_sensitive = resources.any?(&:sensitive_template_content?)
|
|
153
161
|
global_mode = resources.all?(&:global?)
|
|
154
162
|
out, err, st = kubectl.run(*command, log_failure: false, output_is_sensitive: output_is_sensitive,
|
|
155
163
|
attempts: 2, use_namespace: !global_mode)
|
|
156
164
|
|
|
165
|
+
tags = statsd_tags + (dry_run ? ['dry_run:true'] : ['dry_run:false'])
|
|
166
|
+
Krane::StatsD.client.distribution('apply_all.duration', Krane::StatsD.duration(start), tags: tags)
|
|
157
167
|
if st.success?
|
|
158
168
|
log_pruning(out) if prune
|
|
159
169
|
else
|
|
160
|
-
record_apply_failure(err, resources: resources)
|
|
170
|
+
record_apply_failure(err, resources: resources) unless dry_run
|
|
161
171
|
raise FatalDeploymentError, "Command failed: #{Shellwords.join(command)}"
|
|
162
172
|
end
|
|
163
173
|
end
|
|
164
174
|
end
|
|
165
|
-
measure_method(:apply_all)
|
|
166
175
|
|
|
167
176
|
def log_pruning(kubectl_output)
|
|
168
177
|
pruned = kubectl_output.scan(/^(.*) pruned$/)
|
data/lib/krane/runner_task.rb
CHANGED
data/lib/krane/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: krane
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Katrina Verey
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: exe
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date:
|
|
13
|
+
date: 2021-04-13 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: activesupport
|
|
@@ -91,7 +91,7 @@ dependencies:
|
|
|
91
91
|
version: '2.8'
|
|
92
92
|
- - "<"
|
|
93
93
|
- !ruby/object:Gem::Version
|
|
94
|
-
version: '
|
|
94
|
+
version: '4'
|
|
95
95
|
type: :runtime
|
|
96
96
|
prerelease: false
|
|
97
97
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -101,7 +101,7 @@ dependencies:
|
|
|
101
101
|
version: '2.8'
|
|
102
102
|
- - "<"
|
|
103
103
|
- !ruby/object:Gem::Version
|
|
104
|
-
version: '
|
|
104
|
+
version: '4'
|
|
105
105
|
- !ruby/object:Gem::Dependency
|
|
106
106
|
name: oj
|
|
107
107
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -375,7 +375,7 @@ dependencies:
|
|
|
375
375
|
- !ruby/object:Gem::Version
|
|
376
376
|
version: 0.89.1
|
|
377
377
|
- !ruby/object:Gem::Dependency
|
|
378
|
-
name:
|
|
378
|
+
name: simplecov
|
|
379
379
|
requirement: !ruby/object:Gem::Requirement
|
|
380
380
|
requirements:
|
|
381
381
|
- - ">="
|
|
@@ -460,6 +460,7 @@ files:
|
|
|
460
460
|
- lib/krane/kubernetes_resource/horizontal_pod_autoscaler.rb
|
|
461
461
|
- lib/krane/kubernetes_resource/ingress.rb
|
|
462
462
|
- lib/krane/kubernetes_resource/job.rb
|
|
463
|
+
- lib/krane/kubernetes_resource/mutating_webhook_configuration.rb
|
|
463
464
|
- lib/krane/kubernetes_resource/network_policy.rb
|
|
464
465
|
- lib/krane/kubernetes_resource/persistent_volume_claim.rb
|
|
465
466
|
- lib/krane/kubernetes_resource/pod.rb
|
|
@@ -511,7 +512,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
511
512
|
requirements:
|
|
512
513
|
- - ">="
|
|
513
514
|
- !ruby/object:Gem::Version
|
|
514
|
-
version: 2.
|
|
515
|
+
version: 2.6.0
|
|
515
516
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
516
517
|
requirements:
|
|
517
518
|
- - ">="
|