kubernetes-deploy 0.22.0 → 0.23.0

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.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +8 -0
  3. data/CHANGELOG.md +16 -0
  4. data/README.md +32 -0
  5. data/exe/kubernetes-deploy +2 -15
  6. data/exe/kubernetes-render +32 -0
  7. data/kubernetes-deploy.gemspec +5 -3
  8. data/lib/kubernetes-deploy.rb +5 -3
  9. data/lib/kubernetes-deploy/cluster_resource_discovery.rb +34 -0
  10. data/lib/kubernetes-deploy/container_logs.rb +25 -13
  11. data/lib/kubernetes-deploy/deploy_task.rb +68 -50
  12. data/lib/kubernetes-deploy/errors.rb +1 -0
  13. data/lib/kubernetes-deploy/formatted_logger.rb +16 -2
  14. data/lib/kubernetes-deploy/kubeclient_builder/google_friendly_config.rb +4 -6
  15. data/lib/kubernetes-deploy/kubectl.rb +20 -9
  16. data/lib/kubernetes-deploy/kubernetes_resource.rb +5 -6
  17. data/lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb +3 -4
  18. data/lib/kubernetes-deploy/kubernetes_resource/daemon_set.rb +4 -5
  19. data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +7 -8
  20. data/lib/kubernetes-deploy/kubernetes_resource/memcached.rb +4 -5
  21. data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +7 -5
  22. data/lib/kubernetes-deploy/kubernetes_resource/pod_set_base.rb +12 -6
  23. data/lib/kubernetes-deploy/kubernetes_resource/redis.rb +5 -6
  24. data/lib/kubernetes-deploy/kubernetes_resource/replica_set.rb +23 -5
  25. data/lib/kubernetes-deploy/kubernetes_resource/role.rb +22 -0
  26. data/lib/kubernetes-deploy/kubernetes_resource/service.rb +8 -4
  27. data/lib/kubernetes-deploy/kubernetes_resource/stateful_set.rb +2 -3
  28. data/lib/kubernetes-deploy/oj.rb +4 -0
  29. data/lib/kubernetes-deploy/options_helper.rb +27 -0
  30. data/lib/kubernetes-deploy/remote_logs.rb +10 -4
  31. data/lib/kubernetes-deploy/render_task.rb +119 -0
  32. data/lib/kubernetes-deploy/renderer.rb +1 -1
  33. data/lib/kubernetes-deploy/resource_cache.rb +64 -0
  34. data/lib/kubernetes-deploy/resource_watcher.rb +27 -6
  35. data/lib/kubernetes-deploy/restart_task.rb +5 -6
  36. data/lib/kubernetes-deploy/runner_task.rb +6 -10
  37. data/lib/kubernetes-deploy/statsd.rb +60 -7
  38. data/lib/kubernetes-deploy/template_discovery.rb +15 -0
  39. data/lib/kubernetes-deploy/version.rb +1 -1
  40. data/pull_request_template.md +8 -0
  41. metadata +47 -5
  42. data/lib/kubernetes-deploy/resource_discovery.rb +0 -19
  43. data/lib/kubernetes-deploy/sync_mediator.rb +0 -80
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 93fa4f3f00a9ff96a2dfc20fa26ead2ccf77d5e2
4
- data.tar.gz: 6b1e6a305c64c94ffb1cb38b5f7d722ba2e11fd6
2
+ SHA256:
3
+ metadata.gz: 01ddead06a28a399d7364d5789c2bcb1cee0e0aa4ae471a4af1a90c32973050b
4
+ data.tar.gz: ebcedef932871bd542db69bc0b48fb6f47f2482c712aa9380a79cb872b6c3e82
5
5
  SHA512:
