Class: Hiera::Backend::Consul_backend

Inherits:
Object
  • Object
show all
Defined in:
lib/hiera/backend/consul_backend.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConsul_backend

Returns a new instance of Consul_backend.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/hiera/backend/consul_backend.rb', line 21

def initialize
  begin
    @config = Config[:consul]
  rescue StandardError => e
    raise ConfigurationError, "[hiera-consul]: config cannot be read: #{e}"
  end

  verify_config!
  parse_hosts!

  @consul = setup_consul
  use_ssl!

  @cache = {}
  build_cache!
end

Class Attribute Details

.api_versionObject (readonly)

Returns the value of attribute api_version.



16
17
18
# File 'lib/hiera/backend/consul_backend.rb', line 16

def api_version
  @api_version
end

Instance Attribute Details

#consulObject (readonly)

Returns the value of attribute consul.



19
20
21
# File 'lib/hiera/backend/consul_backend.rb', line 19

def consul
  @consul
end

#hostObject (readonly)

Returns the value of attribute host.



19
20
21
# File 'lib/hiera/backend/consul_backend.rb', line 19

def host
  @host
end

Instance Method Details

#build_cache!Object



239
240
241
242
243
244
245
246
247
248
# File 'lib/hiera/backend/consul_backend.rb', line 239

def build_cache!
  services = query_services
  return nil unless services.is_a? Hash

  services.each do |key, _|
    cache_service(key)
  end

  debug("Cache: #{@cache}")
end

#cache_node(key, node, node_hash) ⇒ Object

Store the value of a particular node



261
262
263
264
265
266
267
# File 'lib/hiera/backend/consul_backend.rb', line 261

def cache_node(key, node, node_hash)
  node_hash.each do |property, value|
    next if property == 'ServiceID'

    update_cache(key, value, property, node)
  end
end

#cache_service(key) ⇒ Object



250
251
252
253
254
255
256
257
258
# File 'lib/hiera/backend/consul_backend.rb', line 250

def cache_service(key)
  service = query_service(key)
  return nil unless service.is_a?(Array)

  service.each do |node_hash|
    node = node_hash['Node']
    cache_node(key, node, node_hash)
  end
end

#config_ssl!Object



113
114
115
116
117
118
119
120
121
# File 'lib/hiera/backend/consul_backend.rb', line 113

def config_ssl!
  msg = '[hiera-consul]: use_ssl is enabled but no ssl_cert is set'
  fail msg unless @config[:ssl_cert]

  ssl_verify!
  ssl_store!
  ssl_key!
  ssl_cert!
end

#consul_fallbackObject



99
100
101
102
# File 'lib/hiera/backend/consul_backend.rb', line 99

def consul_fallback
  debug "Could not reach #{@host}, retrying with #{@hosts.first}"
  setup_consul
end

#filter_paths(paths, key) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/hiera/backend/consul_backend.rb', line 153

def filter_paths(paths, key)
  paths.reduce([]) do |acc, path|
    if "#{path}/#{key}".match('//')
      # Check that we are not looking somewhere that will make hiera
      # crash subsequent lookups
      debug("The specified path #{path}/#{key} is malformed, skipping")
    elsif path !~ %r{^/v\d/(catalog|kv)/}
      # We only support querying the catalog or the kv store
      debug("We only support queries to catalog and kv and you asked #{path}, skipping")
    else
      acc << path
    end
    acc
  end
end

#lookup(key, scope, order_override, _resolution_type) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/hiera/backend/consul_backend.rb', line 38

def lookup(key, scope, order_override, _resolution_type)
  answer = nil

  paths = resolve_paths(key, scope, order_override)
  paths.unshift(order_override) if order_override

  filtered_paths = filter_paths(paths, key)

  filtered_paths.each do |path|
    return @cache[key] if path == 'services' && @cache.key?(key)

    debug("Lookup #{path}/#{key} on #{@host}:#{@config[:port]}")

    answer = wrapquery("#{path}/#{key}")
    break if answer
  end

  answer
end

#parse_hosts!Object



79
80
81
82
83
84
85
86
# File 'lib/hiera/backend/consul_backend.rb', line 79

def parse_hosts!
  @hosts =
    if @config[:host].is_a?(String)
      [@config[:host]]
    else
      @config[:host]
    end
end

#parse_result(res) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/hiera/backend/consul_backend.rb', line 169

def parse_result(res)
  # Consul always returns an array
  res_array = JSON.parse(res)

  # See if we are a k/v return or a catalog return
  unless res_array.length > 0
    debug('Jumped as array empty')
    return nil
  end

  if res_array.first.include? 'Value'
    Base64.decode64(res_array.first['Value'])
  else
    res_array
  end
end

#query_service(key) ⇒ Object



233
234
235
236
237
# File 'lib/hiera/backend/consul_backend.rb', line 233

def query_service(key)
  path = "/#{self.class.api_version}/catalog/service/#{key}"
  debug("Querying #{path}")
  wrapquery(path)
end

#query_servicesObject



227
228
229
230
231
# File 'lib/hiera/backend/consul_backend.rb', line 227

