Class: Puppet::Resource::Catalog::Puppetdb

Inherits:
Indirector::REST
  • Object
show all
Includes:
Util::Puppetdb, Util::Puppetdb::CommandNames
Defined in:
lib/puppet/indirector/catalog/puppetdb.rb

Constant Summary collapse

Relationships =
{
  :before    => {:direction => :forward, :relationship => 'before'},
  :require   => {:direction => :reverse, :relationship => 'required-by'},
  :notify    => {:direction => :forward, :relationship => 'notifies'},
  :subscribe => {:direction => :reverse, :relationship => 'subscription-of'},
}
UnorderedMetaparams =

Metaparams that may contain arrays, but whose semantics are fundamentally unordered

[:alias, :audit, :before, :check, :notify, :require, :subscribe, :tag]

Constants included from Util::Puppetdb::CommandNames

Util::Puppetdb::CommandNames::CommandDeactivateNode, Util::Puppetdb::CommandNames::CommandReplaceCatalog, Util::Puppetdb::CommandNames::CommandReplaceFacts, Util::Puppetdb::CommandNames::CommandStoreReport

Instance Method Summary collapse

Methods included from Util::Puppetdb

#config, config, included, log_x_deprecation_header, port, #profile, puppet3compat?, server, #submit_command, to_bool, to_wire_time, url_path

Instance Method Details

#add_environment(hash, environment) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Include environment in hash, returning the complete hash.

Parameters:

  • hash (Hash)

    original data hash

  • environment (String)

    environment

Returns:

  • (Hash)

    returns original hash augmented with environment



69
70
71
72
73
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 69

def add_environment(hash, environment)
  hash['environment'] = environment

  hash
end

#add_namevar_aliases(hash, catalog) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
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/indirector/catalog/puppetdb.rb', line 134

def add_namevar_aliases(hash, catalog)
  resources = hash['resources']
  profile("Add namevar aliases (resource count: #{resources.count})",
          [:puppetdb, :namevar_aliases, :add]) do
    resources.each do |resource|
      real_resource = catalog.resource(resource['type'], resource['title'])

      # Resources with composite namevars can't be referred to by
      # anything other than their title when declaring
      # relationships. Trying to snag the :alias for these resources
      # will only return _part_ of the name (a problem with Puppet
      # proper), so skipping the adding of aliases for these resources
      # is both an optimization and a safeguard.
      next if real_resource.key_attributes.count > 1

      aliases = [real_resource[:alias]].flatten.compact

      # Non-isomorphic resources aren't unique based on namevar, so we can't
      # use it as an alias
      type = real_resource.resource_type
      if !type.respond_to?(:isomorphic?) or type.isomorphic?
        # This makes me a little sad.  It turns out that the "to_hash" method
        #  of Puppet::Resource can have side effects.  In particular, if the
        #  resource type specifies a title_pattern, calling "to_hash" will trigger
        #  the title_pattern processing, which can have the side effect of
        #  populating the namevar (potentially with a munged value).  Thus,
        #  it is important that we search for namevar aliases in that hash
        #  rather than in the resource itself.
        real_resource_hash = real_resource.to_hash

        name = real_resource_hash[real_resource.send(:namevar)]
        unless name.nil? or real_resource.title == name or aliases.include?(name)
          aliases << name
        end
      end

      resource['parameters']['alias'] = aliases unless aliases.empty?
    end
  end

  hash
end

#add_parameters_if_missing(hash) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 122

def add_parameters_if_missing(hash)
  resources = hash['resources']
  profile("Add parameters if missing (resource count: #{resources.count})",
          [:puppetdb, :parameters, :add_missing]) do
    resources.each do |resource|
      resource['parameters'] ||= {}
    end
  end

  hash
end

#add_producer_timestamp(hash, producer_timestamp) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Include producer_timestamp in hash, returning the complete hash.

Parameters:

  • hash (Hash)

    original data hash

  • producer_timestamp (String or nil)

    producer_tiemstamp

Returns:

  • (Hash)

    returns original hash augmented with producer_timestamp



81
82
83
84
85
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 81

def add_producer_timestamp(hash, producer_timestamp)
  hash['producer-timestamp'] = producer_timestamp

  hash
end

#add_transaction_uuid(hash, transaction_uuid) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Include transaction_uuid in hash, returning the complete hash.

Parameters:

  • hash (Hash)

    original data hash

  • transaction_uuid (String)

    transaction_uuid

Returns:

  • (Hash)

    returns original hash augmented with transaction_uuid



93
94
95
96
97
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 93

def add_transaction_uuid(hash, transaction_uuid)
  hash['transaction-uuid'] = transaction_uuid

  hash
end

#edge_to_s(specifier_resource, referred_resource, param) ⇒ Object



