krane 2.0.0 → 2.1.4

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: 57338bf07bebe1b05ce6701e0c89f2c91db5bd083818b35bd9f307aa669de5ba
4
- data.tar.gz: 94850854c314bb1f05bbc43baa2759dc10e53ee9845c8f3d9b0d33e1a2d7dfd6
3
+ metadata.gz: 7ae8bb4b0c7f920136990847601e19c7767075a89ead99781213feaf309d623d
4
+ data.tar.gz: 90ad58122ca788ebd4010e62e624c0d73a0c153e48c942673e244e7fbb4ccb32
5
5
  SHA512:
6
- metadata.gz: a0414c56878acd433ef3fabf36dd71920ab887adef61ce6b88c3a9dd298fedfb98c8cea39f0a2dfee1d789144ee3b175274e5ecccfd9ff08aad97322cc709463
7
- data.tar.gz: 4f12d3be973fafb33dbbeb5f1f036afc6aa0cd5013b28a063c957befe34a0b65247e1a6b2390de49b52f5f6a9285de2206d5bbda819549a1a82e70e426c61a39
6
+ metadata.gz: 7e09bdb58b4bca4380b77bb10589a2e5b57aa31e9d8838c4b6efc68ccbf58b5af84c360a1a2710564164e4d6f19419cd38d7b3952559c56d5f7dc026d432a194
7
+ data.tar.gz: 659649bf0d0cfbe04d3fad0f32f3a05cc6ed9e117ca0755bf808d898b60dbf0d5be88cad609de1589cd93eb2b808e6cc9994a73f7b21267920b679b71afd138e
@@ -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: []
@@ -264,7 +265,7 @@ Style/MethodCallWithArgsParentheses:
264
265
  - raise
265
266
  - puts
266
267
  Exclude:
267
- - Gemfile
268
+ - '**/Gemfile'
268
269
 
269
270
  Style/MethodDefParentheses:
270
271
  EnforcedStyle: require_parentheses
@@ -675,7 +676,7 @@ Style/LineEndConcatenation:
675
676
  Style/MethodCallWithoutArgsParentheses:
676
677
  Enabled: true
677
678
 
678
- Style/MethodMissingSuper:
679
+ Lint/MissingSuper:
679
680
  Enabled: true
680
681
 
681
682
  Style/MissingRespondToMissing:
@@ -965,7 +966,7 @@ Lint/UselessAccessModifier:
965
966
  Lint/UselessAssignment:
966
967
  Enabled: true
967
968
 
968
- Lint/UselessComparison:
969
+ Lint/BinaryOperatorWithIdenticalOperands:
969
970
  Enabled: true
970
971
 
971
972
  Lint/UselessElseWithoutRescue:
@@ -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
@@ -1,5 +1,40 @@
1
1
  ## next
2
2
 
