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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 825f058c113d4ae975ee5937866b639ac4d705f5cf9493ab414b56a183bd862e
4
- data.tar.gz: 2a6e43245a95f182e63d9f81d085f62aede3d16c74f4184d2f07bb6f0db55563
3
+ metadata.gz: a0641a291f2f01c007051cf5001bc97ecb9216b55e7204d954c920083e0d303a
4
+ data.tar.gz: 2c2415cfd94a63f046b94e90c943a7f9e01a46207833c1d5d2a498ceeb1ff333
5
5
  SHA512:
6
- metadata.gz: dbd772b8de088a986c62075c1443069be9fed802cf04958144ff377c26f3f594e9e2a6b5e64e1570452e936016e03fdc079b7dad986019cc27473e22ebf3bec1
7
- data.tar.gz: ff2824c32e6421056842a190fe8658f0fc903b3c866919b0a83f4bb4446b76d5d5cffad739fcba0c1564bb5079c02c21eb2022df30f379f9876e190157fe1ece
6
+ metadata.gz: 18a4e86f4f144dfa23ebc191b7d06382605ada3b050e8e1c9e717cbc9db981f16b419fb27a342f4e1267663f8f82fb12bcebfe0697c9ac4ac40c15108ab73072
7
+ data.tar.gz: 314bdadd3d8f864895c13e81193845f4632fa46dc23550f12955a5ffe7a7b15ed3df1e12d769d5aefdb8ee4b7e5c3e3e13d5f8378f5c828a2e4bf3c04db8c56c
@@ -2,7 +2,7 @@ inherit_from:
2
2
  - http://shopify.github.io/ruby-style-guide/rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.3
5
+ TargetRubyVersion: 2.4
6
6
 
7
7
  Naming/FileName:
8
8
  Enabled: true
@@ -1,6 +1,6 @@
1
1
  containers:
2
2
  default:
3
- docker: circleci/ruby:2.5.1-node-browsers
3
+ docker: circleci/ruby:2.4.6-node-browsers
4
4
 
5
5
  steps:
6
6
  - label: Lint
@@ -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.3+
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
@@ -17,5 +17,5 @@ docker run --rm \
17
17
  -e VERBOSE=1 \
18
18
  -e PARALLELISM=$PARALLELISM \
19
19
  -w /usr/src/app \
20
- ruby:2.3 \
20
+ ruby:2.4 \
21
21
  bin/test
data/dev.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: kubernetes-deploy
3
3
  up:
4
- - ruby: 2.3.3 # Matches gemspec
4
+ - ruby: 2.4.6 # Matches gemspec
5
5
  - bundler
6
6
  - homebrew:
7
7
  - Caskroom/cask/minikube
@@ -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.3.0'
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', '>= 2.3.2')
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
@@ -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 PROTECTED_NAMESPACES.include?(@namespace) && prune
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: true,
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 PROTECTED_NAMESPACES.include?(@namespace)
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
- if bad_files.present?
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 unidentified_errors.present? && filenames_with_sensitive_content.any?
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
- elsif unidentified_errors.present?
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 server_dry_runnable?
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
- _, err, st = validate_with_dry_run_option(kubectl, "--dry-run")
475
- if st.success? && server_dry_runnable?
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
- if st.success? || err.match(SERVER_DRY_RUN_DISABLED_ERROR)
478
- return true
479
- end
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:, bindings:)
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
- @renderer = KubernetesDeploy::Renderer.new(
13
- current_sha: current_sha,
14
- bindings: bindings,
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
- filenames = if only_filenames.empty?
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(filenames)
38
- render_filenames(stream, filenames)
31
+ validate_configuration(ts, only_filenames)
32
+ count = render_templates(stream, ts)
39
33
 
40
- @logger.summary.add_action("Successfully rendered #{filenames.size} template(s)")
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 render_filenames(stream, filenames)
50
- exceptions = []
51
- @logger.phase_heading("Rendering template(s)")
52
-
53
- filenames.each do |filename|
54
- begin
55
- render_filename(filename, stream)
56
- rescue KubernetesDeploy::InvalidTemplateError => exception
57
- exceptions << exception
58
- log_invalid_template(exception)
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
- unless exceptions.empty?
63
- raise exceptions[0]
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 render_filename(filename, stream)
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
- if filenames.empty?
90
- errors << "no templates found in template dir #{@template_dir}"
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
- absolute_template_dir = File.expand_path(@template_dir)
94
-
95
- filenames.each do |filename|
96
- absolute_file = File.expand_path(File.join(@template_dir, filename))
97
- if !File.exist?(absolute_file)
98
- errors << "Filename \"#{absolute_file}\" could not be found"
99
- elsif !File.file?(absolute_file)
100
- errors << "Filename \"#{absolute_file}\" is not a file"
101
- elsif !absolute_file.start_with?(absolute_template_dir)
102
- errors << "Filename \"#{absolute_file}\" is outside the template directory," \
103
- " which was resolved as #{absolute_template_dir}"
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 validate_args)
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 with_resource_definitions(render_erb: false, current_sha: nil, bindings: nil)
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.each do |filename|
27
+ with_delayed_exceptions(@files, KubernetesDeploy::InvalidTemplateError) do |filename|
24
28
  next if filename.end_with?(EjsonSecretProvisioner::EJSON_SECRETS_FILE)
25
- templates(filename: filename) do |r_def|
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 with_resource_definitions(render_erb: false, current_sha: nil, bindings: nil)
110
- @template_sets.each do |template_set|
111
- template_set.with_resource_definitions(
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
- ) do |r_def|
116
- yield r_def
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.28.0"
3
+ VERSION = "0.29.0"
4
4
  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.28.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-20 00:00:00.000000000 Z
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.3.0
358
+ version: 2.4.0
361
359
  required_rubygems_version: !ruby/object:Gem::Requirement
362
360
  requirements:
363
361
  - - ">="