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.

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value = {}) ⇒ ElasticREST

Returns a new instance of ElasticREST.



177
178
179
180
# File 'lib/puppet/provider/elastic_rest.rb', line 177

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

Class Attribute Details

.api_discovery_uriObject

Returns the value of attribute api_discovery_uri.



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

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.



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

def api_uri
  @api_uri
end

.discrete_resource_creationObject

Returns the value of attribute discrete_resource_creation.



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

def discrete_resource_creation
  @discrete_resource_creation
end

.metadataObject

Returns the value of attribute metadata.



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

def 
  @metadata
end

.metadata_pipelineObject

Returns the value of attribute metadata_pipeline.



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

def 
  @metadata_pipeline
end

.query_stringObject

Returns the value of attribute query_string.



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

def query_string
  @query_string
end

Class Method Details

.api_objects(protocol = 'http', host = 'localhost', port = 9200, timeout = 10, username = nil, password = nil, ca_file = nil, ca_path = nil, validate_tls = true) ⇒ 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.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/puppet/provider/elastic_rest.rb', line 87

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

  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 && http.respond_to?(method)
  end

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

  results = []

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

  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.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/puppet/provider/elastic_rest.rb', line 66

def self.format_uri(resource_path, property_flush = {})
  return api_uri if resource_path.nil? || api_resource_style == :bare

  if discrete_resource_creation && !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.



140
141
142
# File 'lib/puppet/provider/elastic_rest.rb', line 140

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.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/puppet/provider/elastic_rest.rb', line 148

def self.prefetch(resources)
  # Get all relevant API access methods from the resources we know about
  res = resources.map do |_, resource|
    p = resource.parameters
    [
      p[:protocol].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),
      (p.key?(:validate_tls) ? p[:validate_tls].value : true),
    ]
    # Deduplicate identical settings, and fetch templates
  end.uniq
  res = res.map do |api|
    api_objects(*api)
    # Flatten and deduplicate the array, instantiate providers, and do the
    # typical association dance
  end
  res.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



116
117
118
119
120
121
122
123
124
125
# File 'lib/puppet/provider/elastic_rest.rb', line 116

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

.process_metadata(raw_metadata) ⇒ Object

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



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

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

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

Perform a REST API request against the indicated endpoint.

Returns:

  • Net::HTTPResponse



31
32
33
34
35
36
37
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
# File 'lib/puppet/provider/elastic_rest.rb', line 31

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

  if username && password
    req.basic_auth username, password
  elsif username || 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`.



274
275
276
# File 'lib/puppet/provider/elastic_rest.rb', line 274

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

#destroyObject

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



283
284
285
# File 'lib/puppet/provider/elastic_rest.rb', line 283

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

#exists?Boolean

Returns:

  • (Boolean)


278
279
280
# File 'lib/puppet/provider/elastic_rest.rb', line 278

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

#flushObject

Call Elasticsearch’s REST API to appropriately PUT/DELETE/or otherwise update any managed API objects.



195
196
197
198
199
200
201
202
203
204
205
206
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
# File 'lib/puppet/provider/elastic_rest.rb', line 195

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'
  %i[ca_file ca_path].each do |arg|
    http.send "#{arg}=".to_sym, resource[arg] if !resource[arg].nil? && http.respond_to?(arg)
  end

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

  # 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) \
                    && 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
  @property_hash = self.class.api_objects(
    resource[:protocol],
    resource[:host],
    resource[:port],
    resource[:timeout],
    resource[:username],
    resource[:password],
    resource[:ca_file],
    resource[:ca_path],
    resource[:validate_tls].nil? ? true : resource[:validate_tls]
  ).find do |t|
    t[:name] == resource[:name]
  end
end

#generate_bodyObject

Generate a request body



183
184
185
186
187
188
189
190
191
# File 'lib/puppet/provider/elastic_rest.rb', line 183

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

#metadataObject

Fetch arbitrary metadata for the class from an instance object.

Returns:

  • String



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

def 
  self.class.
end

#query_stringObject

Retrieve the class query_string variable

Returns:

  • String



24
25
26
# File 'lib/puppet/provider/elastic_rest.rb', line 24

def query_string
  self.class.query_string
end