3
+ ## 2.1.4
4
+
5
+ *Enhancements*
6
+ - 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).
7
+ - Evaluate progress condition only after progress deadline seconds have passed since deploy invocation [#765](https://github.com/Shopify/krane/pull/765).
8
+
9
+ *Other*
10
+ - Dropped support for Ruby 2.5 due to EoL. [#782](https://github.com/Shopify/krane/pull/782).
11
+ - Only patch JSON when run as CLI, not as library [#779](https://github.com/Shopify/krane/pull/779).
12
+
13
+ ## 2.1.3
14
+
15
+ - Add version exception for ServiceNetworkEndpointGroup (GKE resource) [#768](https://github.com/Shopify/krane/pull/768)
16
+ - Kubernetes 1.14 and lower is no longer officially supported as of this version ([#766](https://github.com/Shopify/krane/pull/766))
17
+
18
+ ## 2.1.2
19
+
20
+ - Add version exception for FrontendConfig (GKE resource) [#761](https://github.com/Shopify/krane/pull/761)
21
+
22
+ ## 2.1.1
23
+
24
+ *Bug Fixes*
25
+ - Fix the way environment variables are passed into the EJSON decryption invocation [#759](https://github.com/Shopify/krane/pull/759)
26
+
27
+ ## 2.1.0
28
+
29
+ *Features*
30
+ - _(experimental)_ Override deploy method via annotation. This feature is considered alpha and should not be considered stable [#753](https://github.com/Shopify/krane/pull/753)
31
+
32
+ *Enhancements*
33
+ - Increased the number of attempts on kubectl commands during Initializing deploy phase [#749](https://github.com/Shopify/krane/pull/749)
34
+ - Increased attempts on kubectl apply command during deploy [#751](https://github.com/Shopify/krane/pull/751)
35
+ - Whitelist context deadline error during kubctl dry run [#754](https://github.com/Shopify/krane/pull/754)
36
+ - Allow specifying a kubeconfig per task in the internal API [#746](https://github.com/Shopify/krane/pull/746)
37
+
3
38
  ## 2.0.0
4
39
 
5
40
  *Breaking Changes*
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
 
@@ -159,6 +159,11 @@ before the deployment is considered successful.
159
159
  - _Default_: `true`
160
160
  - `true`: The custom resource will be deployed in the pre-deploy phase.
161
161
  - All other values: The custom resource will be deployed in the main deployment phase.
162
+ - `krane.shopify.io/deploy-method-override`: Cause a resource to be deployed by the specified `kubectl` command, instead of the default `apply`.
163
+ - _Compatibility_: Cannot be used for `PodDisruptionBudget`, since it always uses `create/replace-force`
164
+ - _Accepted values_: `create`, `replace`, and `replace-force`
165
+ - _Warning_: Resources whose deploy method is overridden are no longer subject to pruning on deploy.
166
+ - This feature is _experimental_ and may be removed at any time.
162
167
 
163
168
 
164
169
  ### Running tasks at the beginning of a deploy
@@ -488,7 +493,7 @@ resource to restart.
488
493
 
489
494
  ## Prerequisites
490
495
 
491
- * 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`.
492
497
  * The `Pod` specification in that template has a container named `task-runner`.
493
498
 
494
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,10 +1,11 @@
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
- - Caskroom/cask/minikube
7
+ - homebrew/cask/minikube
8
+ - hyperkit
8
9
  - custom:
9
10
  name: Install the minikube fork of driver-hyperkit
10
11
  met?: command -v docker-machine-driver-hyperkit
@@ -12,7 +13,7 @@ up:
12
13
  - custom:
13
14
  name: Minikube Cluster
14
15
  met?: test $(minikube status | grep Running | wc -l) -ge 2 && $(minikube status | grep -q 'Configured')
15
- meet: minikube start --kubernetes-version=v1.11.10 --vm-driver=hyperkit
16
+ meet: minikube start --kubernetes-version=v1.15.12 --vm-driver=hyperkit
16
17
  down: minikube stop
17
18
  commands:
18
19
  reset-minikube: minikube delete && rm -rf ~/.minikube
@@ -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")
@@ -56,6 +56,6 @@ Gem::Specification.new do |spec|
56
56
  spec.add_development_dependency("byebug")
57
57
  spec.add_development_dependency("ruby-prof")
58
58
  spec.add_development_dependency("ruby-prof-flamegraph")
59
- spec.add_development_dependency("rubocop", "~> 0.88.0")
60
- spec.add_development_dependency("codecov")
59
+ spec.add_development_dependency("rubocop", "~> 0.89.1")
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'
@@ -23,9 +23,7 @@ module Krane
23
23
  fetch_resources(namespaced: namespaced).uniq { |r| r['kind'] }.map do |resource|
24
24
  next unless resource['verbs'].one? { |v| v == "delete" }
25
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("/")
26
+ gvk_string(api_versions, resource)
29
27
  end.compact
30
28
  end
31
29
 
@@ -89,7 +87,10 @@ module Krane
89
87
  version_override = { "CronJob" => "v1beta1", "VolumeAttachment" => "v1beta1",
90
88
  "CSIDriver" => "v1beta1", "Ingress" => "v1beta1",
91
89
  "CSINode" => "v1beta1", "Job" => "v1",
92
- "IngressClass" => "v1beta1" }
90
+ "IngressClass" => "v1beta1", "FrontendConfig" => "v1beta1",
91
+ "ServiceNetworkEndpointGroup" => "v1beta1",
92
+ "EnvoyFilter" => "v1alpha3",
93
+ "TCPIngress" => "v1beta1" }
93
94
 
94
95
  pattern = /v(?<major>\d+)(?<pre>alpha|beta)?(?<minor>\d+)?/
95
96
  latest = versions.sort_by do |version|
@@ -100,6 +101,23 @@ module Krane
100
101
  version_override.fetch(kind, latest)
101
102
  end
102
103
 
104
+ def gvk_string(api_versions, resource)
105
+ apiversion = resource['apiversion'].to_s
106
+
107
+ ## In kubectl 1.20 APIGroups was replaced by APIVersions
108
+ if apiversion.empty?
109
+ apigroup = resource['apigroup'].to_s
110
+ group_versions = api_versions[apigroup]
111
+
112
+ version = version_for_kind(group_versions, resource['kind'])
113
+ apigroup = 'core' if apigroup.empty?
114
+ apiversion = "#{apigroup}/#{version}"
115
+ end
116
+
117
+ apiversion = "core/#{apiversion}" unless apiversion.include?("/")
118
+ [apiversion, resource['kind']].compact.join("/")
119
+ end
120
+
103
121
  def fetch_crds
104
122
  raw_json, err, st = kubectl.run("get", "CustomResourceDefinition", output: "json", attempts: 5,
105
123
  use_namespace: false)
@@ -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
@@ -85,6 +85,10 @@ module Krane
85
85
  kubectl.server_version
86
86
  end
87
87
 
88
+ attr_reader :task_config
89
+
90
+ delegate :kubeclient_builder, to: :task_config
91
+
88
92
  # Initializes the deploy task
89
93
  #
90
94
  # @param namespace [String] Kubernetes namespace (*required*)
@@ -101,10 +105,10 @@ module Krane
101
105
  # @param render_erb [Boolean] Enable ERB rendering
102
106
  def initialize(namespace:, context:, current_sha: nil, logger: nil, kubectl_instance: nil, bindings: {},
103
107
  global_timeout: nil, selector: nil, filenames: [], protected_namespaces: nil,
104
- render_erb: false)
108
+ render_erb: false, kubeconfig: nil)
105
109
  @logger = logger || Krane::FormattedLogger.build(namespace, context)
106
110
  @template_sets = TemplateSets.from_dirs_and_files(paths: filenames, logger: @logger, render_erb: render_erb)
107
- @task_config = Krane::TaskConfig.new(context, namespace, @logger)
111
+ @task_config = Krane::TaskConfig.new(context, namespace, @logger, kubeconfig)
108
112
  @bindings = bindings
109
113
  @namespace = namespace
110
114
  @namespace_tags = []
@@ -190,10 +194,6 @@ module Krane
190
194
  selector: @selector, statsd_tags: statsd_tags, current_sha: @current_sha)
191
195
  end
192
196
 
193
- def kubeclient_builder
194
- @kubeclient_builder ||= KubeclientBuilder.new
195
- end
196
-
197
197
  def cluster_resource_discoverer
198
198
  @cluster_resource_discoverer ||= ClusterResourceDiscovery.new(
199
199
  task_config: @task_config,
@@ -279,13 +279,17 @@ module Krane
279
279
 
280
280
  def validate_resources(resources)
281
281
  validate_globals(resources)
282
+ batch_dry_run_success = validate_dry_run(resources)
282
283
  Krane::Concurrency.split_across_threads(resources) do |r|
283
- r.validate_definition(kubectl, selector: @selector)
284
+ # No need to pass in kubectl (and do per-resource dry run apply) if batch dry run succeeded
285
+ if batch_dry_run_success
286
+ r.validate_definition(kubectl: nil, selector: @selector, dry_run: false)
287
+ else
288
+ r.validate_definition(kubectl: kubectl, selector: @selector, dry_run: true)
289
+ end
284
290
  end
285
-
286
291
  failed_resources = resources.select(&:validation_failed?)
287
292
  if failed_resources.present?
288
-
289
293
  failed_resources.each do |r|
290
294
  content = File.read(r.file_path) if File.file?(r.file_path) && !r.sensitive_template_content?
291
295
  record_invalid_template(logger: @logger, err: r.validation_error_msg,
@@ -308,6 +312,10 @@ module Krane
308
312
  "Use GlobalDeployTask instead."
309
313
  end
310
314
 
315
+ def validate_dry_run(resources)
316
+ resource_deployer.dry_run(resources)
317
+ end
318
+
311
319
  def namespace_definition
312
320
  @namespace_definition ||= begin
313
321
  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
@@ -133,7 +133,7 @@ module Krane
133
133
  end
134
134
 
135
135
  def decrypt_ejson(key_dir)
136
- out, err, st = Open3.capture3("EJSON_KEYDIR=#{key_dir} ejson decrypt #{@ejson_file}")
136
+ out, err, st = Open3.capture3({ 'EJSON_KEYDIR' => key_dir.to_s }, 'ejson', 'decrypt', @ejson_file.to_s)
137
137
  unless st.success?
138
138
  # older ejson versions dump some errors to STDOUT
139
139
  msg = err.presence || out
@@ -25,7 +25,8 @@ module Krane
25
25
  class GlobalDeployTask
26
26
  extend Krane::StatsD::MeasureMethods
27
27
  include TemplateReporting
28
- delegate :context, :logger, :global_kinds, to: :@task_config
28
+ delegate :context, :logger, :global_kinds, :kubeclient_builder, to: :@task_config
29
+ attr_reader :task_config
29
30
 
30
31
  # Initializes the deploy task
31
32
  #
@@ -33,10 +34,10 @@ module Krane
33
34
  # @param global_timeout [Integer] Timeout in seconds
34
35
  # @param selector [Hash] Selector(s) parsed by Krane::LabelSelector (*required*)
35
36
  # @param filenames [Array<String>] An array of filenames and/or directories containing templates (*required*)
36
- def initialize(context:, global_timeout: nil, selector: nil, filenames: [], logger: nil)
37
+ def initialize(context:, global_timeout: nil, selector: nil, filenames: [], logger: nil, kubeconfig: nil)
37
38
  template_paths = filenames.map { |path| File.expand_path(path) }
38
39
 
39
- @task_config = TaskConfig.new(context, nil, logger)
40
+ @task_config = TaskConfig.new(context, nil, logger, kubeconfig)
40
41
  @template_sets = TemplateSets.from_dirs_and_files(paths: template_paths,
41
42
  logger: @task_config.logger, render_erb: false)
42
43
  @global_timeout = global_timeout
@@ -130,7 +131,7 @@ module Krane
130
131
  validate_globals(resources)
131
132
 
132
133
  Concurrency.split_across_threads(resources) do |r|
133
- r.validate_definition(@kubectl, selector: @selector)
134
+ r.validate_definition(kubectl: @kubectl, selector: @selector)
134
135
  end
135
136
 
136
137
  failed_resources = resources.select(&:validation_failed?)
@@ -189,10 +190,6 @@ module Krane
189
190
  @kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
190
191
  end
191
192
 
192
- def kubeclient_builder
193
- @kubeclient_builder ||= KubeclientBuilder.new
194
- end
195
-
196
193
  def prune_whitelist
197
194
  cluster_resource_discoverer.prunable_resources(namespaced: false)
198
195
  end
@@ -10,8 +10,10 @@ module Krane
10
10
  end
11
11
  end
12
12
 
13
- def initialize(kubeconfig: ENV["KUBECONFIG"])
14
- files = kubeconfig || "#{Dir.home}/.kube/config"
13
+ attr_reader :kubeconfig_files
14
+
15
+ def initialize(kubeconfig: nil)
16
+ files = kubeconfig || ENV["KUBECONFIG"] || "#{Dir.home}/.kube/config"
15
17
  # Split the list by colon for Linux and Mac, and semicolon for Windows.
16
18
  @kubeconfig_files = files.split(/[:;]/).map!(&:strip).reject(&:empty?)
17
19
  end
@@ -7,6 +7,7 @@ module Krane
7
7
  not_found: /NotFound/,
8
8
  client_timeout: /Client\.Timeout exceeded while awaiting headers/,
9
9
  empty: /\A\z/,
10
+ context_deadline: /context deadline exceeded/,
10
11
  }
11
12
  DEFAULT_TIMEOUT = 15
12
13
  MAX_RETRY_DELAY = 16
@@ -14,7 +15,7 @@ module Krane
14
15
 
15
16
  class ResourceNotFoundError < StandardError; end
16
17
 
17
- delegate :namespace, :context, :logger, to: :@task_config
18
+ delegate :namespace, :context, :logger, :kubeconfig, to: :@task_config
18
19
 
19
20
  def initialize(task_config:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT,
20
21
  output_is_sensitive_default: false)
@@ -34,7 +35,8 @@ module Krane
34
35
 
35
36
  (1..attempts).to_a.each do |current_attempt|
36
37
  logger.debug("Running command (attempt #{current_attempt}): #{cmd.join(' ')}")
37
- out, err, st = Open3.capture3(*cmd)
38
+ env = { 'KUBECONFIG' => kubeconfig }
39
+ out, err, st = Open3.capture3(env, *cmd)
38
40
 
39
41
  # https://github.com/Shopify/krane/issues/395
40
42
  unless out.valid_encoding?
@@ -62,7 +64,8 @@ module Krane
62
64
  else
63
65
  logger.debug("Kubectl err: #{output_is_sensitive ? '<suppressed sensitive output>' : err}")
64
66
  end
65
- StatsD.client.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1] })
67
+ StatsD.client.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1],
68
+ max_attempt: attempts, current_attempt: current_attempt })
66
69
 
67
70
  break unless retriable_err?(err, retry_whitelist) && current_attempt < attempts
68
71
  sleep(retry_delay(current_attempt))
@@ -79,7 +82,7 @@ module Krane
79
82
  def version_info
80
83
  @version_info ||=
81
84
  begin
82
- response, _, status = run("version", use_namespace: false, log_failure: true)
85
+ response, _, status = run("version", use_namespace: false, log_failure: true, attempts: 2)
83
86
  raise KubectlError, "Could not retrieve kubectl version info" unless status.success?
84
87
  extract_version_info_from_kubectl_response(response)
85
88
  end
@@ -97,6 +100,14 @@ module Krane
97
100
  server_version >= Gem::Version.new(SERVER_DRY_RUN_MIN_VERSION)
98
101
  end
99
102
 
103
+ def dry_run_flag
104
+ if server_version >= Gem::Version.new("1.18")
105
+ "--dry-run=server"
106
+ else
107
+ "--server-dry-run"
108
+ end
109
+ end
110
+
100
111
  private
101
112
 
102
113
  def build_command_from_options(args, use_namespace, use_context, output)
@@ -35,6 +35,8 @@ module Krane
35
35
  If you have reason to believe it will succeed, retry the deploy to continue to monitor the rollout.
36
36
  MSG
37
37
 
38
+ ALLOWED_DEPLOY_METHOD_OVERRIDES = %w(create replace replace-force)
39
+ DEPLOY_METHOD_OVERRIDE_ANNOTATION = "deploy-method-override"
38
40
  TIMEOUT_OVERRIDE_ANNOTATION = "timeout-override"
39
41
  LAST_APPLIED_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
40
42
  SENSITIVE_TEMPLATE_CONTENT = false
@@ -132,11 +134,12 @@ module Krane
132
134
  Kubeclient::Resource.new(@definition)
133
135
  end
134
136
 
135
- def validate_definition(kubectl, selector: nil)
137
+ def validate_definition(kubectl:, selector: nil, dry_run: true)
136
138
  @validation_errors = []
137
139
  validate_selector(selector) if selector
138
140
  validate_timeout_annotation
139
- validate_spec_with_kubectl(kubectl)
141
+ validate_deploy_method_override_annotation
142
+ validate_spec_with_kubectl(kubectl) if dry_run
140
143
  @validation_errors.present?
141
144
  end
142
145
 
@@ -237,10 +240,14 @@ module Krane
237
240
  if @definition.dig("metadata", "name").blank? && uses_generate_name?
238
241
  :create
239
242
  else
240
- :apply
243
+ deploy_method_override || :apply
241
244
  end
242
245
  end
243
246
 
247
+ def deploy_method_override
248
+ krane_annotation_value(DEPLOY_METHOD_OVERRIDE_ANNOTATION)&.to_sym
249
+ end
250
+
244
251
  def sync_debug_info(kubectl)
245
252
  @debug_events = fetch_events(kubectl) unless ENV[DISABLE_FETCHING_EVENT_INFO]
246
253
  @debug_logs = fetch_debug_logs if print_debug_logs? && !ENV[DISABLE_FETCHING_LOG_INFO]
@@ -504,6 +511,16 @@ module Krane
504
511
  @validation_errors << "#{timeout_annotation_key} annotation is invalid: #{e}"
505
512
  end
506
513
 
514
+ def validate_deploy_method_override_annotation
515
+ deploy_method_override_value = krane_annotation_value(DEPLOY_METHOD_OVERRIDE_ANNOTATION)
516
+ deploy_method_override_annotation_key = Annotation.for(DEPLOY_METHOD_OVERRIDE_ANNOTATION)
517
+ return unless deploy_method_override_value
518
+ unless ALLOWED_DEPLOY_METHOD_OVERRIDES.include?(deploy_method_override_value)
519
+ @validation_errors << "#{deploy_method_override_annotation_key} is invalid: Accepted values are: " \
520
+ "#{ALLOWED_DEPLOY_METHOD_OVERRIDES.join(', ')} but got #{deploy_method_override_value}"
521
+ end
522
+ end
523
+
507
524
  def krane_annotation_value(suffix)
508
525
  @definition.dig("metadata", "annotations", Annotation.for(suffix))
509
526
  end
@@ -543,9 +560,14 @@ module Krane
543
560
 
544
561
  # Server side dry run is only supported on apply
545
562
  def validate_with_server_side_dry_run(kubectl)
546
- command = ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
563
+ command = if kubectl.server_version >= Gem::Version.new('1.18')
564
+ ["apply", "-f", file_path, "--dry-run=server", "--output=name"]
565
+ else
566
+ ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
567
+ end
568
+
547
569
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
548
- retry_whitelist: [:client_timeout, :empty], attempts: 3)
570
+ retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3)
549
571
  end
550
572
 
551
573
  # Local dry run is supported on only create and apply
@@ -555,7 +577,7 @@ module Krane
555
577
  verb = deploy_method == :apply ? "apply" : "create"
556
578
  command = [verb, "-f", file_path, "--dry-run", "--output=name"]
557
579
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
558
- retry_whitelist: [:client_timeout, :empty], attempts: 3, use_namespace: !global?)
580
+ retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3, use_namespace: !global?)
559
581
  end
560
582
 
561
583
  def labels
@@ -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.
@@ -63,6 +63,7 @@ module Krane
63
63
  attr_reader :name
64
64
 
65
65
  def initialize(definition)
66
+ super(definition: definition, namespace: nil, context: nil, logger: nil)
66
67
  @definition = definition
67
68
  @name = definition.dig("metadata", "name").to_s
68
69
  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)
@@ -95,11 +102,10 @@ module Krane
95
102
  applyables, individuals = resources.partition { |r| r.deploy_method == :apply }
96
103
  # Prunable resources should also applied so that they can be pruned
97
104
  pruneable_types = @prune_whitelist.map { |t| t.split("/").last }
98
- applyables += individuals.select { |r| pruneable_types.include?(r.type) }
105
+ applyables += individuals.select { |r| pruneable_types.include?(r.type) && !r.deploy_method_override }
99
106
 
100
107
  individuals.each do |individual_resource|
101
108
  individual_resource.deploy_started_at = Time.now.utc
102
-
103
109
  case individual_resource.deploy_method
104
110
  when :create
105
111
  err, status = create_resource(individual_resource)
@@ -129,17 +135,17 @@ module Krane
129
135
  end
130
136
  end
131
137
 
132
- def apply_all(resources, prune)
138
+ def apply_all(resources, prune, dry_run: false)
133
139
  return unless resources.present?
134
- command = %w(apply)
140
+ start = Time.now.utc
135
141
 
142
+ command = %w(apply)
136
143
  Dir.mktmpdir do |tmp_dir|
137
144
  resources.each do |r|
138
145
  FileUtils.symlink(r.file_path, tmp_dir)
139
- r.deploy_started_at = Time.now.utc
146
+ r.deploy_started_at = Time.now.utc unless dry_run
140
147
  end
141
148
  command.push("-f", tmp_dir)
142
-
143
149
  if prune && @prune_whitelist.present?
144
150
  command.push("--prune")
145
151
  if @selector
@@ -150,20 +156,22 @@ module Krane
150
156
  @prune_whitelist.each { |type| command.push("--prune-whitelist=#{type}") }
151
157
  end
152
158
 
159
+ command.push(kubectl.dry_run_flag) if dry_run
153
160
  output_is_sensitive = resources.any?(&:sensitive_template_content?)
154
161
  global_mode = resources.all?(&:global?)
155
162
  out, err, st = kubectl.run(*command, log_failure: false, output_is_sensitive: output_is_sensitive,
156
- use_namespace: !global_mode)
163
+ attempts: 2, use_namespace: !global_mode)
157
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)
158
167
  if st.success?
159
168
  log_pruning(out) if prune
160
169
  else
161
- record_apply_failure(err, resources: resources)
170
+ record_apply_failure(err, resources: resources) unless dry_run
162
171
  raise FatalDeploymentError, "Command failed: #{Shellwords.join(command)}"
163
172
  end
164
173
  end
165
174
  end
166
- measure_method(:apply_all)
167
175
 
168
176
  def log_pruning(kubectl_output)
169
177
  pruned = kubectl_output.scan(/^(.*) pruned$/)
@@ -22,15 +22,19 @@ module Krane
22
22
  HTTP_OK_RANGE = 200..299
23
23
  ANNOTATION = "shipit.shopify.io/restart"
24
24
 
25
+ attr_reader :task_config
26
+
27
+ delegate :kubeclient_builder, to: :task_config
28
+
25
29
  # Initializes the restart task
26
30
  #
27
31
  # @param context [String] Kubernetes context / cluster (*required*)
28
32
  # @param namespace [String] Kubernetes namespace (*required*)
29
33
  # @param logger [Object] Logger object (defaults to an instance of Krane::FormattedLogger)
30
34
  # @param global_timeout [Integer] Timeout in seconds
31
- def initialize(context:, namespace:, logger: nil, global_timeout: nil)
35
+ def initialize(context:, namespace:, logger: nil, global_timeout: nil, kubeconfig: nil)
32
36
  @logger = logger || Krane::FormattedLogger.build(namespace, context)
33
- @task_config = Krane::TaskConfig.new(context, namespace, @logger)
37
+ @task_config = Krane::TaskConfig.new(context, namespace, @logger, kubeconfig)
34
38
  @context = context
35
39
  @namespace = namespace
36
40
  @global_timeout = global_timeout
@@ -220,9 +224,5 @@ module Krane
220
224
  def v1beta1_kubeclient
221
225
  @v1beta1_kubeclient ||= kubeclient_builder.build_v1beta1_kubeclient(@context)
222
226
  end
223
-
224
- def kubeclient_builder
225
- @kubeclient_builder ||= KubeclientBuilder.new
226
- end
227
227
  end
228
228
  end
@@ -16,7 +16,9 @@ module Krane
16
16
  class RunnerTask
17
17
  class TaskTemplateMissingError < TaskConfigurationError; end
18
18
 
19
- attr_reader :pod_name
19
+ attr_reader :pod_name, :task_config
20
+
21
+ delegate :kubeclient_builder, to: :task_config
20
22
 
21
23
  # Initializes the runner task
22
24
  #
@@ -24,9 +26,9 @@ module Krane
24
26
  # @param context [String] Kubernetes context / cluster (*required*)
25
27
  # @param logger [Object] Logger object (defaults to an instance of Krane::FormattedLogger)
26
28
  # @param global_timeout [Integer] Timeout in seconds
27
- def initialize(namespace:, context:, logger: nil, global_timeout: nil)
29
+ def initialize(namespace:, context:, logger: nil, global_timeout: nil, kubeconfig: nil)
28
30
  @logger = logger || Krane::FormattedLogger.build(namespace, context)
29
- @task_config = Krane::TaskConfig.new(context, namespace, @logger)
31
+ @task_config = Krane::TaskConfig.new(context, namespace, @logger, kubeconfig)
30
32
  @namespace = namespace
31
33
  @context = context
32
34
  @global_timeout = global_timeout
@@ -116,7 +118,7 @@ module Krane
116
118
  end
117
119
 
118
120
  def validate_pod(pod)
119
- pod.validate_definition(kubectl)
121
+ pod.validate_definition(kubectl: kubectl)
120
122
  end
121
123
 
122
124
  def watch_pod(pod)
@@ -200,10 +202,6 @@ module Krane
200
202
  @kubeclient ||= kubeclient_builder.build_v1_kubeclient(@context)
201
203
  end
202
204
 
203
- def kubeclient_builder
204
- @kubeclient_builder ||= KubeclientBuilder.new
205
- end
206
-
207
205
  def statsd_tags(status)
208
206
  %W(namespace:#{@namespace} context:#{@context} status:#{status})
209
207
  end
@@ -4,12 +4,13 @@ require 'krane/cluster_resource_discovery'
4
4
 
5
5
  module Krane
6
6
  class TaskConfig
7
- attr_reader :context, :namespace, :logger
7
+ attr_reader :context, :namespace, :logger, :kubeconfig
8
8
 
9
- def initialize(context, namespace, logger = nil)
9
+ def initialize(context, namespace, logger = nil, kubeconfig = nil)
10
10
  @context = context
11
11
  @namespace = namespace
12
12
  @logger = logger || FormattedLogger.build(@namespace, @context)
13
+ @kubeconfig = kubeconfig || ENV['KUBECONFIG']
13
14
  end
14
15
 
15
16
  def global_kinds
@@ -18,5 +19,9 @@ module Krane
18
19
  cluster_resource_discoverer.fetch_resources(namespaced: false).map { |g| g["kind"] }
19
20
  end
20
21
  end
22
+
23
+ def kubeclient_builder
24
+ @kubeclient_builder ||= KubeclientBuilder.new(kubeconfig: kubeconfig)
25
+ end
21
26
  end
22
27
  end
@@ -45,7 +45,7 @@ module Krane
45
45
  end
46
46
 
47
47
  _, err, st = @kubectl.run("config", "get-contexts", context, "-o", "name",
48
- use_namespace: false, use_context: false, log_failure: false)
48
+ use_namespace: false, use_context: false, log_failure: false, attempts: 2)
49
49
 
50
50
  unless st.success?
51
51
  @errors << if err.match("error: context #{context} not found")
@@ -58,7 +58,7 @@ module Krane
58
58
 
59
59
  def validate_context_reachable
60
60
  _, err, st = @kubectl.run("get", "namespaces", "-o", "name",
61
- use_namespace: false, log_failure: false)
61
+ use_namespace: false, log_failure: false, attempts: 2)
62
62
 
63
63
  unless st.success?
64
64
  @errors << "Something went wrong connecting to #{context}. #{err} "
@@ -71,7 +71,7 @@ module Krane
71
71
  end
72
72
 
73
73
  _, err, st = @kubectl.run("get", "namespace", "-o", "name", namespace,
74
- use_namespace: false, log_failure: false)
74
+ use_namespace: false, log_failure: false, attempts: 3)
75
75
 
76
76
  unless st.success?
77
77
  @errors << if err.match("Error from server [(]NotFound[)]: namespace")
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Krane
3
- VERSION = "2.0.0"
3
+ VERSION = "2.1.4"
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.0.0
4
+ version: 2.1.4
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-08-26 00:00:00.000000000 Z
13
+ date: 2021-01-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -366,16 +366,16 @@ dependencies:
366
366
  requirements:
367
367
  - - "~>"
368
368
  - !ruby/object:Gem::Version
369
- version: 0.88.0
369
+ version: 0.89.1
370
370
  type: :development
371
371
  prerelease: false
372
372
  version_requirements: !ruby/object:Gem::Requirement
373
373
  requirements:
374
374
  - - "~>"
375
375
  - !ruby/object:Gem::Version
376
- version: 0.88.0
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
  - - ">="
@@ -511,7 +511,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
511
511
  requirements:
512
512
  - - ">="
513
513
  - !ruby/object:Gem::Version
514
- version: 2.5.0
514
+ version: 2.6.0
515
515
  required_rubygems_version: !ruby/object:Gem::Requirement
516
516
  requirements:
517
517
  - - ">="