kubernetes-deploy 0.29.0 → 1.0.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.nightly.yml +7 -0
  3. data/.rubocop.yml +0 -12
  4. data/.shopify-build/{kubernetes-deploy.yml → krane.yml} +8 -2
  5. data/1.0-Upgrade.md +109 -0
  6. data/CHANGELOG.md +60 -0
  7. data/CONTRIBUTING.md +2 -2
  8. data/Gemfile +1 -0
  9. data/README.md +86 -2
  10. data/dev.yml +3 -1
  11. data/dev/flamegraph-from-tests +1 -1
  12. data/exe/kubernetes-deploy +12 -9
  13. data/exe/kubernetes-render +9 -7
  14. data/exe/kubernetes-restart +3 -3
  15. data/exe/kubernetes-run +1 -1
  16. data/kubernetes-deploy.gemspec +5 -5
  17. data/lib/krane.rb +5 -3
  18. data/lib/{kubernetes-deploy → krane}/bindings_parser.rb +1 -1
  19. data/lib/krane/cli/deploy_command.rb +25 -13
  20. data/lib/krane/cli/global_deploy_command.rb +55 -0
  21. data/lib/krane/cli/krane.rb +12 -3
  22. data/lib/krane/cli/render_command.rb +19 -9
  23. data/lib/krane/cli/restart_command.rb +4 -4
  24. data/lib/krane/cli/run_command.rb +4 -4
  25. data/lib/krane/cli/version_command.rb +1 -1
  26. data/lib/krane/cluster_resource_discovery.rb +113 -0
  27. data/lib/{kubernetes-deploy → krane}/common.rb +8 -9
  28. data/lib/krane/concerns/template_reporting.rb +29 -0
  29. data/lib/{kubernetes-deploy → krane}/concurrency.rb +1 -1
  30. data/lib/{kubernetes-deploy → krane}/container_logs.rb +3 -2
  31. data/lib/{kubernetes-deploy → krane}/deferred_summary_logging.rb +2 -2
  32. data/lib/{kubernetes-deploy → krane}/delayed_exceptions.rb +0 -0
  33. data/lib/krane/deploy_task.rb +16 -0
  34. data/lib/krane/deploy_task_config_validator.rb +29 -0
  35. data/lib/krane/deprecated_deploy_task.rb +404 -0
  36. data/lib/{kubernetes-deploy → krane}/duration_parser.rb +1 -3
  37. data/lib/{kubernetes-deploy → krane}/ejson_secret_provisioner.rb +10 -13
  38. data/lib/krane/errors.rb +28 -0
  39. data/lib/{kubernetes-deploy → krane}/formatted_logger.rb +2 -2
  40. data/lib/krane/global_deploy_task.rb +210 -0
  41. data/lib/krane/global_deploy_task_config_validator.rb +12 -0
  42. data/lib/{kubernetes-deploy → krane}/kubeclient_builder.rb +13 -5
  43. data/lib/{kubernetes-deploy → krane}/kubectl.rb +14 -16
  44. data/lib/{kubernetes-deploy → krane}/kubernetes_resource.rb +110 -27
  45. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/cloudsql.rb +1 -1
  46. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/config_map.rb +1 -1
  47. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/cron_job.rb +1 -1
  48. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/custom_resource.rb +2 -2
  49. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/custom_resource_definition.rb +1 -5
  50. data/lib/krane/kubernetes_resource/daemon_set.rb +90 -0
  51. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/deployment.rb +2 -2
  52. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/horizontal_pod_autoscaler.rb +1 -1
  53. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/ingress.rb +1 -1
  54. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/job.rb +1 -1
  55. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/network_policy.rb +1 -1
  56. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/persistent_volume_claim.rb +1 -1
  57. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod.rb +6 -2
  58. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_disruption_budget.rb +2 -2
  59. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_set_base.rb +3 -3
  60. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_template.rb +1 -1
  61. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/replica_set.rb +2 -2
  62. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/resource_quota.rb +1 -1
  63. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/role.rb +1 -1
  64. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/role_binding.rb +1 -1
  65. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/secret.rb +1 -1
  66. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/service.rb +2 -2
  67. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/service_account.rb +1 -1
  68. data/lib/{kubernetes-deploy → krane}/kubernetes_resource/stateful_set.rb +2 -2
  69. data/lib/{kubernetes-deploy → krane}/label_selector.rb +1 -1
  70. data/lib/{kubernetes-deploy → krane}/oj.rb +0 -0
  71. data/lib/{kubernetes-deploy → krane}/options_helper.rb +2 -2
  72. data/lib/{kubernetes-deploy → krane}/remote_logs.rb +2 -2
  73. data/lib/krane/render_task.rb +149 -0
  74. data/lib/{kubernetes-deploy → krane}/renderer.rb +1 -1
  75. data/lib/{kubernetes-deploy → krane}/resource_cache.rb +10 -9
  76. data/lib/krane/resource_deployer.rb +265 -0
  77. data/lib/{kubernetes-deploy → krane}/resource_watcher.rb +24 -25
  78. data/lib/krane/restart_task.rb +228 -0
  79. data/lib/{kubernetes-deploy → krane}/rollout_conditions.rb +1 -1
  80. data/lib/krane/runner_task.rb +212 -0
  81. data/lib/{kubernetes-deploy → krane}/runner_task_config_validator.rb +1 -1
  82. data/lib/{kubernetes-deploy → krane}/statsd.rb +13 -27
  83. data/lib/krane/task_config.rb +22 -0
  84. data/lib/{kubernetes-deploy → krane}/task_config_validator.rb +1 -1
  85. data/lib/{kubernetes-deploy → krane}/template_sets.rb +5 -5
  86. data/lib/krane/version.rb +4 -0
  87. data/lib/kubernetes-deploy/deploy_task.rb +6 -608
  88. data/lib/kubernetes-deploy/errors.rb +1 -26
  89. data/lib/kubernetes-deploy/render_task.rb +5 -122
  90. data/lib/kubernetes-deploy/rescue_krane_exceptions.rb +18 -0
  91. data/lib/kubernetes-deploy/restart_task.rb +6 -198
  92. data/lib/kubernetes-deploy/runner_task.rb +6 -184
  93. metadata +96 -70
  94. data/lib/kubernetes-deploy/cluster_resource_discovery.rb +0 -34
  95. data/lib/kubernetes-deploy/kubernetes_resource/daemon_set.rb +0 -54
  96. data/lib/kubernetes-deploy/task_config.rb +0 -16
  97. data/lib/kubernetes-deploy/version.rb +0 -4
