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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a6ce0892fb00b50930df4316057572b08245e946d7eb9e60d938c76e4ba826e
4
- data.tar.gz: 903999c3121acbd58162ebfffedb10fbfa22a347262c7108bc3c499527654a22
3
+ metadata.gz: 0f0acd44dff6399afd220b377ae825e964d07da8a0574525ff7b3f966857bf80
4
+ data.tar.gz: 31c0f004b0f4f5db39e73b5fbde06b54bfce2bfbf393676f44b036397e303515
5
5
  SHA512:
6
- metadata.gz: ca770038ea36996205013d9baae03f4acb6da0b37e57aa7f261d1c95a5846999135bb147392c1016a9c0c04377258e07e155f5f3a994e4a3e1fd8bffb69f296d
7
- data.tar.gz: b1c3fc517252cd4be20cf11a7dbf2381591890aeb6074859a6058f2dfd0becd060039800704106451dff116494a4725af93c2d0175550437df1a9c94428453b1
6
+ metadata.gz: 240a8713adc34a340ff3d127828c3a1a740d3543656b6b0c730a26db295b7b6f9c306d9fa53cf10ad425a00116d99895b2feec207aae94a1f3b3b5b571bef006
7
+ data.tar.gz: dd13e23ab5b37b152665ce4bd29a10cb77a30115938df6a50ef394b3bccf84ea51fcf12ba7590bd899c071fb083df70eeb94095b1a72086932a9e0122db1cdb3
@@ -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,14 +8,29 @@ 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
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.12)'
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
- # Flip this back to v1.16-latest when 1.16.14 comes out (see #733)
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 [![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,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.5+
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.5}" \
19
+ ruby:"${RUBY_VERSION:-2.6.6}" \
21
20
  bin/test
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
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.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")
32
32
  spec.add_dependency("ejson", "~> 1.0")
33
33
  spec.add_dependency("colorize", "~> 0.8")
34
- spec.add_dependency("statsd-instrument", ['>= 2.8', "< 3.1"])
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("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,87 +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",
93
- "ServiceNetworkEndpointGroup" => "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
94
78
 
95
- pattern = /v(?<major>\d+)(?<pre>alpha|beta)?(?<minor>\d+)?/
96
- latest = versions.sort_by do |version|
97
- match = version.match(pattern)
98
- pre = { "alpha" => 0, "beta" => 1, nil => 2 }.fetch(match[:pre])
99
- [match[:major].to_i, pre, match[:minor].to_i]
100
- end.last
101
- 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
+ }
102
91
  end
103
92
 
104
93
  def fetch_crds
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'
@@ -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.3"
3
+ VERSION = "2.1.8"
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.3
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: 2020-12-14 00:00:00.000000000 Z
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: '3.1'
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: '3.1'
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: 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
  - - ">="