Class: Puppet::Provider::ElasticREST

Inherits:
Puppet::Provider
  • Object
show all
Defined in:
lib/puppet/provider/elastic_rest.rb

Overview

Parent class encapsulating general-use functions for children REST-based providers. rubocop:disable Metrics/ClassLength

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value = {}) ⇒ ElasticREST

Returns a new instance of ElasticREST.



187
188
189
190
# File 'lib/puppet/provider/elastic_rest.rb', line 187

def initialize(value = {})
  super(value)
  @property_flush = {}
end

Class Attribute Details

.api_discovery_uriObject

Returns the value of attribute api_discovery_uri.



10
11
12
# File 'lib/puppet/provider/elastic_rest.rb', line 10

def api_discovery_uri
  @api_discovery_uri
end

.api_resource_styleObject

Returns the value of attribute api_resource_style.



11
12
13
# File 'lib/puppet/provider/elastic_rest.rb', line 11

def api_resource_style
  @api_resource_style
end

.api_uriObject

Returns the value of attribute api_uri.



12
13
14
# File 'lib/puppet/provider/elastic_rest.rb', line 12

def api_uri
  @api_uri
end

.discrete_resource_creationObject

Returns the value of attribute discrete_resource_creation.



13
14
15
# File 'lib/puppet/provider/elastic_rest.rb', line 13

def discrete_resource_creation
  @discrete_resource_creation
end

.metadataObject

Returns the value of attribute metadata.



14
15
16
# File 'lib/puppet/provider/elastic_rest.rb', line 14

def 
  @metadata
end

.metadata_pipelineObject

Returns the value of attribute metadata_pipeline.



15
16
17
# File 'lib/puppet/provider/elastic_rest.rb', line 15

def 
  @metadata_pipeline
end

.query_stringObject

Returns the value of attribute query_string.



16
17
18
# File 'lib/puppet/provider/elastic_rest.rb', line 16

def query_string
  @query_string
end

Class Method Details

.api_objects(protocol = 'http', validate_tls = true, host = 'localhost', port = 9200, timeout = 10, username = nil, password = nil, ca_file = nil, ca_path = nil) ⇒ Object

Fetch Elasticsearch API objects. Accepts a variety of argument functions dictating how to connect to the Elasticsearch API.

Returns:

  • Array an array of Hashes representing the found API objects, whether they be templates, pipelines, et cetera.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/puppet/provider/elastic_rest.rb', line 95

def self.api_objects(protocol = 'http', \
                     validate_tls = true, \
                     host = 'localhost', \
                     port = 9200, \
                     timeout = 10, \
                     username = nil, \
                     password = nil, \
                     ca_file = nil, \
                     ca_path = nil)

  uri = URI("#{protocol}://#{host}:#{port}/#{format_uri(api_discovery_uri)}")
  http = Net::HTTP.new uri.host, uri.port
  req = Net::HTTP::Get.new uri.request_uri

  http.use_ssl = uri.scheme == 'https'
  [[ca_file, :ca_file=], [ca_path, :ca_path=]].each do |arg, method|
    http.send method, arg if arg and http.respond_to? method
  end

  response = rest http, req, validate_tls, timeout, username, password

  results = []

  if response.respond_to? :code and response.code.to_i == 200
    results = process_body(response.body)
  end

  results
end

.format_uri(resource_path, property_flush = {}) ⇒ Object

Helper to format a remote URL request for Elasticsearch which takes into account path ordering, et cetera.



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/puppet/provider/elastic_rest.rb', line 75

def self.format_uri(resource_path, property_flush = {})
  return api_uri if resource_path.nil? or api_resource_style == :bare
  if discrete_resource_creation and not property_flush[:ensure].nil?
    resource_path
  else
    case api_resource_style
    when :prefix
      resource_path + '/' + api_uri
    else
      api_uri + '/' + resource_path
    end
  end
end

.instancesObject

Fetch an array of provider objects from the Elasticsearch API.



152
153
154
# File 'lib/puppet/provider/elastic_rest.rb', line 152

def self.instances
  api_objects.map { |resource| new resource }
end

.prefetch(resources) ⇒ Object

Unlike a typical #prefetch, which just ties discovered #instances to the correct resources, we need to quantify all the ways the resources in the catalog know about Elasticsearch API access and use those settings to fetch any templates we can before associating resources and providers.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/puppet/provider/elastic_rest.rb', line 160

def self.prefetch(resources)
  # Get all relevant API access methods from the resources we know about
  resources.map do |_, resource|
    p = resource.parameters
    [
      p[:protocol].value,
      p[:validate_tls].value,
      p[:host].value,
      p[:port].value,
      p[:timeout].value,
      (p.key?(:username) ? p[:username].value : nil),
      (p.key?(:password) ? p[:password].value : nil),
      (p.key?(:ca_file) ? p[:ca_file].value : nil),
      (p.key?(:ca_path) ? p[:ca_path].value : nil)
    ]
    # Deduplicate identical settings, and fetch templates
  end.uniq.map do |api|
    api_objects(*api)
    # Flatten and deduplicate the array, instantiate providers, and do the
    # typical association dance
  end.flatten.uniq.map { |resource| new resource }.each do |prov|
    if (resource = resources[prov.name])
      resource.provider = prov
    end
  end
end

.process_body(body) ⇒ Object

Process the JSON response body



126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/puppet/provider/elastic_rest.rb', line 126

