kubernetes-deploy 0.25.0 → 0.26.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.buildkite/pipeline.nightly.yml +0 -9
- data/.buildkite/pipeline.yml +0 -9
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +164 -0
- data/README.md +29 -104
- data/dev.yml +3 -3
- data/exe/kubernetes-deploy +32 -21
- data/exe/kubernetes-render +20 -12
- data/exe/kubernetes-restart +5 -1
- data/lib/kubernetes-deploy.rb +1 -0
- data/lib/kubernetes-deploy/bindings_parser.rb +20 -8
- data/lib/kubernetes-deploy/cluster_resource_discovery.rb +1 -1
- data/lib/kubernetes-deploy/container_logs.rb +6 -0
- data/lib/kubernetes-deploy/deploy_task.rb +85 -44
- data/lib/kubernetes-deploy/ejson_secret_provisioner.rb +38 -84
- data/lib/kubernetes-deploy/errors.rb +8 -0
- data/lib/kubernetes-deploy/kubeclient_builder.rb +52 -20
- data/lib/kubernetes-deploy/kubeclient_builder/kube_config.rb +1 -1
- data/lib/kubernetes-deploy/kubectl.rb +11 -10
- data/lib/kubernetes-deploy/kubernetes_resource.rb +47 -9
- data/lib/kubernetes-deploy/kubernetes_resource/custom_resource.rb +1 -1
- data/lib/kubernetes-deploy/kubernetes_resource/custom_resource_definition.rb +1 -1
- data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +1 -1
- data/lib/kubernetes-deploy/kubernetes_resource/horizontal_pod_autoscaler.rb +12 -4
- data/lib/kubernetes-deploy/kubernetes_resource/network_policy.rb +22 -0
- data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +2 -0
- data/lib/kubernetes-deploy/kubernetes_resource/secret.rb +23 -0
- data/lib/kubernetes-deploy/label_selector.rb +42 -0
- data/lib/kubernetes-deploy/options_helper.rb +31 -15
- data/lib/kubernetes-deploy/remote_logs.rb +6 -1
- data/lib/kubernetes-deploy/renderer.rb +5 -1
- data/lib/kubernetes-deploy/resource_cache.rb +4 -1
- data/lib/kubernetes-deploy/restart_task.rb +22 -10
- data/lib/kubernetes-deploy/runner_task.rb +5 -3
- data/lib/kubernetes-deploy/version.rb +1 -1
- metadata +6 -2
data/dev.yml
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
---
|
2
2
|
name: kubernetes-deploy
|
3
3
|
up:
|
4
|
-
- ruby: 2.3.3
|
4
|
+
- ruby: 2.3.3 # Matches gemspec
|
5
5
|
- bundler
|
6
6
|
- homebrew:
|
7
7
|
- Caskroom/cask/minikube
|
8
8
|
- custom:
|
9
9
|
name: Minikube Cluster
|
10
|
-
met?: test $(minikube status | grep Running | wc -l) -
|
11
|
-
meet: minikube start --kubernetes-version=v1.
|
10
|
+
met?: test $(minikube status | grep Running | wc -l) -ge 2 && $(minikube status | grep -q 'Correctly Configured')
|
11
|
+
meet: minikube start --kubernetes-version=v1.11.6 --vm-driver=hyperkit
|
12
12
|
down: minikube stop
|
13
13
|
commands:
|
14
14
|
reset-minikube: minikube delete && rm -rf ~/.minikube
|
data/exe/kubernetes-deploy
CHANGED
@@ -11,23 +11,29 @@ prune = true
|
|
11
11
|
bindings = {}
|
12
12
|
verbose_log_prefix = false
|
13
13
|
max_watch_seconds = nil
|
14
|
+
selector = nil
|
14
15
|
|
15
16
|
ARGV.options do |opts|
|
17
|
+
parser = KubernetesDeploy::BindingsParser.new
|
16
18
|
opts.on("--bindings=BINDINGS", "Expose additional variables to ERB templates " \
|
17
|
-
"(format: k1=v1,k2=v2, JSON string or file (JSON or YAML) path prefixed by '@')")
|
18
|
-
bindings.merge!(KubernetesDeploy::BindingsParser.parse(binds))
|
19
|
-
end
|
19
|
+
"(format: k1=v1,k2=v2, JSON string or file (JSON or YAML) path prefixed by '@')") { |b| parser.add(b) }
|
20
20
|
|
21
21
|
opts.on("--skip-wait", "Skip verification of non-priority-resource success (not recommended)") { skip_wait = true }
|
22
22
|
prot_ns = KubernetesDeploy::DeployTask::PROTECTED_NAMESPACES.join(', ')
|
23
23
|
opts.on("--allow-protected-ns", "Enable deploys to #{prot_ns}; requires --no-prune") { allow_protected_ns = true }
|
24
24
|
opts.on("--no-prune", "Disable deletion of resources that do not appear in the template dir") { prune = false }
|
25
|
-
opts.on("--template-dir=DIR", "Set the template dir (default: config/deploy/$ENVIRONMENT)")
|
25
|
+
opts.on("--template-dir=DIR", "Set the template dir (default: config/deploy/$ENVIRONMENT).") do |d|
|
26
|
+
template_dir = d
|
27
|
+
end
|
26
28
|
opts.on("--verbose-log-prefix", "Add [context][namespace] to the log prefix") { verbose_log_prefix = true }
|
27
29
|
opts.on("--max-watch-seconds=seconds",
|
28
30
|
"Timeout error is raised if it takes longer than the specified number of seconds") do |t|
|
29
31
|
max_watch_seconds = t.to_i
|
30
32
|
end
|
33
|
+
opts.on("--selector=SELECTOR", "Ensure that all resources in your template dir match the given selector, " \
|
34
|
+
"and restrict pruning to deployed resources it selects. (format: k1=v1,k2=v2)") do |s|
|
35
|
+
selector = KubernetesDeploy::LabelSelector.parse(s)
|
36
|
+
end
|
31
37
|
|
32
38
|
opts.on_tail("-h", "--help", "Print this help") do
|
33
39
|
puts opts
|
@@ -38,32 +44,37 @@ ARGV.options do |opts|
|
|
38
44
|
exit
|
39
45
|
end
|
40
46
|
opts.parse!
|
47
|
+
bindings = parser.parse
|
41
48
|
end
|
42
49
|
|
43
50
|
namespace = ARGV[0]
|
44
51
|
context = ARGV[1]
|
45
|
-
template_dir = KubernetesDeploy::OptionsHelper.default_and_check_template_dir(template_dir)
|
46
|
-
revision = KubernetesDeploy::OptionsHelper.revision_from_environment
|
47
52
|
logger = KubernetesDeploy::FormattedLogger.build(namespace, context, verbose_prefix: verbose_log_prefix)
|
48
53
|
|
49
|
-
runner = KubernetesDeploy::DeployTask.new(
|
50
|
-
namespace: namespace,
|
51
|
-
context: context,
|
52
|
-
current_sha: revision,
|
53
|
-
template_dir: template_dir,
|
54
|
-
bindings: bindings,
|
55
|
-
logger: logger,
|
56
|
-
max_watch_seconds: max_watch_seconds
|
57
|
-
)
|
58
|
-
|
59
54
|
begin
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
55
|
+
KubernetesDeploy::OptionsHelper.with_validated_template_dir(template_dir) do |dir|
|
56
|
+
runner = KubernetesDeploy::DeployTask.new(
|
57
|
+
namespace: namespace,
|
58
|
+
context: context,
|
59
|
+
current_sha: ENV["REVISION"],
|
60
|
+
template_dir: dir,
|
61
|
+
bindings: bindings,
|
62
|
+
logger: logger,
|
63
|
+
max_watch_seconds: max_watch_seconds,
|
64
|
+
selector: selector,
|
65
|
+
)
|
66
|
+
|
67
|
+
runner.run!(
|
68
|
+
verify_result: !skip_wait,
|
69
|
+
allow_protected_ns: allow_protected_ns,
|
70
|
+
prune: prune
|
71
|
+
)
|
72
|
+
end
|
65
73
|
rescue KubernetesDeploy::DeploymentTimeoutError
|
66
74
|
exit(70)
|
67
75
|
rescue KubernetesDeploy::FatalDeploymentError
|
68
76
|
exit(1)
|
77
|
+
rescue KubernetesDeploy::OptionsHelper::OptionsError => e
|
78
|
+
logger.error(e.message)
|
79
|
+
exit(1)
|
69
80
|
end
|
data/exe/kubernetes-render
CHANGED
@@ -9,24 +9,32 @@ template_dir = nil
|
|
9
9
|
bindings = {}
|
10
10
|
|
11
11
|
ARGV.options do |opts|
|
12
|
+
parser = KubernetesDeploy::BindingsParser.new
|
12
13
|
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 '@')")
|
14
|
-
|
14
|
+
"(format: k1=v1,k2=v2, JSON string or file (JSON or YAML) path prefixed by '@')") { |b| parser.add(b) }
|
15
|
+
opts.on("--template-dir=DIR", "Set the template dir (default: config/deploy/$ENVIRONMENT)." ) do |d|
|
16
|
+
template_dir = d
|
15
17
|
end
|
16
|
-
opts.on("--template-dir=DIR", "Set the template dir (default: config/deploy/$ENVIRONMENT)") { |v| template_dir = v }
|
17
18
|
opts.parse!
|
19
|
+
bindings = parser.parse
|
18
20
|
end
|
19
21
|
|
20
22
|
templates = ARGV
|
21
23
|
logger = KubernetesDeploy::FormattedLogger.build(verbose_prefix: false)
|
22
|
-
revision = KubernetesDeploy::OptionsHelper.revision_from_environment
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
begin
|
26
|
+
KubernetesDeploy::OptionsHelper.with_validated_template_dir(template_dir) do |dir|
|
27
|
+
runner = KubernetesDeploy::RenderTask.new(
|
28
|
+
logger: logger,
|
29
|
+
current_sha: ENV["REVISION"],
|
30
|
+
template_dir: dir,
|
31
|
+
bindings: bindings,
|
32
|
+
)
|
30
33
|
|
31
|
-
success = runner.run(STDOUT, templates)
|
32
|
-
exit(1) unless success
|
34
|
+
success = runner.run(STDOUT, templates)
|
35
|
+
exit(1) unless success
|
36
|
+
end
|
37
|
+
rescue KubernetesDeploy::OptionsHelper::OptionsError => e
|
38
|
+
logger.error(e.message)
|
39
|
+
exit(1)
|
40
|
+
end
|
data/exe/kubernetes-restart
CHANGED
@@ -8,9 +8,13 @@ require 'kubernetes-deploy/restart_task'
|
|
8
8
|
|
9
9
|
raw_deployments = nil
|
10
10
|
max_watch_seconds = nil
|
11
|
+
selector = nil
|
11
12
|
ARGV.options do |opts|
|
12
13
|
opts.on("--deployments=LIST") { |v| raw_deployments = v.split(",") }
|
13
14
|
opts.on("--max-watch-seconds=seconds") { |t| max_watch_seconds = t.to_i }
|
15
|
+
opts.on("--selector=SELECTOR", "Restarts deployments matching selector (format: k1=v1,k2=v2)") do |s|
|
16
|
+
selector = KubernetesDeploy::LabelSelector.parse(s)
|
17
|
+
end
|
14
18
|
opts.parse!
|
15
19
|
end
|
16
20
|
|
@@ -21,7 +25,7 @@ logger = KubernetesDeploy::FormattedLogger.build(namespace, context)
|
|
21
25
|
restart = KubernetesDeploy::RestartTask.new(namespace: namespace, context: context, logger: logger,
|
22
26
|
max_watch_seconds: max_watch_seconds)
|
23
27
|
begin
|
24
|
-
restart.perform!(raw_deployments)
|
28
|
+
restart.perform!(raw_deployments, selector: selector)
|
25
29
|
rescue KubernetesDeploy::DeploymentTimeoutError
|
26
30
|
exit(70)
|
27
31
|
rescue KubernetesDeploy::FatalDeploymentError
|
data/lib/kubernetes-deploy.rb
CHANGED
@@ -21,6 +21,7 @@ require 'kubernetes-deploy/concurrency'
|
|
21
21
|
require 'kubernetes-deploy/bindings_parser'
|
22
22
|
require 'kubernetes-deploy/duration_parser'
|
23
23
|
require 'kubernetes-deploy/resource_cache'
|
24
|
+
require 'kubernetes-deploy/label_selector'
|
24
25
|
|
25
26
|
module KubernetesDeploy
|
26
27
|
MIN_KUBE_VERSION = '1.10.0'
|
@@ -4,17 +4,29 @@ require 'yaml'
|
|
4
4
|
require 'csv'
|
5
5
|
|
6
6
|
module KubernetesDeploy
|
7
|
-
|
8
|
-
|
7
|
+
class BindingsParser
|
8
|
+
def self.parse(string)
|
9
|
+
new(string).parse
|
10
|
+
end
|
9
11
|
|
10
|
-
def
|
11
|
-
|
12
|
+
def initialize(initial_string = nil)
|
13
|
+
@raw_bindings = Array(initial_string)
|
14
|
+
end
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
def add(string)
|
17
|
+
@raw_bindings << string
|
18
|
+
end
|
16
19
|
|
17
|
-
|
20
|
+
def parse
|
21
|
+
result = {}
|
22
|
+
@raw_bindings.each do |string|
|
23
|
+
bindings = parse_file(string) || parse_json(string) || parse_csv(string)
|
24
|
+
unless bindings
|
25
|
+
raise ArgumentError, "Failed to parse bindings."
|
26
|
+
end
|
27
|
+
result.deep_merge!(bindings)
|
28
|
+
end
|
29
|
+
result
|
18
30
|
end
|
19
31
|
|
20
32
|
private
|
@@ -19,7 +19,7 @@ module KubernetesDeploy
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def fetch_crds
|
22
|
-
raw_json, _, st = kubectl.run("get", "CustomResourceDefinition", "-a", "
|
22
|
+
raw_json, _, st = kubectl.run("get", "CustomResourceDefinition", "-a", output: "json", attempts: 5)
|
23
23
|
if st.success?
|
24
24
|
JSON.parse(raw_json)["items"]
|
25
25
|
else
|
@@ -13,6 +13,7 @@ module KubernetesDeploy
|
|
13
13
|
@logger = logger
|
14
14
|
@lines = []
|
15
15
|
@next_print_index = 0
|
16
|
+
@printed_latest = false
|
16
17
|
end
|
17
18
|
|
18
19
|
def sync
|
@@ -33,12 +34,17 @@ module KubernetesDeploy
|
|
33
34
|
end
|
34
35
|
|
35
36
|
@next_print_index = lines.length
|
37
|
+
@printed_latest = true
|
36
38
|
end
|
37
39
|
|
38
40
|
def print_all
|
39
41
|
lines.each { |line| @logger.info("\t#{line}") }
|
40
42
|
end
|
41
43
|
|
44
|
+
def printing_started?
|
45
|
+
@printed_latest
|
46
|
+
end
|
47
|
+
|
42
48
|
private
|
43
49
|
|
44
50
|
def fetch_latest
|
@@ -14,6 +14,7 @@ require 'kubernetes-deploy/kubernetes_resource'
|
|
14
14
|
persistent_volume_claim
|
15
15
|
pod
|
16
16
|
redis
|
17
|
+
network_policy
|
17
18
|
memcached
|
18
19
|
service
|
19
20
|
pod_template
|
@@ -30,6 +31,7 @@ require 'kubernetes-deploy/kubernetes_resource'
|
|
30
31
|
job
|
31
32
|
custom_resource_definition
|
32
33
|
horizontal_pod_autoscaler
|
34
|
+
secret
|
33
35
|
).each do |subresource|
|
34
36
|
require "kubernetes-deploy/kubernetes_resource/#{subresource}"
|
35
37
|
end
|
@@ -43,7 +45,6 @@ require 'kubernetes-deploy/template_discovery'
|
|
43
45
|
|
44
46
|
module KubernetesDeploy
|
45
47
|
class DeployTask
|
46
|
-
include KubeclientBuilder
|
47
48
|
extend KubernetesDeploy::StatsD::MeasureMethods
|
48
49
|
|
49
50
|
PROTECTED_NAMESPACES = %w(
|
@@ -58,11 +59,11 @@ module KubernetesDeploy
|
|
58
59
|
# core/v1/PersistentVolumeClaim -- would delete data
|
59
60
|
# core/v1/ReplicationController -- superseded by deployments/replicasets
|
60
61
|
# extensions/v1beta1/ReplicaSet -- managed by deployments
|
61
|
-
# core/v1/Secret -- should not committed / managed by shipit
|
62
62
|
|
63
63
|
def predeploy_sequence
|
64
64
|
before_crs = %w(
|
65
65
|
ResourceQuota
|
66
|
+
NetworkPolicy
|
66
67
|
)
|
67
68
|
after_crs = %w(
|
68
69
|
ConfigMap
|
@@ -70,6 +71,7 @@ module KubernetesDeploy
|
|
70
71
|
ServiceAccount
|
71
72
|
Role
|
72
73
|
RoleBinding
|
74
|
+
Secret
|
73
75
|
Pod
|
74
76
|
)
|
75
77
|
|
@@ -82,10 +84,12 @@ module KubernetesDeploy
|
|
82
84
|
core/v1/Pod
|
83
85
|
core/v1/Service
|
84
86
|
core/v1/ResourceQuota
|
87
|
+
core/v1/Secret
|
85
88
|
batch/v1/Job
|
86
89
|
extensions/v1beta1/DaemonSet
|
87
90
|
extensions/v1beta1/Deployment
|
88
91
|
extensions/v1beta1/Ingress
|
92
|
+
networking.k8s.io/v1/NetworkPolicy
|
89
93
|
apps/v1beta1/StatefulSet
|
90
94
|
autoscaling/v1/HorizontalPodAutoscaler
|
91
95
|
policy/v1beta1/PodDisruptionBudget
|
@@ -99,7 +103,7 @@ module KubernetesDeploy
|
|
99
103
|
end
|
100
104
|
|
101
105
|
def initialize(namespace:, context:, current_sha:, template_dir:, logger:, kubectl_instance: nil, bindings: {},
|
102
|
-
max_watch_seconds: nil)
|
106
|
+
max_watch_seconds: nil, selector: nil)
|
103
107
|
@namespace = namespace
|
104
108
|
@namespace_tags = []
|
105
109
|
@context = context
|
@@ -114,6 +118,7 @@ module KubernetesDeploy
|
|
114
118
|
logger: @logger,
|
115
119
|
bindings: bindings,
|
116
120
|
)
|
121
|
+
@selector = selector
|
117
122
|
end
|
118
123
|
|
119
124
|
def run(*args)
|
@@ -134,7 +139,6 @@ module KubernetesDeploy
|
|
134
139
|
|
135
140
|
@logger.phase_heading("Checking initial resource statuses")
|
136
141
|
check_initial_status(resources)
|
137
|
-
create_ejson_secrets(prune)
|
138
142
|
|
139
143
|
if deploy_has_priority_resources?(resources)
|
140
144
|
@logger.phase_heading("Predeploying priority resources")
|
@@ -187,6 +191,10 @@ module KubernetesDeploy
|
|
187
191
|
|
188
192
|
private
|
189
193
|
|
194
|
+
def kubeclient_builder
|
195
|
+
@kubeclient_builder ||= KubeclientBuilder.new
|
196
|
+
end
|
197
|
+
|
190
198
|
def cluster_resource_discoverer
|
191
199
|
@cluster_resource_discoverer ||= ClusterResourceDiscovery.new(
|
192
200
|
namespace: @namespace,
|
@@ -196,11 +204,27 @@ module KubernetesDeploy
|
|
196
204
|
)
|
197
205
|
end
|
198
206
|
|
207
|
+
def ejson_provisioner
|
208
|
+
@ejson_provisioner ||= EjsonSecretProvisioner.new(
|
209
|
+
namespace: @namespace,
|
210
|
+
context: @context,
|
211
|
+
template_dir: @template_dir,
|
212
|
+
logger: @logger,
|
213
|
+
statsd_tags: @namespace_tags,
|
214
|
+
selector: @selector,
|
215
|
+
)
|
216
|
+
end
|
217
|
+
|
199
218
|
def deploy_has_priority_resources?(resources)
|
200
219
|
resources.any? { |r| predeploy_sequence.include?(r.type) }
|
201
220
|
end
|
202
221
|
|
203
222
|
def predeploy_priority_resources(resource_list)
|
223
|
+
bare_pods = resource_list.select { |resource| resource.is_a?(Pod) }
|
224
|
+
if bare_pods.count == 1
|
225
|
+
bare_pods.first.stream_logs = true
|
226
|
+
end
|
227
|
+
|
204
228
|
predeploy_sequence.each do |resource_type|
|
205
229
|
matching_resources = resource_list.select { |r| r.type == resource_type }
|
206
230
|
next if matching_resources.empty?
|
@@ -221,7 +245,9 @@ module KubernetesDeploy
|
|
221
245
|
measure_method(:predeploy_priority_resources, 'priority_resources.duration')
|
222
246
|
|
223
247
|
def validate_resources(resources)
|
224
|
-
KubernetesDeploy::Concurrency.split_across_threads(resources)
|
248
|
+
KubernetesDeploy::Concurrency.split_across_threads(resources) do |r|
|
249
|
+
r.validate_definition(kubectl, selector: @selector)
|
250
|
+
end
|
225
251
|
failed_resources = resources.select(&:validation_failed?)
|
226
252
|
return unless failed_resources.present?
|
227
253
|
|
@@ -240,20 +266,9 @@ module KubernetesDeploy
|
|
240
266
|
end
|
241
267
|
measure_method(:check_initial_status, "initial_status.duration")
|
242
268
|
|
243
|
-
def
|
244
|
-
|
245
|
-
namespace: @namespace,
|
246
|
-
context: @context,
|
247
|
-
template_dir: @template_dir,
|
248
|
-
logger: @logger,
|
249
|
-
prune: prune,
|
250
|
-
)
|
251
|
-
return unless ejson.secret_changes_required?
|
252
|
-
|
253
|
-
@logger.phase_heading("Deploying kubernetes secrets from #{EjsonSecretProvisioner::EJSON_SECRETS_FILE}")
|
254
|
-
ejson.run
|
269
|
+
def secrets_from_ejson
|
270
|
+
ejson_provisioner.resources
|
255
271
|
end
|
256
|
-
measure_method(:create_ejson_secrets)
|
257
272
|
|
258
273
|
def discover_resources
|
259
274
|
resources = []
|
@@ -269,11 +284,15 @@ module KubernetesDeploy
|
|
269
284
|
@logger.info(" - #{r.id}")
|
270
285
|
end
|
271
286
|
end
|
287
|
+
secrets_from_ejson.each do |secret|
|
288
|
+
resources << secret
|
289
|
+
@logger.info(" - #{secret.id} (from ejson)")
|
290
|
+
end
|
272
291
|
if (global = resources.select(&:global?).presence)
|
273
292
|
@logger.warn("Detected non-namespaced #{'resource'.pluralize(global.count)} which will never be pruned:")
|
274
293
|
global.each { |r| @logger.warn(" - #{r.id}") }
|
275
294
|
end
|
276
|
-
resources
|
295
|
+
resources.sort
|
277
296
|
end
|
278
297
|
measure_method(:discover_resources)
|
279
298
|
|
@@ -297,30 +316,16 @@ module KubernetesDeploy
|
|
297
316
|
raise FatalDeploymentError, "Failed to render and parse template"
|
298
317
|
end
|
299
318
|
|
300
|
-
def record_invalid_template(err:, filename:, content:)
|
319
|
+
def record_invalid_template(err:, filename:, content: nil)
|
301
320
|
debug_msg = ColorizedString.new("Invalid template: #{filename}\n").red
|
302
321
|
debug_msg += "> Error message:\n#{FormattedLogger.indent_four(err)}"
|
303
|
-
debug_msg += "\n> Template content:\n#{FormattedLogger.indent_four(content)}"
|
322
|
+
debug_msg += "\n> Template content:\n#{FormattedLogger.indent_four(content)}" if content
|
304
323
|
@logger.summary.add_paragraph(debug_msg)
|
305
324
|
end
|
306
325
|
|
307
326
|
def validate_configuration(allow_protected_ns:, prune:)
|
308
327
|
errors = []
|
309
|
-
|
310
|
-
errors << "$KUBECONFIG not set"
|
311
|
-
elsif config_files.empty?
|
312
|
-
errors << "Kube config file name(s) not set in $KUBECONFIG"
|
313
|
-
else
|
314
|
-
config_files.each do |f|
|
315
|
-
unless File.file?(f)
|
316
|
-
errors << "Kube config not found at #{f}"
|
317
|
-
end
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
if @current_sha.blank?
|
322
|
-
errors << "Current SHA must be specified"
|
323
|
-
end
|
328
|
+
errors += kubeclient_builder.validate_config_files
|
324
329
|
|
325
330
|
if !File.directory?(@template_dir)
|
326
331
|
errors << "Template directory `#{@template_dir}` doesn't exist"
|
@@ -354,6 +359,8 @@ module KubernetesDeploy
|
|
354
359
|
|
355
360
|
confirm_context_exists
|
356
361
|
confirm_namespace_exists
|
362
|
+
confirm_ejson_keys_not_prunable if prune
|
363
|
+
@logger.info("Using resource selector #{@selector}") if @selector
|
357
364
|
@namespace_tags |= tags_from_namespace_labels
|
358
365
|
@logger.info("All required parameters and files are present")
|
359
366
|
end
|
@@ -365,6 +372,9 @@ module KubernetesDeploy
|
|
365
372
|
|
366
373
|
if resources.length > 1
|
367
374
|
@logger.info("Deploying resources:")
|
375
|
+
resources.each do |r|
|
376
|
+
@logger.info("- #{r.id} (#{r.pretty_timeout_type})")
|
377
|
+
end
|
368
378
|
else
|
369
379
|
resource = resources.first
|
370
380
|
@logger.info("Deploying #{resource.id} (#{resource.pretty_timeout_type})")
|
@@ -377,7 +387,6 @@ module KubernetesDeploy
|
|
377
387
|
applyables += individuals.select { |r| pruneable_types.include?(r.type) }
|
378
388
|
|
379
389
|
individuals.each do |r|
|
380
|
-
@logger.info("- #{r.id} (#{r.pretty_timeout_type})") if resources.length > 1
|
381
390
|
r.deploy_started_at = Time.now.utc
|
382
391
|
case r.deploy_method
|
383
392
|
when :replace
|
@@ -421,23 +430,28 @@ module KubernetesDeploy
|
|
421
430
|
|
422
431
|
Dir.mktmpdir do |tmp_dir|
|
423
432
|
resources.each do |r|
|
424
|
-
@logger.info("- #{r.id} (#{r.pretty_timeout_type})") if resources.length > 1
|
425
433
|
FileUtils.symlink(r.file_path, tmp_dir)
|
426
434
|
r.deploy_started_at = Time.now.utc
|
427
435
|
end
|
428
436
|
command.push("-f", tmp_dir)
|
429
437
|
|
430
438
|
if prune
|
431
|
-
command.push("--prune"
|
439
|
+
command.push("--prune")
|
440
|
+
if @selector
|
441
|
+
command.push("--selector", @selector.to_s)
|
442
|
+
else
|
443
|
+
command.push("--all")
|
444
|
+
end
|
432
445
|
prune_whitelist.each { |type| command.push("--prune-whitelist=#{type}") }
|
433
446
|
end
|
434
447
|
|
435
|
-
|
448
|
+
output_is_sensitive = resources.any?(&:kubectl_output_is_sensitive?)
|
449
|
+
out, err, st = kubectl.run(*command, log_failure: false, output_is_sensitive: output_is_sensitive)
|
436
450
|
|
437
451
|
if st.success?
|
438
452
|
log_pruning(out) if prune
|
439
453
|
else
|
440
|
-
record_apply_failure(err)
|
454
|
+
record_apply_failure(err, resources: resources)
|
441
455
|
raise FatalDeploymentError, "Command failed: #{Shellwords.join(command)}"
|
442
456
|
end
|
443
457
|
end
|
@@ -452,22 +466,37 @@ module KubernetesDeploy
|
|
452
466
|
@logger.summary.add_action("pruned #{pruned.length} #{'resource'.pluralize(pruned.length)}")
|
453
467
|
end
|
454
468
|
|
455
|
-
def record_apply_failure(err)
|
469
|
+
def record_apply_failure(err, resources: [])
|
456
470
|
warn_msg = "WARNING: Any resources not mentioned in the error(s) below were likely created/updated. " \
|
457
471
|
"You may wish to roll back this deploy."
|
458
472
|
@logger.summary.add_paragraph(ColorizedString.new(warn_msg).yellow)
|
459
473
|
|
460
474
|
unidentified_errors = []
|
475
|
+
filenames_with_sensitive_content = resources
|
476
|
+
.select(&:kubectl_output_is_sensitive?)
|
477
|
+
.map { |r| File.basename(r.file_path) }
|
478
|
+
|
461
479
|
err.each_line do |line|
|
462
480
|
bad_files = find_bad_files_from_kubectl_output(line)
|
463
481
|
if bad_files.present?
|
464
|
-
bad_files.each
|
482
|
+
bad_files.each do |f|
|
483
|
+
if filenames_with_sensitive_content.include?(f[:filename])
|
484
|
+
# Hide the error and template contents in case it has senitive information
|
485
|
+
record_invalid_template(err: "SUPPRESSED FOR SECURITY", filename: f[:filename], content: nil)
|
486
|
+
else
|
487
|
+
record_invalid_template(err: f[:err], filename: f[:filename], content: f[:content])
|
488
|
+
end
|
489
|
+
end
|
465
490
|
else
|
466
491
|
unidentified_errors << line
|
467
492
|
end
|
468
493
|
end
|
469
494
|
|
470
|
-
if unidentified_errors.present?
|
495
|
+
if unidentified_errors.present? && filenames_with_sensitive_content.any?
|
496
|
+
warn_msg = "WARNING: There was an error applying some or all resources. The raw output may be sensitive and " \
|
497
|
+
"so cannot be displayed."
|
498
|
+
@logger.summary.add_paragraph(ColorizedString.new(warn_msg).yellow)
|
499
|
+
elsif unidentified_errors.present?
|
471
500
|
heading = ColorizedString.new('Unidentified error(s):').red
|
472
501
|
msg = FormattedLogger.indent_four(unidentified_errors.join)
|
473
502
|
@logger.summary.add_paragraph("#{heading}\n#{msg}")
|
@@ -526,6 +555,18 @@ module KubernetesDeploy
|
|
526
555
|
@logger.info("Namespace #{@namespace} found")
|
527
556
|
end
|
528
557
|
|
558
|
+
# make sure to never prune the ejson-keys secret
|
559
|
+
def confirm_ejson_keys_not_prunable
|
560
|
+
secret = ejson_provisioner.ejson_keys_secret
|
561
|
+
return unless secret.dig("metadata", "annotations", KubernetesResource::LAST_APPLIED_ANNOTATION)
|
562
|
+
|
563
|
+
@logger.error("Deploy cannot proceed because protected resource " \
|
564
|
+
"Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET} would be pruned.")
|
565
|
+
raise EjsonPrunableError
|
566
|
+
rescue Kubectl::ResourceNotFoundError => e
|
567
|
+
@logger.debug("Secret/#{EjsonSecretProvisioner::EJSON_KEYS_SECRET} does not exist: #{e}")
|
568
|
+
end
|
569
|
+
|
529
570
|
def tags_from_namespace_labels
|
530
571
|
namespace_info = nil
|
531
572
|
with_retries(2) do
|