kubernetes-deploy 0.26.4 → 0.26.5

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: a537ded5641b9cb4b9813594e6da9d7ea06876721b26ce4785e48f0b933536cf
4
- data.tar.gz: 8ee185bc26198298d5ed8c18aba4736a67bc46bd2526a72d71b74c988ac9e657
3
+ metadata.gz: c11bffff21178fb845d50aff17aa56f3044cb95b235ff6db2463c94def2d2589
4
+ data.tar.gz: 54ee8d3c14e3d7ca013d6e9a822aa0772dde1b4738bcce67efb3f96c490ddb08
5
5
  SHA512:
6
- metadata.gz: 33f35406225136f90fa99219f5545d9052e2d323055f87d0d42c81640549cda6f18c0748d661ae4ba4c4ab7a1bcb0e7c717e6c8081ded664849befd2bf571d3c
7
- data.tar.gz: 3d3e78256ad83ea456f9a92106c81b03c0212fc3d0940cdfbb990a45214f7250af1110c4f3231dde8b828ab4113b68c3ce5dfc28d675a001d19eea686b2bb9ca
6
+ metadata.gz: e79311a4c0c7af04db41ee384d5ed1da79afdb1f2676c02595ced432ddc6da38dc08179bc5791bbde34e6e0e8da01bd2ca102e0e4ed5af275ee0542b55a68ea8
7
+ data.tar.gz: b4db7e51c731bb1bac8d4eeea6bd0df469fe17cf82ee3fc171211d56fad146f3a60321f6a38d93ecb081e27ca723205fd59e5b475dec130ba695205cd5a31dbf
@@ -1,4 +1,11 @@
1
1
  steps:
2
+ - name: 'Run Test Suite (:kubernetes: 1.14-latest)'
3
+ command: bin/ci
4
+ agents:
5
+ queue: k8s-ci
6
+ env:
7
+ LOGGING_LEVEL: 4
8
+ KUBERNETES_VERSION: v1.14-latest
2
9
  - name: 'Run Test Suite (:kubernetes: 1.13-latest)'
3
10
  command: bin/ci
4
11
  agents:
@@ -0,0 +1,2 @@
1
+ enabled:
2
+ - cla
@@ -0,0 +1 @@
1
+ v1
@@ -0,0 +1,46 @@
1
+ containers:
2
+ default:
3
+ docker: circleci/ruby:2.5.1-node-browsers
4
+
5
+ steps:
6
+ - label: Lint
7
+ timeout: 5m
8
+ run:
9
+ - bundle exec rubocop
10
+ dependencies:
11
+ - bundler
12
+ - label: 'Run Test Suite (:kubernetes: 1.14-latest)'
13
+ command: bin/ci
14
+ agents:
15
+ queue: k8s-ci
16
+ env:
17
+ LOGGING_LEVEL: "4"
18
+ KUBERNETES_VERSION: v1.14-latest
19
+ - label: 'Run Test Suite (:kubernetes: 1.13-latest)'
20
+ command: bin/ci
21
+ agents:
22
+ queue: k8s-ci
23
+ env:
24
+ LOGGING_LEVEL: "4"
25
+ KUBERNETES_VERSION: v1.13-latest
26
+ - label: 'Run Test Suite (:kubernetes: 1.12-latest)'
27
+ command: bin/ci
28
+ agents:
29
+ queue: k8s-ci
30
+ env:
31
+ LOGGING_LEVEL: "4"
32
+ KUBERNETES_VERSION: v1.12-latest
33
+ - label: 'Run Test Suite (:kubernetes: 1.11-latest)'
34
+ command: bin/ci
35
+ agents:
36
+ queue: k8s-ci
37
+ env:
38
+ LOGGING_LEVEL: "4"
39
+ KUBERNETES_VERSION: v1.11-latest
40
+ - label: 'Run Test Suite (:kubernetes: 1.10-latest)'
41
+ command: bin/ci
42
+ agents:
43
+ queue: minikube-ci
44
+ env:
45
+ LOGGING_LEVEL: "4"
46
+ KUBERNETES_VERSION: v1.10-latest
@@ -1,5 +1,18 @@
1
1
  ## next