6
- metadata.gz: 88e0586648309f18d49b7d7e27650edd273bc0fdef8ae9bb0a0aa639ed6e4016fb0d7c6d2a1c25caede0774bb5fdc30b79d100e0ade49fcd6dd1a3823d20dc3a
7
- data.tar.gz: a2ae7f6b727838b2c090c4208d0d321dee080902f8a50057c62f53691888294121211b863225945d5178ceea24876299e49ebdee1bc70f911e66eafd129e28fe
6
+ metadata.gz: 837dcaf96f89beae9f9a57ceb207595d602f1e039217007750112828a8076d0337c17bb6a75bcc2d5a5a8c531f8b0db363a19293d5dc6ce011cdd70440c888a7
7
+ data.tar.gz: e8e6484dbfae1b867542a02c39f7c96ed4434ab3f900f49fea72523466ea9be23ce2d1f2209d9fb609b7e71af63d1c047ab6f98f756ec28eef8bb47245fb9180
@@ -9,3 +9,11 @@ Style/TrailingCommaInArrayLiteral:
9
9
 
10
10
  Style/TrailingCommaInHashLiteral:
11
11
  Enabled: false
12
+
13
+ Style/MethodCallWithArgsParentheses:
14
+ Enabled: false
15
+
16
+ Naming/FileName:
17
+ Enabled: true
18
+ Exclude:
19
+ - lib/kubernetes-deploy.rb
@@ -1,3 +1,19 @@
1
+ ## 0.23.0
2
+
3
+ *Features*
4
+ - New command: `kubernetes-render` is a tool for rendering ERB templates to raw Kubernetes YAML. It's useful for seeing what `kubernetes-deploy` does before actually invoking `kubectl` on the rendered YAML. It's also useful for outputting YAML that can be passed to other tools, for validation or introspection purposes. ([#375](https://github.com/Shopify/kubernetes-deploy/pull/375/files))
5
+ - **[Breaking change]** This release completes the conversion of `kubernetes-deploy` StatsD metrics to `distribution`s, which was done for `kubernetes-restart` and `kubernetes-run` in v0.22.0.
6
+ - Several new distribution metrics are available to give insight into the timing of each step of the deploy process: `KubernetesDeploy.validate_configuration.duration`, `KubernetesDeploy.discover_resources.duration`, `KubernetesDeploy.validate_resources.duration`, `KubernetesDeploy.initial_status.duration`, `KubernetesDeploy.create_ejson_secrets.duration`, `KubernetesDeploy.apply_all.duration`, `KubernetesDeploy.sync.duration`
7
+ - **[Breaking change]** `KubernetesDeploy.resource.duration` no longer includes `sha` or `resource` tags. ([#392](https://github.com/Shopify/kubernetes-deploy/pull/392))
8
+
9
+ *Enhancements*
10
+ - Roles are now predeployed before RoleBindings ([#380](https://github.com/Shopify/kubernetes-deploy/pull/380))
11
+ - Several performance enhancements for deploys to namespaces with hundreds of resources.
12
+ - KubernetesDeploy no longer modifies the global StatsD configuration when used as a gem ([#384](https://github.com/Shopify/kubernetes-deploy/pull/384))
13
+
14
+ *Bug fixes*
15
+ - Handle out-of-order arrival of entries from different streams when processing logs ([#401](https://github.com/Shopify/kubernetes-deploy/pull/401))
16
+
1
17
  ## 0.22.0
2
18
 
3
19
  *Features*
data/README.md CHANGED
@@ -50,6 +50,10 @@ This repo also includes related tools for [running tasks](#kubernetes-run) and [
50
50
  * [Prerequisites](#prerequisites-1)
51
51
  * [Usage](#usage-2)
52
52
 
53
+ **KUBERNETES-RENDER**
54
+ * [Prerequisites](#prerequisites-2)
55
+ * [Usage](#usage-3)
56
+
53
57
  **DEVELOPMENT**
54
58
  * [Setup](#setup)
55
59
  * [Running the test suite locally](#running-the-test-suite-locally)
@@ -371,6 +375,34 @@ Based on this specification `kubernetes-run` will create a new pod with the entr
371
375
 
372
376
 
373
377
 
378
+ # kubernetes-render
379
+
380
+ `kubernetes-render` is a tool for rendering ERB templates to raw Kubernetes YAML. It's useful for seeing what `kubernetes-deploy` does before actually invoking `kubectl` on the rendered YAML. It's also useful for outputting YAML that can be passed to other tools, for validation or introspection purposes.
381
+
382
+
383
+ ## Prerequisites
384
+
385
+ * `kubernetes-render` does __not__ require a running cluster or an active kubernetes context, which is nice if you want to run it in a CI environment, potentially alongside something like https://github.com/garethr/kubeval to make sure your configuration is sound.
386
+ * Like the other `kubernetes-deploy` commands, `kubernetes-render` requires the `$REVISION` environment variable to be set, and will make it available as `current_sha` in your ERB templates.
387
+
388
+ ## Usage
389
+
390
+ To render all templates in your template dir, run:
391
+
392
+ ```
393
+ kubernetes-render --template-dir=./path/to/template/dir
394
+ ```
395
+
396
+ To render some templates in a template dir, run kubernetes-render with the names of the templates to render:
397
+
398
+ ```
399
+ kubernetes-render --template-dir=./path/to/template/dir this-template.yaml.erb that-template.yaml.erb
400
+ ```
401
+
402
+ *Options:*
403
+
404
+ - `--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`.
405
+ - `--bindings=BINDINGS`: Makes additional variables available to your ERB templates. For example, `kubernetes-render --bindings=color=blue,size=large some-template.yaml.erb` will expose `color` and `size` to `some-template.yaml.erb`.
374
406
 
375
407
 
376
408
  # Development
@@ -40,23 +40,10 @@ ARGV.options do |opts|
40
40
  opts.parse!
41
41
  end
42
42
 
43
- if !template_dir && ENV.key?("ENVIRONMENT")
44
- template_dir = "config/deploy/#{ENV['ENVIRONMENT']}"
45
- end
46
-
47
- if !template_dir || template_dir.empty?
48
- puts "Template directory is unknown. " \
49
- "Either specify --template-dir argument or set $ENVIRONMENT to use config/deploy/$ENVIRONMENT as a default path."
50
- exit 1
51
- end
52
-
53
- revision = ENV.fetch('REVISION') do
54
- puts "ENV['REVISION'] is missing. Please specify the commit SHA"
55
- exit 1
56
- end
57
-
58
43
  namespace = ARGV[0]
59
44
  context = ARGV[1]
45
+ template_dir = KubernetesDeploy::OptionsHelper.default_and_check_template_dir(template_dir)
46
+ revision = KubernetesDeploy::OptionsHelper.revision_from_environment
60
47
  logger = KubernetesDeploy::FormattedLogger.build(namespace, context, verbose_prefix: verbose_log_prefix)
61
48
 
62
49
  runner = KubernetesDeploy::DeployTask.new(
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'kubernetes-deploy'
5
+ require 'kubernetes-deploy/render_task'
6
+ require 'optparse'
7
+
8
+ template_dir = nil
9
+ bindings = {}
10
+
11
+ ARGV.options do |opts|
12
+ opts.on("--bindings=BINDINGS", "Expose additional variables to ERB templates " \
13
+ "(format: k1=v1,k2=v2, JSON string or file (JSON or YAML) path prefixed by '@')") do |binds|
14
+ bindings.merge!(KubernetesDeploy::BindingsParser.parse(binds))
15
+ end
16
+ opts.on("--template-dir=DIR", "Set the template dir (default: config/deploy/$ENVIRONMENT)") { |v| template_dir = v }
17
+ opts.parse!
18
+ end
19
+
20
+ templates = ARGV
21
+ logger = KubernetesDeploy::FormattedLogger.build(verbose_prefix: false)
22
+ revision = KubernetesDeploy::OptionsHelper.revision_from_environment
23
+
24
+ runner = KubernetesDeploy::RenderTask.new(
25
+ logger: logger,
26
+ current_sha: revision,
27
+ template_dir: template_dir,
28
+ bindings: bindings,
29
+ )
30
+
31
+ success = runner.run(STDOUT, templates)
32
+ exit 1 unless success
@@ -15,12 +15,12 @@ Gem::Specification.new do |spec|
15
15
  spec.homepage = "https://github.com/Shopify/kubernetes-deploy"
16
16
  spec.license = "MIT"
17
17
 
18
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ spec.files = %x(git ls-files -z).split("\x0").reject do |f|
19
19
  f.match(%r{^(test|spec|features)/})
20
20
  end
21
21
  spec.bindir = "exe"
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
- spec.require_paths = ["lib"]
23
+ spec.require_paths = %w(lib)
24
24
 
25
25
  spec.required_ruby_version = '>= 2.3.0'
26
26
  spec.add_dependency "activesupport", ">= 5.0"
@@ -28,7 +28,9 @@ Gem::Specification.new do |spec|
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"
31
- spec.add_dependency "statsd-instrument", "~> 2.3"
31
+ spec.add_dependency "statsd-instrument", '~> 2.3', '>= 2.3.2'
32
+ spec.add_dependency "oj", "~> 3.7"
33
+ spec.add_dependency "concurrent-ruby", "~> 1.1"
32
34
 
33
35
  spec.add_development_dependency "bundler"
34
36
  spec.add_development_dependency "rake", "~> 10.0"
@@ -11,16 +11,18 @@ require 'active_support/duration'
11
11
  require 'colorized_string'
12
12
 
13
13
  require 'kubernetes-deploy/version'
14
+ require 'kubernetes-deploy/oj'
14
15
  require 'kubernetes-deploy/errors'
15
16
  require 'kubernetes-deploy/formatted_logger'
16
- require 'kubernetes-deploy/deploy_task'
17
+ require 'kubernetes-deploy/options_helper'
17
18
  require 'kubernetes-deploy/statsd'
19
+ require 'kubernetes-deploy/deploy_task'
18
20
  require 'kubernetes-deploy/concurrency'
19
21
  require 'kubernetes-deploy/bindings_parser'
20
22
  require 'kubernetes-deploy/duration_parser'
21
- require 'kubernetes-deploy/sync_mediator'
23
+ require 'kubernetes-deploy/resource_cache'
22
24
 
23
25
  module KubernetesDeploy
24
26
  MIN_KUBE_VERSION = '1.9.0'
25
- KubernetesDeploy::StatsD.build
27
+ StatsD.build
26
28
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KubernetesDeploy
4
+ class ClusterResourceDiscovery
5
+ def initialize(namespace:, context:, logger:, namespace_tags:)
6
+ @namespace = namespace
7
+ @context = context
8
+ @logger = logger
9
+ @namespace_tags = namespace_tags
10
+ end
11
+
12
+ def crds
13
+ @crds ||= fetch_crds.map do |cr_def|
14
+ CustomResourceDefinition.new(namespace: @namespace, context: @context, logger: @logger,
15
+ definition: cr_def, statsd_tags: @namespace_tags)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def fetch_crds
22
+ raw_json, _, st = kubectl.run("get", "CustomResourceDefinition", "-a", "--output=json", attempts: 5)
23
+ if st.success?
24
+ JSON.parse(raw_json)["items"]
25
+ else
26
+ []
27
+ end
28
+ end
29
+
30
+ def kubectl
31
+ @kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: true)
32
+ end
33
+ end
34
+ end
@@ -5,18 +5,20 @@ module KubernetesDeploy
5
5
 
6
6
  DEFAULT_LINE_LIMIT = 250
7
7
 
8
- def initialize(parent_id:, container_name:, logger:)
8
+ def initialize(parent_id:, container_name:, namespace:, context:, logger:)
9
9
  @parent_id = parent_id
10
10
  @container_name = container_name
11
+ @namespace = namespace
12
+ @context = context
11
13
  @logger = logger
12
14
  @lines = []
13
15
  @next_print_index = 0
14
16
  end
15
17
 
16
- def sync(kubectl)
17
- new_logs = fetch_latest(kubectl)
18
+ def sync
19
+ new_logs = fetch_latest
18
20
  return unless new_logs.present?
19
- @lines += deduplicate(new_logs)
21
+ @lines += sort_and_deduplicate(new_logs)
20
22
  end
21
23
 
22
24
  def empty?
@@ -39,7 +41,7 @@ module KubernetesDeploy
39
41
 
40
42
  private
41
43
 
42
- def fetch_latest(kubectl)
44
+ def fetch_latest
43
45
  cmd = ["logs", @parent_id, "--container=#{container_name}", "--timestamps"]
44
46
  cmd << if @last_timestamp.present?
45
47
  "--since-time=#{rfc3339_timestamp(@last_timestamp)}"
@@ -50,22 +52,32 @@ module KubernetesDeploy
50
52
  out.split("\n")
51
53
  end
52
54
 
55
+ def kubectl
56
+ @kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: false)
57
+ end
58
+
53
59
  def rfc3339_timestamp(time)
54
60
  time.strftime("%FT%T.%N%:z")
55
61
  end
56
62
 
57
- def deduplicate(logs)
58
- deduped = []
59
- check_for_duplicate = true
63
+ def sort_and_deduplicate(logs)
64
+ parsed_lines = logs.map { |line| split_timestamped_line(line) }
65
+ sorted_lines = parsed_lines.sort do |(timestamp1, _msg1), (timestamp2, _msg2)|
66
+ if timestamp1.nil?
67
+ -1
68
+ elsif timestamp2.nil?
69
+ 1
70
+ else
71
+ timestamp1 <=> timestamp2
72
+ end
73
+ end
60
74
 
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
75
+ deduped = []
76
+ sorted_lines.each do |timestamp, msg|
77
+ next if likely_duplicate?(timestamp)
65
78
  @last_timestamp = timestamp if timestamp
66
79
  deduped << msg
67
80
  end
68
-
69
81
  deduped
70
82
  end
71
83
 
@@ -38,11 +38,13 @@ require 'kubernetes-deploy/kubectl'
38
38
  require 'kubernetes-deploy/kubeclient_builder'
39
39
  require 'kubernetes-deploy/ejson_secret_provisioner'
40
40
  require 'kubernetes-deploy/renderer'
41
- require 'kubernetes-deploy/resource_discovery'
41
+ require 'kubernetes-deploy/cluster_resource_discovery'
42
+ require 'kubernetes-deploy/template_discovery'
42
43
 
43
44
  module KubernetesDeploy
44
45
  class DeployTask
45
46
  include KubeclientBuilder
47
+ extend KubernetesDeploy::StatsD::MeasureMethods
46
48
 
47
49
  PREDEPLOY_SEQUENCE = %w(
48
50
  ResourceQuota
@@ -52,6 +54,7 @@ module KubernetesDeploy
52
54
  ConfigMap
53
55
  PersistentVolumeClaim
54
56
  ServiceAccount
57
+ Role
55
58
  RoleBinding
56
59
  Pod
57
60
  )
@@ -85,7 +88,7 @@ module KubernetesDeploy
85
88
  policy/v1beta1/PodDisruptionBudget
86
89
  batch/v1beta1/CronJob
87
90
  )
88
- wl + cluster_resource_discoverer.crds(@sync_mediator).select(&:prunable?).map(&:group_version_kind)
91
+ wl + cluster_resource_discoverer.crds.select(&:prunable?).map(&:group_version_kind)
89
92
  end
90
93
 
91
94
  def server_version
@@ -108,7 +111,6 @@ module KubernetesDeploy
108
111
  logger: @logger,
109
112
  bindings: bindings,
110
113
  )
111
- @sync_mediator = SyncMediator.new(namespace: @namespace, context: @context, logger: @logger)
112
114
  end
113
115
 
114
116
  def run(*args)
@@ -124,33 +126,16 @@ module KubernetesDeploy
124
126
 
125
127
  @logger.phase_heading("Initializing deploy")
126
128
  validate_configuration(allow_protected_ns: allow_protected_ns, prune: prune)
127
- confirm_context_exists
128
- confirm_namespace_exists
129
- @namespace_tags |= tags_from_namespace_labels
130
129
  resources = discover_resources
131
- validate_definitions(resources)
130
+ validate_resources(resources)
132
131
 
133
132
  @logger.phase_heading("Checking initial resource statuses")
134
- @sync_mediator.sync(resources)
135
- resources.each { |r| @logger.info(r.pretty_status) }
136
-
137
- ejson = EjsonSecretProvisioner.new(
138
- namespace: @namespace,
139
- context: @context,
140
- template_dir: @template_dir,
141
- logger: @logger,
142
- prune: prune,
143
- )
144
- if ejson.secret_changes_required?
145
- @logger.phase_heading("Deploying kubernetes secrets from #{EjsonSecretProvisioner::EJSON_SECRETS_FILE}")
146
- ejson.run
147
- end
133
+ check_initial_status(resources)
134
+ create_ejson_secrets(prune)
148
135
 
149
136
  if deploy_has_priority_resources?(resources)
150
137
  @logger.phase_heading("Predeploying priority resources")
151
- start_priority_resource = Time.now.utc
152
138
  predeploy_priority_resources(resources)
153
- ::StatsD.measure('priority_resources.duration', StatsD.duration(start_priority_resource), tags: statsd_tags)
154
139
  end
155
140
 
156
141
  @logger.phase_heading("Deploying all resources")
@@ -159,9 +144,7 @@ module KubernetesDeploy
159
144
  end
160
145
 
161
146
  if verify_result
162
- start_normal_resource = Time.now.utc
163
- deploy_resources(resources, prune: prune, verify: true)
164
- ::StatsD.measure('normal_resources.duration', StatsD.duration(start_normal_resource), tags: statsd_tags)
147
+ deploy_all_resources(resources, prune: prune, verify: true)
165
148
  failed_resources = resources.reject(&:deploy_succeeded?)
166
149
  success = failed_resources.empty?
167
150
  if !success && failed_resources.all?(&:deploy_timed_out?)
@@ -169,7 +152,7 @@ module KubernetesDeploy
169
152
  end
170
153
  raise FatalDeploymentError unless success
171
154
  else
172
- deploy_resources(resources, prune: prune, verify: false)
155
+ deploy_all_resources(resources, prune: prune, verify: false)
173
156
  @logger.summary.add_action("deployed #{resources.length} #{'resource'.pluralize(resources.length)}")
174
157
  warning = <<~MSG
175
158
  Deploy result verification is disabled for this deploy.
@@ -177,32 +160,37 @@ module KubernetesDeploy
177
160
  MSG
178
161
  @logger.summary.add_paragraph(ColorizedString.new(warning).yellow)
179
162
  end
180
- ::StatsD.event("Deployment of #{@namespace} succeeded",
163
+ StatsD.event("Deployment of #{@namespace} succeeded",
181
164
  "Successfully deployed all #{@namespace} resources to #{@context}",
182
165
  alert_type: "success", tags: statsd_tags << "status:success")
183
- ::StatsD.measure('all_resources.duration', StatsD.duration(start), tags: statsd_tags << "status:success")
166
+ StatsD.distribution('all_resources.duration', StatsD.duration(start), tags: statsd_tags << "status:success")
184
167
  @logger.print_summary(:success)
185
168
  rescue DeploymentTimeoutError
186
169
  @logger.print_summary(:timed_out)
187
- ::StatsD.event("Deployment of #{@namespace} timed out",
170
+ StatsD.event("Deployment of #{@namespace} timed out",
188
171
  "One or more #{@namespace} resources failed to deploy to #{@context} in time",
189
172
  alert_type: "error", tags: statsd_tags << "status:timeout")
190
- ::StatsD.measure('all_resources.duration', StatsD.duration(start), tags: statsd_tags << "status:timeout")
173
+ StatsD.distribution('all_resources.duration', StatsD.duration(start), tags: statsd_tags << "status:timeout")
191
174
  raise
192
175
  rescue FatalDeploymentError => error
193
176
  @logger.summary.add_action(error.message) if error.message != error.class.to_s
194
177
  @logger.print_summary(:failure)
195
- ::StatsD.event("Deployment of #{@namespace} failed",
178
+ StatsD.event("Deployment of #{@namespace} failed",
196
179
  "One or more #{@namespace} resources failed to deploy to #{@context}",
197
180
  alert_type: "error", tags: statsd_tags << "status:failed")
198
- ::StatsD.measure('all_resources.duration', StatsD.duration(start), tags: statsd_tags << "status:failed")
181
+ StatsD.distribution('all_resources.duration', StatsD.duration(start), tags: statsd_tags << "status:failed")
199
182
  raise
200
183
  end
201
184
 
202
185
  private
203
186
 
204
187
  def cluster_resource_discoverer
205
- ResourceDiscovery.new(namespace: @namespace, context: @context, logger: @logger, namespace_tags: @namespace_tags)
188
+ @cluster_resource_discoverer ||= ClusterResourceDiscovery.new(
189
+ namespace: @namespace,
190
+ context: @context,
191
+ logger: @logger,
192
+ namespace_tags: @namespace_tags
193
+ )
206
194
  end
207
195
 
208
196
  def deploy_has_priority_resources?(resources)
@@ -219,7 +207,7 @@ module KubernetesDeploy
219
207
  fail_count = failed_resources.length
220
208
  if fail_count > 0
221
209
  KubernetesDeploy::Concurrency.split_across_threads(failed_resources) do |r|
222
- r.sync_debug_info(@sync_mediator.kubectl)
210
+ r.sync_debug_info(kubectl)
223
211
  end
224
212
  failed_resources.each { |r| @logger.summary.add_paragraph(r.debug_message) }
225
213
  raise FatalDeploymentError, "Failed to deploy #{fail_count} priority #{'resource'.pluralize(fail_count)}"
@@ -227,8 +215,9 @@ module KubernetesDeploy
227
215
  @logger.blank_line
228
216
  end
229
217
  end
218
+ measure_method(:predeploy_priority_resources, 'priority_resources.duration')
230
219
 
231
- def validate_definitions(resources)
220
+ def validate_resources(resources)
232
221
  KubernetesDeploy::Concurrency.split_across_threads(resources) { |r| r.validate_definition(kubectl) }
233
222
  failed_resources = resources.select(&:validation_failed?)
234
223
  return unless failed_resources.present?
@@ -239,14 +228,35 @@ module KubernetesDeploy
239
228
  end
240
229
  raise FatalDeploymentError, "Template validation failed"
241
230
  end
231
+ measure_method(:validate_resources)
232
+
233
+ def check_initial_status(resources)
234
+ cache = ResourceCache.new(@namespace, @context, @logger)
235
+ KubernetesDeploy::Concurrency.split_across_threads(resources) { |r| r.sync(cache) }
236
+ resources.each { |r| @logger.info(r.pretty_status) }
237
+ end
238
+ measure_method(:check_initial_status, "initial_status.duration")
239
+
240
+ def create_ejson_secrets(prune)
241
+ ejson = EjsonSecretProvisioner.new(
242
+ namespace: @namespace,
243
+ context: @context,
244
+ template_dir: @template_dir,
245
+ logger: @logger,
246
+ prune: prune,
247
+ )
248
+ return unless ejson.secret_changes_required?
249
+
250
+ @logger.phase_heading("Deploying kubernetes secrets from #{EjsonSecretProvisioner::EJSON_SECRETS_FILE}")
251
+ ejson.run
252
+ end
253
+ measure_method(:create_ejson_secrets)
242
254
 
243
255
  def discover_resources
244
256
  resources = []
245
257
  @logger.info("Discovering templates:")
246
258
 
247
- Dir.foreach(@template_dir) do |filename|
248
- next unless filename.end_with?(".yml.erb", ".yml", ".yaml", ".yaml.erb")
249
-
259
+ TemplateDiscovery.new(@template_dir).templates.each do |filename|
250
260
  split_templates(filename) do |r_def|
251
261
  r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger,
252
262
  definition: r_def, statsd_tags: @namespace_tags)
@@ -260,11 +270,12 @@ module KubernetesDeploy
260
270
  end
261
271
  resources
262
272
  end
273
+ measure_method(:discover_resources)
263
274
 
264
275
  def split_templates(filename)
265
276
  file_content = File.read(File.join(@template_dir, filename))
266
277
  rendered_content = @renderer.render_template(filename, file_content)
267
- YAML.load_stream(rendered_content) do |doc|
278
+ YAML.load_stream(rendered_content, "<rendered> #{filename}") do |doc|
268
279
  next if doc.blank?
269
280
  unless doc.is_a?(Hash)
270
281
  raise InvalidTemplateError.new("Template is not a valid Kubernetes manifest",
@@ -283,15 +294,11 @@ module KubernetesDeploy
283
294
 
284
295
  def record_invalid_template(err:, filename:, content:)
285
296
  debug_msg = ColorizedString.new("Invalid template: #{filename}\n").red
286
- debug_msg += "> Error message:\n#{indent_four(err)}"
287
- debug_msg += "\n> Template content:\n#{indent_four(content)}"
297
+ debug_msg += "> Error message:\n#{FormattedLogger.indent_four(err)}"
298
+ debug_msg += "\n> Template content:\n#{FormattedLogger.indent_four(content)}"
288
299
  @logger.summary.add_paragraph(debug_msg)
289
300
  end
290
301
 
291
- def indent_four(str)
292
- " " + str.gsub("\n", "\n ")
293
- end
294
-
295
302
  def validate_configuration(allow_protected_ns:, prune:)
296
303
  errors = []
297
304
  if ENV["KUBECONFIG"].blank?
@@ -340,8 +347,12 @@ module KubernetesDeploy
340
347
  raise FatalDeploymentError, "Configuration invalid"
341
348
  end
342
349
 
350
+ confirm_context_exists
351
+ confirm_namespace_exists
352
+ @namespace_tags |= tags_from_namespace_labels
343
353
  @logger.info("All required parameters and files are present")
344
354
  end
355
+ measure_method(:validate_configuration)
345
356
 
346
357
  def deploy_resources(resources, prune: false, verify:, record_summary: true)
347
358
  return if resources.empty?
@@ -388,12 +399,17 @@ module KubernetesDeploy
388
399
  apply_all(applyables, prune)
389
400
 
390
401
  if verify
391
- watcher = ResourceWatcher.new(resources: resources, sync_mediator: @sync_mediator,
392
- logger: @logger, deploy_started_at: deploy_started_at, timeout: @max_watch_seconds)
402
+ watcher = ResourceWatcher.new(resources: resources, logger: @logger, deploy_started_at: deploy_started_at,
403
+ timeout: @max_watch_seconds, namespace: @namespace, context: @context, sha: @current_sha)
393
404
  watcher.run(record_summary: record_summary)
394
405
  end
395
406
  end
396
407
 
408
+ def deploy_all_resources(resources, prune: false, verify:, record_summary: true)
409
+ deploy_resources(resources, prune: prune, verify: verify, record_summary: record_summary)
410
+ end
411
+ measure_method(:deploy_all_resources, 'normal_resources.duration')
412
+
397
413
  def apply_all(resources, prune)
398
414
  return unless resources.present?
399
415
  command = %w(apply)
@@ -421,6 +437,7 @@ module KubernetesDeploy
421
437
  end
422
438
  end
423
439
  end
440
+ measure_method(:apply_all)
424
441
 
425
442
  def log_pruning(kubectl_output)
426
443
  pruned = kubectl_output.scan(/^(.*) pruned$/)
@@ -446,8 +463,9 @@ module KubernetesDeploy
446
463
  end
447
464
 
448
465
  if unidentified_errors.present?
449
- msg = "#{ColorizedString.new('Unidentified error(s):').red}\n#{indent_four(unidentified_errors.join)}"
450
- @logger.summary.add_paragraph(msg)
466
+ heading = ColorizedString.new('Unidentified error(s):').red
467
+ msg = FormattedLogger.indent_four(unidentified_errors.join)
468
+ @logger.summary.add_paragraph("#{heading}\n#{msg}")
451
469
  end
452
470
  end
453
471