krane 2.1.2 → 2.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ad41e92d16ba261b260e69cef284e77c6a0849f3470f56de06b16cafc802d8b
4
- data.tar.gz: 006ad4dc7823bff047095de7442c3aabf6cd8660f45f745f0d75d23e342fc483
3
+ metadata.gz: ea9b225a3125e3282de257f17c7d0b1752e710378810b3d6bef4b491e93b9a1d
4
+ data.tar.gz: e527ce76d2a9a8fcded6d430894ce845b50d852a18c836006e4d322bbdc33c3f
5
5
  SHA512:
6
- metadata.gz: cdad877f2a08f48fd9f483795c4b8b90ec8b60661da4a52dfdad3f52d2f1faf913946a0fc714537f374efac635f0b541e2b79fe16f2bc66daee4e23871502e2f
7
- data.tar.gz: 2024fc45779c55e0209de8d543379be64f7f42c94c58b084ce22021806f4680c142fb7d36b60c506d1cf53a63e1c0a8f3511f8674b1ab6d11fa05542d00a6f2e
6
+ metadata.gz: efa243480b90f8ed0c694a012bbb3c0ef359b911beff69e9661201d355e131d92553f241ad1ee33b983125c516575be2d3243fe4e3ede37832220fefa6b01930
7
+ data.tar.gz: d736754902a0a6bed299e8cfb2fb41cd0f849da183bddfc327fb319e120dc2a4e7ae1ffd56520beec94c867206dbbaa046cd9fb114f5bfae3b632f909e38d28c
@@ -13,31 +13,3 @@ steps:
13
13
  env:
14
14
  LOGGING_LEVEL: 4
15
15
  KUBERNETES_VERSION: v1.15-latest
16
- - name: 'Run Test Suite (:kubernetes: 1.14-latest)'
17
- command: bin/ci
18
- agents:
19
- queue: k8s-ci
20
- env:
21
- LOGGING_LEVEL: 4
22
- KUBERNETES_VERSION: v1.14-latest
23
- - name: 'Run Test Suite (:kubernetes: 1.13-latest)'
24
- command: bin/ci
25
- agents:
26
- queue: k8s-ci
27
- env:
28
- LOGGING_LEVEL: 4
29
- KUBERNETES_VERSION: v1.13-latest
30
- - name: 'Run Test Suite (:kubernetes: 1.12-latest)'
31
- command: bin/ci
32
- agents:
33
- queue: k8s-ci
34
- env:
35
- LOGGING_LEVEL: 4
36
- KUBERNETES_VERSION: v1.12-latest
37
- - name: 'Run Test Suite (:kubernetes: 1.11-latest)'
38
- command: bin/ci
39
- agents:
40
- queue: k8s-ci
41
- env:
42
- LOGGING_LEVEL: 4
43
- KUBERNETES_VERSION: v1.11-latest
@@ -195,6 +195,7 @@ Style/FrozenStringLiteralComment:
195
195
  SupportedStyles:
196
196
  - always
197
197
  - never
198
+ SafeAutoCorrect: true
198
199
 
199
200
  Style/GlobalVars:
200
201
  AllowedVariables: []
@@ -1,6 +1,6 @@
1
1
  containers:
2
2
  default:
3
- docker: circleci/ruby:2.5.7
3
+ docker: circleci/ruby:2.6.6
4
4
 
5
5
  steps:
6
6
  - label: Lint
@@ -8,61 +8,47 @@ steps:
8
8
  run:
9
9
  - bundle: ~
10
10
  - bundle exec rubocop
11
- - label: 'Run Test Suite (:kubernetes: 1.18-latest :ruby: 2.7)'
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.18-latest
18
- RUBY_VERSION: "2.7"
19
- - label: 'Run Test Suite (:kubernetes: 1.17-latest)'
20
- command: bin/ci
21
- agents:
22
- queue: k8s-ci
23
- env:
24
- LOGGING_LEVEL: "4"
25
- KUBERNETES_VERSION: v1.17-latest
26
- - label: 'Run Test Suite (:kubernetes: 1.16.12)'
27
- command: bin/ci
28
- agents:
29
- queue: k8s-ci
30
- env:
31
- LOGGING_LEVEL: "4"
32
- # Flip this back to v1.16-latest when 1.16.14 comes out (see #733)
33
- KUBERNETES_VERSION: v1.16.12
34
- - label: 'Run Test Suite (:kubernetes: 1.15-latest)'
17
+ KUBERNETES_VERSION: v1.20-latest
18
+ RUBY_VERSION: "3.0"
19
+ - label: 'Run Test Suite (:kubernetes: 1.19-latest :ruby: 2.7)'
35
20
  command: bin/ci