@@ -10,15 +10,14 @@ require 'active_support/core_ext/hash/keys'
10
10
  require 'active_support/core_ext/array/conversions'
11
11
  require 'colorized_string'
12
12
 
13
- require 'kubernetes-deploy/version'
14
- require 'kubernetes-deploy/oj'
15
- require 'kubernetes-deploy/errors'
16
- require 'kubernetes-deploy/formatted_logger'
17
- require 'kubernetes-deploy/statsd'
18
- require 'kubernetes-deploy/task_config'
19
- require 'kubernetes-deploy/task_config_validator'
13
+ require 'krane/version'
14
+ require 'krane/oj'
15
+ require 'krane/errors'
16
+ require 'krane/formatted_logger'
17
+ require 'krane/statsd'
18
+ require 'krane/task_config'
19
+ require 'krane/task_config_validator'
20
20
 
21
- module KubernetesDeploy
21
+ module Krane
22
22
  MIN_KUBE_VERSION = '1.11.0'
23
- StatsD.build
24
23
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Krane
4
+ module TemplateReporting
5
+ def record_invalid_template(logger:, err:, filename:, content: nil)
6
+ debug_msg = ColorizedString.new("Invalid template: #{filename}\n").red
7
+ debug_msg += "> Error message:\n#{Krane::FormattedLogger.indent_four(err)}"
8
+ if content
9
+ debug_msg += if content =~ /kind:\s*Secret/
10
+ "\n> Template content: Suppressed because it may contain a Secret"
11
+ else
12
+ "\n> Template content:\n#{Krane::FormattedLogger.indent_four(content)}"
13
+ end
14
+ end
15
+ logger.summary.add_paragraph(debug_msg)
16
+ end
17
+
18
+ def record_warnings(logger:, warning:, filename:)
19
+ warn_msg = "Template warning: #{filename}\n"
20
+ warn_msg += "> Warning message:\n#{Krane::FormattedLogger.indent_four(warning)}"
21
+ logger.summary.add_paragraph(ColorizedString.new(warn_msg).yellow)
22
+ end
23
+
24
+ def add_para_from_list(logger:, action:, enum:)
25
+ logger.summary.add_action(action)
26
+ logger.summary.add_paragraph(enum.map { |e| "- #{e}" }.join("\n"))
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- module KubernetesDeploy
2
+ module Krane
3
3
  module Concurrency