def query_services
  path = "/#{self.class.api_version}/catalog/services"
  debug("Querying #{@host}#{path}")
  wrapquery(path)
end

#request(httpreq) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/hiera/backend/consul_backend.rb', line 214

def request(httpreq)
  @consul.request(httpreq)
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, Errno::ECONNREFUSED => e
  if @hosts.length >= 1
    consul_fallback
    retry
  else
    debug('Could not connect to Consul')
    raise Exception, e.message unless @config[:failure] == 'graceful'
    return nil
  end
end

#resolve_paths(key, scope, order_override) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/hiera/backend/consul_backend.rb', line 58

def resolve_paths(key, scope, order_override)
  if @config[:base]
    Backend.datasources(scope, order_override) do |source|
      url = "#{@config[:base]}/#{source}"
      Backend.parse_string(url, scope, 'key' => key)
    end
  elsif @config[:paths]
    @config[:paths].map { |p| Backend.parse_string(p, scope, 'key' => key) }
  else
    probable_source = @config[:base] ? :base : :paths
    raise ConfigurationError, "[hiera-consul]: There is an issue with your hierarchy. Please check #{probable_source} configuration"
    exit 1
  end
end

#setup_consulObject



88
89
90
91
92
93
94
95
96
97
# File 'lib/hiera/backend/consul_backend.rb', line 88

def setup_consul
  fail '[hiera-consul]: No consul server is available' if @hosts.empty?
  @host = @hosts.shift

  debug "Trying #{@host}"
  @consul              = Net::HTTP.new(@host, @config[:port])
  @consul.read_timeout = @config[:http_read_timeout] || 10
  @consul.open_timeout = @config[:http_connect_timeout] || 10
  @consul
end

#ssl_cert!Object



148
149
150
151
# File 'lib/hiera/backend/consul_backend.rb', line 148

def ssl_cert!
  debug("ssl_cert: #{File.expand_path(@config[:ssl_cert])}")
  @consul.cert = OpenSSL::X509::Certificate.new(File.read(@config[:ssl_cert]))
end

#ssl_key!Object



143
144
145
146
# File 'lib/hiera/backend/consul_backend.rb', line 143

def ssl_key!
  debug("ssl_key: #{File.expand_path(@config[:ssl_key])}")
  @consul.key = OpenSSL::PKey::RSA.new(File.read(@config[:ssl_key]))
end

#ssl_store!Object



137
138
139
140
141
# File 'lib/hiera/backend/consul_backend.rb', line 137

def ssl_store!
  @store = OpenSSL::X509::Store.new
  @store.add_cert(OpenSSL::X509::Certificate.new(File.read(@config[:ssl_ca_cert])))
  @consul.cert_store = @store
end

#ssl_verify!Object



123
124
125
126
127
128
129
# File 'lib/hiera/backend/consul_backend.rb', line 123

def ssl_verify!
  if @config[:ssl_verify]
    @consul.verify_mode = OpenSSL::SSL::VERIFY_PEER
  else
    @consul.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end
end

#storeObject



131
132
133
134
135
# File 'lib/hiera/backend/consul_backend.rb', line 131

def store
  return @store if @store
  ssl_store!
  @store
end

#token(path) ⇒ Object

Token is passed only when querying kv store



210
211
212
# File 'lib/hiera/backend/consul_backend.rb', line 210

def token(path)
  "?token=#{@config[:token]}" if @config[:token] && path =~ %r{^/v\d/kv/}
end

#update_cache(key, value, property, node) ⇒ Object



269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/hiera/backend/consul_backend.rb', line 269

def update_cache(key, value, property, node)
  @cache["#{key}_#{property}_#{node}"] = value unless property == 'Node'

  if @cache.key?("#{key}_#{property}")
    @cache["#{key}_#{property}_array"].push(value)
  else
    # Value of the first registered node
    @cache["#{key}_#{property}"] = value

    # Values of all nodes
    @cache["#{key}_#{property}_array"] = [value]
  end
end

#use_ssl!Object



104
105
106
107
108
109
110
111
# File 'lib/hiera/backend/consul_backend.rb', line 104

def use_ssl!
  if @config[:use_ssl]
    @consul.use_ssl = true
    config_ssl!
  else
    @consul.use_ssl = false
  end
end

#verify_config!Object

Raises:



73
74
75
76
77
# File 'lib/hiera/backend/consul_backend.rb', line 73

def verify_config!
  return true if
    @config[:host] && @config[:port] && (@config[:paths] || @config[:base])
  raise ConfigurationError, '[hiera-consul]: Missing minimum configuration, please check hiera.yaml'
end

#wrapquery(path) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/hiera/backend/consul_backend.rb', line 186

def wrapquery(path)
  httpreq = Net::HTTP::Get.new("#{path}#{token(path)}")
  result  = request(httpreq)

  if result.nil?
    debug('No response from any server')
    return nil
  end

  unless result.is_a?(Net::HTTPSuccess)
    debug("HTTP response code was #{result.code}")
    return nil
  end

  if result.body == 'null'
    debug('Jumped as consul null is not valid')
    return nil
  end

  debug("Answer was #{result.body}")
  parse_result(result.body)
end