36
21
  agents:
37
22
  queue: k8s-ci
38
23
  env:
39
24
  LOGGING_LEVEL: "4"
40
- KUBERNETES_VERSION: v1.15-latest
41
- - label: 'Run Test Suite (:kubernetes: 1.14-latest)'
25
+ KUBERNETES_VERSION: v1.19-latest
26
+ RUBY_VERSION: "2.7"
27
+ - label: 'Run Test Suite (:kubernetes: 1.18-latest)'
42
28
  command: bin/ci
43
29
  agents:
44
30
  queue: k8s-ci
45
31
  env:
46
32
  LOGGING_LEVEL: "4"
47
- KUBERNETES_VERSION: v1.14-latest
48
- - label: 'Run Test Suite (:kubernetes: 1.13-latest)'
33
+ KUBERNETES_VERSION: v1.18-latest
34
+ - label: 'Run Test Suite (:kubernetes: 1.17-latest)'
49
35
  command: bin/ci
50
36
  agents:
51
37
  queue: k8s-ci
52
38
  env:
53
39
  LOGGING_LEVEL: "4"
54
- KUBERNETES_VERSION: v1.13-latest
55
- - label: 'Run Test Suite (:kubernetes: 1.12-latest)'
40
+ KUBERNETES_VERSION: v1.17-latest
41
+ - label: 'Run Test Suite (:kubernetes: 1.16-latest)'
56
42
  command: bin/ci
57
43
  agents:
58
44
  queue: k8s-ci
59
45
  env:
60
46
  LOGGING_LEVEL: "4"
61
- KUBERNETES_VERSION: v1.12-latest
62
- - label: 'Run Test Suite (:kubernetes: 1.11-latest)'
47
+ KUBERNETES_VERSION: v1.16-latest
48
+ - label: 'Run Test Suite (:kubernetes: 1.15-latest)'
63
49
  command: bin/ci
64
50
  agents:
65
51
  queue: k8s-ci
66
52
  env:
67
53
  LOGGING_LEVEL: "4"
68
- KUBERNETES_VERSION: v1.11-latest
54
+ KUBERNETES_VERSION: v1.15-latest
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  ## next
2
2
 