def self.process_body(body)
  results = JSON.parse(body).map do |object_name, api_object|
    {
      :name     => object_name,
      :ensure   => :present,
        => (api_object),
      :provider => name
    }
  end

  results
end

.process_metadata(raw_metadata) ⇒ Object

Passes API objects through arbitrary Procs/lambdas in order to postprocess API responses.



141
142
143
144
145
146
147
148
149
# File 'lib/puppet/provider/elastic_rest.rb', line 141

def self.()
  if .is_a? Array and !.empty?
    .reduce() do |md, processor|
      processor.call md
    end
  else
    
  end
end

.rest(http, req, validate_tls = true, timeout = 10, username = nil, password = nil) ⇒ Object

Perform a REST API request against the indicated endpoint.

rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity

Returns:

  • Net::HTTPResponse



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/puppet/provider/elastic_rest.rb', line 38

def self.rest(http, \
              req, \
              validate_tls = true, \
              timeout = 10, \
              username = nil, \
              password = nil)

  if username and password
    req.basic_auth username, password
  elsif username or password
    Puppet.warning(
      'username and password must both be defined, skipping basic auth'
    )
  end

  req['Accept'] = 'application/json'

  http.read_timeout = timeout
  http.open_timeout = timeout
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless validate_tls

  begin
    http.request req
  rescue EOFError => e
    # Because the provider attempts a best guess at API access, we
    # only fail when HTTP operations fail for mutating methods.
    unless %w[GET OPTIONS HEAD].include? req.method
      raise Puppet::Error,
            "Received '#{e}' from the Elasticsearch API. Are your API settings correct?"
    end
  end
end

Instance Method Details

#createObject

Set this provider’s ‘:ensure` property to `:present`.



291
292
293
# File 'lib/puppet/provider/elastic_rest.rb', line 291

def create
  @property_flush[:ensure] = :present
end

#destroyObject

Set this provider’s ‘:ensure` property to `:absent`.



300
301
302
# File 'lib/puppet/provider/elastic_rest.rb', line 300

def destroy
  @property_flush[:ensure] = :absent
end

#exists?Boolean

Returns:

  • (Boolean)


295
296
297
# File 'lib/puppet/provider/elastic_rest.rb', line 295

def exists?
  @property_hash[:ensure] == :present
end

#flushObject

Call Elasticsearch’s REST API to appropriately PUT/DELETE/or otherwise update any managed API objects. rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/puppet/provider/elastic_rest.rb', line 207

def flush
  Puppet.debug('Got to flush')
  uri = URI(
    format(
      '%s://%s:%d/%s',
      resource[:protocol],
      resource[:host],
      resource[:port],
      self.class.format_uri(resource[:name], @property_flush)
    )
  )
  uri.query = URI.encode_www_form query_string if query_string

  Puppet.debug("Generated URI = #{uri.inspect}")

  case @property_flush[:ensure]
  when :absent
    req = Net::HTTP::Delete.new uri.request_uri
  else
    req = Net::HTTP::Put.new uri.request_uri
    req.body = generate_body
    Puppet.debug("Generated body looks like: #{req.body.inspect}")
    # As of Elasticsearch 6.x, required when requesting with a payload (so we
    # set it always to be safe)
    req['Content-Type'] = 'application/json' if req['Content-Type'].nil?
  end

  http = Net::HTTP.new uri.host, uri.port
  http.use_ssl = uri.scheme == 'https'
  [:ca_file, :ca_path].each do |arg|
    if !resource[arg].nil? and http.respond_to? arg
      http.send "#{arg}=".to_sym, resource[arg]
    end
  end

  response = self.class.rest(
    http,
    req,
    resource[:validate_tls],
    resource[:timeout],
    resource[:username],
    resource[:password]
  )

  # Attempt to return useful error output
  unless response.code.to_i == 200
    Puppet.debug("Non-OK reponse: Body = #{response.body.inspect}")
    json = JSON.parse(response.body)

    err_msg = if json.key? 'error'
                if json['error'].is_a? Hash \
                    and json['error'].key? 'root_cause'
                  # Newer versions have useful output
                  json['error']['root_cause'].first['reason']
                else
                  # Otherwise fallback to old-style error messages
                  json['error']
                end
              else
                # As a last resort, return the response error code
                "HTTP #{response.code}"
              end

    raise Puppet::Error, "Elasticsearch API responded with: #{err_msg}"
  end
  # rubocop:enable Metrics/CyclomaticComplexity
  # rubocop:enable Metrics/PerceivedComplexity

  @property_hash = self.class.api_objects(
    resource[:protocol],
    resource[:validate_tls],
    resource[:host],
    resource[:port],
    resource[:timeout],
    resource[:username],
    resource[:password],
    resource[:ca_file],
    resource[:ca_path]
  ).detect do |t|
    t[:name] == resource[:name]
  end
end

#generate_bodyObject

Generate a request body



193
194
195
196
197
198
199
200
201
# File 'lib/puppet/provider/elastic_rest.rb', line 193

def generate_body
  JSON.generate(
    if  != :content and @property_flush[:ensure] == :present
      { .to_s => resource[] }
    else
      resource[]
    end
  )
end

#metadataObject

Fetch arbitrary metadata for the class from an instance object.

Returns:

  • String



22
23
24
# File 'lib/puppet/provider/elastic_rest.rb', line 22

def 
  self.class.
end

#query_stringObject

Retrieve the class query_string variable

Returns:

  • String



29
30
31
# File 'lib/puppet/provider/elastic_rest.rb', line 29

def query_string
  self.class.query_string
end