kubernetes-deploy 0.20.6 → 0.21.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
  SHA1:
3
- metadata.gz: 5f4739131c95e48be2df66d1ca820fc63fa92a0f
4
- data.tar.gz: ef6afd733102c00d3ac9b42571da84a1edde0970
3
+ metadata.gz: 1155a58879313f8d0466b9d551a5ba9fb4a0ccab
4
+ data.tar.gz: a5856d1be46d1aef9a6393e4b5f95715e5832284
5
5
  SHA512:
6
- metadata.gz: a025aaf9b1e4cb4689e8ad0e4101502f460146f5dbe36ed1da096a5b53679e24cf75dbb5f84cbe1ec0b739988a0eafebe20237c69a51614d130b551b8456fbfa
7
- data.tar.gz: 4364997f2d82b1b80530ba1c5c52797dd009e5a6ab882570df0937479200cd17099fb8ac9d99527bc98dd0dfa4b8859ca6a9bd4e075d25d2fe055c32ea0fd40c
6
+ metadata.gz: 9eecadcbaa7709aae55ba3baac6e0afaf120277df59c1dae1a407bd49f6f07755757448de776b7f33ed30298cacad081276ba2afcee4927d2b56ae899ea9a196
7
+ data.tar.gz: 75cc0f78449088f47d621da287477c77a01ef33a0d1460fc5739676fee82d5a6652542412c483cd2ad80c708ed72a8350573a57515e848100b5c27cb6b9a2129
@@ -1,5 +1,19 @@
1
+ shared: &shared
2
+ retry:
3
+ automatic:
4
+ - exit_status: "*"
5
+ limit: 1
1
6
  steps:
7
+ - name: 'Run Test Suite (:kubernetes: 1.11-latest)'
8
+ <<: *shared
9
+ command: bin/ci
10
+ agents:
11
+ queue: k8s-ci
12
+ env:
13
+ LOGGING_LEVEL: 4
14
+ KUBERNETES_VERSION: v1.11-latest
2
15
  - name: 'Run Test Suite (:kubernetes: 1.10-latest)'
16
+ <<: *shared
3
17
  command: bin/ci
4
18
  agents:
5
19
  queue: minikube-ci
@@ -7,6 +21,7 @@ steps:
7
21
  LOGGING_LEVEL: 4
8
22
  KUBERNETES_VERSION: v1.10-latest
9
23
  - name: 'Run Test Suite (:kubernetes: 1.9-latest)'
24
+ <<: *shared
10
25
  command: bin/ci
11
26
  agents:
12
27
  queue: minikube-ci
@@ -14,16 +29,10 @@ steps:
14
29
  LOGGING_LEVEL: 4
15
30
  KUBERNETES_VERSION: v1.9-latest
16
31
  - name: 'Run Test Suite (:kubernetes: 1.8-latest)'
32
+ <<: *shared
17
33
  command: bin/ci
18
34
  agents:
19
35
  queue: minikube-ci
20
36
  env:
21
37
  LOGGING_LEVEL: 4
22
38
  KUBERNETES_VERSION: v1.8-latest
23
- - name: 'Run Test Suite (:kubernetes: 1.7-latest)'
24
- command: bin/ci
25
- agents:
26
- queue: minikube-ci
27
- env:
28
- LOGGING_LEVEL: 4
29
- KUBERNETES_VERSION: v1.7-latest
@@ -1,5 +1,19 @@
1
+ shared: &shared
2
+ retry:
3
+ automatic:
4
+ - exit_status: "*"
5
+ limit: 1
1
6
  steps:
7
+ - name: 'Run Test Suite (:kubernetes: 1.11-latest)'
8
+ <<: *shared
9
+ command: bin/ci
10
+ agents:
11
+ queue: k8s-ci
12
+ env:
13
+ LOGGING_LEVEL: 4
14
+ KUBERNETES_VERSION: v1.11-latest
2
15
  - name: 'Run Test Suite (:kubernetes: 1.10-latest)'
16
+ <<: *shared
3
17
  command: bin/ci
4
18
  agents:
5
19
  queue: minikube-ci
@@ -7,6 +21,7 @@ steps:
7
21
  LOGGING_LEVEL: 4
8
22
  KUBERNETES_VERSION: v1.10-latest
9
23
  - name: 'Run Test Suite (:kubernetes: 1.9-latest)'
24
+ <<: *shared
10
25
  command: bin/ci
11
26
  agents:
12
27
  queue: minikube-ci
@@ -14,16 +29,10 @@ steps:
14
29
  LOGGING_LEVEL: 4
15
30
  KUBERNETES_VERSION: v1.9-latest
16
31
  - name: 'Run Test Suite (:kubernetes: 1.8-latest)'
32
+ <<: *shared
17
33
  command: bin/ci
18
34
  agents:
19
35
  queue: minikube-ci
20
36
  env:
21
37
  LOGGING_LEVEL: 4
22
38
  KUBERNETES_VERSION: v1.8-latest
23
- - name: 'Run Test Suite (:kubernetes: 1.7-latest)'
24
- command: bin/ci
25
- agents:
26
- queue: minikube-ci
27
- env:
28
- LOGGING_LEVEL: 4
29
- KUBERNETES_VERSION: v1.7-latest
@@ -1,6 +1,11 @@
1
- ### Master
1
+ ## 0.21.0
2
2
 
