kubernetes-deploy 0.28.0 → 0.29.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/.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
|
- - ">="
|