kubernetes-deploy 0.18.1 → 0.19.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: 820602ac9a434949fb86a0f1cb85996962d4ecb2
4
- data.tar.gz: 492b43ccf6a0683b93cc3b187c6711e9bdb176b1
3
+ metadata.gz: 99f56eb5da1d7f17885cff3d19787b45a1484504
4
+ data.tar.gz: e2919b899b313e2c4be0af2b7731816f9c8336de
5
5
  SHA512:
6
- metadata.gz: 23706dd66b3844c4b6fece858621bf4b9cea5c0c65568b972cb6afdfab64a1f8d45957c7095a3ca8af96f9c488f383f2f1fd69d99d5affdbf9545d9c0c7ac948
7
- data.tar.gz: 64bf0d15a51c97be05e80a3b64b0e3f985f0f6a0bd137fbd1e6353b13bec404e6dd150a66f34c15f0f24cfee66a1838efba2d6260bf6ece9bf5c4c0903e55e12
6
+ metadata.gz: 103546e46a2e476e1cc1ae339fcce8362fd199d144d890d1b895ed46848181ba822dcb713eb672581f625b25e3992c4ea9439e59888c4b3f0b74b44d156bba50
7
+ data.tar.gz: 7aeb474292e52f2b3e9ead4f038732804949c253da2119993c89d64e1357d7920c2031bce184821cb711c091ed54f8b6edf181c47d72b9242c3d2e0c972d7355
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ### 0.19.0
2
+ *Features*
3
+ - Added `--max-watch-seconds=seconds` to kubernetes-restart and kubernetes-deploy. When set
4
+ a timeout error is raised if it takes longer than _seconds_ for any resource to deploy.
5
+ - Adds YAML and JSON file reference support to the kubernetes-deploy `--bindings` argument ([#269](https://github.com/Shopify/kubernetes-deploy/pull/269))
6
+
7
+ *Enhancements*
8
+ - Prune resource quotas ([#264](https://github.com/Shopify/kubernetes-deploy/pull/264/files))
9
+
10
+ *Bug Fixes*
11
+ - Update gemspec to reflect need for ActiveSupport >= 5.0([#270](https://github.com/Shopify/kubernetes-deploy/pull/270))
12
+
1
13
  ### 0.18.1
2
14
  *Enhancements*
3
15
  - Change the way the resource watcher fetches resources to make it more efficient for large deploys. Deploys with hundreds of resources are expected to see a measurable performance improvement from this change. ([#251](https://github.com/Shopify/kubernetes-deploy/pull/251))
data/README.md CHANGED
@@ -107,9 +107,8 @@ Refer to `kubernetes-deploy --help` for the authoritative set of options.
107
107
  - `--template-dir=DIR`: Used to set the deploy directory. Set `$ENVIRONMENT` instead to use `config/deploy/$ENVIRONMENT`.
108
108
  - `--bindings=BINDINGS`: Makes additional variables available to your ERB templates. For example, `kubernetes-deploy my-app cluster1 --bindings=color=blue,size=large` will expose `color` and `size`.
109
109
  - `--no-prune`: Skips pruning of resources that are no longer in your Kubernetes template set. Not recommended, as it allows your namespace to accumulate cruft that is not reflected in your deploy directory.
110
-
111
-
112
-
110
+ - `--max-watch-seconds=seconds`: Raise a timeout error if it takes longer than _seconds_ for any
111
+ resource to deploy.
113
112
 
114
113
 
115
114
  ### Using templates and variables
@@ -121,7 +120,24 @@ All templates must be YAML formatted. You can also use ERB. The following local
121
120
  * `current_sha`: The value of `$REVISION`
122
121
  * `deployment_id`: A randomly generated identifier for the deploy. Useful for creating unique names for task-runner pods (e.g. a pod that runs rails migrations at the beginning of deploys).
123
122
 
124
- You can add additional variables using the `--bindings=BINDINGS` option which can be formated as comma separated or as JSON. For example, `kubernetes-deploy my-app cluster1 --bindings=color=blue,size=large` or `kubernetes-deploy my-app cluster1 --bindings='{"color":"blue","size":"large"}'` will expose `color` and `size` in your templates. Complex JSON data will be converted to a Hash for use in templates.
123
+ You can add additional variables using the `--bindings=BINDINGS` option which can be formated as comma separated string, JSON string or path to a JSON or YAML file. Complex JSON or YAML data will be converted to a Hash for use in templates. To load a file the argument should include the relative file path prefixed with an `@` sign. An argument error will be raised if the string argument cannot be parsed, the referenced file does not include a valid extension (`.json`, `.yaml` or `.yml`) or the referenced file does not exist.
124
+
125
+ #### Bindings examples
126
+
127
+ ```
128
+ # Comma separated string. Exposes, 'color' and 'size'
129
+ $ kubernetes-deploy my-app cluster1 --bindings=color=blue,size=large
130
+
131
+ # JSON string. Exposes, 'color' and 'size'
132
+ $ kubernetes-deploy my-app cluster1 --bindings='{"color":"blue","size":"large"}'
133
+
134
+ # Load JSON file from ./config
135
+ $ kubernetes-deploy my-app cluster1 --bindings='@config/production.json'
136
+
137
+ # Load YAML file from ./config (.yaml or .yml supported)
138
+ $ kubernetes-deploy my-app cluster1 --bindings='@config/production.yaml'
139
+ ```
140
+
125
141
 
126
142
  #### Using partials
127
143
 
@@ -10,10 +10,11 @@ allow_protected_ns = false
10
10
  prune = true
11
11
  bindings = {}
12
12
  verbose_log_prefix = false
13
+ max_watch_seconds = nil
13
14
 
14
15
  ARGV.options do |opts|
15
16
  opts.on("--bindings=BINDINGS", "Expose additional variables to ERB templates " \
16
- "(format: k1=v1,k2=v2 or JSON)") do |binds|
17
+ "(format: k1=v1,k2=v2, JSON string or file (JSON or YAML) path prefixed by '@')") do |binds|
17
18
  bindings = KubernetesDeploy::BindingsParser.parse(binds)
18
19
  end
19
20
 
@@ -23,6 +24,10 @@ ARGV.options do |opts|
23
24
  opts.on("--no-prune", "Disable deletion of resources that do not appear in the template dir") { prune = false }
24
25
  opts.on("--template-dir=DIR", "Set the template dir (default: config/deploy/$ENVIRONMENT)") { |v| template_dir = v }
25
26
  opts.on("--verbose-log-prefix", "Add [context][namespace] to the log prefix") { verbose_log_prefix = true }
27
+ opts.on("--max-watch-seconds=seconds",
28
+ "Timeout error is raised if it takes longer than the specified number of seconds") do |t|
29
+ max_watch_seconds = t.to_i
30
+ end
26
31
 
27
32
  opts.on_tail("-h", "--help", "Print this help") do
28
33
  puts opts
@@ -60,7 +65,8 @@ runner = KubernetesDeploy::DeployTask.new(
60
65
  current_sha: revision,
61
66
  template_dir: template_dir,
62
67
  bindings: bindings,
63
- logger: logger
68
+ logger: logger,
69
+ max_watch_seconds: max_watch_seconds
64
70
  )
65
71
 
66
72
  begin
@@ -7,8 +7,10 @@ require 'kubernetes-deploy'
7
7
  require 'kubernetes-deploy/restart_task'
8
8
 
9
9
  raw_deployments = nil
10
+ max_watch_seconds = nil
10
11
  ARGV.options do |opts|
11
12
  opts.on("--deployments=LIST") { |v| raw_deployments = v.split(",") }
13
+ opts.on("--max-watch-seconds=seconds") { |t| max_watch_seconds = t.to_i }
12
14
  opts.parse!
13
15
  end
14
16
 
@@ -16,7 +18,8 @@ namespace = ARGV[0]
16
18
  context = ARGV[1]
17
19
  logger = KubernetesDeploy::FormattedLogger.build(namespace, context)
18
20
 
19
- restart = KubernetesDeploy::RestartTask.new(namespace: namespace, context: context, logger: logger)
21
+ restart = KubernetesDeploy::RestartTask.new(namespace: namespace, context: context, logger: logger,
22
+ max_watch_seconds: max_watch_seconds)
20
23
  begin
21
24
  restart.perform!(raw_deployments)
22
25
  rescue KubernetesDeploy::DeploymentTimeoutError
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ["lib"]
24
24
 
25
25
  spec.required_ruby_version = '>= 2.3.0'
26
- spec.add_dependency "activesupport", ">= 4.2"
26
+ spec.add_dependency "activesupport", ">= 5.0"
27
27
  spec.add_dependency "kubeclient", "~> 2.4"
28
28
  spec.add_dependency "rest-client", ">= 1.7" # Minimum required by kubeclient. Remove when kubeclient releases v3.0.
29
29
  spec.add_dependency "googleauth", ">= 0.5"
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'json'
3
+ require 'yaml'
3
4
  require 'csv'
4
5
 
5
6
  module KubernetesDeploy
@@ -7,7 +8,7 @@ module KubernetesDeploy
7
8
  extend self
8
9
 
9
10
  def parse(string)
10
- bindings = parse_json(string) || parse_csv(string)
11
+ bindings = parse_file(string) || parse_json(string) || parse_csv(string)
11
12
 
12
13
  unless bindings
13
14
  raise ArgumentError, "Failed to parse bindings."
@@ -18,6 +19,27 @@ module KubernetesDeploy
18
19
 
19
20
  private
20
21
 
22
+ def parse_file(string)
23
+ return unless string =~ /\A@/
24
+
25
+ begin
26
+ file_path = string.gsub(/\A@/, '')
27
+
28
+ case File.extname(file_path)
29
+ when '.json'
30
+ bindings = parse_json(File.read(file_path))
31
+ when '.yaml', '.yml'
32
+ bindings = YAML.load(File.read(file_path))
33
+ else
34
+ raise ArgumentError, "Supplied file does not appear to be JSON or YAML"
35
+ end
36
+
37
+ bindings
38
+ rescue Errno::ENOENT
39
+ raise ArgumentError, "Supplied file does not exist: #{string}"
40
+ end
41
+ end
42
+
21
43
  def parse_json(string)
22
44
  bindings = JSON.parse(string)
23
45
 
@@ -68,6 +68,7 @@ module KubernetesDeploy
68
68
  core/v1/ConfigMap
69
69
  core/v1/Pod
70
70
  core/v1/Service
71
+ core/v1/ResourceQuota
71
72
  batch/v1/Job
72
73
  extensions/v1beta1/DaemonSet
73
74
  extensions/v1beta1/Deployment
@@ -88,13 +89,15 @@ module KubernetesDeploy
88
89
 
89
90
  NOT_FOUND_ERROR = 'NotFound'
90
91
 
91
- def initialize(namespace:, context:, current_sha:, template_dir:, logger:, kubectl_instance: nil, bindings: {})
92
+ def initialize(namespace:, context:, current_sha:, template_dir:, logger:, kubectl_instance: nil, bindings: {},
93
+ max_watch_seconds: nil)
92
94
  @namespace = namespace
93
95
  @context = context
94
96
  @current_sha = current_sha
95
97
  @template_dir = File.expand_path(template_dir)
96
98
  @logger = logger
97
99
  @kubectl = kubectl_instance
100
+ @max_watch_seconds = max_watch_seconds
98
101
  @renderer = KubernetesDeploy::Renderer.new(
99
102
  current_sha: @current_sha,
100
103
  template_dir: @template_dir,
@@ -376,7 +379,7 @@ module KubernetesDeploy
376
379
 
377
380
  if verify
378
381
  watcher = ResourceWatcher.new(resources: resources, sync_mediator: @sync_mediator,
379
- logger: @logger, deploy_started_at: deploy_started_at)
382
+ logger: @logger, deploy_started_at: deploy_started_at, timeout: @max_watch_seconds)
380
383
  watcher.run(record_summary: record_summary)
381
384
  end
382
385
  end
@@ -158,9 +158,13 @@ module KubernetesDeploy
158
158
  @debug_info_synced = true
159
159
  end
160
160
 
161
- def debug_message
161
+ def debug_message(cause = nil, info_hash = {})
162
162
  helpful_info = []
163
- if deploy_failed?
163
+ if cause == :gave_up
164
+ helpful_info << ColorizedString.new("#{id}: GLOBAL WATCH TIMEOUT (#{info_hash[:timeout]} seconds)").yellow
165
+ helpful_info << "If you expected it to take longer than #{info_hash[:timeout]} seconds for your deploy"\
166
+ " to roll out, increase --max-watch-seconds."
167
+ elsif deploy_failed?
164
168
  helpful_info << ColorizedString.new("#{id}: FAILED").red
165
169
  helpful_info << failure_message if failure_message.present?
166
170
  elsif deploy_timed_out?
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class ResourceWatcher
4
- def initialize(resources:, sync_mediator:, logger:, deploy_started_at: Time.now.utc, operation_name: "deploy")
4
+ def initialize(resources:, sync_mediator:, logger:, deploy_started_at: Time.now.utc,
5
+ operation_name: "deploy", timeout: nil)
5
6
  unless resources.is_a?(Enumerable)
6
7
  raise ArgumentError, <<~MSG
7
8
  ResourceWatcher expects Enumerable collection, got `#{resources.class}` instead
@@ -12,13 +13,17 @@ module KubernetesDeploy
12
13
  @sync_mediator = sync_mediator
13
14
  @deploy_started_at = deploy_started_at
14
15
  @operation_name = operation_name
16
+ @timeout = timeout
15
17
  end
16
18
 
17
19
  def run(delay_sync: 3.seconds, reminder_interval: 30.seconds, record_summary: true)
18
- delay_sync_until = last_message_logged_at = Time.now.utc
20
+ delay_sync_until = last_message_logged_at = monitoring_started = Time.now.utc
19
21
  remainder = @resources.dup
20
22
 
21
23
  while remainder.present?
24
+ if @timeout && (Time.now.utc - monitoring_started > @timeout)
25
+ report_and_give_up(remainder)
26
+ end
22
27
  if Time.now.utc < delay_sync_until
23
28
  sleep(delay_sync_until - Time.now.utc)
24
29
  end
@@ -69,20 +74,30 @@ module KubernetesDeploy
69
74
  @logger.info(msg)
70
75
  end
71
76
 
77
+ def report_and_give_up(remaining_resources)
78
+ successful_resources, failed_resources = (@resources - remaining_resources).partition(&:deploy_succeeded?)
79
+ record_success_statuses(successful_resources)
80
+ record_failed_statuses(failed_resources, remaining_resources)
81
+
82
+ if failed_resources.present? && !failed_resources.all?(&:deploy_timed_out?)
83
+ raise FatalDeploymentError
84
+ else
85
+ raise DeploymentTimeoutError
86
+ end
87
+ end
88
+
72
89
  def record_statuses_for_summary(resources)
73
90
  successful_resources, failed_resources = resources.partition(&:deploy_succeeded?)
74
- fail_count = failed_resources.length
75
- success_count = successful_resources.length
91
+ record_success_statuses(successful_resources)
92
+ record_failed_statuses(failed_resources)
93
+ end
76
94
 
77
- if success_count > 0
78
- @logger.summary.add_action("successfully #{@operation_name}ed #{success_count} "\
79
- "#{'resource'.pluralize(success_count)}")
80
- final_statuses = successful_resources.map(&:pretty_status).join("\n")
81
- @logger.summary.add_paragraph("#{ColorizedString.new('Successful resources').green}\n#{final_statuses}")
82
- end
95
+ def record_failed_statuses(failed_resources, global_timeouts = [])
96
+ fail_count = failed_resources.length + global_timeouts.length
83
97
 
84
98
  if fail_count > 0
85
99
  timeouts, failures = failed_resources.partition(&:deploy_timed_out?)
100
+ timeouts += global_timeouts
86
101
  if timeouts.present?
87
102
  @logger.summary.add_action(
88
103
  "timed out waiting for #{timeouts.length} #{'resource'.pluralize(timeouts.length)} to #{@operation_name}"
@@ -94,10 +109,21 @@ module KubernetesDeploy
94
109
  "failed to #{@operation_name} #{failures.length} #{'resource'.pluralize(failures.length)}"
95
110
  )
96
111
  end
97
- KubernetesDeploy::Concurrency.split_across_threads(failed_resources) do |r|
112
+ KubernetesDeploy::Concurrency.split_across_threads(failed_resources + global_timeouts) do |r|
98
113
  r.sync_debug_info(@sync_mediator.kubectl)
99
114
  end
100
115
  failed_resources.each { |r| @logger.summary.add_paragraph(r.debug_message) }
116
+ global_timeouts.each { |r| @logger.summary.add_paragraph(r.debug_message(:gave_up, timeout: @timeout)) }
117
+ end
118
+ end
119
+
120
+ def record_success_statuses(successful_resources)
121
+ success_count = successful_resources.length
122
+ if success_count > 0
123
+ @logger.summary.add_action("successfully #{@operation_name}ed #{success_count} "\
124
+ "#{'resource'.pluralize(success_count)}")
125
+ final_statuses = successful_resources.map(&:pretty_status).join("\n")
126
+ @logger.summary.add_paragraph("#{ColorizedString.new('Successful resources').green}\n#{final_statuses}")
101
127
  end
102
128
  end
103
129
 
@@ -20,11 +20,12 @@ module KubernetesDeploy
20
20
  HTTP_OK_RANGE = 200..299
21
21
  ANNOTATION = "shipit.shopify.io/restart"
22
22
 
23
- def initialize(context:, namespace:, logger:)
23
+ def initialize(context:, namespace:, logger:, max_watch_seconds: nil)
24
24
  @context = context
25
25
  @namespace = namespace
26
26
  @logger = logger
27
27
  @sync_mediator = SyncMediator.new(namespace: @namespace, context: @context, logger: @logger)
28
+ @max_watch_seconds = max_watch_seconds
28
29
  end
29
30
 
30
31
  def perform(*args)
@@ -50,7 +51,7 @@ module KubernetesDeploy
50
51
  @logger.phase_heading("Waiting for rollout")
51
52
  resources = build_watchables(deployments, start)
52
53
  ResourceWatcher.new(resources: resources, sync_mediator: @sync_mediator,
53
- logger: @logger, operation_name: "restart").run
54
+ logger: @logger, operation_name: "restart", timeout: @max_watch_seconds).run
54
55
  failed_resources = resources.reject(&:deploy_succeeded?)
55
56
  success = failed_resources.empty?
56
57
  if !success && failed_resources.all?(&:deploy_timed_out?)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.18.1"
3
+ VERSION = "0.19.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.18.1
4
+ version: 0.19.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-04-06 00:00:00.000000000 Z
12
+ date: 2018-04-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '4.2'
20
+ version: '5.0'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '4.2'
27
+ version: '5.0'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: kubeclient
30
30
  requirement: !ruby/object:Gem::Requirement