tobsch-krane 1.0.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.
- checksums.yaml +7 -0
- data/.buildkite/pipeline.nightly.yml +43 -0
- data/.github/probots.yml +2 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +17 -0
- data/.shopify-build/VERSION +1 -0
- data/.shopify-build/kubernetes-deploy.yml +53 -0
- data/1.0-Upgrade.md +185 -0
- data/CHANGELOG.md +431 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +164 -0
- data/Gemfile +16 -0
- data/ISSUE_TEMPLATE.md +25 -0
- data/LICENSE.txt +21 -0
- data/README.md +655 -0
- data/Rakefile +36 -0
- data/bin/ci +21 -0
- data/bin/setup +16 -0
- data/bin/test +47 -0
- data/dev.yml +28 -0
- data/dev/flamegraph-from-tests +35 -0
- data/exe/krane +5 -0
- data/krane.gemspec +44 -0
- data/lib/krane.rb +7 -0
- data/lib/krane/bindings_parser.rb +88 -0
- data/lib/krane/cli/deploy_command.rb +75 -0
- data/lib/krane/cli/global_deploy_command.rb +54 -0
- data/lib/krane/cli/krane.rb +91 -0
- data/lib/krane/cli/render_command.rb +41 -0
- data/lib/krane/cli/restart_command.rb +34 -0
- data/lib/krane/cli/run_command.rb +54 -0
- data/lib/krane/cli/version_command.rb +13 -0
- data/lib/krane/cluster_resource_discovery.rb +113 -0
- data/lib/krane/common.rb +23 -0
- data/lib/krane/concerns/template_reporting.rb +29 -0
- data/lib/krane/concurrency.rb +18 -0
- data/lib/krane/container_logs.rb +106 -0
- data/lib/krane/deferred_summary_logging.rb +95 -0
- data/lib/krane/delayed_exceptions.rb +14 -0
- data/lib/krane/deploy_task.rb +363 -0
- data/lib/krane/deploy_task_config_validator.rb +29 -0
- data/lib/krane/duration_parser.rb +27 -0
- data/lib/krane/ejson_secret_provisioner.rb +154 -0
- data/lib/krane/errors.rb +28 -0
- data/lib/krane/formatted_logger.rb +57 -0
- data/lib/krane/global_deploy_task.rb +210 -0
- data/lib/krane/global_deploy_task_config_validator.rb +12 -0
- data/lib/krane/kubeclient_builder.rb +156 -0
- data/lib/krane/kubectl.rb +120 -0
- data/lib/krane/kubernetes_resource.rb +621 -0
- data/lib/krane/kubernetes_resource/cloudsql.rb +43 -0
- data/lib/krane/kubernetes_resource/config_map.rb +22 -0
- data/lib/krane/kubernetes_resource/cron_job.rb +18 -0
- data/lib/krane/kubernetes_resource/custom_resource.rb +87 -0
- data/lib/krane/kubernetes_resource/custom_resource_definition.rb +98 -0
- data/lib/krane/kubernetes_resource/daemon_set.rb +90 -0
- data/lib/krane/kubernetes_resource/deployment.rb +213 -0
- data/lib/krane/kubernetes_resource/horizontal_pod_autoscaler.rb +65 -0
- data/lib/krane/kubernetes_resource/ingress.rb +18 -0
- data/lib/krane/kubernetes_resource/job.rb +60 -0
- data/lib/krane/kubernetes_resource/network_policy.rb +22 -0
- data/lib/krane/kubernetes_resource/persistent_volume_claim.rb +80 -0
- data/lib/krane/kubernetes_resource/pod.rb +269 -0
- data/lib/krane/kubernetes_resource/pod_disruption_budget.rb +23 -0
- data/lib/krane/kubernetes_resource/pod_set_base.rb +71 -0
- data/lib/krane/kubernetes_resource/pod_template.rb +20 -0
- data/lib/krane/kubernetes_resource/replica_set.rb +92 -0
- data/lib/krane/kubernetes_resource/resource_quota.rb +22 -0
- data/lib/krane/kubernetes_resource/role.rb +22 -0
- data/lib/krane/kubernetes_resource/role_binding.rb +22 -0
- data/lib/krane/kubernetes_resource/secret.rb +24 -0
- data/lib/krane/kubernetes_resource/service.rb +104 -0
- data/lib/krane/kubernetes_resource/service_account.rb +22 -0
- data/lib/krane/kubernetes_resource/stateful_set.rb +70 -0
- data/lib/krane/label_selector.rb +42 -0
- data/lib/krane/oj.rb +4 -0
- data/lib/krane/options_helper.rb +39 -0
- data/lib/krane/remote_logs.rb +60 -0
- data/lib/krane/render_task.rb +118 -0
- data/lib/krane/renderer.rb +118 -0
- data/lib/krane/resource_cache.rb +68 -0
- data/lib/krane/resource_deployer.rb +265 -0
- data/lib/krane/resource_watcher.rb +171 -0
- data/lib/krane/restart_task.rb +228 -0
- data/lib/krane/rollout_conditions.rb +103 -0
- data/lib/krane/runner_task.rb +212 -0
- data/lib/krane/runner_task_config_validator.rb +18 -0
- data/lib/krane/statsd.rb +65 -0
- data/lib/krane/task_config.rb +22 -0
- data/lib/krane/task_config_validator.rb +96 -0
- data/lib/krane/template_sets.rb +173 -0
- data/lib/krane/version.rb +4 -0
- data/pull_request_template.md +8 -0
- data/screenshots/deploy-demo.gif +0 -0
- data/screenshots/migrate-logs.png +0 -0
- data/screenshots/missing-secret-fail.png +0 -0
- data/screenshots/success.png +0 -0
- data/screenshots/test-output.png +0 -0
- metadata +375 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'colorized_string'
|
3
|
+
|
4
|
+
module Krane
|
5
|
+
# Adds the methods krane requires to your logger class.
|
6
|
+
# These methods include helpers for logging consistent headings, as well as facilities for
|
7
|
+
# displaying key information later, in a summary section, rather than when it occurred.
|
8
|
+
module DeferredSummaryLogging
|
9
|
+
attr_reader :summary
|
10
|
+
def initialize(*args)
|
11
|
+
reset
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
@summary = DeferredSummary.new
|
17
|
+
@current_phase = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def blank_line(level = :info)
|
21
|
+
public_send(level, "")
|
22
|
+
end
|
23
|
+
|
24
|
+
def phase_heading(phase_name)
|
25
|
+
@current_phase += 1
|
26
|
+
heading("Phase #{@current_phase}: #{phase_name}")
|
27
|
+
end
|
28
|
+
|
29
|
+
def heading(text, secondary_msg = '', secondary_msg_color = :cyan)
|
30
|
+
padding = (100.0 - (text.length + secondary_msg.length)) / 2
|
31
|
+
blank_line
|
32
|
+
part1 = ColorizedString.new("#{'-' * padding.floor}#{text}").cyan
|
33
|
+
part2 = ColorizedString.new(secondary_msg).colorize(secondary_msg_color)
|
34
|
+
part3 = ColorizedString.new('-' * padding.ceil).cyan
|
35
|
+
info(part1 + part2 + part3)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Outputs the deferred summary information saved via @logger.summary.add_action and @logger.summary.add_paragraph
|
39
|
+
def print_summary(status)
|
40
|
+
status_string = status.to_s.humanize.upcase
|
41
|
+
if status == :success
|
42
|
+
heading("Result: ", status_string, :green)
|
43
|
+
level = :info
|
44
|
+
elsif status == :timed_out
|
45
|
+
heading("Result: ", status_string, :yellow)
|
46
|
+
level = :fatal
|
47
|
+
else
|
48
|
+
heading("Result: ", status_string, :red)
|
49
|
+
level = :fatal
|
50
|
+
end
|
51
|
+
|
52
|
+
if (actions_sentence = summary.actions_sentence.presence)
|
53
|
+
public_send(level, actions_sentence)
|
54
|
+
blank_line(level)
|
55
|
+
end
|
56
|
+
|
57
|
+
summary.paragraphs.each do |para|
|
58
|
+
msg_lines = para.split("\n")
|
59
|
+
msg_lines.each { |line| public_send(level, line) }
|
60
|
+
blank_line(level) unless para == summary.paragraphs.last
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class DeferredSummary
|
65
|
+
attr_reader :paragraphs
|
66
|
+
|
67
|
+
def initialize
|
68
|
+
@actions_taken = []
|
69
|
+
@paragraphs = []
|
70
|
+
end
|
71
|
+
|
72
|
+
def actions_sentence
|
73
|
+
return unless @actions_taken.present?
|
74
|
+
@actions_taken.to_sentence.capitalize
|
75
|
+
end
|
76
|
+
|
77
|
+
# Saves a sentence fragment to be displayed in the first sentence of the summary section
|
78
|
+
#
|
79
|
+
# Example:
|
80
|
+
# # The resulting summary will begin with "Created 3 secrets and failed to deploy 2 resources"
|
81
|
+
# @logger.summary.add_action("created 3 secrets")
|
82
|
+
# @logger.summary.add_action("failed to deploy 2 resources")
|
83
|
+
def add_action(sentence_fragment)
|
84
|
+
@actions_taken << sentence_fragment
|
85
|
+
end
|
86
|
+
|
87
|
+
# Adds a paragraph to be displayed in the summary section
|
88
|
+
# Paragraphs will be printed in the order they were added, separated by a blank line
|
89
|
+
# This can be used to log a block of data on a particular topic, e.g. debug info for a particular failed resource
|
90
|
+
def add_paragraph(paragraph)
|
91
|
+
paragraphs << paragraph
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DelayedExceptions
|
4
|
+
def with_delayed_exceptions(enumerable, *catch, &block)
|
5
|
+
exceptions = []
|
6
|
+
enumerable.each do |i|
|
7
|
+
begin
|
8
|
+
block.call(i)
|
9
|
+
rescue *catch => e
|
10
|
+
exceptions << e
|
11
|
+
end
|
12
|
+
end.tap { raise exceptions.first if exceptions.first }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,363 @@
|
|
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 DeployTask
|
50
|
+
extend Krane::StatsD::MeasureMethods
|
51
|
+
include Krane::TemplateReporting
|
52
|
+
|
53
|
+
PROTECTED_NAMESPACES = %w(
|
54
|
+
default
|
55
|
+
kube-system
|
56
|
+
kube-public
|
57
|
+
)
|
58
|
+
|
59
|
+
def predeploy_sequence
|
60
|
+
before_crs = %w(
|
61
|
+
ResourceQuota
|
62
|
+
NetworkPolicy
|
63
|
+
)
|
64
|
+
after_crs = %w(
|
65
|
+
ConfigMap
|
66
|
+
PersistentVolumeClaim
|
67
|
+
ServiceAccount
|
68
|
+
Role
|
69
|
+
RoleBinding
|
70
|
+
Secret
|
71
|
+
Pod
|
72
|
+
)
|
73
|
+
|
74
|
+
before_crs + cluster_resource_discoverer.crds.select(&:predeployed?).map(&:kind) + after_crs
|
75
|
+
end
|
76
|
+
|
77
|
+
def prune_whitelist
|
78
|
+
cluster_resource_discoverer.prunable_resources(namespaced: true)
|
79
|
+
end
|
80
|
+
|
81
|
+
def server_version
|
82
|
+
kubectl.server_version
|
83
|
+
end
|
84
|
+
|
85
|
+
# Initializes the deploy task
|
86
|
+
#
|
87
|
+
# @param namespace [String] Kubernetes namespace (*required*)
|
88
|
+
# @param context [String] Kubernetes context (*required*)
|
89
|
+
# @param current_sha [String] The SHA of the commit
|
90
|
+
# @param logger [Object] Logger object (defaults to an instance of Krane::FormattedLogger)
|
91
|
+
# @param kubectl_instance [Kubectl] Kubectl instance
|
92
|
+
# @param bindings [Hash] Bindings parsed by Krane::BindingsParser
|
93
|
+
# @param global_timeout [Integer] Timeout in seconds
|
94
|
+
# @param selector [Hash] Selector(s) parsed by Krane::LabelSelector
|
95
|
+
# @param filenames [Array<String>] An array of filenames and/or directories containing templates (*required*)
|
96
|
+
# @param protected_namespaces [Array<String>] Array of protected Kubernetes namespaces (defaults
|
97
|
+
# to Krane::DeployTask::PROTECTED_NAMESPACES)
|
98
|
+
# @param render_erb [Boolean] Enable ERB rendering
|
99
|
+
def initialize(namespace:, context:, current_sha: nil, logger: nil, kubectl_instance: nil, bindings: {},
|
100
|
+
global_timeout: nil, selector: nil, filenames: [], protected_namespaces: nil,
|
101
|
+
render_erb: false)
|
102
|
+
@logger = logger || Krane::FormattedLogger.build(namespace, context)
|
103
|
+
@template_sets = TemplateSets.from_dirs_and_files(paths: filenames, logger: @logger, render_erb: render_erb)
|
104
|
+
@task_config = Krane::TaskConfig.new(context, namespace, @logger)
|
105
|
+
@bindings = bindings
|
106
|
+
@namespace = namespace
|
107
|
+
@namespace_tags = []
|
108
|
+
@context = context
|
109
|
+
@current_sha = current_sha
|
110
|
+
@kubectl = kubectl_instance
|
111
|
+
@global_timeout = global_timeout
|
112
|
+
@selector = selector
|
113
|
+
@protected_namespaces = protected_namespaces || PROTECTED_NAMESPACES
|
114
|
+
@render_erb = render_erb
|
115
|
+
end
|
116
|
+
|
117
|
+
# Runs the task, returning a boolean representing success or failure
|
118
|
+
#
|
119
|
+
# @return [Boolean]
|
120
|
+
def run(*args)
|
121
|
+
run!(*args)
|
122
|
+
true
|
123
|
+
rescue FatalDeploymentError
|
124
|
+
false
|
125
|
+
end
|
126
|
+
|
127
|
+
# Runs the task, raising exceptions in case of issues
|
128
|
+
#
|
129
|
+
# @param verify_result [Boolean] Wait for completion and verify success
|
130
|
+
# @param prune [Boolean] Enable deletion of resources that do not appear in the template dir
|
131
|
+
#
|
132
|
+
# @return [nil]
|
133
|
+
def run!(verify_result: true, prune: true)
|
134
|
+
start = Time.now.utc
|
135
|
+
@logger.reset
|
136
|
+
|
137
|
+
@logger.phase_heading("Initializing deploy")
|
138
|
+
validate_configuration(prune: prune)
|
139
|
+
resources = discover_resources
|
140
|
+
validate_resources(resources)
|
141
|
+
|
142
|
+
@logger.phase_heading("Checking initial resource statuses")
|
143
|
+
check_initial_status(resources)
|
144
|
+
|
145
|
+
if deploy_has_priority_resources?(resources)
|
146
|
+
@logger.phase_heading("Predeploying priority resources")
|
147
|
+
resource_deployer.predeploy_priority_resources(resources, predeploy_sequence)
|
148
|
+
end
|
149
|
+
|
150
|
+
@logger.phase_heading("Deploying all resources")
|
151
|
+
if @protected_namespaces.include?(@namespace) && prune
|
152
|
+
raise FatalDeploymentError, "Refusing to deploy to protected namespace '#{@namespace}' with pruning enabled"
|
153
|
+
end
|
154
|
+
|
155
|
+
resource_deployer.deploy!(resources, verify_result, prune)
|
156
|
+
|
157
|
+
StatsD.client.event("Deployment of #{@namespace} succeeded",
|
158
|
+
"Successfully deployed all #{@namespace} resources to #{@context}",
|
159
|
+
alert_type: "success", tags: statsd_tags + %w(status:success))
|
160
|
+
StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
|
161
|
+
tags: statsd_tags + %w(status:success))
|
162
|
+
@logger.print_summary(:success)
|
163
|
+
rescue DeploymentTimeoutError
|
164
|
+
@logger.print_summary(:timed_out)
|
165
|
+
StatsD.client.event("Deployment of #{@namespace} timed out",
|
166
|
+
"One or more #{@namespace} resources failed to deploy to #{@context} in time",
|
167
|
+
alert_type: "error", tags: statsd_tags + %w(status:timeout))
|
168
|
+
StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
|
169
|
+
tags: statsd_tags + %w(status:timeout))
|
170
|
+
raise
|
171
|
+
rescue FatalDeploymentError => error
|
172
|
+
@logger.summary.add_action(error.message) if error.message != error.class.to_s
|
173
|
+
@logger.print_summary(:failure)
|
174
|
+
StatsD.client.event("Deployment of #{@namespace} failed",
|
175
|
+
"One or more #{@namespace} resources failed to deploy to #{@context}",
|
176
|
+
alert_type: "error", tags: statsd_tags + %w(status:failed))
|
177
|
+
StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
|
178
|
+
tags: statsd_tags + %w(status:failed))
|
179
|
+
raise
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def resource_deployer
|
185
|
+
@resource_deployer ||= Krane::ResourceDeployer.new(task_config: @task_config,
|
186
|
+
prune_whitelist: prune_whitelist, global_timeout: @global_timeout,
|
187
|
+
selector: @selector, statsd_tags: statsd_tags, current_sha: @current_sha)
|
188
|
+
end
|
189
|
+
|
190
|
+
def kubeclient_builder
|
191
|
+
@kubeclient_builder ||= KubeclientBuilder.new
|
192
|
+
end
|
193
|
+
|
194
|
+
def cluster_resource_discoverer
|
195
|
+
@cluster_resource_discoverer ||= ClusterResourceDiscovery.new(
|
196
|
+
task_config: @task_config,
|
197
|
+
namespace_tags: @namespace_tags
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
def ejson_provisioners
|
202
|
+
@ejson_provisoners ||= @template_sets.ejson_secrets_files.map do |ejson_secret_file|
|
203
|
+
EjsonSecretProvisioner.new(
|
204
|
+
task_config: @task_config,
|
205
|
+
ejson_keys_secret: ejson_keys_secret,
|
206
|
+
ejson_file: ejson_secret_file,
|
207
|
+
statsd_tags: @namespace_tags,
|
208
|
+
selector: @selector,
|
209
|
+
)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def deploy_has_priority_resources?(resources)
|
214
|
+
resources.any? { |r| predeploy_sequence.include?(r.type) }
|
215
|
+
end
|
216
|
+
|
217
|
+
def check_initial_status(resources)
|
218
|
+
cache = ResourceCache.new(@task_config)
|
219
|
+
Krane::Concurrency.split_across_threads(resources) { |r| r.sync(cache) }
|
220
|
+
resources.each { |r| @logger.info(r.pretty_status) }
|
221
|
+
end
|
222
|
+
measure_method(:check_initial_status, "initial_status.duration")
|
223
|
+
|
224
|
+
def secrets_from_ejson
|
225
|
+
ejson_provisioners.flat_map(&:resources)
|
226
|
+
end
|
227
|
+
|
228
|
+
def discover_resources
|
229
|
+
@logger.info("Discovering resources:")
|
230
|
+
resources = []
|
231
|
+
crds_by_kind = cluster_resource_discoverer.crds.group_by(&:kind)
|
232
|
+
@template_sets.with_resource_definitions(current_sha: @current_sha, bindings: @bindings) do |r_def|
|
233
|
+
crd = crds_by_kind[r_def["kind"]]&.first
|
234
|
+
r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger, definition: r_def,
|
235
|
+
statsd_tags: @namespace_tags, crd: crd, global_names: @task_config.global_kinds)
|
236
|
+
resources << r
|
237
|
+
@logger.info(" - #{r.id}")
|
238
|
+
end
|
239
|
+
|
240
|
+
secrets_from_ejson.each do |secret|
|
241
|
+
resources << secret
|
242
|
+
@logger.info(" - #{secret.id} (from ejson)")
|
243
|
+
end
|
244
|
+
|
245
|
+
resources.sort
|
246
|
+
rescue InvalidTemplateError => e
|
247
|
+
record_invalid_template(logger: @logger, err: e.message, filename: e.filename,
|
248
|
+
content: e.content)
|
249
|
+
raise FatalDeploymentError, "Failed to render and parse template"
|
250
|
+
end
|
251
|
+
measure_method(:discover_resources)
|
252
|
+
|
253
|
+
def validate_configuration(prune:)
|
254
|
+
task_config_validator = DeployTaskConfigValidator.new(@protected_namespaces, prune,
|
255
|
+
@task_config, kubectl, kubeclient_builder)
|
256
|
+
errors = []
|
257
|
+
errors += task_config_validator.errors
|
258
|
+
errors += @template_sets.validate
|
259
|
+
unless errors.empty?
|
260
|
+
add_para_from_list(logger: @logger, action: "Configuration invalid", enum: errors)
|
261
|
+
raise Krane::TaskConfigurationError
|
262
|
+
end
|
263
|
+
|
264
|
+
confirm_ejson_keys_not_prunable if prune
|
265
|
+
@logger.info("Using resource selector #{@selector}") if @selector
|
266
|
+
@namespace_tags |= tags_from_namespace_labels
|
267
|
+
@logger.info("All required parameters and files are present")
|
268
|
+
end
|
269
|
+
measure_method(:validate_configuration)
|
270
|
+
|
271
|
+
def validate_resources(resources)
|
272
|
+
validate_globals(resources)
|
273
|
+
Krane::Concurrency.split_across_threads(resources) do |r|
|
274
|
+
r.validate_definition(kubectl, selector: @selector)
|
275
|
+
end
|
276
|
+
|
277
|
+
resources.select(&:has_warnings?).each do |resource|
|
278
|
+
record_warnings(logger: @logger, warning: resource.validation_warning_msg,
|
279
|
+
filename: File.basename(resource.file_path))
|
280
|
+
end
|
281
|
+
|
282
|
+
failed_resources = resources.select(&:validation_failed?)
|
283
|
+
if failed_resources.present?
|
284
|
+
|
285
|
+
failed_resources.each do |r|
|
286
|
+
content = File.read(r.file_path) if File.file?(r.file_path) && !r.sensitive_template_content?
|
287
|
+
record_invalid_template(logger: @logger, err: r.validation_error_msg,
|
288
|
+
filename: File.basename(r.file_path), content: content)
|
289
|
+
end
|
290
|
+
raise FatalDeploymentError, "Template validation failed"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
measure_method(:validate_resources)
|
294
|
+
|
295
|
+
def validate_globals(resources)
|
296
|
+
return unless (global = resources.select(&:global?).presence)
|
297
|
+
global_names = global.map do |resource|
|
298
|
+
"#{resource.name} (#{resource.type}) in #{File.basename(resource.file_path)}"
|
299
|
+
end
|
300
|
+
global_names = FormattedLogger.indent_four(global_names.join("\n"))
|
301
|
+
|
302
|
+
@logger.summary.add_paragraph(ColorizedString.new("Global resources:\n#{global_names}").yellow)
|
303
|
+
raise FatalDeploymentError, "This command is namespaced and cannot be used to deploy global resources. "\
|
304
|
+
"Use GlobalDeployTask instead."
|
305
|
+
end
|
306
|
+
|
307
|
+
def namespace_definition
|
308
|
+
@namespace_definition ||= begin
|
309
|
+
definition, _err, st = kubectl.run("get", "namespace", @namespace, use_namespace: false,
|
310
|
+
log_failure: true, raise_if_not_found: true, attempts: 3, output: 'json')
|
311
|
+
st.success? ? JSON.parse(definition, symbolize_names: true) : nil
|
312
|
+
end
|
313
|
+
rescue Kubectl::ResourceNotFoundError
|
314
|
+
nil
|
315
|
+
end
|
316
|
+
|
317
|
+
# make sure to never prune the ejson-keys secret
|
318
|
+
def confirm_ejson_keys_not_prunable
|
319
|
+
return unless ejson_keys_secret.dig("metadata", "annotations", KubernetesResource::LAST_APPLIED_ANNOTATION)
|
320
|
+
|
321
|
+
@logger.error("Deploy cannot proceed because protected resource " \
|
322
|
+
"Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET} would be pruned.")
|
323
|
+
raise EjsonPrunableError
|
324
|
+
rescue Kubectl::ResourceNotFoundError => e
|
325
|
+
@logger.debug("Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET} does not exist: #{e}")
|
326
|
+
end
|
327
|
+
|
328
|
+
def tags_from_namespace_labels
|
329
|
+
return [] if namespace_definition.blank?
|
330
|
+
namespace_labels = namespace_definition.fetch(:metadata, {}).fetch(:labels, {})
|
331
|
+
namespace_labels.map { |key, value| "#{key}:#{value}" }
|
332
|
+
end
|
333
|
+
|
334
|
+
def kubectl
|
335
|
+
@kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
|
336
|
+
end
|
337
|
+
|
338
|
+
def ejson_keys_secret
|
339
|
+
@ejson_keys_secret ||= begin
|
340
|
+
out, err, st = kubectl.run("get", "secret", EjsonSecretProvisioner::EJSON_KEYS_SECRET, output: "json",
|
341
|
+
raise_if_not_found: true, attempts: 3, output_is_sensitive: true, log_failure: true)
|
342
|
+
unless st.success?
|
343
|
+
raise EjsonSecretError, "Error retrieving Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET}: #{err}"
|
344
|
+
end
|
345
|
+
JSON.parse(out)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def statsd_tags
|
350
|
+
tags = %W(namespace:#{@namespace} context:#{@context}) | @namespace_tags
|
351
|
+
@current_sha.nil? ? tags : %W(sha:#{@current_sha}) | tags
|
352
|
+
end
|
353
|
+
|
354
|
+
def with_retries(limit)
|
355
|
+
retried = 0
|
356
|
+
while retried <= limit
|
357
|
+
success = yield
|
358
|
+
break if success
|
359
|
+
retried += 1
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|