4
4
  MAX_THREADS = 8
5
5
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- module KubernetesDeploy
2
+ module Krane
3
3
  class ContainerLogs
4
4
  attr_reader :lines, :container_name
5
5
 
@@ -59,7 +59,8 @@ module KubernetesDeploy
59
59
  end
60
60
 
61
61
  def kubectl
62
- @kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: false)
62
+ task_config = TaskConfig.new(@context, @namespace, @logger)
63
+ @kubectl ||= Kubectl.new(task_config: task_config, log_failure_by_default: false)
63
64
  end
64
65
 
65
66
  def rfc3339_timestamp(time)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  require 'colorized_string'
3
3
 
4
- module KubernetesDeploy
5
- # Adds the methods kubernetes-deploy requires to your logger class.
4
+ module Krane
5
+ # Adds the methods krane requires to your logger class.
6
6
  # These methods include helpers for logging consistent headings, as well as facilities for
7
7
  # displaying key information later, in a summary section, rather than when it occurred.
8
8
  module DeferredSummaryLogging
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'krane/deprecated_deploy_task'
4
+
5
+ module Krane
6
+ class DeployTask < Krane::DeprecatedDeployTask
7
+ def initialize(**args)
8
+ raise "Use Krane::DeployGlobalTask to deploy global resources" if args[:allow_globals]
9
+ super(args.merge(allow_globals: false))
10
+ end
11
+
12
+ def prune_whitelist
13
+ cluster_resource_discoverer.prunable_resources(namespaced: true)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module Krane
3
+ class DeployTaskConfigValidator < TaskConfigValidator
4
+ def initialize(protected_namespaces, allow_protected_ns, prune, *arguments)
5
+ super(*arguments)
6
+ @protected_namespaces = protected_namespaces
7
+ @allow_protected_ns = allow_protected_ns
8
+ @prune = prune
9
+ @validations += %i(validate_protected_namespaces)
10
+ end
11
+
12
+ private
13
+
14
+ def validate_protected_namespaces
15
+ if @protected_namespaces.include?(namespace)
16
+ if @allow_protected_ns && @prune
17
+ @errors << "Refusing to deploy to protected namespace '#{namespace}' with pruning enabled"
18
+ elsif @allow_protected_ns
19
+ logger.warn("You're deploying to protected namespace #{namespace}, which cannot be pruned.")
20
+ logger.warn("Existing resources can only be removed manually with kubectl. " \
21
+ "Removing templates from the set deployed will have no effect.")
22
+ logger.warn("***Please do not deploy to #{namespace} unless you really know what you are doing.***")
23
+ else
24
+ @errors << "Refusing to deploy to protected namespace '#{namespace}'"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,404 @@
1
+ # frozen_string_literal: true
2
+ require 'yaml'
3
+ require 'shellwords'
4
+ require 'tempfile'
5
+ require 'fileutils'
6
+
7
+ require 'krane/common'
8
+ require 'krane/concurrency'
9
+ require 'krane/resource_cache'
10
+ require 'krane/kubernetes_resource'
11
+ %w(
12
+ custom_resource
13
+ cloudsql
14
+ config_map
15
+ deployment
16
+ ingress
17
+ persistent_volume_claim
18
+ pod
19
+ network_policy
20
+ service
21
+ pod_template
22
+ pod_disruption_budget
23
+ replica_set
24
+ service_account
25
+ daemon_set
26
+ resource_quota
27
+ stateful_set
28
+ cron_job
29
+ job
30
+ custom_resource_definition
31
+ horizontal_pod_autoscaler
32
+ secret
33
+ ).each do |subresource|
34
+ require "krane/kubernetes_resource/#{subresource}"
35
+ end
36
+ require 'krane/resource_watcher'
37
+ require 'krane/kubectl'
38
+ require 'krane/kubeclient_builder'
39
+ require 'krane/ejson_secret_provisioner'
40
+ require 'krane/renderer'
41
+ require 'krane/cluster_resource_discovery'
42
+ require 'krane/template_sets'
43
+ require 'krane/deploy_task_config_validator'
44
+ require 'krane/resource_deployer'
45
+ require 'krane/concerns/template_reporting'
46
+
47
+ module Krane
48
+ # Ship resources to a namespace
49
+ class DeprecatedDeployTask
50
+ extend Krane::StatsD::MeasureMethods
51
+ include Krane::TemplateReporting
52
+
53
+ PROTECTED_NAMESPACES = %w(
54
+ default
55
+ kube-system
56
+ kube-public
57
+ )
58
+ # Things removed from default prune whitelist at https://github.com/kubernetes/kubernetes/blob/0dff56b4d88ec7551084bf89028dbeebf569620e/pkg/kubectl/cmd/apply.go#L411:
59
+ # core/v1/Namespace -- not namespaced
60
+ # core/v1/PersistentVolume -- not namespaced
61
+ # core/v1/Endpoints -- managed by services
62
+ # core/v1/PersistentVolumeClaim -- would delete data
63
+ # core/v1/ReplicationController -- superseded by deployments/replicasets
64
+
65
+ def predeploy_sequence
66
+ before_crs = %w(
67
+ ResourceQuota
68
+ NetworkPolicy
69
+ )
70
+ after_crs = %w(
71
+ ConfigMap
72
+ PersistentVolumeClaim
73
+ ServiceAccount
74
+ Role
75
+ RoleBinding
76
+ Secret
77
+ Pod
78
+ )
79
+
80
+ before_crs + cluster_resource_discoverer.crds.select(&:predeployed?).map(&:kind) + after_crs
81
+ end
82
+
83
+ def prune_whitelist
84
+ wl = %w(
85
+ core/v1/ConfigMap
86
+ core/v1/Pod
87
+ core/v1/Service
88
+ core/v1/ResourceQuota
89
+ core/v1/Secret
90
+ core/v1/ServiceAccount
91
+ core/v1/PodTemplate
92
+ core/v1/PersistentVolumeClaim
93
+ batch/v1/Job
94
+ apps/v1/ReplicaSet
95
+ apps/v1/DaemonSet
96
+ apps/v1/Deployment
97
+ extensions/v1beta1/Ingress
98
+ networking.k8s.io/v1/NetworkPolicy
99
+ apps/v1/StatefulSet
100
+ autoscaling/v1/HorizontalPodAutoscaler
101
+ policy/v1beta1/PodDisruptionBudget
102
+ batch/v1beta1/CronJob
103
+ rbac.authorization.k8s.io/v1/Role
104
+ rbac.authorization.k8s.io/v1/RoleBinding
105
+ )
106
+ wl + cluster_resource_discoverer.crds.select(&:prunable?).map(&:group_version_kind)
107
+ end
108
+
109
+ def server_version
110
+ kubectl.server_version
111
+ end
112
+
113
+ # Initializes the deploy task
114
+ #
115
+ # @param namespace [String] Kubernetes namespace
116
+ # @param context [String] Kubernetes context
117
+ # @param current_sha [String] The SHA of the commit
118
+ # @param logger [Object] Logger object (defaults to an instance of Krane::FormattedLogger)
119
+ # @param kubectl_instance [Kubectl] Kubectl instance
120
+ # @param bindings [Hash] Bindings parsed by Krane::BindingsParser
121
+ # @param max_watch_seconds [Integer] Timeout in seconds
122
+ # @param selector [Hash] Selector(s) parsed by Krane::LabelSelector
123
+ # @param template_paths [Array<String>] An array of template paths
124
+ # @param template_dir [String] Path to a directory with templates (deprecated)
125
+ # @param protected_namespaces [Array<String>] Array of protected Kubernetes namespaces (defaults
126
+ # to Krane::DeployTask::PROTECTED_NAMESPACES)
127
+ # @param render_erb [Boolean] Enable ERB rendering
128
+ def initialize(namespace:, context:, current_sha:, logger: nil, kubectl_instance: nil, bindings: {},
129
+ max_watch_seconds: nil, selector: nil, template_paths: [], template_dir: nil, protected_namespaces: nil,
130
+ render_erb: true, allow_globals: false)
131
+ template_dir = File.expand_path(template_dir) if template_dir
132
+ template_paths = (template_paths.map { |path| File.expand_path(path) } << template_dir).compact
133
+
134
+ @logger = logger || Krane::FormattedLogger.build(namespace, context)
135
+ @template_sets = TemplateSets.from_dirs_and_files(paths: template_paths, logger: @logger)
136
+ @task_config = Krane::TaskConfig.new(context, namespace, @logger)
137
+ @bindings = bindings
138
+ @namespace = namespace
139
+ @namespace_tags = []
140
+ @context = context
141
+ @current_sha = current_sha
142
+ @kubectl = kubectl_instance
143
+ @max_watch_seconds = max_watch_seconds
144
+ @selector = selector
145
+ @protected_namespaces = protected_namespaces || PROTECTED_NAMESPACES
146
+ @render_erb = render_erb
147
+ @allow_globals = allow_globals
148
+ end
149
+
150
+ # Runs the task, returning a boolean representing success or failure
151
+ #
152
+ # @return [Boolean]
153
+ def run(*args)
154
+ run!(*args)
155
+ true
156
+ rescue FatalDeploymentError
157
+ false
158
+ end
159
+
160
+ # Runs the task, raising exceptions in case of issues
161
+ #
162
+ # @param verify_result [Boolean] Wait for completion and verify success
163
+ # @param allow_protected_ns [Boolean] Enable deploying to protected namespaces
164
+ # @param prune [Boolean] Enable deletion of resources that do not appear in the template dir
165
+ #
166
+ # @return [nil]
167
+ def run!(verify_result: true, allow_protected_ns: false, prune: true)
168
+ start = Time.now.utc
169
+ @logger.reset
170
+
171
+ @logger.phase_heading("Initializing deploy")
172
+ validate_configuration(allow_protected_ns: allow_protected_ns, prune: prune)
173
+ resources = discover_resources
174
+ validate_resources(resources)
175
+
176
+ @logger.phase_heading("Checking initial resource statuses")
177
+ check_initial_status(resources)
178
+
179
+ if deploy_has_priority_resources?(resources)
180
+ @logger.phase_heading("Predeploying priority resources")
181
+ resource_deployer.predeploy_priority_resources(resources, predeploy_sequence)
182
+ end
183
+
184
+ @logger.phase_heading("Deploying all resources")
185
+ if @protected_namespaces.include?(@namespace) && prune
186
+ raise FatalDeploymentError, "Refusing to deploy to protected namespace '#{@namespace}' with pruning enabled"
187
+ end
188
+
189
+ resource_deployer.deploy!(resources, verify_result, prune)
190
+
191
+ StatsD.client.event("Deployment of #{@namespace} succeeded",
192
+ "Successfully deployed all #{@namespace} resources to #{@context}",
193
+ alert_type: "success", tags: statsd_tags + %w(status:success))
194
+ StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
195
+ tags: statsd_tags + %w(status:success))
196
+ @logger.print_summary(:success)
197
+ rescue DeploymentTimeoutError
198
+ @logger.print_summary(:timed_out)
199
+ StatsD.client.event("Deployment of #{@namespace} timed out",
200
+ "One or more #{@namespace} resources failed to deploy to #{@context} in time",
201
+ alert_type: "error", tags: statsd_tags + %w(status:timeout))
202
+ StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
203
+ tags: statsd_tags + %w(status:timeout))
204
+ raise
205
+ rescue FatalDeploymentError => error
206
+ @logger.summary.add_action(error.message) if error.message != error.class.to_s
207
+ @logger.print_summary(:failure)
208
+ StatsD.client.event("Deployment of #{@namespace} failed",
209
+ "One or more #{@namespace} resources failed to deploy to #{@context}",
210
+ alert_type: "error", tags: statsd_tags + %w(status:failed))
211
+ StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
212
+ tags: statsd_tags + %w(status:failed))
213
+ raise
214
+ end
215
+
216
+ private
217
+
218
+ def resource_deployer
219
+ @resource_deployer ||= Krane::ResourceDeployer.new(task_config: @task_config,
220
+ prune_whitelist: prune_whitelist, max_watch_seconds: @max_watch_seconds,
221
+ selector: @selector, statsd_tags: statsd_tags, current_sha: @current_sha)
222
+ end
223
+
224
+ def kubeclient_builder
225
+ @kubeclient_builder ||= KubeclientBuilder.new
226
+ end
227
+
228
+ def cluster_resource_discoverer
229
+ @cluster_resource_discoverer ||= ClusterResourceDiscovery.new(
230
+ task_config: @task_config,
231
+ namespace_tags: @namespace_tags
232
+ )
233
+ end
234
+
235
+ def ejson_provisioners
236
+ @ejson_provisoners ||= @template_sets.ejson_secrets_files.map do |ejson_secret_file|
237
+ EjsonSecretProvisioner.new(
238
+ task_config: @task_config,
239
+ ejson_keys_secret: ejson_keys_secret,
240
+ ejson_file: ejson_secret_file,
241
+ statsd_tags: @namespace_tags,
242
+ selector: @selector,
243
+ )
244
+ end
245
+ end
246
+
247
+ def deploy_has_priority_resources?(resources)
248
+ resources.any? { |r| predeploy_sequence.include?(r.type) }
249
+ end
250
+
251
+ def check_initial_status(resources)
252
+ cache = ResourceCache.new(@task_config)
253
+ Krane::Concurrency.split_across_threads(resources) { |r| r.sync(cache) }
254
+ resources.each { |r| @logger.info(r.pretty_status) }
255
+ end
256
+ measure_method(:check_initial_status, "initial_status.duration")
257
+
258
+ def secrets_from_ejson
259
+ ejson_provisioners.flat_map(&:resources)
260
+ end
261
+
262
+ def discover_resources
263
+ @logger.info("Discovering resources:")
264
+ resources = []
265
+ crds_by_kind = cluster_resource_discoverer.crds.group_by(&:kind)
266
+ @template_sets.with_resource_definitions(render_erb: @render_erb,
267
+ current_sha: @current_sha, bindings: @bindings) do |r_def|
268
+ crd = crds_by_kind[r_def["kind"]]&.first
269
+ r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger, definition: r_def,
270
+ statsd_tags: @namespace_tags, crd: crd, global_names: @task_config.global_kinds)
271
+ resources << r
272
+ @logger.info(" - #{r.id}")
273
+ end
274
+
275
+ secrets_from_ejson.each do |secret|
276
+ resources << secret
277
+ @logger.info(" - #{secret.id} (from ejson)")
278
+ end
279
+
280
+ resources.sort
281
+ rescue InvalidTemplateError => e
282
+ record_invalid_template(logger: @logger, err: e.message, filename: e.filename,
283
+ content: e.content)
284
+ raise FatalDeploymentError, "Failed to render and parse template"
285
+ end
286
+ measure_method(:discover_resources)
287
+
288
+ def validate_configuration(allow_protected_ns:, prune:)
289
+ task_config_validator = DeployTaskConfigValidator.new(@protected_namespaces, allow_protected_ns, prune,
290
+ @task_config, kubectl, kubeclient_builder)
291
+ errors = []
292
+ errors += task_config_validator.errors
293
+ errors += @template_sets.validate
294
+ unless errors.empty?
295
+ add_para_from_list(logger: @logger, action: "Configuration invalid", enum: errors)
296
+ raise Krane::TaskConfigurationError
297
+ end
298
+
299
+ confirm_ejson_keys_not_prunable if prune
300
+ @logger.info("Using resource selector #{@selector}") if @selector
301
+ @namespace_tags |= tags_from_namespace_labels
302
+ @logger.info("All required parameters and files are present")
303
+ end
304
+ measure_method(:validate_configuration)
305
+
306
+ def validate_resources(resources)
307
+ validate_globals(resources)
308
+ Krane::Concurrency.split_across_threads(resources) do |r|
309
+ r.validate_definition(kubectl, selector: @selector)
310
+ end
311
+
312
+ resources.select(&:has_warnings?).each do |resource|
313
+ record_warnings(logger: @logger, warning: resource.validation_warning_msg,
314
+ filename: File.basename(resource.file_path))
315
+ end
316
+
317
+ failed_resources = resources.select(&:validation_failed?)
318
+ if failed_resources.present?
319
+
320
+ failed_resources.each do |r|
321
+ content = File.read(r.file_path) if File.file?(r.file_path) && !r.sensitive_template_content?
322
+ record_invalid_template(logger: @logger, err: r.validation_error_msg,
323
+ filename: File.basename(r.file_path), content: content)
324
+ end
325
+ raise FatalDeploymentError, "Template validation failed"
326
+ end
327
+ end
328
+ measure_method(:validate_resources)
329
+
330
+ def validate_globals(resources)
331
+ return unless (global = resources.select(&:global?).presence)
332
+ global_names = global.map do |resource|
333
+ "#{resource.name} (#{resource.type}) in #{File.basename(resource.file_path)}"
334
+ end
335
+ global_names = FormattedLogger.indent_four(global_names.join("\n"))
336
+
337
+ if @allow_globals
338
+ msg = "The ability for this task to deploy global resources will be removed in the next version,"\
339
+ " which will affect the following resources:"
340
+ msg += "\n#{global_names}"
341
+ @logger.summary.add_paragraph(ColorizedString.new(msg).yellow)
342
+ else
343
+ @logger.summary.add_paragraph(ColorizedString.new("Global resources:\n#{global_names}").yellow)
344
+ raise FatalDeploymentError, "This command is namespaced and cannot be used to deploy global resources."
345
+ end
346
+ end
347
+
348
+ def namespace_definition
349
+ @namespace_definition ||= begin
350
+ definition, _err, st = kubectl.run("get", "namespace", @namespace, use_namespace: false,
351
+ log_failure: true, raise_if_not_found: true, attempts: 3, output: 'json')
352
+ st.success? ? JSON.parse(definition, symbolize_names: true) : nil
353
+ end
354
+ rescue Kubectl::ResourceNotFoundError
355
+ nil
356
+ end
357
+
358
+ # make sure to never prune the ejson-keys secret
359
+ def confirm_ejson_keys_not_prunable
360
+ return unless ejson_keys_secret.dig("metadata", "annotations", KubernetesResource::LAST_APPLIED_ANNOTATION)
361
+
362
+ @logger.error("Deploy cannot proceed because protected resource " \
363
+ "Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET} would be pruned.")
364
+ raise EjsonPrunableError
365
+ rescue Kubectl::ResourceNotFoundError => e
366
+ @logger.debug("Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET} does not exist: #{e}")
367
+ end
368
+
369
+ def tags_from_namespace_labels
370
+ return [] if namespace_definition.blank?
371
+ namespace_labels = namespace_definition.fetch(:metadata, {}).fetch(:labels, {})
372
+ namespace_labels.map { |key, value| "#{key}:#{value}" }
373
+ end
374
+
375
+ def kubectl
376
+ @kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
377
+ end
378
+
379
+ def ejson_keys_secret
380
+ @ejson_keys_secret ||= begin
381
+ out, err, st = kubectl.run("get", "secret", EjsonSecretProvisioner::EJSON_KEYS_SECRET, output: "json",
382
+ raise_if_not_found: true, attempts: 3, output_is_sensitive: true, log_failure: true)
383
+ unless st.success?
384
+ raise EjsonSecretError, "Error retrieving Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET}: #{err}"
385
+ end
386
+ JSON.parse(out)
387
+ end
388
+ end
389
+
390
+ def statsd_tags
391
+ tags = %W(namespace:#{@namespace} context:#{@context}) | @namespace_tags
392
+ @current_sha.nil? ? tags : %W(sha:#{@current_sha}) | tags
393
+ end
394
+
395
+ def with_retries(limit)
396
+ retried = 0
397
+ while retried <= limit
398
+ success = yield
399
+ break if success
400
+ retried += 1
401
+ end
402
+ end
403
+ end
404
+ end