consul-template-generator 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ba8f1717006ac33f420edd8b83719bdb68aae412
4
+ data.tar.gz: c0069022ebf8e26f26565428d15d7d553b791169
5
+ SHA512:
6
+ metadata.gz: 63b020316a8f3291225f0ecc82e95e9ecd60b0a99205ecad5c5e8089c6c19c457422a292959fb724ee38dc60a9e1aea4e4b6b21924fe3fd2cb41fa1495ac29ae
7
+ data.tar.gz: 05eec4beb3cd09c1135ae92c59ded085fb678adec808ce483ff38f0fe35ead70510f439713c64a6e5bf0ebce7856ad205b2f7ed4e751cb17ba313b657bc0e6b4
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ *.swp
3
+ bin/*
4
+ !bin/consul-template-generator
5
+ .bundle/
6
+ coverage/
7
+ install.sh
8
+ test.rb
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,70 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ consul-template-generator (0.1.0)
5
+ diplomat (~> 0.12.0)
6
+ popen4 (~> 0.1.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ Platform (0.4.0)
12
+ addressable (2.3.8)
13
+ colorize (0.7.7)
14
+ crack (0.4.2)
15
+ safe_yaml (~> 1.0.0)
16
+ diff-lcs (1.2.5)
17
+ diplomat (0.12.0)
18
+ faraday (~> 0.9)
19
+ json (~> 1.8)
20
+ docile (1.1.5)
21
+ faraday (0.9.1)
22
+ multipart-post (>= 1.2, < 3)
23
+ hirb (0.7.3)
24
+ json (1.8.3)
25
+ multipart-post (2.0.0)
26
+ open4 (1.3.4)
27
+ popen4 (0.1.2)
28
+ Platform (>= 0.4.0)
29
+ open4 (>= 0.4.0)
30
+ rack (1.6.1)
31
+ rake (10.4.2)
32
+ rspec (3.3.0)
33
+ rspec-core (~> 3.3.0)
34
+ rspec-expectations (~> 3.3.0)
35
+ rspec-mocks (~> 3.3.0)
36
+ rspec-core (3.3.0)
37
+ rspec-support (~> 3.3.0)
38
+ rspec-expectations (3.3.0)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.3.0)
41
+ rspec-mocks (3.3.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.3.0)
44
+ rspec-support (3.3.0)
45
+ safe_yaml (1.0.4)
46
+ simplecov (0.10.0)
47
+ docile (~> 1.1.0)
48
+ json (~> 1.8)
49
+ simplecov-html (~> 0.10.0)
50
+ simplecov-console (0.2.0)
51
+ colorize
52
+ hirb
53
+ simplecov
54
+ simplecov-html (0.10.0)
55
+ webmock (1.21.0)
56
+ addressable (>= 2.3.6)
57
+ crack (>= 0.3.2)
58
+
59
+ PLATFORMS
60
+ ruby
61
+
62
+ DEPENDENCIES
63
+ bundler (~> 1.7)
64
+ consul-template-generator!
65
+ rack (~> 1.6)
66
+ rake (~> 10.0)
67
+ rspec (~> 3.3)
68
+ simplecov (~> 0.10)
69
+ simplecov-console (~> 0.2)
70
+ webmock (~> 1.21)
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'consul/template/generator/cmd'
4
+
5
+ include Consul::Template::Generator
6
+
7
+ def verify_opts(opts)
8
+ !opts[:key].nil? && !opts[:template].nil?
9
+ end
10
+
11
+
12
+ options = {}
13
+ opt_parser = OptionParser.new do |opts|
14
+ opts.banner = <<EOC
15
+ consul-template-generator [options] <command>
16
+
17
+ Available commands:
18
+ once -- Run once and exit. On success, the rendered template will be inserted into the KV store whether or not it has changed.
19
+ run -- Run continually, uploading the rendered template when a change is detected.
20
+
21
+ Options:
22
+ EOC
23
+
24
+ options[:consul] = '127.0.0.1:8500'
25
+ opts.on('-c HOSTNAME', '--consul HOSTNAME', 'Hostname/port used to connect to consul [default: 127.0.0.1:8500]') do |h|
26
+ options[:consul] = h
27
+ end
28
+
29
+ options[:template] = nil
30
+ opts.on('-t TEMPLATE', '--template TEMPLATE', 'Consul-Template ctmpl file to monitor (required)') do |t|
31
+ options[:template] = t
32
+ end
33
+
34
+ options[:key] = nil
35
+ opts.on('-k KEY', '--key KEY', 'Key to store rendered template in (required)') do |k|
36
+ options[:key] = k
37
+ end
38
+
39
+ options[:proxy] = nil
40
+ opts.on('-p PROXY_URL', '--proxy PROXY_URL', 'Proxy URL if required (e.g. http://proxy.example.com:3128)') do |p|
41
+ options[:proxy] = p
42
+ end
43
+
44
+ options[:unset_proxy] = false
45
+ opts.on(nil, '--unset-proxy', "Use if 'http_proxy' is set in your environment, but you don't want to use it...") do |u|
46
+ options[:unset_proxy] = true
47
+ end
48
+
49
+ options[:log_level] = :info
50
+ opts.on('-l LOG_LEVEL', '--log-level LOG_LEVEL', "Log level, options are 'debug', 'info', 'error' [default: info]") do |l|
51
+ options[:log_level] = l.to_sym
52
+ end
53
+ end
54
+
55
+ opt_parser.parse!
56
+
57
+ unless verify_opts(options)
58
+ STDOUT.puts "Both '--key' and '--template' must be provided"
59
+ puts opt_parser
60
+ exit(1)
61
+ end
62
+
63
+ if options[:unset_proxy]
64
+ ENV['http_proxy'] = nil
65
+ end
66
+
67
+ CMD.configure(options[:consul], options[:template], options[:key], options[:log_level], options[:proxy])
68
+
69
+ ec = 1
70
+ cmd = ARGV[0]
71
+ case cmd
72
+ when 'run'
73
+ ec = CMD.run
74
+ when 'once'
75
+ ec = CMD.run_once
76
+ else
77
+ puts "Unknown command: #{cmd}"
78
+ puts opt_parser
79
+ end
80
+
81
+ exit(ec)
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'consul/template/generator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'consul-template-generator'
8
+ spec.version = Consul::Template::Generator::VERSION
9
+ spec.authors = ['Brian Oldfield']
10
+ spec.email = ['brian.oldfield@socrata.com']
11
+ spec.summary = %q{Wrapper around consul template which uploads renterd templates to consul's KV store}
12
+ spec.description = %q{When using complex consul-template templates or distributing them across many hosts, you run the risk of DoSing your consul cluster. Using consul-template-generator you can instead delegate the watching/rendering of templates to a single host and have downstream clients instead use a simple consul-template KV watch to retrieve the template and write it to disk.}
13
+ spec.homepage = 'http://github.com/boldfield/consul-template-generator'
14
+ spec.license = 'Apache'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/consul-template-generator}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec', '~> 3.3'
24
+ spec.add_development_dependency 'simplecov', '~> 0.10'
25
+ spec.add_development_dependency 'simplecov-console', '~> 0.2'
26
+ spec.add_development_dependency 'webmock', '~> 1.21'
27
+ spec.add_development_dependency 'rack', '~> 1.6'
28
+ spec.add_dependency 'diplomat', '~> 0.12.0'
29
+ spec.add_dependency 'popen4', '~> 0.1.2'
30
+ end
@@ -0,0 +1,69 @@
1
+ require_relative 'generator/cmd'
2
+ require_relative 'generator/ct'
3
+ require_relative 'generator/configuration'
4
+ require_relative 'generator/error'
5
+ require_relative 'generator/init'
6
+ require_relative 'generator/key_value'
7
+ require_relative 'generator/run'
8
+ require_relative 'generator/version'
9
+
10
+ require 'diplomat'
11
+ require 'faraday'
12
+
13
+ module Consul
14
+ module Template
15
+ module Generator
16
+ class << self
17
+ attr_accessor :config
18
+ attr_accessor :create_session, :renew_session, :destroy_session
19
+ def configure
20
+ self.config ||= Consul::Template::Generator::Configuration.new
21
+ self.config.node = `hostname`.strip
22
+ self.config.consul_host = '127.0.0.1:8500'
23
+
24
+ yield self.config
25
+
26
+ if self.config.consul_template_binary.nil?
27
+ ct_binary = `which consul-template`.strip
28
+ if ct_binary.empty?
29
+ raise "consul-template must be in your $PATH or configure the location to the executable"
30
+ end
31
+ self.config.consul_template_binary = ct_binary
32
+ end
33
+
34
+ if self.config.template.nil?
35
+ raise "template must be defined in configuration"
36
+ end
37
+
38
+ if self.config.template_key.nil?
39
+ raise "template_key must be defined in configuration"
40
+ end
41
+
42
+ Diplomat.configure do |config|
43
+ config.url = "http://#{self.config.consul_host}"
44
+ config.options = self.config.client_options
45
+ end
46
+ end
47
+
48
+ def create_session(name)
49
+ Diplomat::Session.create({:Node => self.config.node, :Name => name})
50
+ end
51
+
52
+ def renew_session(sess_id)
53
+ # There is an outstanding bug in Diplomat::Session.renew with a PR to fix
54
+ # https://github.com/WeAreFarmGeek/diplomat/issues/43
55
+ # Until it's merged and release, we have to skip renewing sessions...
56
+ begin
57
+ #Diplomat::Session.renew sess_id
58
+ rescue Faraday::ResourceNotFound
59
+ raise ConsulSessionExpired
60
+ end
61
+ end
62
+
63
+ def destroy_session(sess_id)
64
+ Diplomat::Session.destroy sess_id
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,71 @@
1
+ require 'consul/template/generator'
2
+
3
+ module Consul
4
+ module Template
5
+ module Generator
6
+ module CMD
7
+ include Consul::Template::Generator
8
+
9
+ def self.configure(consul_host, template, template_key, log_level, proxy = nil)
10
+ Consul::Template::Generator.configure do |config|
11
+ config.log_level = log_level
12
+ config.template = template
13
+ config.template_key = template_key
14
+ config.consul_host = consul_host
15
+ end
16
+ end
17
+
18
+ def self.run
19
+ config = Consul::Template::Generator.config
20
+ uploaded_hash = nil
21
+ begin
22
+ runner = CTRunner.new
23
+ runner.acquire_session_lock do
24
+ config.logger.info "Session lock acquired..."
25
+ begin
26
+ uploaded_hash = runner.run(uploaded_hash) || uploaded_hash
27
+ sleep 5
28
+ rescue Interrupt
29
+ raise # Re-raise to break this rescue block
30
+ rescue ConsulSessionExpired
31
+ config.logger.error "The current consul session has expired."
32
+ break
33
+ rescue Exception => e
34
+ config.logger.error "An error occurred while updating template: #{e.message}"
35
+ config.logger.debug "Sleeping before attempting to update again..."
36
+ sleep 5
37
+ break
38
+ end until false
39
+ end
40
+ rescue Interrupt
41
+ config.logger.error "Recieved interrupt signal, exiting..."
42
+ break
43
+ rescue Exception => e
44
+ config.logger.info "Unable to obtain session lock: #{e.message}"
45
+ config.logger.debug "Sleeping before attempting lock session again..."
46
+ begin
47
+ sleep 5
48
+ rescue Interrupt
49
+ config.logger.error "Recieved interrupt signal, exiting..."
50
+ break
51
+ end
52
+ end until false
53
+ 0
54
+ end
55
+
56
+ def self.run_once
57
+ config = Consul::Template::Generator.config
58
+ begin
59
+ runner = CTRunner.new
60
+ result = runner.run
61
+ rescue Exception => e
62
+ config.logger.error "An unexpected error occurred, unable to process template: #{e.message}"
63
+ 1
64
+ else
65
+ 0
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,65 @@
1
+ module Consul
2
+ module Template
3
+ module Generator
4
+ module STDLogLvl
5
+ class << self
6
+ def debug() 1 end
7
+ def info() 2 end
8
+ def error() 3 end
9
+ def off() 4 end
10
+ end
11
+ end
12
+
13
+ module STDLogger
14
+ class << self
15
+ def do_log?(requested_lvl, curr_lvl)
16
+ curr_lvl = Consul::Template::Generator::STDLogLvl.send(curr_lvl.to_sym)
17
+ requested_lvl = Consul::Template::Generator::STDLogLvl.send(requested_lvl.to_sym)
18
+ requested_lvl >= curr_lvl
19
+ end
20
+
21
+ def debug(msg)
22
+ if do_log?(:debug, Consul::Template::Generator.config.log_level)
23
+ STDOUT.puts "[DEBUG] #{msg}"
24
+ end
25
+ end
26
+
27
+ def info(msg)
28
+ if do_log?(:info, Consul::Template::Generator.config.log_level)
29
+ STDOUT.puts "[INFO] #{msg}"
30
+ end
31
+ end
32
+
33
+ def error(msg)
34
+ if do_log?(:error, Consul::Template::Generator.config.log_level)
35
+ STDERR.puts "[ERROR] #{msg}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ class Configuration
42
+ attr_accessor :template, :template_key, :consul_template_binary, :logger, :log_level
43
+ attr_accessor :consul_host, :node, :client_options
44
+
45
+ def initialize
46
+ @log_level = :debug
47
+ @node = nil
48
+ @consul_host = nil
49
+ @template = nil
50
+ @template_key = nil
51
+ @client_options = {}
52
+ @logger = Consul::Template::Generator::STDLogger
53
+ end
54
+
55
+ def lock_key
56
+ "/lock/#{@template_key.sub(/^\//, '')}"
57
+ end
58
+
59
+ def session_lock_key
60
+ "/lock/session/#{@template_key.sub(/^\//, '')}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,23 @@
1
+ require 'open4'
2
+ require 'digest'
3
+
4
+ module Consul
5
+ module Template
6
+ module Generator
7
+ class CTRunner
8
+ def render_template
9
+ body = nil
10
+ cmd = %{#{@config.consul_template_binary} -dry -once -template #{@config.template}}
11
+ procs = ::Open4.popen4(*cmd) do |pid, stdin, stdout, stderr|
12
+ body = stdout.read.strip
13
+ # consul-template -dry inserts '> \n' at the top of stdout, remove it
14
+ body.sub!(/^>\s+\n/, '')
15
+ end
16
+ status = procs.to_i
17
+ hash = ::Digest::MD5.hexdigest(body)
18
+ return status, body, hash
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ class ConsulSessionExpired < StandardError; end
2
+
3
+ class KeyNotLockedError < StandardError; end
4
+
5
+ class TemplateUploadError < StandardError; end
6
+
7
+ class TemplateRenderError < StandardError; end
@@ -0,0 +1,17 @@
1
+ module Consul
2
+ module Template
3
+ module Generator
4
+ class CTRunner
5
+ attr_accessor :session
6
+
7
+ def initialize(consul_session_id = nil)
8
+ if consul_session_id.nil?
9
+ consul_session_id = Consul::Template::Generator.create_session 'consul-template-generator'
10
+ end
11
+ @session = consul_session_id
12
+ @config = Consul::Template::Generator.config
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ require 'diplomat'
2
+
3
+ module Consul
4
+ module Template
5
+ module Generator
6
+ class CTRunner
7
+
8
+ def acquire_lock(lock_key = nil)
9
+ lock_key ||= @config.lock_key
10
+ @config.logger.debug "Attempting to acquire lock on key: #{lock_key}"
11
+ Consul::Template::Generator.renew_session @session
12
+ unless Diplomat::Lock.acquire(lock_key, @session)
13
+ raise KeyNotLockedError, "Unable to acquire lock on key: #{lock_key}"
14
+ end
15
+ @config.logger.debug "Lock acquired on key: #{lock_key}"
16
+
17
+ begin
18
+ yield
19
+ ensure
20
+ Diplomat::Lock.release(lock_key, @session)
21
+ end
22
+ end
23
+
24
+ def acquire_session_lock
25
+ acquire_lock(@config.session_lock_key) do
26
+ yield
27
+ end
28
+ end
29
+
30
+ def upload_template(raw_template)
31
+ @config.logger.info "Uploading key: #{@config.template_key}"
32
+ begin
33
+ Diplomat::Kv.put(@config.template_key, raw_template)
34
+ rescue Exception => e
35
+ raise TemplateUploadError, "Encountered an unexpected error while uploading template: #{e.message}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,38 @@
1
+ require 'consul/template/generator/ct'
2
+
3
+ module Consul
4
+ module Template
5
+ module Generator
6
+ class CTRunner
7
+ def run(comp_hash = nil)
8
+ status, body, hash, uploaded_hash = nil, nil, nil, nil
9
+ acquire_lock do
10
+ @config.logger.debug "Attempting to render template: #{@config.template}"
11
+ status, body, hash = render_template
12
+ unless status == 0
13
+ raise TemplateRenderError, "consul-template exited with on-zero exit status"
14
+ end
15
+ if body.nil? || body.empty?
16
+ raise TemplateRenderError, "rendered template is nil or empty!"
17
+ end
18
+ @config.logger.debug "Template rendered..."
19
+ if comp_hash.nil? || comp_hash != hash
20
+ @config.logger.info "Change in template discovered, attempting to upload to key #{@config.template_key}"
21
+ @config.logger.debug "Existing hash: #{comp_hash || 'nil'}, new hash: #{hash}"
22
+ uploaded = upload_template(body)
23
+ if uploaded
24
+ @config.logger.info "New template uploaded..."
25
+ uploaded_hash = hash
26
+ else
27
+ raise TemplateUploadError, "Template not uploaded!"
28
+ end
29
+ else
30
+ @config.logger.info "No change in template, skipping upload..."
31
+ end
32
+ end
33
+ return uploaded_hash
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ module Consul
2
+ module Template
3
+ module Generator
4
+ VERSION = '0.1.0'
5
+ end
6
+ end
7
+ end
data/spec/ct_spec.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ require 'open4'
5
+ require 'consul/template/generator'
6
+
7
+ include Consul::Template::Generator
8
+
9
+ def setup_open4(stdout, exit_code, pid)
10
+ @pid = pid
11
+ @status = exit_code
12
+ @stdin = StringIO.new
13
+ @stdout = StringIO.new
14
+ @stderr = StringIO.new
15
+
16
+ @stdout_string = "> \n#{stdout}"
17
+
18
+ @stdout << stdout
19
+ @stdout.rewind
20
+
21
+ allow(Open4).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
22
+ end
23
+
24
+ describe 'Consul::Template::Generator::CTRunner' '#render_template' do
25
+ before do
26
+ Consul::Template::Generator.configure do |config|
27
+ config.template = '/etc/test-template.ctmpl'
28
+ config.template_key = '/test-template'
29
+ end
30
+ end
31
+
32
+ context 'render template' do
33
+ it 'runs successfully' do
34
+ exp_out = 'this is a test'
35
+ exp_hash = '54b0c58c7ce9f2a8b551351102ee0938'
36
+ setup_open4(exp_out, 0, 101)
37
+ runner = CTRunner.new('test-session')
38
+ exit_code, body, hash = runner.render_template
39
+ expect(body).to eql(exp_out)
40
+ expect(hash).to eql(exp_hash)
41
+ expect(exit_code).to eql(@status)
42
+ end
43
+ end
44
+ end
data/spec/init_spec.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'webmock'
3
+
4
+ require 'consul/template/generator'
5
+
6
+ include Consul::Template::Generator
7
+
8
+ describe 'Consul::Template::Generator::CTRunner' '#initialize' do
9
+ before do
10
+ Consul::Template::Generator.configure do |config|
11
+ config.template = 'test-session-template.ctmpl'
12
+ config.template_key = 'test-session-template'
13
+ config.node = 'test-node'
14
+ end
15
+ end
16
+
17
+ context 'initialization of Consul::Template::Generator::CTRunner' do
18
+ it "creates session if token isn't passed" do
19
+ @runner = Consul::Template::Generator::CTRunner.new
20
+ expect(WebMock).to_not have_requested(:put, 'http://127.0.0.1:8500/v1/kv/session/create').with(:body => "{\"Node\": \"test-node\", \"Name\": \"consul-template-generator\"}")
21
+ expect(@runner.session).to eql('test-session-id')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+ require 'webmock'
4
+
5
+ require 'open4'
6
+ require 'consul/template/generator'
7
+
8
+ include Consul::Template::Generator
9
+
10
+ describe 'Consul::Template::Generator::CTRunner' '#acquire_lock' do
11
+ before do
12
+ Consul::Template::Generator.configure do |config|
13
+ config.template = 'test-template.ctmpl'
14
+ config.template_key = '/test-template'
15
+ config.consul_host = '127.0.0.1:8500'
16
+ config.log_level = :off
17
+ end
18
+ end
19
+
20
+ context 'aquires lock' do
21
+ it 'clean lock acquisision' do
22
+ runner = CTRunner.new('test-session')
23
+ runner.acquire_lock do
24
+ expect(true).to be_truthy ## This is simply to assert we are able to execute code in the block
25
+ end
26
+ expect(WebMock).to have_requested(:put, 'http://127.0.0.1:8500/v1/kv/lock/test-template').with(:query => {:acquire => 'test-session'})
27
+ expect(WebMock).to have_requested(:put, 'http://127.0.0.1:8500/v1/kv/lock/test-template').with(:query => {:release => 'test-session'})
28
+ end
29
+
30
+ it 'handles being unable to acquire lock' do
31
+ runner = CTRunner.new('test-session-lock-fail')
32
+ expect { runner.acquire_lock { puts 'hi' } }.to raise_error(KeyNotLockedError)
33
+ expect(WebMock).to have_requested(:put, 'http://127.0.0.1:8500/v1/kv/lock/test-template').with(:query => {:acquire => 'test-session-lock-fail'})
34
+ expect(WebMock).to_not have_requested(:put, 'http://127.0.0.1:8500/v1/kv/lock/test-template').with(:query => {:release => 'test-session-lock-fail'})
35
+ end
36
+ end
37
+ end
38
+
39
+ describe 'Consul::Template::Generator::CTRunner' '#acquire_session_lock' do
40
+ before do
41
+ Consul::Template::Generator.configure do |config|
42
+ config.template = 'test-template.ctmpl'
43
+ config.template_key = '/test-template'
44
+ config.consul_host = '127.0.0.1:8500'
45
+ config.log_level = :off
46
+ end
47
+ end
48
+
49
+ context 'aquires session lock' do
50
+ it 'clean lock acquisision' do
51
+ runner = CTRunner.new('test-session')
52
+ runner.acquire_session_lock do
53
+ expect(true).to be_truthy ## This is simply to assert we are able to execute code in the block
54
+ end
55
+ expect(WebMock).to have_requested(:put, 'http://127.0.0.1:8500/v1/kv/lock/session/test-template').with(:query => {:acquire => 'test-session'})
56
+ expect(WebMock).to have_requested(:put, 'http://127.0.0.1:8500/v1/kv/lock/session/test-template').with(:query => {:release => 'test-session'})
57
+ end
58
+
59
+ it 'handles being unable to acquire session lock' do
60
+ runner = CTRunner.new('test-session-lock-fail')
61
+ expect { runner.acquire_session_lock { puts 'hi' } }.to raise_error(KeyNotLockedError)
62
+ expect(WebMock).to have_requested(:put, 'http://127.0.0.1:8500/v1/kv/lock/session/test-template').with(:query => {:acquire => 'test-session-lock-fail'})
63
+ expect(WebMock).to_not have_requested(:put, 'http://127.0.0.1:8500/v1/kv/session/lock/test-template').with(:query => {:release => 'test-session-lock-fail'})
64
+ end
65
+ end
66
+ end
67
+
68
+ describe 'Consul::Template::Generator::CTRunner' '#upload_template' do
69
+ context 'uploads template' do
70
+ before do
71
+ Consul::Template::Generator.configure do |config|
72
+ config.template = 'test-template.ctmpl'
73
+ config.template_key = '/test-template'
74
+ config.consul_host = '127.0.0.1:8500'
75
+ config.log_level = :off
76
+ end
77
+ end
78
+
79
+ it 'does a clean upload' do
80
+ runner = CTRunner.new('test-session')
81
+ success = runner.upload_template('this is a test')
82
+ expect(success).to be_truthy
83
+ expect(WebMock).to have_requested(:put, 'http://127.0.0.1:8500/v1/kv/test-template').with(:body => 'this is a test')
84
+ end
85
+ end
86
+
87
+ context 'handles template upload failure' do
88
+ before do
89
+ Consul::Template::Generator.configure do |config|
90
+ config.template = 'test-template.ctmpl'
91
+ config.template_key = '/test-template-failure'
92
+ config.consul_host = '127.0.0.1:8500'
93
+ config.log_level = :off
94
+ end
95
+ end
96
+
97
+ it 'does a clean upload' do
98
+ runner = CTRunner.new('test-session')
99
+ expect { runner.upload_template('this is a fail test') }.to raise_error(TemplateUploadError)
100
+ expect(WebMock).to have_requested(:put, 'http://127.0.0.1:8500/v1/kv/test-template-failure').with(:body => 'this is a fail test')
101
+ end
102
+ end
103
+ end
data/spec/run_spec.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ require 'consul/template/generator'
4
+
5
+ include Consul::Template::Generator
6
+
7
+ describe 'Consul::Template::Generator::CTRunner' '#run' do
8
+ before do
9
+ @runner = Consul::Template::Generator::CTRunner.new 'test-session'
10
+ allow(@runner).to receive(:acquire_lock).and_yield
11
+ end
12
+
13
+ context 'run' do
14
+ it 'handles a clean run' do
15
+ exp_hash = 'bbf9afe7431caf5f89a608bc31e8d822'
16
+ expect(@runner).to receive(:render_template).with(no_args).and_return([0, 'test body', exp_hash])
17
+ expect(@runner).to receive(:upload_template).with('test body').and_return(true)
18
+ hash = @runner.run
19
+ expect(hash).to eql(exp_hash)
20
+ end
21
+
22
+ it "does't upload unchanged template" do
23
+ exp_hash = 'bbf9afe7431caf5f89a608bc31e8d822'
24
+ expect(@runner).to receive(:render_template).with(no_args).and_return([0, 'test body', exp_hash])
25
+ expect(@runner).not_to receive(:upload_template)
26
+ hash = @runner.run exp_hash
27
+ expect(hash).to be_nil
28
+ end
29
+
30
+ it 'handles non-zero exit status' do
31
+ expect(@runner).to receive(:render_template).with(no_args).and_return([1, 'not used', 'not used'])
32
+ expect(@runner).not_to receive(:upload_template)
33
+ expect { @runner.run }.to raise_exception(TemplateRenderError)
34
+ end
35
+
36
+ it 'handles empty rendered template' do
37
+ expect(@runner).to receive(:render_template).with(no_args).and_return([0, '', 'not used'])
38
+ expect(@runner).not_to receive(:upload_template)
39
+ expect { @runner.run }.to raise_exception(TemplateRenderError)
40
+ end
41
+
42
+ it 'handles bad return from upload_template' do
43
+ exp_hash = 'bbf9afe7431caf5f89a608bc31e8d822'
44
+ expect(@runner).to receive(:render_template).with(no_args).and_return([0, 'test body', exp_hash])
45
+ expect(@runner).to receive(:upload_template).with('test body').and_return(false)
46
+ expect { @runner.run }.to raise_exception(TemplateUploadError)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,112 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ require 'webmock/rspec'
20
+ require 'rack'
21
+ require 'simplecov'
22
+ require 'simplecov-console'
23
+
24
+ WebMock.disable_net_connect!(allow_localhost: false)
25
+ DIR = File.expand_path(File.dirname(__FILE__))
26
+
27
+ class ConsulApiRack
28
+ def call(env)
29
+ code, ret = 500, 'The tests have changed, fix the rack'
30
+ case env['PATH_INFO']
31
+ when /\/v1\/session\/create/
32
+ code, ret = 200, "{\"ID\": \"test-session-id\"}"
33
+ when /\/v1\/kv\/lock\//
34
+ code, ret = process_lock(env['QUERY_STRING'])
35
+ when /\/v1\/kv\//
36
+ code, ret = process_upload(env['PATH_INFO'])
37
+ end
38
+ [code, { 'Content-Type' => 'application/json' }, [ret]]
39
+ end
40
+
41
+ def process_upload(path)
42
+ code, ret = 500, 'The tests have changed, fix the rack'
43
+ case path
44
+ when /^\/v1\/kv\/test-template$/
45
+ code, ret = 200, 'true'
46
+ when /^\/v1\/kv\/test-template-failure$/
47
+ code, ret = 500, 'false'
48
+ end
49
+ return code, ret
50
+ end
51
+
52
+ def process_lock(qs)
53
+ code, ret = 500, 'The tests have changed, fix the rack'
54
+ case qs
55
+ when /^acquire=test-session$/
56
+ code, ret = 200, 'true'
57
+ when /^acquire=test-session-lock-fail$/
58
+ code, ret = 200, 'false'
59
+ when /release=test-session/
60
+ code, ret = 200, 'true'
61
+ end
62
+ return code, ret
63
+ end
64
+ end
65
+
66
+ RSpec.configure do |config|
67
+ # rspec-expectations config goes here. You can use an alternate
68
+ # assertion/expectation library such as wrong or the stdlib/minitest
69
+ # assertions if you prefer.
70
+ config.expect_with :rspec do |expectations|
71
+ # This option will default to `true` in RSpec 4. It makes the `description`
72
+ # and `failure_message` of custom matchers include text for helper methods
73
+ # defined using `chain`, e.g.:
74
+ # be_bigger_than(2).and_smaller_than(4).description
75
+ # # => "be bigger than 2 and smaller than 4"
76
+ # ...rather than:
77
+ # # => "be bigger than 2"
78
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
79
+ end
80
+
81
+ # rspec-mocks config goes here. You can use an alternate test double
82
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
83
+ config.mock_with :rspec do |mocks|
84
+ # Prevents you from mocking or stubbing a method that does not exist on
85
+ # a real object. This is generally recommended, and will default to
86
+ # `true` in RSpec 4.
87
+ mocks.verify_partial_doubles = true
88
+ end
89
+ config.before(:each) do
90
+ stub_request(:any, /127.0.0.1:8500\//).
91
+ to_rack(ConsulApiRack.new)
92
+ end
93
+
94
+ end
95
+
96
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
97
+ SimpleCov::Formatter::HTMLFormatter,
98
+ SimpleCov::Formatter::Console
99
+ ]
100
+ SimpleCov.minimum_coverage(85)
101
+ SimpleCov.start
102
+
103
+ def capture_stdout(&block)
104
+ original_stdout = $stdout
105
+ $stdout = fake = StringIO.new
106
+ begin
107
+ yield
108
+ ensure
109
+ $stdout = original_stdout
110
+ end
111
+ #fake.string
112
+ end
metadata ADDED
@@ -0,0 +1,202 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: consul-template-generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Oldfield
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '3.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '0.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov-console
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '0.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.21'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.21'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '1.6'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '1.6'
111
+ - !ruby/object:Gem::Dependency
112
+ name: diplomat
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.12.0
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: 0.12.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: popen4
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 0.1.2
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: 0.1.2
139
+ description: When using complex consul-template templates or distributing them across
140
+ many hosts, you run the risk of DoSing your consul cluster. Using consul-template-generator
141
+ you can instead delegate the watching/rendering of templates to a single host and
142
+ have downstream clients instead use a simple consul-template KV watch to retrieve
143
+ the template and write it to disk.
144
+ email:
145
+ - brian.oldfield@socrata.com
146
+ executables:
147
+ - consul-template-generator
148
+ extensions: []
149
+ extra_rdoc_files: []
150
+ files:
151
+ - .gitignore
152
+ - .rspec
153
+ - Gemfile
154
+ - Gemfile.lock
155
+ - bin/consul-template-generator
156
+ - consul-template-generator.gemspec
157
+ - lib/consul/template/generator.rb
158
+ - lib/consul/template/generator/cmd.rb
159
+ - lib/consul/template/generator/configuration.rb
160
+ - lib/consul/template/generator/ct.rb
161
+ - lib/consul/template/generator/error.rb
162
+ - lib/consul/template/generator/init.rb
163
+ - lib/consul/template/generator/key_value.rb
164
+ - lib/consul/template/generator/run.rb
165
+ - lib/consul/template/generator/version.rb
166
+ - spec/ct_spec.rb
167
+ - spec/init_spec.rb
168
+ - spec/key_value_spec.rb
169
+ - spec/run_spec.rb
170
+ - spec/spec_helper.rb
171
+ homepage: http://github.com/boldfield/consul-template-generator
172
+ licenses:
173
+ - Apache
174
+ metadata: {}
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - '>='
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - '>='
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ requirements: []
190
+ rubyforge_project:
191
+ rubygems_version: 2.4.6
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: Wrapper around consul template which uploads renterd templates to consul's
195
+ KV store
196
+ test_files:
197
+ - spec/ct_spec.rb
198
+ - spec/init_spec.rb
199
+ - spec/key_value_spec.rb
200
+ - spec/run_spec.rb
201
+ - spec/spec_helper.rb
202
+ has_rdoc: