consul-template-generator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: