kubernetes-deploy 0.28.0 → 0.29.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/.shopify-build/kubernetes-deploy.yml +1 -1
- data/CHANGELOG.md +7 -1
- data/README.md +1 -1
- data/bin/ci +1 -1
- data/dev.yml +1 -1
- data/kubernetes-deploy.gemspec +2 -2
- data/lib/krane/cli/deploy_command.rb +77 -0
- data/lib/krane/cli/krane.rb +33 -0
- data/lib/krane/cli/render_command.rb +30 -0
- data/lib/krane/cli/run_command.rb +53 -0
- data/lib/kubernetes-deploy/delayed_exceptions.rb +14 -0
- data/lib/kubernetes-deploy/deploy_task.rb +28 -16
- data/lib/kubernetes-deploy/kubectl.rb +5 -0
- data/lib/kubernetes-deploy/kubernetes_resource.rb +14 -6
- data/lib/kubernetes-deploy/options_helper.rb +2 -2
- data/lib/kubernetes-deploy/render_task.rb +52 -45
- data/lib/kubernetes-deploy/runner_task.rb +2 -2
- data/lib/kubernetes-deploy/runner_task_config_validator.rb +1 -7
- data/lib/kubernetes-deploy/template_sets.rb +24 -13
- data/lib/kubernetes-deploy/version.rb +1 -1
- metadata +7 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0641a291f2f01c007051cf5001bc97ecb9216b55e7204d954c920083e0d303a
|
4
|
+
data.tar.gz: 2c2415cfd94a63f046b94e90c943a7f9e01a46207833c1d5d2a498ceeb1ff333
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18a4e86f4f144dfa23ebc191b7d06382605ada3b050e8e1c9e717cbc9db981f16b419fb27a342f4e1267663f8f82fb12bcebfe0697c9ac4ac40c15108ab73072
|
7
|
+
data.tar.gz: 314bdadd3d8f864895c13e81193845f4632fa46dc23550f12955a5ffe7a7b15ed3df1e12d769d5aefdb8ee4b7e5c3e3e13d5f8378f5c828a2e4bf3c04db8c56c
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -3,13 +3,19 @@
|
|
3
3
|
*Important!*
|
4
4
|
- The next release will be 1.0.0, which means that master will contain breaking changes.
|
5
5
|
|
6
|
+
*Bug Fixes*
|
7
|
+
- Handle improper duration values more elegantly with better messaging
|
8
|
+
|
9
|
+
*Other*
|
10
|
+
- We now require Ruby 2.4.x since Ruby 2.3 is past EoL.
|
11
|
+
|
6
12
|
## 0.28.0
|
7
13
|
|
8
14
|
*Enhancements*
|
9
15
|
- Officially support Kubernetes 1.15 ([#546](https://github.com/Shopify/kubernetes-deploy/pull/546))
|
10
16
|
- Make sure that we only declare a Service of type LoadBalancer as deployed after its IP address is published. [#547](https://github.com/Shopify/kubernetes-deploy/pull/547)
|
11
17
|
- Add more validations to `RunnerTask`. [#554](https://github.com/Shopify/kubernetes-deploy/pull/554)
|
12
|
-
- Validate secrets with `--server-dry-run` on supported clusters. [#553](https://github.com/Shopify/kubernetes-deploy/pull/553)
|
18
|
+
- Validate secrets with `--server-dry-run` on supported clusters. [#553](https://github.com/Shopify/kubernetes-deploy/pull/553)
|
13
19
|
*Bug Fixes*
|
14
20
|
- Fix a bug in rendering where we failed to add a yaml doc separator (`---`) to
|
15
21
|
an implicit document if there are multiple documents in the file.
|
data/README.md
CHANGED
@@ -71,7 +71,7 @@ This repo also includes related tools for [running tasks](#kubernetes-run) and [
|
|
71
71
|
|
72
72
|
## Prerequisites
|
73
73
|
|
74
|
-
* Ruby 2.
|
74
|
+
* Ruby 2.4+
|
75
75
|
* Your cluster must be running Kubernetes v1.11.0 or higher<sup>1</sup>
|
76
76
|
* Each app must have a deploy directory containing its Kubernetes templates (see [Templates](#using-templates-and-variables))
|
77
77
|
|
data/bin/ci
CHANGED
data/dev.yml
CHANGED
data/kubernetes-deploy.gemspec
CHANGED
@@ -22,13 +22,13 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = %w(lib)
|
24
24
|
|
25
|
-
spec.required_ruby_version = '>= 2.
|
25
|
+
spec.required_ruby_version = '>= 2.4.0'
|
26
26
|
spec.add_dependency("activesupport", ">= 5.0")
|
27
27
|
spec.add_dependency("kubeclient", "~> 4.3")
|
28
28
|
spec.add_dependency("googleauth", "~> 0.8.0")
|
29
29
|
spec.add_dependency("ejson", "~> 1.0")
|
30
30
|
spec.add_dependency("colorize", "~> 0.8")
|
31
|
-
spec.add_dependency("statsd-instrument", '~> 2.3
|
31
|
+
spec.add_dependency("statsd-instrument", '~> 2.3.2')
|
32
32
|
spec.add_dependency("oj", "~> 3.0")
|
33
33
|
spec.add_dependency("concurrent-ruby", "~> 1.1")
|
34
34
|
spec.add_dependency("jsonpath", "~> 0.9.6")
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Krane
|
4
|
+
module CLI
|
5
|
+
class DeployCommand
|
6
|
+
DEFAULT_DEPLOY_TIMEOUT = '300s'
|
7
|
+
PROTECTED_NAMESPACES = %w(
|
8
|
+
default
|
9
|
+
kube-system
|
10
|
+
kube-public
|
11
|
+
)
|
12
|
+
OPTIONS = {
|
13
|
+
"bindings" => { type: :array, banner: "foo=bar abc=def",
|
14
|
+
desc: "Expose additional variables to ERB templates (format: k1=v1 k2=v2, JSON string or file "\
|
15
|
+
"(JSON or YAML) path prefixed by '@')" },
|
16
|
+
"filenames" => { type: :string, banner: '/tmp/my-resource.yml', aliases: :f, required: true,
|
17
|
+
desc: "Path to file or directory that contains the configuration to apply" },
|
18
|
+
"global-timeout" => { type: :string, banner: "duration", default: DEFAULT_DEPLOY_TIMEOUT,
|
19
|
+
desc: "Max duration to monitor workloads correctly deployed" },
|
20
|
+
"protected-namespaces" => { type: :array, banner: "namespace1 namespace2 namespaceN",
|
21
|
+
desc: "Enable deploys to a list of selected namespaces; set to an empty string "\
|
22
|
+
"to disable",
|
23
|
+
default: PROTECTED_NAMESPACES },
|
24
|
+
"prune" => { type: :boolean, desc: "Enable deletion of resources that do not appear in the template dir",
|
25
|
+
default: true },
|
26
|
+
"render-erb" => { type: :boolean, desc: "Enable ERB rendering", default: false },
|
27
|
+
"selector" => { type: :string, banner: "'label=value'",
|
28
|
+
desc: "Select workloads by selector(s)" },
|
29
|
+
"verbose-log-prefix" => { type: :boolean, desc: "Add [context][namespace] to the log prefix",
|
30
|
+
default: true },
|
31
|
+
"verify-result" => { type: :boolean, default: true,
|
32
|
+
desc: "Verify workloads correctly deployed" },
|
33
|
+
}
|
34
|
+
|
35
|
+
def self.from_options(namespace, context, options)
|
36
|
+
require 'kubernetes-deploy/deploy_task'
|
37
|
+
require 'kubernetes-deploy/options_helper'
|
38
|
+
require 'kubernetes-deploy/bindings_parser'
|
39
|
+
require 'kubernetes-deploy/label_selector'
|
40
|
+
|
41
|
+
bindings_parser = KubernetesDeploy::BindingsParser.new
|
42
|
+
options[:bindings]&.each { |binding_pair| bindings_parser.add(binding_pair) }
|
43
|
+
|
44
|
+
selector = KubernetesDeploy::LabelSelector.parse(options[:selector]) if options[:selector]
|
45
|
+
|
46
|
+
logger = KubernetesDeploy::FormattedLogger.build(namespace, context,
|
47
|
+
verbose_prefix: options['verbose-log-prefix'])
|
48
|
+
|
49
|
+
protected_namespaces = options['protected-namespaces']
|
50
|
+
if options['protected-namespaces'].size == 1 && %w('' "").include?(options['protected-namespaces'][0])
|
51
|
+
protected_namespaces = []
|
52
|
+
end
|
53
|
+
|
54
|
+
KubernetesDeploy::OptionsHelper.with_processed_template_paths([options[:filenames]],
|
55
|
+
require_explicit_path: true) do |paths|
|
56
|
+
deploy = KubernetesDeploy::DeployTask.new(
|
57
|
+
namespace: namespace,
|
58
|
+
context: context,
|
59
|
+
current_sha: ENV["REVISION"],
|
60
|
+
template_paths: paths,
|
61
|
+
bindings: bindings_parser.parse,
|
62
|
+
logger: logger,
|
63
|
+
max_watch_seconds: KubernetesDeploy::DurationParser.new(options["global-timeout"]).parse!.to_i,
|
64
|
+
selector: selector,
|
65
|
+
protected_namespaces: protected_namespaces,
|
66
|
+
)
|
67
|
+
|
68
|
+
deploy.run!(
|
69
|
+
verify_result: options["verify-result"],
|
70
|
+
allow_protected_ns: !protected_namespaces.empty?,
|
71
|
+
prune: options[:prune]
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/krane/cli/krane.rb
CHANGED
@@ -4,6 +4,9 @@ require 'krane'
|
|
4
4
|
require 'thor'
|
5
5
|
require 'krane/cli/version_command'
|
6
6
|
require 'krane/cli/restart_command'
|
7
|
+
require 'krane/cli/run_command'
|
8
|
+
require 'krane/cli/render_command'
|
9
|
+
require 'krane/cli/deploy_command'
|
7
10
|
|
8
11
|
module Krane
|
9
12
|
module CLI
|
@@ -17,6 +20,14 @@ module Krane
|
|
17
20
|
task_options.each { |option_name, config| method_option(option_name, config) }
|
18
21
|
end
|
19
22
|
|
23
|
+
desc("render", "Render templates")
|
24
|
+
expand_options(RenderCommand::OPTIONS)
|
25
|
+
def render
|
26
|
+
rescue_and_exit do
|
27
|
+
RenderCommand.from_options(options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
20
31
|
desc("version", "Prints the version")
|
21
32
|
expand_options(VersionCommand::OPTIONS)
|
22
33
|
def version
|
@@ -31,6 +42,22 @@ module Krane
|
|
31
42
|
end
|
32
43
|
end
|
33
44
|
|
45
|
+
desc("run NAMESPACE CONTEXT", "Run a pod that exits upon completing a task")
|
46
|
+
expand_options(RunCommand::OPTIONS)
|
47
|
+
def run_command(namespace, context)
|
48
|
+
rescue_and_exit do
|
49
|
+
RunCommand.from_options(namespace, context, options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
desc("deploy NAMESPACE CONTEXT", "Ship resources to a namespace")
|
54
|
+
expand_options(DeployCommand::OPTIONS)
|
55
|
+
def deploy(namespace, context)
|
56
|
+
rescue_and_exit do
|
57
|
+
DeployCommand.from_options(namespace, context, options)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
34
61
|
def self.exit_on_failure?
|
35
62
|
true
|
36
63
|
end
|
@@ -43,6 +70,12 @@ module Krane
|
|
43
70
|
exit(TIMEOUT_EXIT_CODE)
|
44
71
|
rescue KubernetesDeploy::FatalDeploymentError
|
45
72
|
exit(FAILURE_EXIT_CODE)
|
73
|
+
rescue KubernetesDeploy::DurationParser::ParsingError => e
|
74
|
+
STDERR.puts(<<~ERROR_MESSAGE)
|
75
|
+
Error parsing duration
|
76
|
+
#{e.message}. Duration must be a full ISO8601 duration or time value (e.g. 300s, 10m, 1h)
|
77
|
+
ERROR_MESSAGE
|
78
|
+
exit(FAILURE_EXIT_CODE)
|
46
79
|
end
|
47
80
|
end
|
48
81
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Krane
|
4
|
+
module CLI
|
5
|
+
class RenderCommand
|
6
|
+
OPTIONS = {
|
7
|
+
bindings: { type: :array, desc: 'Bindings for erb' },
|
8
|
+
filenames: { type: :array, required: true, aliases: 'f', desc: 'Directories and files to render' },
|
9
|
+
}
|
10
|
+
|
11
|
+
def self.from_options(options)
|
12
|
+
require 'kubernetes-deploy/render_task'
|
13
|
+
require 'kubernetes-deploy/bindings_parser'
|
14
|
+
require 'kubernetes-deploy/options_helper'
|
15
|
+
|
16
|
+
bindings_parser = KubernetesDeploy::BindingsParser.new
|
17
|
+
options[:bindings]&.each { |b| bindings_parser.add(b) }
|
18
|
+
|
19
|
+
KubernetesDeploy::OptionsHelper.with_processed_template_paths(options[:filenames]) do |paths|
|
20
|
+
runner = KubernetesDeploy::RenderTask.new(
|
21
|
+
current_sha: ENV["REVISION"],
|
22
|
+
template_paths: paths,
|
23
|
+
bindings: bindings_parser.parse,
|
24
|
+
)
|
25
|
+
runner.run!(STDOUT)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Krane
|
4
|
+
module CLI
|
5
|
+
class RunCommand
|
6
|
+
DEFAULT_RUN_TIMEOUT = '300s'
|
7
|
+
|
8
|
+
OPTIONS = {
|
9
|
+
"global-timeout" => {
|
10
|
+
type: :string,
|
11
|
+
banner: "duration",
|
12
|
+
desc: "Timeout error is raised if the pod runs for longer than the specified number of seconds",
|
13
|
+
default: DEFAULT_RUN_TIMEOUT,
|
14
|
+
},
|
15
|
+
"arguments" => {
|
16
|
+
type: :string,
|
17
|
+
banner: '"ARG1 ARG2 ARG3"',
|
18
|
+
desc: "Override the default arguments for the command with a space-separated list of arguments",
|
19
|
+
},
|
20
|
+
"verify-result" => { type: :boolean, desc: "Wait for completion and verify pod success", default: true },
|
21
|
+
"command" => { type: :array, desc: "Override the default command in the container image" },
|
22
|
+
"template" => {
|
23
|
+
type: :string,
|
24
|
+
desc: "The template file you'll be rendering",
|
25
|
+
default: 'task-runner-template',
|
26
|
+
},
|
27
|
+
"env-vars" => {
|
28
|
+
type: :string,
|
29
|
+
banner: "VAR=val,FOO=bar",
|
30
|
+
desc: "A Comma-separated list of env vars",
|
31
|
+
default: '',
|
32
|
+
},
|
33
|
+
}
|
34
|
+
|
35
|
+
def self.from_options(namespace, context, options)
|
36
|
+
require "kubernetes-deploy/runner_task"
|
37
|
+
runner = KubernetesDeploy::RunnerTask.new(
|
38
|
+
namespace: namespace,
|
39
|
+
context: context,
|
40
|
+
max_watch_seconds: KubernetesDeploy::DurationParser.new(options["global-timeout"]).parse!.to_i,
|
41
|
+
)
|
42
|
+
|
43
|
+
runner.run!(
|
44
|
+
verify_result: options['verify-result'],
|
45
|
+
task_template: options['template'],
|
46
|
+
entrypoint: options['command'],
|
47
|
+
args: options['arguments']&.split(" "),
|
48
|
+
env_vars: options['env-vars'].split(','),
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
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
|
@@ -105,7 +105,8 @@ module KubernetesDeploy
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def initialize(namespace:, context:, current_sha:, logger: nil, kubectl_instance: nil, bindings: {},
|
108
|
-
max_watch_seconds: nil, selector: nil, template_paths: [], template_dir: nil
|
108
|
+
max_watch_seconds: nil, selector: nil, template_paths: [], template_dir: nil, protected_namespaces: nil,
|
109
|
+
render_erb: true)
|
109
110
|
template_dir = File.expand_path(template_dir) if template_dir
|
110
111
|
template_paths = (template_paths.map { |path| File.expand_path(path) } << template_dir).compact
|
111
112
|
|
@@ -120,6 +121,8 @@ module KubernetesDeploy
|
|
120
121
|
@kubectl = kubectl_instance
|
121
122
|
@max_watch_seconds = max_watch_seconds
|
122
123
|
@selector = selector
|
124
|
+
@protected_namespaces = protected_namespaces || PROTECTED_NAMESPACES
|
125
|
+
@render_erb = render_erb
|
123
126
|
end
|
124
127
|
|
125
128
|
def run(*args)
|
@@ -147,7 +150,7 @@ module KubernetesDeploy
|
|
147
150
|
end
|
148
151
|
|
149
152
|
@logger.phase_heading("Deploying all resources")
|
150
|
-
if
|
153
|
+
if @protected_namespaces.include?(@namespace) && prune
|
151
154
|
raise FatalDeploymentError, "Refusing to deploy to protected namespace '#{@namespace}' with pruning enabled"
|
152
155
|
end
|
153
156
|
|
@@ -283,7 +286,7 @@ module KubernetesDeploy
|
|
283
286
|
@logger.info("Discovering resources:")
|
284
287
|
resources = []
|
285
288
|
crds_by_kind = cluster_resource_discoverer.crds.group_by(&:kind)
|
286
|
-
@template_sets.with_resource_definitions(render_erb:
|
289
|
+
@template_sets.with_resource_definitions(render_erb: @render_erb,
|
287
290
|
current_sha: @current_sha, bindings: @bindings) do |r_def|
|
288
291
|
crd = crds_by_kind[r_def["kind"]]&.first
|
289
292
|
r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger, definition: r_def,
|
@@ -334,7 +337,7 @@ module KubernetesDeploy
|
|
334
337
|
|
335
338
|
if @namespace.blank?
|
336
339
|
errors << "Namespace must be specified"
|
337
|
-
elsif
|
340
|
+
elsif @protected_namespaces.include?(@namespace)
|
338
341
|
if allow_protected_ns && prune
|
339
342
|
errors << "Refusing to deploy to protected namespace '#{@namespace}' with pruning enabled"
|
340
343
|
elsif allow_protected_ns
|
@@ -475,27 +478,36 @@ module KubernetesDeploy
|
|
475
478
|
.select(&:sensitive_template_content?)
|
476
479
|
.map { |r| File.basename(r.file_path) }
|
477
480
|
|
481
|
+
server_dry_run_validated_resource = resources
|
482
|
+
.select(&:server_dry_run_validated?)
|
483
|
+
.map { |r| File.basename(r.file_path) }
|
484
|
+
|
478
485
|
err.each_line do |line|
|
479
486
|
bad_files = find_bad_files_from_kubectl_output(line)
|
480
|
-
|
481
|
-
bad_files.each do |f|
|
482
|
-
if filenames_with_sensitive_content.include?(f[:filename])
|
483
|
-
# Hide the error and template contents in case it has senitive information
|
484
|
-
record_invalid_template(err: "SUPPRESSED FOR SECURITY", filename: f[:filename], content: nil)
|
485
|
-
else
|
486
|
-
record_invalid_template(err: f[:err], filename: f[:filename], content: f[:content])
|
487
|
-
end
|
488
|
-
end
|
489
|
-
else
|
487
|
+
unless bad_files.present?
|
490
488
|
unidentified_errors << line
|
489
|
+
next
|
490
|
+
end
|
491
|
+
|
492
|
+
bad_files.each do |f|
|
493
|
+
err_msg = f[:err]
|
494
|
+
if filenames_with_sensitive_content.include?(f[:filename])
|
495
|
+
# Hide the error and template contents in case it has sensitive information
|
496
|
+
# we display full error messages as we assume there's no sensitive info leak after server-dry-run
|
497
|
+
err_msg = "SUPPRESSED FOR SECURITY" unless server_dry_run_validated_resource.include?(f[:filename])
|
498
|
+
record_invalid_template(err: err_msg, filename: f[:filename], content: nil)
|
499
|
+
else
|
500
|
+
record_invalid_template(err: err_msg, filename: f[:filename], content: f[:content])
|
501
|
+
end
|
491
502
|
end
|
492
503
|
end
|
504
|
+
return unless unidentified_errors.any?
|
493
505
|
|
494
|
-
if
|
506
|
+
if (filenames_with_sensitive_content - server_dry_run_validated_resource).present?
|
495
507
|
warn_msg = "WARNING: There was an error applying some or all resources. The raw output may be sensitive and " \
|
496
508
|
"so cannot be displayed."
|
497
509
|
@logger.summary.add_paragraph(ColorizedString.new(warn_msg).yellow)
|
498
|
-
|
510
|
+
else
|
499
511
|
heading = ColorizedString.new('Unidentified error(s):').red
|
500
512
|
msg = FormattedLogger.indent_four(unidentified_errors.join)
|
501
513
|
@logger.summary.add_paragraph("#{heading}\n#{msg}")
|
@@ -9,6 +9,7 @@ module KubernetesDeploy
|
|
9
9
|
}
|
10
10
|
DEFAULT_TIMEOUT = 15
|
11
11
|
MAX_RETRY_DELAY = 16
|
12
|
+
SERVER_DRY_RUN_MIN_VERSION = "1.13"
|
12
13
|
|
13
14
|
class ResourceNotFoundError < StandardError; end
|
14
15
|
|
@@ -84,6 +85,10 @@ module KubernetesDeploy
|
|
84
85
|
version_info[:server]
|
85
86
|
end
|
86
87
|
|
88
|
+
def server_dry_run_enabled?
|
89
|
+
server_version >= Gem::Version.new(SERVER_DRY_RUN_MIN_VERSION)
|
90
|
+
end
|
91
|
+
|
87
92
|
private
|
88
93
|
|
89
94
|
def build_command_from_options(args, use_namespace, use_context, output)
|
@@ -121,6 +121,7 @@ module KubernetesDeploy
|
|
121
121
|
@validation_errors = []
|
122
122
|
@validation_warnings = []
|
123
123
|
@instance_data = {}
|
124
|
+
@server_dry_run_validated = false
|
124
125
|
end
|
125
126
|
|
126
127
|
def to_kubeclient_resource
|
@@ -346,10 +347,14 @@ module KubernetesDeploy
|
|
346
347
|
self.class::SENSITIVE_TEMPLATE_CONTENT
|
347
348
|
end
|
348
349
|
|
349
|
-
def
|
350
|
+
def server_dry_runnable_resource?
|
350
351
|
self.class::SERVER_DRY_RUNNABLE
|
351
352
|
end
|
352
353
|
|
354
|
+
def server_dry_run_validated?
|
355
|
+
@server_dry_run_validated
|
356
|
+
end
|
357
|
+
|
353
358
|
class Event
|
354
359
|
EVENT_SEPARATOR = "ENDEVENT--BEGINEVENT"
|
355
360
|
FIELD_SEPARATOR = "ENDFIELD--BEGINFIELD"
|
@@ -471,12 +476,15 @@ module KubernetesDeploy
|
|
471
476
|
end
|
472
477
|
|
473
478
|
def validate_spec_with_kubectl(kubectl)
|
474
|
-
|
475
|
-
if
|
479
|
+
err = ""
|
480
|
+
if kubectl.server_dry_run_enabled? && server_dry_runnable_resource?
|
476
481
|
_, err, st = validate_with_dry_run_option(kubectl, "--server-dry-run")
|
477
|
-
|
478
|
-
|
479
|
-
|
482
|
+
@server_dry_run_validated = st.success?
|
483
|
+
return true if st.success?
|
484
|
+
end
|
485
|
+
|
486
|
+
if err.empty? || err.match(SERVER_DRY_RUN_DISABLED_ERROR)
|
487
|
+
_, err, st = validate_with_dry_run_option(kubectl, "--dry-run")
|
480
488
|
end
|
481
489
|
|
482
490
|
return true if st.success?
|
@@ -6,9 +6,9 @@ module KubernetesDeploy
|
|
6
6
|
|
7
7
|
STDIN_TEMP_FILE = "from_stdin.yml.erb"
|
8
8
|
class << self
|
9
|
-
def with_processed_template_paths(template_paths)
|
9
|
+
def with_processed_template_paths(template_paths, require_explicit_path: false)
|
10
10
|
validated_paths = []
|
11
|
-
if template_paths.empty?
|
11
|
+
if template_paths.empty? && !require_explicit_path
|
12
12
|
validated_paths << default_template_dir
|
13
13
|
else
|
14
14
|
template_paths.uniq!
|
@@ -3,18 +3,16 @@ require 'tempfile'
|
|
3
3
|
|
4
4
|
require 'kubernetes-deploy/common'
|
5
5
|
require 'kubernetes-deploy/renderer'
|
6
|
+
require 'kubernetes-deploy/template_sets'
|
6
7
|
|
7
8
|
module KubernetesDeploy
|
8
9
|
class RenderTask
|
9
|
-
def initialize(logger: nil, current_sha:, template_dir
|
10
|
+
def initialize(logger: nil, current_sha:, template_dir: nil, template_paths: [], bindings:)
|
10
11
|
@logger = logger || KubernetesDeploy::FormattedLogger.build
|
11
12
|
@template_dir = template_dir
|
12
|
-
@
|
13
|
-
|
14
|
-
|
15
|
-
template_dir: @template_dir,
|
16
|
-
logger: @logger,
|
17
|
-
)
|
13
|
+
@template_paths = template_paths.map { |path| File.expand_path(path) }
|
14
|
+
@bindings = bindings
|
15
|
+
@current_sha = current_sha
|
18
16
|
end
|
19
17
|
|
20
18
|
def run(*args)
|
@@ -28,16 +26,12 @@ module KubernetesDeploy
|
|
28
26
|
@logger.reset
|
29
27
|
@logger.phase_heading("Initializing render task")
|
30
28
|
|
31
|
-
|
32
|
-
Dir.foreach(@template_dir).select { |filename| filename.end_with?(".yml.erb", ".yml", ".yaml", ".yaml.erb") }
|
33
|
-
else
|
34
|
-
only_filenames
|
35
|
-
end
|
29
|
+
ts = TemplateSets.from_dirs_and_files(paths: template_sets_paths(only_filenames), logger: @logger)
|
36
30
|
|
37
|
-
validate_configuration(
|
38
|
-
|
31
|
+
validate_configuration(ts, only_filenames)
|
32
|
+
count = render_templates(stream, ts)
|
39
33
|
|
40
|
-
@logger.summary.add_action("Successfully rendered #{
|
34
|
+
@logger.summary.add_action("Successfully rendered #{count} template(s)")
|
41
35
|
@logger.print_summary(:success)
|
42
36
|
rescue KubernetesDeploy::FatalDeploymentError
|
43
37
|
@logger.print_summary(:failure)
|
@@ -46,29 +40,38 @@ module KubernetesDeploy
|
|
46
40
|
|
47
41
|
private
|
48
42
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
43
|
+
def template_sets_paths(only_filenames)
|
44
|
+
if @template_paths.present?
|
45
|
+
# Validation will catch @template_paths & @template_dir being present
|
46
|
+
@template_paths
|
47
|
+
elsif only_filenames.blank?
|
48
|
+
[File.expand_path(@template_dir || '')]
|
49
|
+
else
|
50
|
+
absolute_template_dir = File.expand_path(@template_dir || '')
|
51
|
+
only_filenames.map do |name|
|
52
|
+
File.join(absolute_template_dir, name)
|
59
53
|
end
|
60
54
|
end
|
55
|
+
end
|
61
56
|
|
62
|
-
|
63
|
-
|
57
|
+
def render_templates(stream, template_sets)
|
58
|
+
@logger.phase_heading("Rendering template(s)")
|
59
|
+
count = 0
|
60
|
+
template_sets.with_resource_definitions_and_filename(render_erb: true,
|
61
|
+
current_sha: @current_sha, bindings: @bindings, raw: true) do |rendered_content, filename|
|
62
|
+
write_to_stream(rendered_content, filename, stream)
|
63
|
+
count += 1
|
64
64
|
end
|
65
|
+
|
66
|
+
count
|
67
|
+
rescue KubernetesDeploy::InvalidTemplateError => exception
|
68
|
+
log_invalid_template(exception)
|
69
|
+
raise
|
65
70
|
end
|
66
71
|
|
67
|
-
def
|
72
|
+
def write_to_stream(rendered_content, filename, stream)
|
68
73
|
file_basename = File.basename(filename)
|
69
74
|
@logger.info("Rendering #{file_basename}...")
|
70
|
-
file_content = File.read(File.join(@template_dir, filename))
|
71
|
-
rendered_content = @renderer.render_template(filename, file_content)
|
72
75
|
implicit = []
|
73
76
|
YAML.parse_stream(rendered_content, "<rendered> #{filename}") { |d| implicit << d.implicit }
|
74
77
|
if rendered_content.present?
|
@@ -82,28 +85,32 @@ module KubernetesDeploy
|
|
82
85
|
raise InvalidTemplateError.new("Template is not valid YAML. #{exception.message}", filename: filename)
|
83
86
|
end
|
84
87
|
|
85
|
-
def validate_configuration(filenames)
|
88
|
+
def validate_configuration(template_sets, filenames)
|
86
89
|
@logger.info("Validating configuration")
|
87
90
|
errors = []
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
+
if @template_dir.present? && @template_paths.present?
|
92
|
+
errors << "template_dir and template_paths can not be combined"
|
93
|
+
elsif @template_dir.blank? && @template_paths.blank?
|
94
|
+
errors << "template_dir or template_paths must be set"
|
91
95
|
end
|
92
96
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
97
|
+
if filenames.present?
|
98
|
+
if @template_dir.nil?
|
99
|
+
errors << "template_dir must be set to use filenames"
|
100
|
+
else
|
101
|
+
absolute_template_dir = File.expand_path(@template_dir)
|
102
|
+
filenames.each do |filename|
|
103
|
+
absolute_file = File.expand_path(File.join(@template_dir, filename))
|
104
|
+
unless absolute_file.start_with?(absolute_template_dir)
|
105
|
+
errors << "Filename \"#{absolute_file}\" is outside the template directory," \
|
106
|
+
" which was resolved as #{absolute_template_dir}"
|
107
|
+
end
|
108
|
+
end
|
104
109
|
end
|
105
110
|
end
|
106
111
|
|
112
|
+
errors += template_sets.validate
|
113
|
+
|
107
114
|
unless errors.empty?
|
108
115
|
@logger.summary.add_action("Configuration invalid")
|
109
116
|
@logger.summary.add_paragraph(errors.map { |err| "- #{err}" }.join("\n"))
|
@@ -154,8 +154,8 @@ module KubernetesDeploy
|
|
154
154
|
raise TaskConfigurationError, message
|
155
155
|
end
|
156
156
|
|
157
|
-
container.command = entrypoint
|
158
|
-
container.args = args
|
157
|
+
container.command = entrypoint if entrypoint
|
158
|
+
container.args = args if args
|
159
159
|
|
160
160
|
env_args = env_vars.map do |env|
|
161
161
|
key, value = env.split('=', 2)
|
@@ -5,17 +5,11 @@ module KubernetesDeploy
|
|
5
5
|
super(*arguments)
|
6
6
|
@template = template
|
7
7
|
@args = args
|
8
|
-
@validations += %i(validate_template
|
8
|
+
@validations += %i(validate_template)
|
9
9
|
end
|
10
10
|
|
11
11
|
private
|
12
12
|
|
13
|
-
def validate_args
|
14
|
-
if @args.blank?
|
15
|
-
@errors << "Args can't be nil"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
13
|
def validate_template
|
20
14
|
if @template.blank?
|
21
15
|
@errors << "Task template name can't be nil"
|
@@ -1,17 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'kubernetes-deploy/delayed_exceptions'
|
3
|
+
require 'kubernetes-deploy/ejson_secret_provisioner'
|
2
4
|
|
3
5
|
module KubernetesDeploy
|
4
6
|
class TemplateSets
|
7
|
+
include DelayedExceptions
|
5
8
|
VALID_TEMPLATES = %w(.yml.erb .yml .yaml .yaml.erb)
|
6
9
|
# private inner class
|
7
10
|
class TemplateSet
|
11
|
+
include DelayedExceptions
|
8
12
|
def initialize(template_dir:, file_whitelist: [], logger:)
|
9
13
|
@template_dir = template_dir
|
10
14
|
@files = file_whitelist
|
11
15
|
@logger = logger
|
12
16
|
end
|
13
17
|
|
14
|
-
def
|
18
|
+
def with_resource_definitions_and_filename(render_erb: false, current_sha: nil, bindings: nil, raw: false)
|
15
19
|
if render_erb
|
16
20
|
@renderer = Renderer.new(
|
17
21
|
template_dir: @template_dir,
|
@@ -20,11 +24,9 @@ module KubernetesDeploy
|
|
20
24
|
bindings: bindings,
|
21
25
|
)
|
22
26
|
end
|
23
|
-
@files
|
27
|
+
with_delayed_exceptions(@files, KubernetesDeploy::InvalidTemplateError) do |filename|
|
24
28
|
next if filename.end_with?(EjsonSecretProvisioner::EJSON_SECRETS_FILE)
|
25
|
-
templates(filename: filename)
|
26
|
-
yield r_def
|
27
|
-
end
|
29
|
+
templates(filename: filename, raw: raw) { |r_def| yield r_def, filename }
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
@@ -59,7 +61,7 @@ module KubernetesDeploy
|
|
59
61
|
|
60
62
|
private
|
61
63
|
|
62
|
-
def templates(filename:)
|
64
|
+
def templates(filename:, raw:)
|
63
65
|
file_content = File.read(File.join(@template_dir, filename))
|
64
66
|
rendered_content = @renderer ? @renderer.render_template(filename, file_content) : file_content
|
65
67
|
YAML.load_stream(rendered_content, "<rendered> #{filename}") do |doc|
|
@@ -68,8 +70,9 @@ module KubernetesDeploy
|
|
68
70
|
raise InvalidTemplateError.new("Template is not a valid Kubernetes manifest",
|
69
71
|
filename: filename, content: doc)
|
70
72
|
end
|
71
|
-
yield doc
|
73
|
+
yield doc unless raw
|
72
74
|
end
|
75
|
+
yield rendered_content if raw
|
73
76
|
rescue InvalidTemplateError => err
|
74
77
|
err.filename ||= filename
|
75
78
|
raise err
|
@@ -106,18 +109,26 @@ module KubernetesDeploy
|
|
106
109
|
end
|
107
110
|
end
|
108
111
|
|
109
|
-
def
|
110
|
-
@template_sets
|
111
|
-
template_set.
|
112
|
+
def with_resource_definitions_and_filename(render_erb: false, current_sha: nil, bindings: nil, raw: false)
|
113
|
+
with_delayed_exceptions(@template_sets, KubernetesDeploy::InvalidTemplateError) do |template_set|
|
114
|
+
template_set.with_resource_definitions_and_filename(
|
112
115
|
render_erb: render_erb,
|
113
116
|
current_sha: current_sha,
|
114
|
-
bindings: bindings
|
115
|
-
|
116
|
-
|
117
|
+
bindings: bindings,
|
118
|
+
raw: raw
|
119
|
+
) do |r_def, filename|
|
120
|
+
yield r_def, filename
|
117
121
|
end
|
118
122
|
end
|
119
123
|
end
|
120
124
|
|
125
|
+
def with_resource_definitions(render_erb: false, current_sha: nil, bindings: nil, raw: false)
|
126
|
+
with_resource_definitions_and_filename(render_erb: render_erb,
|
127
|
+
current_sha: current_sha, bindings: bindings, raw: raw) do |r_def, _|
|
128
|
+
yield r_def
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
121
132
|
def ejson_secrets_files
|
122
133
|
@template_sets.map(&:ejson_secrets_file).compact
|
123
134
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kubernetes-deploy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.29.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katrina Verey
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-09-
|
12
|
+
date: 2019-09-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -86,9 +86,6 @@ dependencies:
|
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
88
|
- - "~>"
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: '2.3'
|
91
|
-
- - ">="
|
92
89
|
- !ruby/object:Gem::Version
|
93
90
|
version: 2.3.2
|
94
91
|
type: :runtime
|
@@ -96,9 +93,6 @@ dependencies:
|
|
96
93
|
version_requirements: !ruby/object:Gem::Requirement
|
97
94
|
requirements:
|
98
95
|
- - "~>"
|
99
|
-
- !ruby/object:Gem::Version
|
100
|
-
version: '2.3'
|
101
|
-
- - ">="
|
102
96
|
- !ruby/object:Gem::Version
|
103
97
|
version: 2.3.2
|
104
98
|
- !ruby/object:Gem::Dependency
|
@@ -279,8 +273,11 @@ files:
|
|
279
273
|
- exe/kubernetes-run
|
280
274
|
- kubernetes-deploy.gemspec
|
281
275
|
- lib/krane.rb
|
276
|
+
- lib/krane/cli/deploy_command.rb
|
282
277
|
- lib/krane/cli/krane.rb
|
278
|
+
- lib/krane/cli/render_command.rb
|
283
279
|
- lib/krane/cli/restart_command.rb
|
280
|
+
- lib/krane/cli/run_command.rb
|
284
281
|
- lib/krane/cli/version_command.rb
|
285
282
|
- lib/kubernetes-deploy.rb
|
286
283
|
- lib/kubernetes-deploy/bindings_parser.rb
|
@@ -289,6 +286,7 @@ files:
|
|
289
286
|
- lib/kubernetes-deploy/concurrency.rb
|
290
287
|
- lib/kubernetes-deploy/container_logs.rb
|
291
288
|
- lib/kubernetes-deploy/deferred_summary_logging.rb
|
289
|
+
- lib/kubernetes-deploy/delayed_exceptions.rb
|
292
290
|
- lib/kubernetes-deploy/deploy_task.rb
|
293
291
|
- lib/kubernetes-deploy/duration_parser.rb
|
294
292
|
- lib/kubernetes-deploy/ejson_secret_provisioner.rb
|
@@ -357,7 +355,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
357
355
|
requirements:
|
358
356
|
- - ">="
|
359
357
|
- !ruby/object:Gem::Version
|
360
|
-
version: 2.
|
358
|
+
version: 2.4.0
|
361
359
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
362
360
|
requirements:
|
363
361
|
- - ">="
|