365
366
367
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 365

def edge_to_s(specifier_resource, referred_resource, param)
  "#{specifier_resource} { #{param} => #{referred_resource} }"
end

#extract_extra_request_data(request) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



22
23
24
25
26
27
28
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 22

def extract_extra_request_data(request)
  {
    :transaction_uuid => request.options[:transaction_uuid],
    :environment => request.environment,
    :producer_timestamp => request.options[:producer_timestamp] || Time.now.iso8601,
  }
end

#filter_keys(hash) ⇒ Object



347
348
349
350
351
352
353
354
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 347

def filter_keys(hash)
  profile("Filter extraneous keys from the catalog",
          [:puppetdb, :keys, :filter_extraneous]) do
    hash.delete_if do |k,v|
      ! ['name', 'version', 'edges', 'resources'].include?(k)
    end
  end
end

#find(request) ⇒ Object



17
18
19
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 17

def find(request)
  nil
end

#map_aliases_to_title(hash) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 210

def map_aliases_to_title(hash)
  resources = hash['resources']
  aliases = {}

  profile("Map aliases to title (resource count: #{resources.count})",
          [:puppetdb, :aliases, :map_to_title]) do
    resources.each do |resource|
      names = resource['parameters']['alias'] || []
      resource_hash = {'type' => resource['type'], 'title' => resource['title']}
      names.each do |name|
        alias_array = [resource['type'], name]
        aliases[alias_array] = resource_hash
      end
    end
  end

  aliases
end

#munge_catalog(catalog, extra_request_data = {}) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 30

def munge_catalog(catalog, extra_request_data = {})
  profile("Munge catalog", [:puppetdb, :catalog, :munge]) do
    data = profile("Convert catalog to JSON data hash", [:puppetdb, :catalog, :convert_to_hash]) do
      catalog.to_data_hash
    end

    add_parameters_if_missing(data)
    add_namevar_aliases(data, catalog)
    stringify_titles(data)
    stringify_version(data)
    sort_unordered_metaparams(data)
    munge_edges(data)
    synthesize_edges(data, catalog)
    filter_keys(data)
    add_transaction_uuid(data, extra_request_data[:transaction_uuid])
    add_environment(data, extra_request_data[:environment])
    add_producer_timestamp(data, extra_request_data[:producer_timestamp])

    data
  end
end

#munge_edges(hash) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 195

def munge_edges(hash)
  edges = hash['edges']
  profile("Munge edges (edge count: #{edges.count})",
          [:puppetdb, :edges, :munge]) do
    edges.each do |edge|
      %w[source target].each do |vertex|
        edge[vertex] = resource_ref_to_hash(edge[vertex]) if edge[vertex].is_a?(String)
      end
      edge['relationship'] ||= 'contains'
    end

    hash
  end
end

#resource_hash_to_ref(hash) ⇒ Object



361
362
363
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 361

def resource_hash_to_ref(hash)
  "#{hash['type']}[#{hash['title']}]"
end

#resource_ref_to_hash(ref) ⇒ Object



356
357
358
359
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 356

def resource_ref_to_hash(ref)
  ref =~ /^([^\[\]]+)\[(.+)\]$/m
  {'type' => $1, 'title' => $2}
end

#save(request) ⇒ Object



10
11
12
13
14
15
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 10

def save(request)
  profile("catalog#save", [:puppetdb, :catalog, :save, request.key]) do
    catalog = munge_catalog(request.instance, extract_extra_request_data(request))
    submit_command(request.key, catalog, CommandReplaceCatalog, 5)
  end
end

#sort_unordered_metaparams(hash) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 177

def sort_unordered_metaparams(hash)
  resources = hash['resources']
  profile("Sort unordered metaparams (resource count: #{resources.count})",
          [:puppetdb, :metaparams, :sort]) do
    resources.each do |resource|
      params = resource['parameters']
      UnorderedMetaparams.each do |metaparam|
        if params[metaparam].kind_of? Array then
          values = params[metaparam].sort
          params[metaparam] = values unless values.empty?
        end
      end
    end
  end

  hash
end

#stringify_titles(hash) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 110

def stringify_titles(hash)
  resources = hash['resources']
  profile("Stringify titles (resource count: #{resources.count})",
          [:puppetdb, :titles, :stringify]) do
    resources.each do |resource|
      resource['title'] = resource['title'].to_s
    end
  end

  hash
end

#stringify_version(hash) ⇒ Hash

Version is an integer (time since epoch in millis). The wire format specifies version should be a string

Parameters:

  • hash (Hash)

    original data hash

Returns:

  • (Hash)

    returns a modified original hash



104
105
106
107
108
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 104

def stringify_version(hash)
  hash['version'] = hash['version'].to_s

  hash
end