3
+ ## 2.1.7
4
+
5
+ *Enhancements*
6
+ - 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).
7
+
8
+ ## 2.1.6
9
+
10
+ *Enhancements*
11
+ - Remove the need for a hard coded GVK overide list via improvements to cluster discovery [#778](https://github.com/Shopify/krane/pull/778)
12
+
13
+ *Bug Fixes*
14
+ - 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)
15
+
16
+ ## 2.1.5
17
+
18
+ - 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).
19
+
20
+ ## 2.1.4
21
+
22
+ *Enhancements*
23
+ - 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).
24
+ - Evaluate progress condition only after progress deadline seconds have passed since deploy invocation [#765](https://github.com/Shopify/krane/pull/765).
25
+
26
+ *Other*
27
+ - Dropped support for Ruby 2.5 due to EoL. [#782](https://github.com/Shopify/krane/pull/782).
28
+ - Only patch JSON when run as CLI, not as library [#779](https://github.com/Shopify/krane/pull/779).
29
+
30
+ ## 2.1.3
31
+
32
+ - Add version exception for ServiceNetworkEndpointGroup (GKE resource) [#768](https://github.com/Shopify/krane/pull/768)
33
+ - Kubernetes 1.14 and lower is no longer officially supported as of this version ([#766](https://github.com/Shopify/krane/pull/766))
34
+
3
35
  ## 2.1.2
4
36
 
5
37
  - Add version exception for FrontendConfig (GKE resource) [#761](https://github.com/Shopify/krane/pull/761)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # krane [![Build status](https://badge.buildkite.com/35c56e797c3bbd6ba50053aefdded0715898cd8e8c86f7e462.svg?branch=master)](https://buildkite.com/shopify/krane) [![codecov](https://codecov.io/gh/Shopify/kubernetes-deploy/branch/master/graph/badge.svg)](https://codecov.io/gh/Shopify/kubernetes-deploy)
1
+ # krane [![Build status](https://badge.buildkite.com/35c56e797c3bbd6ba50053aefdded0715898cd8e8c86f7e462.svg?branch=master)](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,8 +73,8 @@ If you need the ability to render dynamic values in templates before deploying,
73
73
 
74
74
  ## Prerequisites
75
75
 
76
- * Ruby 2.5+
77
- * Your cluster must be running Kubernetes v1.11.0 or higher<sup>1</sup>
76
+ * Ruby 2.6+
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
80
80
  official compatibility chart below.
@@ -90,7 +90,7 @@ official compatibility chart below.
90
90
 
91
91
  ## Installation
92
92
 
93
- 1. [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-via-curl) (requires v1.11.0 or higher) and make sure it is available in your $PATH
93
+ 1. [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-via-curl) (requires v1.15.0 or higher) and make sure it is available in your $PATH
94
94
  2. Set up your [kubeconfig file](https://kubernetes.io/docs/tasks/access-application-cluster/authenticate-across-clusters-kubeconfig/) for access to your cluster(s).
95
95
  3. `gem install krane`
96
96
 
@@ -493,7 +493,7 @@ resource to restart.
493
493
 
494
494
  ## Prerequisites
495
495
 
496
- * You've already deployed a [`PodTemplate`](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#podtemplate-v1-core) object with field `template` containing a `Pod` specification that does not include the `apiVersion` or `kind` parameters. An example is provided in this repo in `test/fixtures/hello-cloud/template-runner.yml`.
496
+ * You've already deployed a [`PodTemplate`](https://v1-15.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.15/#podtemplate-v1-core) object with field `template` containing a `Pod` specification that does not include the `apiVersion` or `kind` parameters. An example is provided in this repo in `test/fixtures/hello-cloud/template-runner.yml`.
497
497
  * The `Pod` specification in that template has a container named `task-runner`.
498
498
 
499
499
  Based on this specification `krane run` will create a new pod with the entrypoint of the `task-runner ` container overridden with the supplied arguments.
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.5}" \
19
+ ruby:"${RUBY_VERSION:-2.6.6}" \
21
20
  bin/test
data/bin/setup CHANGED
@@ -9,8 +9,8 @@ if [ ! -x "$(which minikube)" ]; then
9
9
  fi
10
10
 
11
11
  if [ ! -x "$(which kubectl)" ]; then
12
- echo -e "\n\033[0;33mPlease install kubectl version 1.11.0 or higher:\nhttps://kubernetes.io/docs/user-guide/prereqs/\033[0m"
12
+ echo -e "\n\033[0;33mPlease install kubectl version 1.15.0 or higher:\nhttps://kubernetes.io/docs/user-guide/prereqs/\033[0m"
13
13
  else
14
14
  KUBECTL_VERSION=$(kubectl version --short --client | grep -oe "v[[:digit:]\.]\+")
15
- echo -e "\n\033[0;32mKubectl version $KUBECTL_VERSION is already installed. This gem requires version v1.11.0 or greater.\033[0m"
15
+ echo -e "\n\033[0;32mKubectl version $KUBECTL_VERSION is already installed. This gem requires version v1.15.0 or greater.\033[0m"
16
16
  fi
data/dev.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: krane
3
3
  up:
4
- - ruby: 2.5.7 # Matches gemspec
4
+ - ruby: 2.6.6 # Matches gemspec
5
5
  - bundler
6
6
  - homebrew:
7
7
  - homebrew/cask/minikube
@@ -13,7 +13,7 @@ up:
13
13
  - custom:
14
14
  name: Minikube Cluster
15
15
  met?: test $(minikube status | grep Running | wc -l) -ge 2 && $(minikube status | grep -q 'Configured')
16
- meet: minikube start --kubernetes-version=v1.11.10 --vm-driver=hyperkit
16
+ meet: minikube start --kubernetes-version=v1.15.12 --vm-driver=hyperkit
17
17
  down: minikube stop
18
18
  commands:
19
19
  reset-minikube: minikube delete && rm -rf ~/.minikube
data/krane.gemspec CHANGED
@@ -25,7 +25,7 @@ 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.5.0'
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")
@@ -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("codecov")
60
+ spec.add_development_dependency("simplecov")
61
61
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  require 'krane'
3
+ require 'krane/oj'
4
4
  require 'thor'
5
5
  require 'krane/cli/version_command'
6
6
  require 'krane/cli/restart_command'
@@ -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,86 +20,74 @@ module Krane
18
20
 
19
21
  def prunable_resources(namespaced:)
20
22
  black_list = %w(Namespace Node ControllerRevision)
21
- api_versions = fetch_api_versions
22
-
23
- fetch_resources(namespaced: namespaced).uniq { |r| r['kind'] }.map do |resource|
24
- next unless resource['verbs'].one? { |v| v == "delete" }
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
- command = %w(api-resources)
39
- command << "--namespaced=#{namespaced}"
40
- raw, err, st = kubectl.run(*command, output: "wide", attempts: 5,
41
- use_namespace: false)
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
- rows = raw.split("\n")
44
- header = rows[0]
45
- resources = rows[1..-1]
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 api-resources: #{err}"
49
+ raise FatalKubeAPIError, "Error retrieving mutatingwebhookconfigurations: #{err}"
63
50
  end
64
51
  end
65
52
 
66
53
  private
67
54
 
68
- # kubectl api-versions returns a list of group/version strings e.g. autoscaling/v2beta2
69
- # A kind may not exist in all versions of the group.
70
- def fetch_api_versions
71
- raw, err, st = kubectl.run("api-versions", attempts: 5, use_namespace: false)
72
- # The "core" group is represented by an empty string
73
- versions = { "" => %w(v1) }
74
- if st.success?
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
- else
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 version_for_kind(versions, kind)
88
- # Override list for kinds that don't appear in the lastest version of a group
89
- version_override = { "CronJob" => "v1beta1", "VolumeAttachment" => "v1beta1",
90
- "CSIDriver" => "v1beta1", "Ingress" => "v1beta1",
91
- "CSINode" => "v1beta1", "Job" => "v1",
92
- "IngressClass" => "v1beta1", "FrontendConfig" => "v1beta1" }
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
93
78
 
94
- pattern = /v(?<major>\d+)(?<pre>alpha|beta)?(?<minor>\d+)?/
95
- latest = versions.sort_by do |version|
96
- match = version.match(pattern)
97
- pre = { "alpha" => 0, "beta" => 1, nil => 2 }.fetch(match[:pre])
98
- [match[:major].to_i, pre, match[:minor].to_i]
99
- end.last
100
- version_override.fetch(kind, latest)
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
+ }
101
91
  end
102
92
 
103
93
  def fetch_crds
@@ -111,7 +101,7 @@ module Krane
111
101
  end
112
102
 
113
103
  def kubectl
114
- @kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
104
+ @kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true, default_timeout: 2)
115
105
  end
116
106
  end
117
107
  end
data/lib/krane/common.rb CHANGED
@@ -11,7 +11,6 @@ require 'active_support/core_ext/array/conversions'
11
11
  require 'colorized_string'
12
12
 
13
13
  require 'krane/version'
14
- require 'krane/oj'
15
14
  require 'krane/errors'
16
15
  require 'krane/formatted_logger'
17
16
  require 'krane/statsd'
@@ -19,5 +18,5 @@ require 'krane/task_config'
19
18
  require 'krane/task_config_validator'
20
19
 
21
20
  module Krane
22
- MIN_KUBE_VERSION = '1.11.0'
21
+ MIN_KUBE_VERSION = '1.15.0'
23
22
  end
@@ -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
@@ -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, selector: nil)
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 = ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
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
- command = %w(apply)
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$/)
@@ -118,7 +118,7 @@ module Krane
118
118
  end
119
119
 
120
120
  def validate_pod(pod)
121
- pod.validate_definition(kubectl)
121
+ pod.validate_definition(kubectl: kubectl)
122
122
  end
123
123
 
124
124
  def watch_pod(pod)
data/lib/krane/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Krane
3
- VERSION = "2.1.2"
3
+ VERSION = "2.1.7"
4
4
  end
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.2
4
+ version: 2.1.7
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: 2020-10-29 00:00:00.000000000 Z
13
+ date: 2021-02-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -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: codecov
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.5.0
515
+ version: 2.6.0
515
516
  required_rubygems_version: !ruby/object:Gem::Requirement
516
517
  requirements:
517
518
  - - ">="