diff --git a/.gitignore b/.gitignore index b4c60c6..29955f4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ /doc/ /pkg/ /spec/reports/ -/spec/support/rendered/*.yml +/spec/support/rendered/* /tmp/ # rspec failure tracking diff --git a/lib/consult.rb b/lib/consult.rb index 96732aa..2a10907 100644 --- a/lib/consult.rb +++ b/lib/consult.rb @@ -19,6 +19,7 @@ module Consult class << self attr_reader :config, :templates + attr_writer :exception_handler def load(config_dir: nil) root directory: config_dir @@ -78,6 +79,10 @@ def render! active_templates.each(&:render) end + def exception_handler + @exception_handler ||= ->(e) { puts e.message } + end + # Map more conventional `token` parameter to Diplomat's `acl_token` configuration. # Additionally, we support ~/.consul-token, similar to Vault's support for ~/.vault-token def consul_token diff --git a/lib/consult/template.rb b/lib/consult/template.rb index 05429e0..477e510 100644 --- a/lib/consult/template.rb +++ b/lib/consult/template.rb @@ -15,20 +15,31 @@ def initialize(name, config) end def render(save: true) - renderer = ERB.new(File.read(path, encoding: 'utf-8'), nil, '-') + # Attempt to render + renderer = ERB.new(contents, nil, '-') result = renderer.result(binding) File.open(dest, 'w') { |f| f << result } if save result rescue StandardError => e - puts "Error rendering template: #{name}" - raise e + Consult.exception_handler.call(e) + nil end def path + return unless @config.key?(:path) resolve @config.fetch(:path) end + def paths + return [] unless @config.key?(:paths) + @config.fetch(:paths).map { |path| resolve(path) } + end + + def vars + @config[:vars] + end + def dest resolve @config.fetch(:dest) end @@ -42,5 +53,26 @@ def expired? return true if !config.key?(:ttl) || !dest.exist? dest.mtime < (Time.now - @config[:ttl].to_i) end + + private + + # Concatenate all the source templates together, in the order provided + # Disk contents go first + def contents + disk_contents + consul_contents + end + + def consul_contents + [@config[:consul_key], @config[:consul_keys]].compact.flatten.map do |key| + Diplomat::Kv.get(key, options: nil, not_found: :return, found: :return) + end.join + end + + # Concatenate all the source templates together, in the order provided + def disk_contents + [path, paths].compact.flatten.map do |file_path| + File.read file_path, encoding: 'utf-8' + end.join + end end end diff --git a/spec/consult_spec.rb b/spec/consult_spec.rb index feac664..450e65c 100644 --- a/spec/consult_spec.rb +++ b/spec/consult_spec.rb @@ -21,5 +21,10 @@ it 'renders without error' do expect { Consult.render! }.to_not raise_exception + + # Verify text templates rendered correctly + %w[elements.txt more_elements.txt consul_elements.txt more_consul_elements.txt multi_pass.txt].each do |template| + expect(FileUtils.compare_file("spec/support/expected/#{template}", "spec/support/rendered/#{template}")).to be true + end end end diff --git a/spec/lib/template_spec.rb b/spec/lib/template_spec.rb index efb1d71..9095ce6 100644 --- a/spec/lib/template_spec.rb +++ b/spec/lib/template_spec.rb @@ -1,5 +1,14 @@ # frozen_string_literal: true +class Handler + class << self + attr_reader :error + def call(error) + @error = error + end + end +end + RSpec.describe Consult::Template do let(:name) { 'database.yml' } let(:config) do @@ -11,6 +20,14 @@ end let(:template) { Consult::Template.new(name, config) } + let(:error_template) { 'Corbin Dallas' } + let(:error_config) do + { + consul_key: 'templates/error_test', + dest: 'rendered/error_test.txt' + } + end + before :all do Consult.load config_dir: 'spec/support' end @@ -75,4 +92,19 @@ expect(template.indent(colon_separated, 1, ':')).to eq ' hello: world' end end + + context 'error handling' do + it 'allows custom error handlers' do + Consult.exception_handler = Handler + Diplomat::Kv.put('templates/error_test', error_template) + template = Consult::Template.new('error_template', error_config) + expect(template.render).to eq error_template + + Diplomat::Kv.delete('templates/error_test') + expect(template.render).to be nil + expect(Handler.error).to be_instance_of Diplomat::KeyNotFound + + expect(File.read(template.dest)).to eq error_template + end + end end diff --git a/spec/support/config/consult.yml b/spec/support/config/consult.yml index b8b56a4..3d08441 100644 --- a/spec/support/config/consult.yml +++ b/spec/support/config/consult.yml @@ -14,6 +14,70 @@ shared: dest: rendered/database.yml ttl: 10 # seconds + elements: + paths: + - templates/elements/air.txt + - templates/elements/fire.txt + dest: rendered/elements.txt + vars: + air: 1 + fire: 2 + + more_elements: + path: templates/elements/air.txt + paths: + - templates/elements/fire.txt + - templates/elements/water.txt + dest: rendered/more_elements.txt + vars: + air: 1 + fire: 2 + water: 3 + + consul_elements: + consul_keys: + - templates/elements/earth + - templates/elements/love + dest: rendered/consul_elements.txt + vars: + earth: 4 + love: 5 + + more_consul_elements: + consul_key: templates/elements/earth + consul_keys: + - templates/elements/love + - templates/elements/aziz + dest: rendered/more_consul_elements.txt + vars: + earth: 4 + love: 5 + aziz: 'Light!' + + multi_pass: + path: templates/elements/air.txt + paths: + - templates/elements/fire.txt + - templates/elements/water.txt + consul_key: templates/elements/earth + consul_keys: + - templates/elements/love + - templates/elements/aziz + dest: rendered/multi_pass.txt + vars: + air: 1 + fire: 2 + water: 3 + earth: 4 + love: 5 + aziz: 'Light!' + + dest_fail: + consul_key: templates/elements/aziz + dest: rendered/nope/dest_fail.keep + vars: + aziz: 'Light!' + test: templates: secrets: diff --git a/spec/support/expected/consul_elements.txt b/spec/support/expected/consul_elements.txt new file mode 100644 index 0000000..707f8e8 --- /dev/null +++ b/spec/support/expected/consul_elements.txt @@ -0,0 +1,2 @@ +Earth is the 4th element +Love is the 5th element! diff --git a/spec/support/expected/elements.txt b/spec/support/expected/elements.txt new file mode 100644 index 0000000..2dd21b4 --- /dev/null +++ b/spec/support/expected/elements.txt @@ -0,0 +1,2 @@ +Air is the 1st element +Fire is the 2nd element diff --git a/spec/support/expected/more_consul_elements.txt b/spec/support/expected/more_consul_elements.txt new file mode 100644 index 0000000..18f796f --- /dev/null +++ b/spec/support/expected/more_consul_elements.txt @@ -0,0 +1,3 @@ +Earth is the 4th element +Love is the 5th element! +Aziz! Light! diff --git a/spec/support/expected/more_elements.txt b/spec/support/expected/more_elements.txt new file mode 100644 index 0000000..a7af96c --- /dev/null +++ b/spec/support/expected/more_elements.txt @@ -0,0 +1,3 @@ +Air is the 1st element +Fire is the 2nd element +Water is the 3rd element diff --git a/spec/support/expected/multi_pass.txt b/spec/support/expected/multi_pass.txt new file mode 100644 index 0000000..0a10480 --- /dev/null +++ b/spec/support/expected/multi_pass.txt @@ -0,0 +1,6 @@ +Air is the 1st element +Fire is the 2nd element +Water is the 3rd element +Earth is the 4th element +Love is the 5th element! +Aziz! Light! diff --git a/spec/support/populate_consul.sh b/spec/support/populate_consul.sh index 372ba52..a8b7c22 100755 --- a/spec/support/populate_consul.sh +++ b/spec/support/populate_consul.sh @@ -13,3 +13,18 @@ curl \ --request PUT \ --data 'db1.local.net' \ http://0.0.0.0:8500/v1/kv/infrastructure/db1/dns + +curl \ + --request PUT \ + --data $'Earth is the <%= vars[:earth] %>th element\n' \ + http://0.0.0.0:8500/v1/kv/templates/elements/earth + +curl \ + --request PUT \ + --data $'Love is the <%= vars[:love] %>th element!\n' \ + http://0.0.0.0:8500/v1/kv/templates/elements/love + +curl \ + --request PUT \ + --data $'Aziz! <%= vars[:aziz] %>\n' \ + http://0.0.0.0:8500/v1/kv/templates/elements/aziz diff --git a/spec/support/templates/elements/air.txt b/spec/support/templates/elements/air.txt new file mode 100644 index 0000000..016dd2b --- /dev/null +++ b/spec/support/templates/elements/air.txt @@ -0,0 +1 @@ +Air is the <%= vars[:air] %>st element diff --git a/spec/support/templates/elements/fire.txt b/spec/support/templates/elements/fire.txt new file mode 100644 index 0000000..34227dc --- /dev/null +++ b/spec/support/templates/elements/fire.txt @@ -0,0 +1 @@ +Fire is the <%= vars[:fire] %>nd element diff --git a/spec/support/templates/elements/water.txt b/spec/support/templates/elements/water.txt new file mode 100644 index 0000000..7b9d40a --- /dev/null +++ b/spec/support/templates/elements/water.txt @@ -0,0 +1 @@ +Water is the <%= vars[:water] %>rd element