2
2
 
3
+ ## 0.26.5
4
+
5
+ *Bug Fixes*
6
+ - Support 'volumeBindingMode: WaitForFirstConsumer' condition in StorageClass. [#479](https://github.com/Shopify/kubernetes-deploy/pull/479)
7
+ - Fix: Undefined method "merge" on LabelSelector. [#488](https://github.com/Shopify/kubernetes-deploy/pull/488)
8
+
9
+ *Enhancements*
10
+ - Officially support Kubernetes 1.14. [#461](https://github.com/Shopify/kubernetes-deploy/pull/461)
11
+ - Allow customising which custom resources are deployed in the pre-deploy phase. [#505](https://github.com/Shopify/kubernetes-deploy/pull/505)
12
+
13
+ *Other*
14
+ - Removes special treatment of GCP authentication by upgrading to `kubeclient` 4.3. [#465](https://github.com/Shopify/kubernetes-deploy/pull/465)
15
+
3
16
  ## 0.26.4
4
17
 
5
18
  *Bug fixes*
@@ -13,6 +13,7 @@ The following is a set of guidelines for contributing to kubernetes-deploy. Plea
13
13
  * [High-level design decisions](#high-level-design-decisions)
14
14
  * [Feature acceptance policies](#feature-acceptance-policies)
15
15
  * [Adding a new resource type](#contributing-a-new-resource-type)
16
+ * [Contributor License Agreement](#contributor-license-agreement)
16
17
 
17
18
  [Development](#development)
18
19
  * [Setup](#setup)
@@ -105,6 +106,11 @@ This gem uses subclasses of `KubernetesResource` to implement custom success/fai
105
106
  6. Add a basic example of the type to the hello-cloud [fixture set](https://github.com/Shopify/kubernetes-deploy/tree/master/test/fixtures/hello-cloud) and appropriate assertions to `#assert_all_up` in [`hello_cloud.rb`](https://github.com/Shopify/kubernetes-deploy/blob/master/test/helpers/fixture_sets/hello_cloud.rb). This will get you coverage in several existing tests, such as `test_full_hello_cloud_set_deploy_succeeds`.
106
107
  7. Add tests for any edge cases you foresee.
107
108
 
109
+ ### Contributor License Agreement
110
+
111
+ New contributors will be required to sign [Shopify's Contributor License Agreement (CLA)](https://cla.shopify.com/).
112
+ There are two versions of the CLA: one for individuals and one for organizations.
113
+
108
114
  # Development
109
115
 
110
116
  ## Setup
@@ -157,6 +163,6 @@ Please make sure you run the tests locally before submitting your PR (see [Runni
157
163
 
158
164
  #### Employees: Triggering CI for a contributed PR
159
165
 
160
- Go to the [kubernetes-deploy-gem pipeline](https://buildkite.com/shopify/kubernetes-deploy-gem) and click "New Build". Use branch `external_contrib_ci` and the specific sha of the commit you want to build. Add `BUILDKITE_REFSPEC="refs/pull/${PR_NUM}/head"` in the Environment Variables section. Since CI is only visible to Shopify employees, you will need to provide any failing tests and output to the the contributor.
166
+ Go to the [kubernetes-deploy pipeline](https://buildkite.com/shopify/kubernetes-deploy) and click "New Build". Use branch `external_contrib_ci` and the specific sha of the commit you want to build. Add `BUILDKITE_REFSPEC="refs/pull/${PR_NUM}/head"` in the Environment Variables section. Since CI is only visible to Shopify employees, you will need to provide any failing tests and output to the the contributor.
161
167
 
162
168
  <img width="350" alt="build external contrib PR" src="https://screenshot.click/2017-11-07--163728_7ovek-wrpwq.png">
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # kubernetes-deploy [![Build status](https://badge.buildkite.com/d1aab6d17b010f418e43f740063fe5343c5d65df654e635a8b.svg?branch=master)](https://buildkite.com/shopify/kubernetes-deploy-gem) [![codecov](https://codecov.io/gh/Shopify/kubernetes-deploy/branch/master/graph/badge.svg)](https://codecov.io/gh/Shopify/kubernetes-deploy)
1
+ # kubernetes-deploy [![Build status](https://badge.buildkite.com/61937e40a1fc69754d9d198be120543d6de310de2ba8d3cb0e.svg?branch=master)](https://buildkite.com/shopify/kubernetes-deploy) [![codecov](https://codecov.io/gh/Shopify/kubernetes-deploy/branch/master/graph/badge.svg)](https://codecov.io/gh/Shopify/kubernetes-deploy)
2
2
 
3
3
  `kubernetes-deploy` is a command line tool that helps you ship changes to a Kubernetes namespace and understand the result. At Shopify, we use it within our much-beloved, open-source [Shipit](https://github.com/Shopify/shipit-engine#kubernetes) deployment app.
4
4
 
@@ -72,7 +72,7 @@ This repo also includes related tools for [running tasks](#kubernetes-run) and [
72
72
  * Each app must have a deploy directory containing its Kubernetes templates (see [Templates](#using-templates-and-variables))
73
73
 
74
74
  <sup>1</sup> We run integration tests against these Kubernetes versions. You can find our
75
- offical compatibility chart below.
75
+ official compatibility chart below.
76
76
 
77
77
  | Kubernetes version | Last officially supported in gem version |
78
78
  | :----------------: | :-------------------: |
@@ -80,6 +80,7 @@ offical compatibility chart below.
80
80
  | 1.6 | 0.15.2 |
81
81
  | 1.7 | 0.20.6 |
82
82
  | 1.8 | 0.21.1 |
83
+ | 1.9 | 0.24.0 |
83
84
 
84
85
  ## Installation
85
86
 
@@ -244,6 +245,11 @@ before the deployment is considered successful.
244
245
  - _Compatibility_: Custom Resource Definition
245
246
  - `true`: The custom resource will be pruned if the resource is not in the deploy directory.
246
247
  - All other values: The custom resource will not be pruned.
248
+ - `kubernetes-deploy.shopify.io/predeployed`: Causes a Custom Resource to be deployed in the pre-deploy phase.
249
+ - _Compatibility_: Custom Resource Definition
250
+ - _Default_: `true`
251
+ - `true`: The custom resource will be deployed in the pre-deploy phase.
252
+ - All other values: The custom resource will be deployed in the main deployment phase.
247
253
 
248
254
  ### Running tasks at the beginning of a deploy
249
255
 
@@ -500,6 +506,12 @@ To render some templates in a template dir, run kubernetes-render with the names
500
506
  kubernetes-render --template-dir=./path/to/template/dir this-template.yaml.erb that-template.yaml.erb
501
507
  ```
502
508
 
509
+ To render a template in a template dir and output it to a file, run kubernetes-render with the name of the template and redirect the output to a file:
510
+
511
+ ```
512
+ kubernetes-render --template-dir=./path/to/template/dir template.yaml.erb > template.yaml
513
+ ```
514
+
503
515
  *Options:*
504
516
 
505
517
  - `--template-dir=DIR`: Used to set the directory to interpret template names relative to. This is often the same directory passed as `--template-dir` when running `kubernetes-deploy` to actually deploy templates. Set `$ENVIRONMENT` instead to use `config/deploy/$ENVIRONMENT`. This flag also supports reading from STDIN. You can do this by using `--template-dir=-`.
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = '>= 2.3.0'
26
26
  spec.add_dependency("activesupport", ">= 5.0")
27
- spec.add_dependency("kubeclient", "~> 4.0")
27
+ spec.add_dependency("kubeclient", "~> 4.3")
28
28
  spec.add_dependency("googleauth", "~> 0.6.6") # https://github.com/google/google-auth-library-ruby/issues/153
29
29
  spec.add_dependency("ejson", "~> 1.0")
30
30
  spec.add_dependency("colorize", "~> 0.8")
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/hash/reverse_merge'
4
5
  require 'active_support/core_ext/hash/slice'
5
6
  require 'active_support/core_ext/numeric/time'
6
7
  require 'active_support/core_ext/string/inflections'
@@ -41,7 +41,7 @@ module KubernetesDeploy
41
41
  when '.json'
42
42
  bindings = parse_json(File.read(file_path))
43
43
  when '.yaml', '.yml'
44
- bindings = YAML.load(File.read(file_path))
44
+ bindings = YAML.safe_load(File.read(file_path))
45
45
  else
46
46
  raise ArgumentError, "Supplied file does not appear to be JSON or YAML"
47
47
  end
@@ -75,7 +75,7 @@ module KubernetesDeploy
75
75
  Pod
76
76
  )
77
77
 
78
- before_crs + cluster_resource_discoverer.crds.map(&:kind) + after_crs
78
+ before_crs + cluster_resource_discoverer.crds.select(&:predeployed?).map(&:kind) + after_crs
79
79
  end
80
80
 
81
81
  def prune_whitelist
@@ -552,15 +552,20 @@ module KubernetesDeploy
552
552
  end
553
553
 
554
554
  def confirm_namespace_exists
555
- st, err = nil
556
- with_retries(2) do
557
- _, err, st = kubectl.run("get", "namespace", @namespace, use_namespace: false, log_failure: true)
558
- st.success? || err.include?(KubernetesDeploy::Kubectl::NOT_FOUND_ERROR_TEXT)
559
- end
560
- raise FatalDeploymentError, "Failed to find namespace. #{err}" unless st.success?
555
+ raise FatalDeploymentError, "Namespace #{@namespace} not found" unless namespace_definition.present?
561
556
  @logger.info("Namespace #{@namespace} found")
562
557
  end
563
558
 
559
+ def namespace_definition
560
+ @namespace_definition ||= begin
561
+ definition, _err, st = kubectl.run("get", "namespace", @namespace, use_namespace: false,
562
+ log_failure: true, raise_if_not_found: true, attempts: 3, output: 'json')
563
+ st.success? ? JSON.parse(definition, symbolize_names: true) : nil
564
+ end
565
+ rescue Kubectl::ResourceNotFoundError
566
+ nil
567
+ end
568
+
564
569
  # make sure to never prune the ejson-keys secret
565
570
  def confirm_ejson_keys_not_prunable
566
571
  secret = ejson_provisioner.ejson_keys_secret
@@ -574,14 +579,8 @@ module KubernetesDeploy
574
579
  end
575
580
 
576
581
  def tags_from_namespace_labels
577
- namespace_info = nil
578
- with_retries(2) do
579
- namespace_info, _, st = kubectl.run("get", "namespace", @namespace, "-o", "json", use_namespace: false,
580
- log_failure: true)
581
- st.success?
582
- end
583
- return [] if namespace_info.blank?
584
- namespace_labels = JSON.parse(namespace_info, symbolize_names: true).fetch(:metadata, {}).fetch(:labels, {})
582
+ return [] if namespace_definition.blank?
583
+ namespace_labels = namespace_definition.fetch(:metadata, {}).fetch(:labels, {})
585
584
  namespace_labels.map { |key, value| "#{key}:#{value}" }
586
585
  end
587
586
 
@@ -63,7 +63,8 @@ module KubernetesDeploy
63
63
  secrets.map do |secret_name, secret_spec|
64
64
  validate_secret_spec(secret_name, secret_spec)
65
65
  resource = generate_secret_resource(secret_name, secret_spec["_type"], secret_spec["data"])
66
- unless resource.validate_definition(@kubectl)
66
+ resource.validate_definition(@kubectl)
67
+ if resource.validation_failed?
67
68
  raise EjsonSecretError, "Resulting resource Secret/#{secret_name} failed validation"
68
69
  end
69
70
  resource
@@ -105,7 +106,7 @@ module KubernetesDeploy
105
106
  end
106
107
 
107
108
  labels = { "name" => secret_name }
108
- labels.reverse_merge!(@selector) if @selector
109
+ labels.reverse_merge!(@selector.to_h) if @selector
109
110
 
110
111
  secret = {
111
112
  'kind' => 'Secret',
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require 'kubeclient'
3
- require 'kubernetes-deploy/kubeclient_builder/kube_config'
4
3
 
5
4
  module KubernetesDeploy
6
5
  class KubeclientBuilder
@@ -96,6 +95,14 @@ module KubernetesDeploy
96
95
  )
97
96
  end
98
97
 
98
+ def build_storage_v1_kubeclient(context)
99
+ build_kubeclient(
100
+ api_version: "v1",
101
+ context: context,
102
+ endpoint_path: "/apis/storage.k8s.io"
103
+ )
104
+ end
105
+
99
106
  def validate_config_files
100
107
  errors = []
101
108
  if @kubeconfig_files.empty?
@@ -118,7 +125,7 @@ module KubernetesDeploy
118
125
 
119
126
  def build_kubeclient(api_version:, context:, endpoint_path: nil)
120
127
  validate_config_files!
121
- @kubeclient_configs ||= @kubeconfig_files.map { |f| KubeConfig.read(f) }
128
+ @kubeclient_configs ||= @kubeconfig_files.map { |f| Kubeclient::Config.read(f) }
122
129
  # Find a context defined in kube conf files that matches the input context by name
123
130
  config = @kubeclient_configs.find { |c| c.contexts.include?(context) }
124
131
  raise ContextMissingError.new(context, @kubeconfig_files) unless config
@@ -2,8 +2,12 @@
2
2
 
3
3
  module KubernetesDeploy
4
4
  class Kubectl
5
+ ERROR_MATCHERS = {
6
+ not_found: /NotFound/,
7
+ client_timeout: /Client\.Timeout exceeded while awaiting headers/,
8
+ }
5
9
  DEFAULT_TIMEOUT = 15
6
- NOT_FOUND_ERROR_TEXT = 'NotFound'
10
+ MAX_RETRY_DELAY = 16
7
11
 
8
12
  class ResourceNotFoundError < StandardError; end
9
13
 
@@ -21,43 +25,45 @@ module KubernetesDeploy
21
25
  end
22
26
 
23
27
  def run(*args, log_failure: nil, use_context: true, use_namespace: true, output: nil,
24
- raise_if_not_found: false, attempts: 1, output_is_sensitive: nil)
28
+ raise_if_not_found: false, attempts: 1, output_is_sensitive: nil, retry_whitelist: nil)
25
29
  log_failure = @log_failure_by_default if log_failure.nil?
26
30
  output_is_sensitive = @output_is_sensitive_default if output_is_sensitive.nil?
27
-
28
- args = args.unshift("kubectl")
29
- args.push("--namespace=#{@namespace}") if use_namespace
30
- args.push("--context=#{@context}") if use_context
31
- args.push("--output=#{output}") if output
32
- args.push("--request-timeout=#{@default_timeout}") if @default_timeout
31
+ cmd = build_command_from_options(args, use_namespace, use_context, output)
33
32
  out, err, st = nil
34
33
 
35
- (1..attempts).to_a.each do |attempt|
36
- @logger.debug("Running command (attempt #{attempt}): #{args.join(' ')}")
37
- out, err, st = Open3.capture3(*args)
34
+ (1..attempts).to_a.each do |current_attempt|
35
+ @logger.debug("Running command (attempt #{current_attempt}): #{cmd.join(' ')}")
36
+ out, err, st = Open3.capture3(*cmd)
38
37
  @logger.debug("Kubectl out: " + out.gsub(/\s+/, ' ')) unless output_is_sensitive
39
38
 
40
39
  break if st.success?
40
+ raise(ResourceNotFoundError, err) if err.match(ERROR_MATCHERS[:not_found]) && raise_if_not_found
41
41
 
42
42
  if log_failure
43
- @logger.warn("The following command failed (attempt #{attempt}/#{attempts}): #{Shellwords.join(args)}")
43
+ warning = if current_attempt == attempts
44
+ "The following command failed (attempt #{current_attempt}/#{attempts})"
45
+ elsif retriable_err?(err, retry_whitelist)
46
+ "The following command failed and will be retried (attempt #{current_attempt}/#{attempts})"
47
+ else
48
+ "The following command failed and cannot be retried"
49
+ end
50
+ @logger.warn("#{warning}: #{Shellwords.join(cmd)}")
44
51
  @logger.warn(err) unless output_is_sensitive
45
- end
46
-
47
- if err.match(NOT_FOUND_ERROR_TEXT)
48
- raise(ResourceNotFoundError, err) if raise_if_not_found
49
52
  else
50
- @logger.debug("Kubectl err: #{err}") unless output_is_sensitive
51
- StatsD.increment('kubectl.error', 1, tags: { context: @context, namespace: @namespace, cmd: args[1] })
53
+ @logger.debug("Kubectl err: #{output_is_sensitive ? '<suppressed sensitive output>' : err}")
52
54
  end
53
- sleep(retry_delay(attempt)) unless attempt == attempts
55
+ StatsD.increment('kubectl.error', 1, tags: { context: @context, namespace: @namespace, cmd: cmd[1] })
56
+
57
+ break unless retriable_err?(err, retry_whitelist) && current_attempt < attempts
58
+ sleep(retry_delay(current_attempt))
54
59
  end
55
60
 
56
61
  [out.chomp, err.chomp, st]
57
62
  end
58
63
 
59
64
  def retry_delay(attempt)
60
- attempt
65
+ # exponential backoff starting at 1s with cap at 16s, offset by up to 0.5s
66
+ [2**(attempt - 1), MAX_RETRY_DELAY].min - Random.rand(0.5).round(1)
61
67
  end
62
68
 
63
69
  def version_info
@@ -79,6 +85,23 @@ module KubernetesDeploy
79
85
 
80
86
  private
81
87
 
88
+ def build_command_from_options(args, use_namespace, use_context, output)
89
+ cmd = ["kubectl"] + args
90
+ cmd.push("--namespace=#{@namespace}") if use_namespace
91
+ cmd.push("--context=#{@context}") if use_context
92
+ cmd.push("--output=#{output}") if output
93
+ cmd.push("--request-timeout=#{@default_timeout}") if @default_timeout
94
+ cmd
95
+ end
96
+
97
+ def retriable_err?(err, retry_whitelist)
98
+ return !err.match(ERROR_MATCHERS[:not_found]) if retry_whitelist.nil?
99
+ retry_whitelist.any? do |retriable|
100
+ raise NotImplementedError, "No matcher defined for #{retriable.inspect}" unless ERROR_MATCHERS.key?(retriable)
101
+ err.match(ERROR_MATCHERS[retriable])
102
+ end
103
+ end
104
+
82
105
  def extract_version_info_from_kubectl_response(response)
83
106
  info = {}
84
107
  response.each_line do |l|
@@ -122,18 +122,8 @@ module KubernetesDeploy
122
122
  @validation_errors = []
123
123
  validate_selector(selector) if selector
124
124
  validate_timeout_annotation
125
-
126
- command = ["create", "-f", file_path, "--dry-run", "--output=name"]
127
- _, err, st = kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?)
128
- return true if st.success?
129
- if sensitive_template_content?
130
- @validation_errors << <<-EOS
131
- Validation for #{id} failed. Detailed information is unavailable as the raw error may contain sensitive data.
132
- EOS
133
- else
134
- @validation_errors << err
135
- end
136
- false
125
+ validate_spec_with_kubectl(kubectl)
126
+ @validation_errors.present?
137
127
  end
138
128
 
139
129
  def validation_error_msg
@@ -433,6 +423,19 @@ module KubernetesDeploy
433
423
  end
434
424
  end
435
425
 
426
+ def validate_spec_with_kubectl(kubectl)
427
+ command = ["create", "-f", file_path, "--dry-run", "--output=name"]
428
+ _, err, st = kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
429
+ retry_whitelist: [:client_timeout], attempts: 3)
430
+
431
+ return true if st.success?
432
+ @validation_errors << if sensitive_template_content?
433
+ "Validation for #{id} failed. Detailed information is unavailable as the raw error may contain sensitive data."
434
+ else
435
+ err
436
+ end
437
+ end
438
+
436
439
  def labels
437
440
  @definition.dig("metadata", "labels")
438
441
  end
@@ -56,6 +56,11 @@ module KubernetesDeploy
56
56
  prunable == "true"
57
57
  end
58
58
 
59
+ def predeployed?
60
+ predeployed = @definition.dig("metadata", "annotations", "kubernetes-deploy.shopify.io/predeployed")
61
+ predeployed.nil? || predeployed == "true"
62
+ end
63
+
59
64
  def rollout_conditions
60
65
  return @rollout_conditions if defined?(@rollout_conditions)
61
66
 
@@ -3,16 +3,78 @@ module KubernetesDeploy
3
3
  class PersistentVolumeClaim < KubernetesResource
4
4
  TIMEOUT = 5.minutes
5
5
 
6
+ def sync(cache)
7
+ super
8
+ @storage_classes = cache.get_all("StorageClass").map { |sc| StorageClass.new(sc) }
9
+ end
10
+
6
11
  def status
7
12
  exists? ? @instance_data["status"]["phase"] : "Not Found"
8
13
  end
9
14
 
10
15
  def deploy_succeeded?
11
- status == "Bound"
16
+ return true if status == "Bound"
17
+
18
+ # if the StorageClass has volumeBindingMode: WaitForFirstConsumer,
19
+ # it won't bind until after a Pod mounts it. But it must be pre-deployed,
20
+ # as the Pod requires it. So 'Pending' must be treated as a 'Success' state
21
+ if storage_class&.volume_binding_mode == "WaitForFirstConsumer"
22
+ return status == "Pending" || status == "Bound"
23
+ end
24
+ false
12
25
  end
13
26
 
14
27
  def deploy_failed?
15
- status == "Lost"
28
+ status == "Lost" || failure_message.present?
29
+ end
30
+
31
+ def failure_message
32
+ if storage_class_name.nil? && @storage_classes.count(&:default?) > 1
33
+ "PVC has no StorageClass specified and there are multiple StorageClasses " \
34
+ "annotated as default. This is an invalid cluster configuration."
35
+ end
36
+ end
37
+
38
+ def timeout_message
39
+ return STANDARD_TIMEOUT_MESSAGE unless storage_class_name.present? && !storage_class
40
+ "PVC specified a StorageClass of #{storage_class_name} but the resource does not exist"
41
+ end
42
+
43
+ private
44
+
45
+ def storage_class_name
46
+ @definition.dig("spec", "storageClassName")
47
+ end
48
+
49
+ def storage_class
50
+ if storage_class_name.present?
51
+ @storage_classes.detect { |sc| sc.name == storage_class_name }
52
+ # storage_class_name = "" is an explicit request for no storage class
53
+ # storage_class_name = nil is an impplicit request for default storage class
54
+ elsif storage_class_name != ""
55
+ @storage_classes.detect(&:default?)
56
+ end
57
+ end
58
+
59
+ class StorageClass < KubernetesResource
60
+ DEFAULT_CLASS_ANNOTATION = "storageclass.kubernetes.io/is-default-class"
61
+ DEFAULT_CLASS_BETA_ANNOTATION = "storageclass.beta.kubernetes.io/is-default-class"
62
+
63
+ attr_reader :name
64
+
65
+ def initialize(definition)
66
+ @definition = definition
67
+ @name = definition.dig("metadata", "name").to_s
68
+ end
69
+
70
+ def volume_binding_mode
71
+ @definition.dig("volumeBindingMode")
72
+ end
73
+
74
+ def default?
75
+ @definition.dig("metadata", "annotations", DEFAULT_CLASS_ANNOTATION) == "true" ||
76
+ @definition.dig("metadata", "annotations", DEFAULT_CLASS_BETA_ANNOTATION) == "true"
77
+ end
16
78
  end
17
79
  end
18
80
  end
@@ -65,10 +65,15 @@ module KubernetesDeploy
65
65
  end
66
66
 
67
67
  def timeout_message
68
- return STANDARD_TIMEOUT_MESSAGE unless readiness_probe_failure?
69
- probe_failure_msgs = @containers.map(&:readiness_fail_reason).compact
70
- header = "The following containers have not passed their readiness probes on at least one pod:\n"
71
- header + probe_failure_msgs.join("\n")
68
+ if readiness_probe_failure?
69
+ probe_failure_msgs = @containers.map(&:readiness_fail_reason).compact
70
+ header = "The following containers have not passed their readiness probes on at least one pod:\n"
71
+ header + probe_failure_msgs.join("\n")
72
+ elsif failed_schedule_reason.present?
73
+ "Pod could not be scheduled because #{failed_schedule_reason}"
74
+ else
75
+ STANDARD_TIMEOUT_MESSAGE
76
+ end
72
77
  end
73
78
 
74
79
  def failure_message
@@ -98,6 +103,16 @@ module KubernetesDeploy
98
103
 
99
104
  private
100
105
 
106
+ def failed_schedule_reason
107
+ if phase == "Pending"
108
+ conditions = @instance_data.dig('status', 'conditions') || []
109
+ unschedulable = conditions.find do |condition|
110
+ condition["type"] == "PodScheduled" && condition["status"] == "False"
111
+ end
112
+ unschedulable&.dig('message')
113
+ end
114
+ end
115
+
101
116
  def failed_phase?
102
117
  phase == FAILED_PHASE_NAME
103
118
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.26.4"
3
+ VERSION = "0.26.5"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubernetes-deploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.26.4
4
+ version: 0.26.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katrina Verey
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-04-29 00:00:00.000000000 Z
12
+ date: 2019-07-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '4.0'
34
+ version: '4.3'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '4.0'
41
+ version: '4.3'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: googleauth
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -239,9 +239,11 @@ extensions: []
239
239
  extra_rdoc_files: []
240
240
  files:
241
241
  - ".buildkite/pipeline.nightly.yml"
242
- - ".buildkite/pipeline.yml"
242
+ - ".github/probots.yml"
243
243
  - ".gitignore"
244
244
  - ".rubocop.yml"
245
+ - ".shopify-build/VERSION"
246
+ - ".shopify-build/kubernetes-deploy.yml"
245
247
  - CHANGELOG.md
246
248
  - CODE_OF_CONDUCT.md
247
249
  - CONTRIBUTING.md
@@ -273,7 +275,6 @@ files:
273
275
  - lib/kubernetes-deploy/errors.rb
274
276
  - lib/kubernetes-deploy/formatted_logger.rb
275
277
  - lib/kubernetes-deploy/kubeclient_builder.rb
276
- - lib/kubernetes-deploy/kubeclient_builder/kube_config.rb
277
278
  - lib/kubernetes-deploy/kubectl.rb
278
279
  - lib/kubernetes-deploy/kubernetes_resource.rb
279
280
  - lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb
@@ -1,29 +0,0 @@
1
- steps:
2
- - name: 'Run Test Suite (:kubernetes: 1.13-latest)'
3
- command: bin/ci
4
- agents:
5
- queue: k8s-ci
6
- env:
7
- LOGGING_LEVEL: 4
8
- KUBERNETES_VERSION: v1.13-latest
9
- - name: 'Run Test Suite (:kubernetes: 1.12-latest)'
10
- command: bin/ci
11
- agents:
12
- queue: k8s-ci
13
- env:
14
- LOGGING_LEVEL: 4
15
- KUBERNETES_VERSION: v1.12-latest
16
- - name: 'Run Test Suite (:kubernetes: 1.11-latest)'
17
- command: bin/ci
18
- agents:
19
- queue: k8s-ci
20
- env:
21
- LOGGING_LEVEL: 4
22
- KUBERNETES_VERSION: v1.11-latest
23
- - name: 'Run Test Suite (:kubernetes: 1.10-latest)'
24
- command: bin/ci
25
- agents:
26
- queue: minikube-ci
27
- env:
28
- LOGGING_LEVEL: 4
29
- KUBERNETES_VERSION: v1.10-latest
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'googleauth'
4
- module KubernetesDeploy
5
- class KubeclientBuilder
6
- class KubeConfig < Kubeclient::Config
7
- def self.read(filename)
8
- parsed = YAML.safe_load(File.read(filename), [Date, Time])
9
- new(parsed, File.dirname(filename))
10
- end
11
-
12
- def fetch_user_auth_options(user)
13
- if user.dig('auth-provider', 'name') == 'gcp'
14
- { bearer_token: Kubeclient::GoogleApplicationDefaultCredentials.token }
15
- else
16
- super
17
- end
18
- end
19
- end
20
- end
21
- end