krane 2.0.0 → 2.1.0

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: a3b3d47fb38961012857a5964baadcc19c6e5c147223383430f820af2029cf75
4
+ data.tar.gz: 94addb8d8743d249ec0437467c8eb5ccf075b41536a329e781ef80fad48dbad3
5
5
  SHA512:
6
- metadata.gz: a0414c56878acd433ef3fabf36dd71920ab887adef61ce6b88c3a9dd298fedfb98c8cea39f0a2dfee1d789144ee3b175274e5ecccfd9ff08aad97322cc709463
7
- data.tar.gz: 4f12d3be973fafb33dbbeb5f1f036afc6aa0cd5013b28a063c957befe34a0b65247e1a6b2390de49b52f5f6a9285de2206d5bbda819549a1a82e70e426c61a39
6
+ metadata.gz: 10038a1f57185f4d78fda538aadb71357f1c7d84b77b9a27873062b7bdd82e06e696666ab639384d84e25c010165bdfdd69533860fc50ce564fc419b09fc01a2
7
+ data.tar.gz: a12bf652e4503d37fd368f38d6219e48e1755ea688016094849d6558440b6db88aba22aaadfa807ac6e9f1ff1e3ceba171bfbf1cdcd07fe95bf7e2cc553a0e90
@@ -264,7 +264,7 @@ Style/MethodCallWithArgsParentheses:
264
264
  - raise
265
265
  - puts
266
266
  Exclude:
267
- - Gemfile
267
+ - '**/Gemfile'
268
268
 
269
269
  Style/MethodDefParentheses:
270
270
  EnforcedStyle: require_parentheses
@@ -675,7 +675,7 @@ Style/LineEndConcatenation:
675
675
  Style/MethodCallWithoutArgsParentheses:
676
676
  Enabled: true
677
677
 
678
- Style/MethodMissingSuper:
678
+ Lint/MissingSuper:
679
679
  Enabled: true
680
680
 
681
681
  Style/MissingRespondToMissing:
@@ -965,7 +965,7 @@ Lint/UselessAccessModifier:
965
965
  Lint/UselessAssignment:
966
966
  Enabled: true
967
967
 
968
- Lint/UselessComparison:
968
+ Lint/BinaryOperatorWithIdenticalOperands:
969
969
  Enabled: true
970
970
 
971
971
  Lint/UselessElseWithoutRescue:
@@ -1,5 +1,16 @@
1
1
  ## next
2
2
 
3
+ ## 2.1.0
4
+
5
+ *Features*
6
+ - _(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)
7
+
8
+ *Enhancements*
9
+ - Increased the number of attempts on kubectl commands during Initializing deploy phase [#749](https://github.com/Shopify/krane/pull/749)
10
+ - Increased attempts on kubectl apply command during deploy [#751](https://github.com/Shopify/krane/pull/751)
11
+ - Whitelist context deadline error during kubctl dry run [#754](https://github.com/Shopify/krane/pull/754)
12
+ - Allow specifying a kubeconfig per task in the internal API [#746](https://github.com/Shopify/krane/pull/746)
13
+
3
14
  ## 2.0.0
4
15
 
5
16
  *Breaking Changes*
data/README.md CHANGED
@@ -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
data/dev.yml CHANGED
@@ -4,7 +4,8 @@ up:
4
4
  - ruby: 2.5.7 # 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
@@ -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")
59
+ spec.add_development_dependency("rubocop", "~> 0.89.1")
60
60
  spec.add_development_dependency("codecov")
61
61
  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,
@@ -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
@@ -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
@@ -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
@@ -136,6 +138,7 @@ module Krane
136
138
  @validation_errors = []
137
139
  validate_selector(selector) if selector
138
140
  validate_timeout_annotation
141
+ validate_deploy_method_override_annotation
139
142
  validate_spec_with_kubectl(kubectl)
140
143
  @validation_errors.present?
141
144
  end
@@ -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
@@ -545,7 +562,7 @@ module Krane
545
562
  def validate_with_server_side_dry_run(kubectl)
546
563
  command = ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
547
564
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
548
- retry_whitelist: [:client_timeout, :empty], attempts: 3)
565
+ retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3)
549
566
  end
550
567
 
551
568
  # Local dry run is supported on only create and apply
@@ -555,7 +572,7 @@ module Krane
555
572
  verb = deploy_method == :apply ? "apply" : "create"
556
573
  command = [verb, "-f", file_path, "--dry-run", "--output=name"]
557
574
  kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
558
- retry_whitelist: [:client_timeout, :empty], attempts: 3, use_namespace: !global?)
575
+ retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3, use_namespace: !global?)
559
576
  end
560
577
 
561
578
  def labels
@@ -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
@@ -95,11 +95,10 @@ module Krane
95
95
  applyables, individuals = resources.partition { |r| r.deploy_method == :apply }
96
96
  # Prunable resources should also applied so that they can be pruned
97
97
  pruneable_types = @prune_whitelist.map { |t| t.split("/").last }
98
- applyables += individuals.select { |r| pruneable_types.include?(r.type) }
98
+ applyables += individuals.select { |r| pruneable_types.include?(r.type) && !r.deploy_method_override }
99
99
 
100
100
  individuals.each do |individual_resource|
101
101
  individual_resource.deploy_started_at = Time.now.utc
102
-
103
102
  case individual_resource.deploy_method
104
103
  when :create
105
104
  err, status = create_resource(individual_resource)
@@ -153,7 +152,7 @@ module Krane
153
152
  output_is_sensitive = resources.any?(&:sensitive_template_content?)
154
153
  global_mode = resources.all?(&:global?)
155
154
  out, err, st = kubectl.run(*command, log_failure: false, output_is_sensitive: output_is_sensitive,
156
- use_namespace: !global_mode)
155
+ attempts: 2, use_namespace: !global_mode)
157
156
 
158
157
  if st.success?
159
158
  log_pruning(out) if prune
@@ -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
@@ -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.0"
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.0
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: 2020-10-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -366,14 +366,14 @@ 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
378
  name: codecov
379
379
  requirement: !ruby/object:Gem::Requirement