kubernetes-deploy 0.25.0 → 0.26.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 +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
|