#synthesize_edges(hash, catalog) ⇒ Object



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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/puppet/indirector/catalog/puppetdb.rb', line 229

def synthesize_edges(hash, catalog)
  profile("Synthesize edges",
          [:puppetdb, :edges, :synthesize]) do
    aliases = map_aliases_to_title(hash)

    resource_table = {}
    profile("Build up resource_table",
            [:puppetdb, :edges, :synthesize, :resource_table, :build]) do
      hash['resources'].each do |resource|
        resource_table[ [resource['type'], resource['title']] ] = resource
      end
    end

    profile("Primary synthesis",
            [:puppetdb, :edges, :synthesize, :primary_synthesis])do
      hash['resources'].each do |resource|
        # Standard virtual resources don't appear in the catalog. However,
        # exported resources which haven't been also collected will appears as
        # exported and virtual (collected ones will only be exported). They will
        # eventually be removed from the catalog, so we can't add edges involving
        # them. Puppet::Resource#to_data_hash omits 'virtual', so we have to
        # look it up in the catalog to find that information. This isn't done in
        # a separate step because we don't actually want to send the field (it
        # will always be false). See ticket #16472.
        #
        # The outer conditional is here because Class[main] can't properly be
        # looked up using catalog.resource and will return nil. See ticket
        # #16473. Yay.
        if real_resource = catalog.resource(resource['type'], resource['title'])
          next if real_resource.virtual?
        end

        Relationships.each do |param,relation|
          if value = resource['parameters'][param]
            [value].flatten.each do |other_ref|
              edge = {'relationship' => relation[:relationship]}

              resource_hash = {'type' => resource['type'], 'title' => resource['title']}
              other_hash = resource_ref_to_hash(other_ref)

              # Puppet doesn't always seem to check this correctly. If we don't
              # users will later get an invalid relationship error instead.
              #
              # Primarily we are trying to catch the non-capitalized resourceref
              # case problem here: http://projects.puppetlabs.com/issues/19474
              # Once that problem is solved and older versions of Puppet that have
              # the bug are no longer supported we can probably remove this code.
              unless other_ref =~ /^[A-Z][a-z0-9_-]*(::[A-Z][a-z0-9_-]*)*\[.*\]/
                rel = edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)
                raise Puppet::Error, "Invalid relationship: #{rel}, because " +
                  "#{other_ref} doesn't seem to be in the correct format. " +
                  "Resource references should be formatted as: " +
                  "Classname['title'] or Modulename::Classname['title'] (take " +
                  "careful note of the capitalization)."
              end

              # This is an unfortunate hack.  Puppet does some weird things w/rt
              # munging off trailing slashes from file resources, and users may
              # legally specify relationships using a different number of trailing
              # slashes than the resource was originally declared with.
              # We do know that for any file resource in the catalog, there should
              # be a canonical entry for it that contains no trailing slashes.  So,
              # here, in case someone has specified a relationship to a file resource
              # and has used one or more trailing slashes when specifying the
              # relationship, we will munge off the trailing slashes before
              # we look up the resource in the catalog to create the edge.
              if other_hash['type'] == 'File' and other_hash['title'] =~ /\/$/
                other_hash['title'] = other_hash['title'].sub(/\/+$/, '')
              end

              other_array = [other_hash['type'], other_hash['title']]

              # Try to find the resource by type/title or look it up as an alias
              # and try that
              other_resource = resource_table[other_array]
              if other_resource.nil? and alias_hash = aliases[other_array]
                other_resource = resource_table[ alias_hash.values_at('type', 'title') ]
              end

              raise Puppet::Error, "Invalid relationship: #{edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)}, because #{other_ref} doesn't seem to be in the catalog" unless other_resource

              # As above, virtual exported resources will eventually be removed,
              # so if a real resource refers to one, it's wrong. Non-virtual
              # exported resources are exported resources that were also
              # collected in this catalog, so they're okay. Virtual non-exported
              # resources can't appear in the catalog in the first place, so it
              # suffices to check for virtual.
              if other_real_resource = catalog.resource(other_resource['type'], other_resource['title'])
                if other_real_resource.virtual?
                  raise Puppet::Error, "Invalid relationship: #{edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)}, because #{other_ref} is exported but not collected"
                end
              end

              # If the ref was an alias, it will have a different title, so use
              # that
              other_hash['title'] = other_resource['title']

              if relation[:direction] == :forward
                edge.merge!('source' => resource_hash, 'target' => other_hash)
              else
                edge.merge!('source' => other_hash, 'target' => resource_hash)
              end
              hash['edges'] << edge
            end
          end
        end
      end
    end

    profile("Make edges unique",
            [:puppetdb, :edges, :synthesize, :make_unique]) do
      hash['edges'].uniq!
    end

    hash
  end
end