3
- *Enhancements*
3
+ *Features*
4
+ - **[Breaking change]** `kubernetes-run` now streams container logs and waits for the pod to succeed or fail **by default**. You can disable this using `--skip-wait`, or you can use `--max-watch-seconds=seconds` to set a time limit on the watch. ([#337](https://github.com/Shopify/kubernetes-deploy/pull/337))
5
+
6
+
7
+ *Other*
8
+ - Kubernetes 1.7 is no longer officially supported as of this version
4
9
 
5
10
  ## 0.20.6
6
11
 
data/README.md CHANGED
@@ -69,19 +69,21 @@ This repo also includes related tools for [running tasks](#kubernetes-run) and [
69
69
  ## Prerequisites
70
70
 
71
71
  * Ruby 2.3+
72
- * Your cluster must be running Kubernetes v1.7.0 or higher<sup>1</sup>
72
+ * Your cluster must be running Kubernetes v1.8.0 or higher<sup>1</sup>
73
73
  * Each app must have a deploy directory containing its Kubernetes templates (see [Templates](#using-templates-and-variables))
74
74
  * You must remove the` kubectl.kubernetes.io/last-applied-configuration` annotation from any resources in the namespace that are not included in your deploy directory. This annotation is added automatically when you create resources with `kubectl apply`. `kubernetes-deploy` will prune any resources that have this annotation and are not in the deploy directory.<sup>2</sup>
75
75
  * Each app managed by `kubernetes-deploy` must have its own exclusive Kubernetes namespace.
76
76
 
77
- <sup>1</sup> We run integration tests against these Kubernetes versions. Kubernetes v1.6 was officially supported in gem versions < 0.16. Kubernetes v1.5 was officially supported in gem versions < 0.12.
77
+ <sup>1</sup> We run integration tests against these Kubernetes versions. Kubernetes v1.7 was officially
78
+ supported in gem versions < 0.21. Kubernetes v1.6 was officially supported in gem versions < 0.16. Kubernetes
79
+ v1.5 was officially supported in gem versions < 0.12.
78
80
 
79
81
  <sup>2</sup> This requirement can be bypassed with the `--no-prune` option, but it is not recommended.
80
82
 
81
83
 
82
84
  ## Installation
83
85
 
84
- 1. [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-via-curl) (requires v1.7.0 or higher) and make sure it is available in your $PATH
86
+ 1. [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-via-curl) (requires v1.8.0 or higher) and make sure it is available in your $PATH
85
87
  2. Set up your [kubeconfig file](https://kubernetes.io/docs/tasks/access-application-cluster/authenticate-across-clusters-kubeconfig/) for access to your cluster(s).
86
88
  3. `gem install kubernetes-deploy`
87
89
 
@@ -352,12 +354,15 @@ Based on this specification `kubernetes-run` will create a new pod with the entr
352
354
 
353
355
  ## Usage
354
356
 
355
- `kubernetes-run <kube namespace> <kube context> <arguments> --entrypoint=/bin/bash`
357
+ `kubernetes-run <kube namespace> <kube context> <arguments> --entrypoint=<entrypoint> --template=<template name>`
356
358
 
357
359
  *Options:*
358
360
 
359
361
  * `--template=TEMPLATE`: Specifies the name of the PodTemplate to use (default is `task-runner-template` if this option is not set).
360
362
  * `--env-vars=ENV_VARS`: Accepts a comma separated list of environment variables to be added to the pod template. For example, `--env-vars="ENV=VAL,ENV2=VAL2"` will make `ENV` and `ENV2` available to the container.
363
+ * `--entrypoint=ENTRYPOINT`: Specify the entrypoint to use to start the task runner container.
364
+ * `--skip-wait`: Skip verification of pod success
365
+ * `--max-watch-seconds=seconds`: Raise a timeout error if the pod runs for longer than the specified number of seconds
361
366
 
362
367
 
363
368
 
@@ -369,7 +374,7 @@ Based on this specification `kubernetes-run` will create a new pod with the entr
369
374
 
370
375
  If you work for Shopify, just run `dev up`, but otherwise:
371
376
 
372
- 1. [Install kubectl version 1.7.0 or higher](https://kubernetes.io/docs/user-guide/prereqs/) and make sure it is in your path
377
+ 1. [Install kubectl version 1.8.0 or higher](https://kubernetes.io/docs/user-guide/prereqs/) and make sure it is in your path
373
378
  2. [Install minikube](https://kubernetes.io/docs/getting-started-guides/minikube/#installation) (required to run the test suite)
374
379
  3. Check out the repo
375
380
  4. Run `bin/setup` to install dependencies
@@ -423,9 +428,18 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopif
423
428
 
424
429
  Contributions to help us support additional resource types or increase the sophistication of our success heuristics for an existing type are especially encouraged! (See tips below)
425
430
 
431
+ ## Feature acceptance guidelines
426
432
 
433
+ - This project's mission is to make it easy to ship changes to a Kubernetes namespace and understand the result. Features that introduce new classes of responsibility to the tool are not usually accepted.
434
+ - Deploys can be a very tempting place to cram features. Imagine a proposed feature actually fits better elsewhere—where might that be? (Examples: validator in CI, custom controller, initializer, pre-processing step in the CD pipeline, or even Kubernetes core)
435
+ - The basic ERB renderer included with the tool is intended as a convenience feature for a better out-of-the box experience. Providing complex rendering capabilities is out of scope of this project's mission, and enhancements in this area may be rejected.
436
+ - The deploy command does not officially support non-namespaced resource types.
437
+ - This project strives to be composable with other tools in the ecosystem, such as renderers and validators. The deploy command must work with any Kubernetes templates provided to it, no matter how they were generated.
438
+ - This project is open-source. Features tied to any specific organization (including Shopify) will be rejected.
439
+ - The deploy command must remain performant when given several hundred resources at a time, generating 1000+ pods. (Technical note: This means only `sync` methods can make calls to the Kuberentes API server during result verification. This both limits the number of API calls made and ensures a consistent view of the world within each polling cycle.)
440
+ - This tool must be able to run concurrent deploys to different targets safely, including when used as a library.
427
441
 
428
- ### Contributing a new resource type
442
+ ## Contributing a new resource type
429
443
 
430
444
  The list of fully supported types is effectively the list of classes found in `lib/kubernetes-deploy/kubernetes_resource/`.
431
445
 
@@ -440,7 +454,7 @@ This gem uses subclasses of `KubernetesResource` to implement custom success/fai
440
454
  4. Add the 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`.
441
455
  5. Add tests for any edge cases you foresee.
442
456
 
443
- ### Code of Conduct
457
+ ## Code of Conduct
444
458
  Everyone is expected to follow our [Code of Conduct](https://github.com/Shopify/kubernetes-deploy/blob/master/CODE_OF_CONDUCT.md).
445
459
 
446
460
 
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.7.0 or higher:\nhttps://kubernetes.io/docs/user-guide/prereqs/\033[0m"
12
+ echo -e "\n\033[0;33mPlease install kubectl version 1.8.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.7.0 or greater.\033[0m"
15
+ echo -e "\n\033[0;32mKubectl version $KUBECTL_VERSION is already installed. This gem requires version v1.8.0 or greater.\033[0m"
16
16
  fi
data/dev.yml CHANGED
@@ -8,7 +8,7 @@ up:
8
8
  - custom:
9
9
  name: Minikube Cluster
10
10
  met?: test $(minikube status | grep Running | wc -l) -eq 2 && $(minikube status | grep -q 'Correctly Configured')
11
- meet: minikube start --kubernetes-version=v1.10.0 --vm-driver=hyperkit
11
+ meet: minikube start --kubernetes-version=v1.10.6 --vm-driver=hyperkit
12
12
  down: minikube stop
13
13
  commands:
14
14
  reset-minikube: minikube delete && rm -rf ~/.minikube
@@ -8,8 +8,15 @@ require 'optparse'
8
8
  template = "task-runner-template"
9
9
  entrypoint = nil
10
10
  env_vars = []
11
+ verify_result = true
12
+ max_watch_seconds = nil
11
13
 
12
14
  ARGV.options do |opts|
15
+ opts.on("--skip-wait", "Skip verification of pod success") { verify_result = false }
16
+ opts.on("--max-watch-seconds=seconds",
17
+ "Timeout error is raised if the pod runs for longer than the specified number of seconds") do |t|
18
+ max_watch_seconds = t.to_i
19
+ end
13
20
  opts.on("--template=TEMPLATE") { |n| template = n }
14
21
  opts.on("--env-vars=ENV_VARS") { |vars| env_vars = vars.split(",") }
15
22
  opts.on("--entrypoint=ENTRYPOINT") { |c| entrypoint = [c] }
@@ -23,10 +30,12 @@ logger = KubernetesDeploy::FormattedLogger.build(namespace, context)
23
30
  runner = KubernetesDeploy::RunnerTask.new(
24
31
  namespace: namespace,
25
32
  context: context,
26
- logger: logger
33
+ logger: logger,
34
+ max_watch_seconds: max_watch_seconds
27
35
  )
28
36
 
29
37
  success = runner.run(
38
+ verify_result: verify_result,
30
39
  task_template: template,
31
40
  entrypoint: entrypoint,
32
41
  args: ARGV[2..-1],
@@ -22,6 +22,6 @@ require 'kubernetes-deploy/duration_parser'
22
22
  require 'kubernetes-deploy/sync_mediator'
23
23
 
24
24
  module KubernetesDeploy
25
- MIN_KUBE_VERSION = '1.7.0'
25
+ MIN_KUBE_VERSION = '1.8.0'
26
26
  KubernetesDeploy::StatsD.build
27
27
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+ module KubernetesDeploy
3
+ class ContainerLogs
4
+ attr_reader :lines, :container_name
5
+
6
+ DEFAULT_LINE_LIMIT = 250
7
+
8
+ def initialize(parent_id:, container_name:, logger:)
9
+ @parent_id = parent_id
10
+ @container_name = container_name
11
+ @logger = logger
12
+ @lines = []
13
+ @next_print_index = 0
14
+ end
15
+
16
+ def sync(kubectl)
17
+ new_logs = fetch_latest(kubectl)
18
+ return unless new_logs.present?
19
+ @lines += deduplicate(new_logs)
20
+ end
21
+
22
+ def empty?
23
+ lines.empty?
24
+ end
25
+
26
+ def print_latest(prefix: false)
27
+ prefix_str = "[#{container_name}] " if prefix
28
+
29
+ lines[@next_print_index..-1].each do |msg|
30
+ @logger.info "#{prefix_str}#{msg}"
31
+ end
32
+
33
+ @next_print_index = lines.length
34
+ end
35
+
36
+ def print_all
37
+ lines.each { |line| @logger.info("\t#{line}") }
38
+ end
39
+
40
+ private
41
+
42
+ def fetch_latest(kubectl)
43
+ cmd = ["logs", @parent_id, "--container=#{container_name}", "--timestamps"]
44
+ cmd << if @last_timestamp.present?
45
+ "--since-time=#{rfc3339_timestamp(@last_timestamp)}"
46
+ else
47
+ "--tail=#{DEFAULT_LINE_LIMIT}"
48
+ end
49
+ out, _err, _st = kubectl.run(*cmd, log_failure: false)
50
+ out.split("\n")
51
+ end
52
+
53
+ def rfc3339_timestamp(time)
54
+ time.strftime("%FT%T.%N%:z")
55
+ end
56
+
57
+ def deduplicate(logs)
58
+ deduped = []
59
+ check_for_duplicate = true
60
+
61
+ logs.each do |line|
62
+ timestamp, msg = split_timestamped_line(line)
63
+ next if check_for_duplicate && likely_duplicate?(timestamp)
64
+ check_for_duplicate = false # logs are ordered, so once we've seen a new one, assume all subsequent logs are new
65
+ @last_timestamp = timestamp if timestamp
66
+ deduped << msg
67
+ end
68
+
69
+ deduped
70
+ end
71
+
72
+ def split_timestamped_line(log_line)
73
+ timestamp, message = log_line.split(" ", 2)
74
+ [Time.parse(timestamp), message]
75
+ rescue ArgumentError
76
+ # If the log file can't be opened, k8s 1.8 writes an error message without a timestamp to stdout
77
+ [nil, log_line]
78
+ end
79
+
80
+ def likely_duplicate?(timestamp)
81
+ return false unless @last_timestamp && timestamp
82
+ # The --since-time granularity the API server supports is not adequate to prevent duplicates
83
+ # This comparison takes the fractional seconds into account
84
+ timestamp <= @last_timestamp
85
+ end
86
+ end
87
+ end
@@ -47,11 +47,15 @@ module KubernetesDeploy
47
47
  level = :fatal
48
48
  end
49
49
 
50
- public_send(level, summary.actions_sentence)
51
- summary.paragraphs.each do |para|
50
+ if actions_sentence = summary.actions_sentence.presence
51
+ public_send(level, actions_sentence)
52
52
  blank_line(level)
53
+ end
54
+
55
+ summary.paragraphs.each do |para|
53
56
  msg_lines = para.split("\n")
54
57
  msg_lines.each { |line| public_send(level, line) }
58
+ blank_line(level) unless para == summary.paragraphs.last
55
59
  end
56
60
  end
57
61
 
@@ -64,11 +68,8 @@ module KubernetesDeploy
64
68
  end
65
69
 
66
70
  def actions_sentence
67
- case @actions_taken.length
68
- when 0 then "No actions taken"
69
- else
70
- @actions_taken.to_sentence.capitalize
71
- end
71
+ return unless @actions_taken.present?
72
+ @actions_taken.to_sentence.capitalize
72
73
  end
73
74
 
74
75
  # Saves a sentence fragment to be displayed in the first sentence of the summary section
@@ -78,7 +78,6 @@ module KubernetesDeploy
78
78
  batch/v1/Job
79
79
  extensions/v1beta1/DaemonSet
80
80
  extensions/v1beta1/Deployment
81
- apps/v1beta1/Deployment
82
81
  extensions/v1beta1/Ingress
83
82
  apps/v1beta1/StatefulSet
84
83
  autoscaling/v1/HorizontalPodAutoscaler
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class FatalDeploymentError < StandardError; end
4
+ class FatalKubeAPIError < FatalDeploymentError; end
4
5
  class KubectlError < StandardError; end
5
6
 
6
7
  class InvalidTemplateError < FatalDeploymentError
@@ -89,7 +89,11 @@ module KubernetesDeploy
89
89
  "#{kube_context.api_endpoint}#{endpoint_path}",
90
90
  api_version,
91
91
  ssl_options: kube_context.ssl_options,
92
- auth_options: kube_context.auth_options
92
+ auth_options: kube_context.auth_options,
93
+ timeouts: {
94
+ open: KubernetesDeploy::Kubectl::DEFAULT_TIMEOUT,
95
+ read: KubernetesDeploy::Kubectl::DEFAULT_TIMEOUT
96
+ }
93
97
  )
94
98
  client.discover
95
99
  client
@@ -2,7 +2,9 @@
2
2
 
3
3
  module KubernetesDeploy
4
4
  class Kubectl
5
- def initialize(namespace:, context:, logger:, log_failure_by_default:, default_timeout: '30s',
5
+ DEFAULT_TIMEOUT = 30
6
+
7
+ def initialize(namespace:, context:, logger:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT,
6
8
  output_is_sensitive: false)
7
9
  @namespace = namespace
8
10
  @context = context
@@ -3,6 +3,8 @@ require 'json'
3
3
  require 'open3'
4
4
  require 'shellwords'
5
5
 
6
+ require 'kubernetes-deploy/remote_logs'
7
+
6
8
  module KubernetesDeploy
7
9
  class KubernetesResource
8
10
  attr_reader :name, :namespace, :context
@@ -87,6 +89,10 @@ module KubernetesDeploy
87
89
  @instance_data = {}
88
90
  end
89
91
 
92
+ def to_kubeclient_resource
93
+ Kubeclient::Resource.new(@definition)
94
+ end
95
+
90
96
  def validate_definition(kubectl)
91
97
  @validation_errors = []
92
98
  validate_timeout_annotation
@@ -118,6 +124,9 @@ module KubernetesDeploy
118
124
  @instance_data = mediator.get_instance(kubectl_resource_type, name)
119
125
  end
120
126
 
127
+ def after_sync
128
+ end
129
+
121
130
  def deploy_failed?
122
131
  false
123
132
  end
@@ -173,9 +182,8 @@ module KubernetesDeploy
173
182
  end
174
183
 
175
184
  def sync_debug_info(kubectl)
176
- @events = fetch_events(kubectl) unless ENV[DISABLE_FETCHING_EVENT_INFO]
177
- @logs = fetch_logs(kubectl) if supports_logs? && !ENV[DISABLE_FETCHING_EVENT_INFO]
178
- @debug_info_synced = true
185
+ @debug_events = fetch_events(kubectl) unless ENV[DISABLE_FETCHING_EVENT_INFO]
186
+ @debug_logs = fetch_debug_logs(kubectl) if print_debug_logs? && !ENV[DISABLE_FETCHING_LOG_INFO]
179
187
  end
180
188
 
181
189
  def debug_message(cause = nil, info_hash = {})
@@ -198,9 +206,9 @@ module KubernetesDeploy
198
206
  end
199
207
  helpful_info << " - Final status: #{status}"
200
208
 
201
- if @events.present?
209
+ if @debug_events.present?
202
210
  helpful_info << " - Events (common success events excluded):"
203
- @events.each do |identifier, event_hashes|
211
+ @debug_events.each do |identifier, event_hashes|
204
212
  event_hashes.each { |event| helpful_info << " [#{identifier}]\t#{event}" }
205
213
  end
206
214
  elsif ENV[DISABLE_FETCHING_EVENT_INFO]
@@ -209,21 +217,24 @@ module KubernetesDeploy
209
217
  helpful_info << " - Events: #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
210
218
  end
211
219
 
212
- if supports_logs?
220
+ if print_debug_logs?
213
221
  if ENV[DISABLE_FETCHING_LOG_INFO]
214
222
  helpful_info << " - Logs: #{DISABLED_LOG_INFO_MESSAGE}"
215
- elsif @logs.blank? || @logs.values.all?(&:blank?)
223
+ elsif @debug_logs.blank?
216
224
  helpful_info << " - Logs: #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
217
225
  else
218
- sorted_logs = @logs.sort_by { |_, log_lines| log_lines.length }
219
- sorted_logs.each do |identifier, log_lines|
220
- if log_lines.empty?
221
- helpful_info << " - Logs from container '#{identifier}': #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
226
+ container_logs = @debug_logs.container_logs.sort_by { |c| c.lines.length }
227
+ container_logs.each do |logs|
228
+ if logs.empty?
229
+ helpful_info << " - Logs from container '#{logs.container_name}': #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
222
230
  next
223
231
  end
224
232
 
225
- helpful_info << " - Logs from container '#{identifier}' (last #{LOG_LINE_COUNT} lines shown):"
226
- log_lines.each do |line|
233
+ if logs.lines.length == ContainerLogs::DEFAULT_LINE_LIMIT
234
+ truncated = " (last #{ContainerLogs::DEFAULT_LINE_LIMIT} lines shown)"
235
+ end
236
+ helpful_info << " - Logs from container '#{logs.container_name}'#{truncated}:"
237
+ logs.lines.each do |line|
227
238
  helpful_info << " #{line}"
228
239
  end
229
240
  end
@@ -366,8 +377,8 @@ module KubernetesDeploy
366
377
  file&.close
367
378
  end
368
379
 
369
- def supports_logs?
370
- respond_to?(:fetch_logs)
380
+ def print_debug_logs?
381
+ false
371
382
  end
372
383
 
373
384
  def statsd_tags
@@ -28,10 +28,13 @@ module KubernetesDeploy
28
28
  observed_generation == current_generation
29
29
  end
30
30
 
31
- def fetch_logs(kubectl)
32
- return {} unless pods.present?
31
+ def fetch_debug_logs(kubectl)
33
32
  most_useful_pod = pods.find(&:deploy_failed?) || pods.find(&:deploy_timed_out?) || pods.first
34
- most_useful_pod.fetch_logs(kubectl)
33
+ most_useful_pod.fetch_debug_logs(kubectl)
34
+ end
35
+
36
+ def print_debug_logs?
37
+ pods.present? # the kubectl command times out if no pods exist
35
38
  end
36
39
 
37
40
  private
@@ -24,9 +24,12 @@ module KubernetesDeploy
24
24
  own_events.merge(@latest_rs.fetch_events(kubectl))
25
25
  end
26
26
 
27
- def fetch_logs(kubectl)
28
- return {} unless @latest_rs.present?
29
- @latest_rs.fetch_logs(kubectl)
27
+ def print_debug_logs?
28
+ @latest_rs.present?
29
+ end
30
+
31
+ def fetch_debug_logs(kubectl)
32
+ @latest_rs.fetch_debug_logs(kubectl)
30
33
  end
31
34
 
32
35
  def deploy_succeeded?
@@ -142,12 +145,6 @@ module KubernetesDeploy
142
145
  def deploy_failing_to_progress?
143
146
  return false unless progress_condition.present?
144
147
 
145
- if @server_version < Gem::Version.new("1.7.7")
146
- # Deployments were being updated prematurely with incorrect progress information
147
- # https://github.com/kubernetes/kubernetes/issues/49637
148
- return false unless Time.now.utc - @deploy_started_at >= progress_deadline.to_i
149
- end
150
-
151
148
  # This assumes that when the controller bumps the observed generation, it also updates/clears all the status
152
149
  # conditions. Specifically, it assumes the progress condition is immediately set to True if a rollout is starting.
153
150
  deploy_started? &&
@@ -10,15 +10,17 @@ module KubernetesDeploy
10
10
  )
11
11
 
12
12
  def initialize(namespace:, context:, definition:, logger:,
13
- statsd_tags: nil, parent: nil, deploy_started_at: nil)
13
+ statsd_tags: nil, parent: nil, deploy_started_at: nil, stream_logs: false)
14
14
  @parent = parent
15
15
  @deploy_started_at = deploy_started_at
16
+
16
17
  @containers = definition.fetch("spec", {}).fetch("containers", []).map { |c| Container.new(c) }
17
18
  unless @containers.present?
18
19
  logger.summary.add_paragraph("Rendered template content:\n#{definition.to_yaml}")
19
20
  raise FatalDeploymentError, "Template is missing required field spec.containers"
20
21
  end
21
22
  @containers += definition["spec"].fetch("initContainers", []).map { |c| Container.new(c, init_container: true) }
23
+ @stream_logs = stream_logs
22
24
  super(namespace: namespace, context: context, definition: definition,
23
25
  logger: logger, statsd_tags: statsd_tags)
24
26
  end
@@ -28,12 +30,19 @@ module KubernetesDeploy
28
30
  raise_predates_deploy_error if exists? && unmanaged? && !deploy_started?
29
31
 
30
32
  if exists?
33
+ logs.sync(mediator.kubectl) if unmanaged?
31
34
  update_container_statuses(@instance_data["status"])
32
35
  else # reset
33
36
  @containers.each(&:reset_status)
34
37
  end
38
+ end
35
39
 
36
- display_logs(mediator) if unmanaged? && deploy_succeeded?
40
+ def after_sync
41
+ if @stream_logs
42
+ logs.print_latest
43
+ elsif unmanaged? && deploy_succeeded?
44
+ logs.print_all
45
+ end
37
46
  end
38
47
 
39
48
  def status
@@ -80,28 +89,25 @@ module KubernetesDeploy
80
89
  "#{phase_problem}#{container_problems}".presence
81
90
  end
82
91
 
83
- # Returns a hash in the following format:
84
- # {
85
- # "app" => ["array of log lines", "received from app container"],
86
- # "nginx" => ["array of log lines", "received from nginx container"]
87
- # }
88
- def fetch_logs(kubectl)
89
- return {} unless exists? && @containers.present?
90
- @containers.each_with_object({}) do |container, container_logs|
91
- cmd = [
92
- "logs",
93
- @name,
94
- "--container=#{container.name}",
95
- "--since-time=#{@deploy_started_at.to_datetime.rfc3339}",
96
- ]
97
- cmd << "--tail=#{LOG_LINE_COUNT}" unless unmanaged?
98
- out, _err, _st = kubectl.run(*cmd, log_failure: false)
99
- container_logs[container.name] = out.split("\n")
100
- end
92
+ def fetch_debug_logs(kubectl)
93
+ logs.sync(kubectl)
94
+ logs
95
+ end
96
+
97
+ def print_debug_logs?
98
+ exists? && !@stream_logs # don't print them a second time
101
99
  end
102
100
 
103
101
  private
104
102
 
103
+ def logs
104
+ @logs ||= KubernetesDeploy::RemoteLogs.new(
105
+ logger: @logger,
106
+ parent_id: id,
107
+ container_names: @containers.map(&:name)
108
+ )
109
+ end
110
+
105
111
  def phase
106
112
  @instance_data.dig("status", "phase") || "Unknown"
107
113
  end
@@ -138,29 +144,6 @@ module KubernetesDeploy
138
144
  @parent.blank?
139
145
  end
140
146
 
141
- def display_logs(mediator)
142
- return if @already_displayed
143
- container_logs = fetch_logs(mediator.kubectl)
144
-
145
- if container_logs.empty?
146
- @logger.warn("No logs found for pod #{id}")
147
- return
148
- end
149
-
150
- container_logs.each do |container_identifier, logs|
151
- if logs.blank?
152
- @logger.warn("No logs found for container '#{container_identifier}'")
153
- else
154
- @logger.blank_line
155
- @logger.info("Logs from #{id} container '#{container_identifier}':")
156
- logs.each { |line| @logger.info("\t#{line}") }
157
- @logger.blank_line
158
- end
159
- end
160
-
161
- @already_displayed = true
162
- end
163
-
164
147
  def raise_predates_deploy_error
165
148
  example_color = :green
166
149
  msg = <<-STRING.strip_heredoc
@@ -16,19 +16,14 @@ module KubernetesDeploy
16
16
  own_events.merge(most_useful_pod.fetch_events(kubectl))
17
17
  end
18
18
 
19
- def fetch_logs(kubectl)
20
- return {} unless pods.present? # the kubectl command times out if no pods exist
21
- container_names.each_with_object({}) do |container_name, container_logs|
22
- out, _err, _st = kubectl.run(
23
- "logs",
24
- id,
25
- "--container=#{container_name}",
26
- "--since-time=#{@deploy_started_at.to_datetime.rfc3339}",
27
- "--tail=#{LOG_LINE_COUNT}",
28
- log_failure: false
29
- )
30
- container_logs[container_name] = out.split("\n")
31
- end
19
+ def fetch_debug_logs(kubectl)
20
+ logs = KubernetesDeploy::RemoteLogs.new(logger: @logger, parent_id: id, container_names: container_names)
21
+ logs.sync(kubectl)
22
+ logs
23
+ end
24
+
25
+ def print_debug_logs?
26
+ pods.present? # the kubectl command times out if no pods exist
32
27
  end
33
28
 
34
29
  private
@@ -25,7 +25,7 @@ module KubernetesDeploy
25
25
  unless @success_assumption_warning_shown
26
26
  @logger.warn("WARNING: Your StatefulSet's updateStrategy is set to OnDelete, "\
27
27
  "which means updates will not be applied until its pods are deleted. "\
28
- "If you are using k8s 1.7+, consider switching to rollingUpdate.")
28
+ "Consider switching to rollingUpdate.")
29
29
  @success_assumption_warning_shown = true
30
30
  end
31
31
  true
@@ -46,9 +46,7 @@ module KubernetesDeploy
46
46
  private
47
47
 
48
48
  def update_strategy
49
- if @server_version < Gem::Version.new("1.7.0")
50
- ONDELETE
51
- elsif exists?
49
+ if exists?
52
50
  @instance_data['spec']['updateStrategy']['type']
53
51
  else
54
52
  'Unknown'
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ require 'kubernetes-deploy/container_logs'
3
+
4
+ module KubernetesDeploy
5
+ class RemoteLogs
6
+ attr_reader :container_logs
7
+
8
+ def initialize(logger:, parent_id:, container_names:)
9
+ @logger = logger
10
+ @parent_id = parent_id
11
+ @container_logs = container_names.map do |n|
12
+ ContainerLogs.new(logger: logger, container_name: n, parent_id: parent_id)
13
+ end
14
+ end
15
+
16
+ def empty?
17
+ @container_logs.all?(&:empty?)
18
+ end
19
+
20
+ def sync(kubectl)
21
+ @container_logs.each { |cl| cl.sync(kubectl) }
22
+ end
23
+
24
+ def print_latest
25
+ @container_logs.each { |cl| cl.print_latest(prefix: @container_logs.length > 1) }
26
+ end
27
+
28
+ def print_all(prevent_duplicate: true)
29
+ return if @already_displayed && prevent_duplicate
30
+
31
+ if @container_logs.all?(&:empty?)
32
+ @logger.warn("No logs found for #{@parent_id}")
33
+ return
34
+ end
35
+
36
+ @container_logs.each do |cl|
37
+ if cl.empty?
38
+ @logger.warn("No logs found for #{@parent_id} container '#{cl.container_name}'")
39
+ else
40
+ @logger.info("Logs from #{@parent_id} container '#{cl.container_name}':")
41
+ cl.print_all
42
+ @logger.blank_line
43
+ end
44
+ end
45
+
46
+ @already_displayed = true
47
+ end
48
+ end
49
+ end
@@ -17,19 +17,16 @@ module KubernetesDeploy
17
17
  end
18
18
 
19
19
  def run(delay_sync: 3.seconds, reminder_interval: 30.seconds, record_summary: true)
20
- delay_sync_until = last_message_logged_at = monitoring_started = Time.now.utc
20
+ last_message_logged_at = monitoring_started = Time.now.utc
21
21
  remainder = @resources.dup
22
22
 
23
23
  while remainder.present?
24
- if @timeout && (Time.now.utc - monitoring_started > @timeout)
25
- report_and_give_up(remainder)
26
- end
27
- if (sleep_duration = delay_sync_until - Time.now.utc) > 0
28
- sleep(sleep_duration)
29
- end
30
- delay_sync_until = Time.now.utc + delay_sync # don't pummel the API if the sync is fast
24
+ report_and_give_up(remainder) if global_timeout?(monitoring_started)
25
+ sleep_until_next_sync(delay_sync)
31
26
 
32
27
  @sync_mediator.sync(remainder)
28
+ remainder.each(&:after_sync)
29
+
33
30
  new_successes, remainder = remainder.partition(&:deploy_succeeded?)
34
31
  new_failures, remainder = remainder.partition(&:deploy_failed?)
35
32
  new_timeouts, remainder = remainder.partition(&:deploy_timed_out?)
@@ -48,6 +45,18 @@ module KubernetesDeploy
48
45
 
49
46
  private
50
47
 
48
+ def global_timeout?(started_at)
49
+ @timeout && (Time.now.utc - started_at > @timeout)
50
+ end
51
+
52
+ def sleep_until_next_sync(min_interval)
53
+ @next_sync_time ||= Time.now.utc
54
+ if (sleep_duration = @next_sync_time - Time.now.utc) > 0
55
+ sleep(sleep_duration)
56
+ end
57
+ @next_sync_time = Time.now.utc + min_interval
58
+ end
59
+
51
60
  def report_what_just_happened(new_successes, new_failures, new_timeouts)
52
61
  watch_time = (Time.now.utc - @deploy_started_at).round(1)
53
62
  new_failures.each do |resource|
@@ -62,7 +71,7 @@ module KubernetesDeploy
62
71
 
63
72
  if new_successes.present?
64
73
  new_successes.each { |r| r.report_status_to_statsd(watch_time) }
65
- success_string = ColorizedString.new("Successfully #{@operation_name}ed in #{watch_time}s:").green
74
+ success_string = ColorizedString.new("Successfully #{past_tense_operation} in #{watch_time}s:").green
66
75
  @logger.info("#{success_string} #{new_successes.map(&:id).join(', ')}")
67
76
  end
68
77
  end
@@ -120,7 +129,7 @@ module KubernetesDeploy
120
129
  def record_success_statuses(successful_resources)
121
130
  success_count = successful_resources.length
122
131
  if success_count > 0
123
- @logger.summary.add_action("successfully #{@operation_name}ed #{success_count} "\
132
+ @logger.summary.add_action("successfully #{past_tense_operation} #{success_count} "\
124
133
  "#{'resource'.pluralize(success_count)}")
125
134
  final_statuses = successful_resources.map(&:pretty_status).join("\n")
126
135
  @logger.summary.add_paragraph("#{ColorizedString.new('Successful resources').green}\n#{final_statuses}")
@@ -130,5 +139,9 @@ module KubernetesDeploy
130
139
  def due_for_reminder?(last_message_logged_at, reminder_interval)
131
140
  (last_message_logged_at.to_f + reminder_interval.to_f) <= Time.now.utc.to_f
132
141
  end
142
+
143
+ def past_tense_operation
144
+ @operation_name == "run" ? "ran" : "#{@operation_name}ed"
145
+ end
133
146
  end
134
147
  end
@@ -8,51 +8,99 @@ module KubernetesDeploy
8
8
  class RunnerTask
9
9
  include KubeclientBuilder
10
10
 
11
- class FatalTaskRunError < FatalDeploymentError; end
12
- class TaskTemplateMissingError < FatalDeploymentError
13
- def initialize(task_template, namespace, context)
14
- super("Pod template `#{task_template}` cannot be found in namespace: `#{namespace}`, context: `#{context}`")
15
- end
16
- end
11
+ class TaskConfigurationError < FatalDeploymentError; end
12
+ class TaskTemplateMissingError < TaskConfigurationError; end
13
+
14
+ attr_reader :pod_name
17
15
 
18
- def initialize(namespace:, context:, logger:)
16
+ def initialize(namespace:, context:, logger:, max_watch_seconds: nil)
19
17
  @logger = logger
20
18
  @namespace = namespace
21
- @kubeclient = build_v1_kubeclient(context)
22
19
  @context = context
20
+ @max_watch_seconds = max_watch_seconds
23
21
  end
24
22
 
25
23
  def run(*args)
26
24
  run!(*args)
27
25
  true
28
- rescue FatalDeploymentError => error
29
- @logger.fatal "#{error.class}: #{error.message}"
26
+ rescue DeploymentTimeoutError, FatalDeploymentError
30
27
  false
31
28
  end
32
29
 
33
- def run!(task_template:, entrypoint:, args:, env_vars: [])
30
+ def run!(task_template:, entrypoint:, args:, env_vars: [], verify_result: true)
34
31
  @logger.reset
35
- @logger.phase_heading("Validating configuration")
32
+
33
+ @logger.phase_heading("Initializing task")
36
34
  validate_configuration(task_template, args)
37
- if kubectl.server_version < Gem::Version.new(MIN_KUBE_VERSION)
38
- @logger.warn(KubernetesDeploy::Errors.server_version_warning(kubectl.server_version))
35
+ pod = build_pod(task_template, entrypoint, args, env_vars, verify_result)
36
+ validate_pod(pod)
37
+
38
+ @logger.phase_heading("Running pod")
39
+ create_pod(pod)
40
+
41
+ if verify_result
42
+ @logger.phase_heading("Streaming logs")
43
+ watch_pod(pod)
44
+ else
45
+ record_status_once(pod)
39
46
  end
40
- @logger.phase_heading("Fetching task template")
41
- raw_template = get_template(task_template)
47
+ @logger.print_summary(:success)
48
+ rescue DeploymentTimeoutError
49
+ @logger.print_summary(:timed_out)
50
+ raise
51
+ rescue FatalDeploymentError
52
+ @logger.print_summary(:failure)
53
+ raise
54
+ end
42
55
 
43
- @logger.phase_heading("Constructing final pod specification")
44
- rendered_template = build_pod_template(raw_template, entrypoint, args, env_vars)
56
+ private
45
57
 
46
- validate_pod_spec(rendered_template)
58
+ def create_pod(pod)
59
+ @logger.info "Creating pod '#{pod.name}'"
60
+ pod.deploy_started_at = Time.now.utc
61
+ kubeclient.create_pod(pod.to_kubeclient_resource)
62
+ @pod_name = pod.name
63
+ @logger.info("Pod creation succeeded")
64
+ rescue KubeException => e
65
+ msg = "Failed to create pod: #{e.class.name}: #{e.message}"
66
+ @logger.summary.add_paragraph(msg)
67
+ raise FatalDeploymentError, msg
68
+ end
47
69
 
48
- @logger.phase_heading("Creating pod")
49
- @logger.info("Starting task runner pod: '#{rendered_template.metadata.name}'")
50
- @kubeclient.create_pod(rendered_template)
70
+ def build_pod(template_name, entrypoint, args, env_vars, verify_result)
71
+ task_template = get_template(template_name)
72
+ @logger.info("Using template '#{template_name}'")
73
+ pod_template = build_pod_definition(task_template)
74
+ set_container_overrides!(pod_template, entrypoint, args, env_vars)
75
+ ensure_valid_restart_policy!(pod_template, verify_result)
76
+ Pod.new(namespace: @namespace, context: @context, logger: @logger, stream_logs: true,
77
+ definition: pod_template.to_hash.deep_stringify_keys, statsd_tags: [])
51
78
  end
52
79
 
53
- private
80
+ def validate_pod(pod)
81
+ pod.validate_definition(kubectl)
82
+ end
83
+
84
+ def watch_pod(pod)
85
+ rw = ResourceWatcher.new(resources: [pod], logger: @logger, timeout: @max_watch_seconds,
86
+ sync_mediator: sync_mediator, operation_name: "run")
87
+ rw.run(delay_sync: 1, reminder_interval: 30.seconds)
88
+ raise DeploymentTimeoutError if pod.deploy_timed_out?
89
+ raise FatalDeploymentError if pod.deploy_failed?
90
+ end
91
+
92
+ def record_status_once(pod)
93
+ pod.sync(sync_mediator)
94
+ warning = <<~STRING
95
+ #{ColorizedString.new('Result verification is disabled for this task.').yellow}
96
+ The following status was observed immediately after pod creation:
97
+ #{pod.pretty_status}
98
+ STRING
99
+ @logger.summary.add_paragraph(warning)
100
+ end
54
101
 
55
102
  def validate_configuration(task_template, args)
103
+ @logger.info("Validating configuration")
56
104
  errors = []
57
105
 
58
106
  if task_template.blank?
@@ -68,71 +116,74 @@ module KubernetesDeploy
68
116
  end
69
117
 
70
118
  begin
71
- @kubeclient.get_namespace(@namespace) if @namespace.present?
119
+ kubeclient.get_namespace(@namespace) if @namespace.present?
120
+ @logger.info "Using namespace '#{@namespace}' in context '#{@context}'"
72
121
  rescue KubeException => e
73
122
  msg = e.error_code == 404 ? "Namespace was not found" : "Could not connect to kubernetes cluster"
74
123
  errors << msg
75
124
  end
76
125
 
77
- raise FatalTaskRunError, "Configuration invalid: #{errors.join(', ')}" unless errors.empty?
126
+ unless errors.empty?
127
+ @logger.summary.add_action("Configuration invalid")
128
+ @logger.summary.add_paragraph(errors.map { |err| "- #{err}" }.join("\n"))
129
+ raise TaskConfigurationError, "Configuration invalid: #{errors.join(', ')}"
130
+ end
131
+
132
+ if kubectl.server_version < Gem::Version.new(MIN_KUBE_VERSION)
133
+ @logger.warn(KubernetesDeploy::Errors.server_version_warning(kubectl.server_version))
134
+ end
78
135
  end
79
136
 
80
137
  def get_template(template_name)
81
- @logger.info(
82
- "Fetching task runner pod template: '#{template_name}' in namespace: '#{@namespace}'"
83
- )
84
-
85
- pod_template = @kubeclient.get_pod_template(template_name, @namespace)
138
+ pod_template = kubeclient.get_pod_template(template_name, @namespace)
86
139
 
87
140
  pod_template.template
88
141
  rescue KubeException => error
89
142
  if error.error_code == 404
90
- raise TaskTemplateMissingError.new(template_name, @namespace, @context)
143
+ msg = "Pod template `#{template_name}` not found in namespace `#{@namespace}`, context `#{@context}`"
144
+ @logger.summary.add_paragraph(msg)
145
+ raise TaskTemplateMissingError, msg
91
146
  else
92
- raise
147
+ raise FatalKubeAPIError, "Error retrieving pod template: #{error.class.name}: #{error.message}"
93
148
  end
94
149
  end
95
150
 
96
- def build_pod_template(base_template, entrypoint, args, env_vars)
97
- @logger.info("Rendering template for task runner pod")
151
+ def build_pod_definition(base_template)
152
+ pod_definition = base_template.dup
153
+ pod_definition.kind = 'Pod'
154
+ pod_definition.apiVersion = 'v1'
155
+ pod_definition.metadata.namespace = @namespace
98
156
 
99
- rendered_template = base_template.dup
100
- rendered_template.kind = 'Pod'
101
- rendered_template.apiVersion = 'v1'
157
+ unique_name = pod_definition.metadata.name + "-" + SecureRandom.hex(8)
158
+ @logger.warn("Name is too long, using '#{unique_name[0..62]}'") if unique_name.length > 63
159
+ pod_definition.metadata.name = unique_name[0..62]
102
160
 
103
- container = rendered_template.spec.containers.find { |cont| cont.name == 'task-runner' }
161
+ pod_definition
162
+ end
104
163
 
105
- raise FatalTaskRunError, "Pod spec does not contain a template container called 'task-runner'" if container.nil?
164
+ def set_container_overrides!(pod_definition, entrypoint, args, env_vars)
165
+ container = pod_definition.spec.containers.find { |cont| cont.name == 'task-runner' }
166
+ if container.nil?
167
+ raise TaskConfigurationError, "Pod spec does not contain a template container called 'task-runner'"
168
+ end
106
169
 
107
170
  container.command = entrypoint
108
171
  container.args = args
109
- container.env ||= []
110
172
 
111
173
  env_args = env_vars.map do |env|
112
174
  key, value = env.split('=', 2)
113
175
  { name: key, value: value }
114
176
  end
115
-
177
+ container.env ||= []
116
178
  container.env = container.env.map(&:to_h) + env_args
117
-
118
- unique_name = rendered_template.metadata.name + "-" + SecureRandom.hex(8)
119
-
120
- @logger.warn("Name is too long, using '#{unique_name[0..62]}'") if unique_name.length > 63
121
- rendered_template.metadata.name = unique_name[0..62]
122
- rendered_template.metadata.namespace = @namespace
123
-
124
- rendered_template
125
179
  end
126
180
 
127
- def validate_pod_spec(pod)
128
- f = Tempfile.new(pod.metadata.name)
129
- f.write recursive_to_h(pod).to_json
130
- f.close
131
-
132
- _out, err, status = kubectl.run("apply", "--dry-run", "-f", f.path)
133
-
134
- unless status.success?
135
- raise FatalTaskRunError, "Invalid pod spec: #{err}"
181
+ def ensure_valid_restart_policy!(template, verify)
182
+ restart_policy = template.spec.restartPolicy
183
+ if verify && restart_policy != "Never"
184
+ @logger.warn("Changed Pod RestartPolicy from '#{restart_policy}' to 'Never'. Disable "\
185
+ "result verification to use '#{restart_policy}'.")
186
+ template.spec.restartPolicy = "Never"
136
187
  end
137
188
  end
138
189
 
@@ -140,18 +191,12 @@ module KubernetesDeploy
140
191
  @kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: true)
141
192
  end
142
193
 
143
- def recursive_to_h(struct)
144
- if struct.is_a?(Array)
145
- return struct.map { |v| v.is_a?(OpenStruct) || v.is_a?(Array) || v.is_a?(Hash) ? recursive_to_h(v) : v }
146
- end
147
-
148
- hash = {}
194
+ def sync_mediator
195
+ @sync_mediator ||= SyncMediator.new(namespace: @namespace, context: @context, logger: @logger)
196
+ end
149
197
 
150
- struct.each_pair do |k, v|
151
- recursive_val = v.is_a?(OpenStruct) || v.is_a?(Array) || v.is_a?(Hash)
152
- hash[k] = recursive_val ? recursive_to_h(v) : v
153
- end
154
- hash
198
+ def kubeclient
199
+ @kubeclient ||= build_v1_kubeclient(@context)
155
200
  end
156
201
  end
157
202
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.20.6"
3
+ VERSION = "0.21.0"
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.20.6
4
+ version: 0.21.0
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: 2018-09-18 00:00:00.000000000 Z
12
+ date: 2018-10-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -213,6 +213,7 @@ files:
213
213
  - lib/kubernetes-deploy.rb
214
214
  - lib/kubernetes-deploy/bindings_parser.rb
215
215
  - lib/kubernetes-deploy/concurrency.rb
216
+ - lib/kubernetes-deploy/container_logs.rb
216
217
  - lib/kubernetes-deploy/deferred_summary_logging.rb
217
218
  - lib/kubernetes-deploy/deploy_task.rb
218
219
  - lib/kubernetes-deploy/duration_parser.rb
@@ -248,6 +249,7 @@ files:
248
249
  - lib/kubernetes-deploy/kubernetes_resource/stateful_set.rb
249
250
  - lib/kubernetes-deploy/kubernetes_resource/statefulservice.rb
250
251
  - lib/kubernetes-deploy/kubernetes_resource/topic.rb
252
+ - lib/kubernetes-deploy/remote_logs.rb
251
253
  - lib/kubernetes-deploy/renderer.rb
252
254
  - lib/kubernetes-deploy/resource_discovery.rb
253
255
  - lib/kubernetes-deploy/resource_watcher.rb