Class: Puppet::CatalogDiff::CompileCatalog

Inherits:
Object
  • Object
show all
Includes:
Preprocessor
Defined in:
lib/puppet/catalog-diff/compilecatalog.rb

Overview

Puppet::CatalogDiff::CompileCatalog allows to retrieve a catalog, using v3/catalog, v4/catalog or PuppetDB

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(node_name, save_directory, server, certless, catalog_from_puppetdb, puppetdb, puppetdb_tls_cert, puppetdb_tls_key, puppetdb_tls_ca, puppetserver_tls_cert, puppetserver_tls_key, puppetserver_tls_ca) ⇒ CompileCatalog

Returns a new instance of CompileCatalog.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 15

def initialize(node_name, save_directory, server, certless, catalog_from_puppetdb, puppetdb, puppetdb_tls_cert, puppetdb_tls_key, puppetdb_tls_ca, puppetserver_tls_cert, puppetserver_tls_key, puppetserver_tls_ca)
  @node_name = node_name
  catalog = if catalog_from_puppetdb
              get_catalog_from_puppetdb(node_name, server, puppetdb, puppetdb_tls_cert, puppetdb_tls_key, puppetdb_tls_ca)
            else
              catalog = compile_catalog(node_name, server, certless, puppetserver_tls_cert, puppetserver_tls_key, puppetserver_tls_ca)
              clean_sensitive_parameters!(catalog)
              clean_nested_sensitive_parameters!(catalog)
              catalog
            end
  catalog = render_pson(catalog)
  begin
    save_catalog_to_disk(save_directory, node_name, catalog, 'pson')
  rescue Exception => e
    Puppet.err("Server returned invalid catalog for #{node_name}")
    save_catalog_to_disk(save_directory, node_name, catalog, 'error')
    raise e.message if catalog =~ %r{.document_type.:.Catalog.}

    raise catalog
  end
end

Instance Attribute Details

#node_nameObject (readonly)

Returns the value of attribute node_name.



13
14
15
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 13

def node_name
  @node_name
end

Instance Method Details

#clean_nested_sensitive_parameters!(catalog) ⇒ Object



143
144
145
146
147
148
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 143

def clean_nested_sensitive_parameters!(catalog)
  # Resources can also contain sensitive data nested deep in hashes/arrays
  catalog['resources'].each do |resource|
    redact_sensitive(resource['parameters']) if resource.key? 'parameters'
  end
end

#clean_sensitive_parameters!(catalog) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 130

def clean_sensitive_parameters!(catalog)
  catalog['resources'].map! do |resource|
    if resource.key? 'sensitive_parameters'
      resource['sensitive_parameters'].each do |p|
        hash = Digest::SHA256.hexdigest Marshal.dump(resource['parameters'][p])
        resource['parameters'][p] = "Sensitive [hash #{hash}]"
      end
      resource.delete('sensitive_parameters')
    end
    resource
  end
end

#compile_catalog(node_name, server, certless, tls_cert, tls_key, tls_ca) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
114
115
116
117
118
119
120
121
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 71

def compile_catalog(node_name, server, certless, tls_cert, tls_key, tls_ca)
  Puppet.debug("Compiling catalog for #{node_name}")
  server, environment = server.split('/')
  environment ||= lookup_environment(node_name)
  server, port = server.split(':')
  port ||= '8140'
  headers = {
    'Accept' => 'pson',
  }

  if certless
    endpoint = '/puppet/v4/catalog'
    headers['Content-Type'] = 'text/json'
    body = {
      certname: node_name,
      environment: environment,
      persistence: {
        facts: false,
        catalog: false,
      },
      options: {
        prefer_requested_environment: true,
      },
    }
  else
    endpoint = "/puppet/v3/catalog/#{node_name}?environment=#{environment}"
  end

  uri = URI("https://#{server}:#{port}#{endpoint}")
  ssl_context = Puppet::CatalogDiff::Tlsfactory.ssl_context(tls_cert, tls_key, tls_ca)
  begin
    ret = if certless
            Puppet.runtime[:http].post(uri, body.to_json, headers: headers, options: { ssl_context: ssl_context })
          else
            Puppet.runtime[:http].get(uri, headers: headers, options: { ssl_context: ssl_context })
          end
    raise "HTTP request to Puppetserver #{server} failed with: HTTP #{ret.code} - #{ret.reason}" unless ret.success?
  rescue Exception => e
    raise "Failed to retrieve catalog for #{node_name} from #{server} in environment #{environment}: #{e.message}"
  end

  begin
    catalog = PSON.parse(ret.body)
  rescue PSON::ParserError => e
    raise "Error parsing json output of puppet catalog query for #{node_name}: #{e.message}. Content: #{ret.body}"
  end
  raise catalog['message'] if catalog.key?('issue_kind')

  catalog = catalog['catalog'] if certless
  catalog
end

#get_catalog_from_puppetdb(node_name, server, puppetdb, puppetdb_tls_cert, puppetdb_tls_key, puppetdb_tls_ca) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 49

def get_catalog_from_puppetdb(node_name, server, puppetdb, puppetdb_tls_cert, puppetdb_tls_key, puppetdb_tls_ca)
  Puppet.debug("Getting PuppetDB catalog for #{node_name} from #{puppetdb}")
  query = ['and', ['=', 'certname', node_name.to_s]]
  _server, environment = server.split('/')
  environment ||= lookup_environment(node_name)
  query.concat([['=', 'environment', environment]])
  json_query = URI.encode_www_form_component(query.to_json)
  request_url = URI("#{puppetdb}/pdb/query/v4/catalogs?query=#{json_query}")
  headers = { 'Accept-Content' => 'application/json' }
  ssl_context = Puppet::CatalogDiff::Tlsfactory.ssl_context(puppetdb_tls_cert, puppetdb_tls_key, puppetdb_tls_ca)
  ret = Puppet.runtime[:http].get(request_url, headers: headers, options: { ssl_context: ssl_context })
  raise "HTTP request to PuppetDB failed with: HTTP #{ret.code} - #{ret.reason}" unless ret.success?

  begin
    catalog = PSON.parse(ret.body)
  rescue PSON::ParserError => e
    raise "Error parsing json output of puppetdb catalog query for #{node_name}: #{e.message}\ncontent: #{ret.body}"
  end

  convert_pdb(catalog)
end

#lookup_environment(node_name) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 37

def lookup_environment(node_name)
  # Compile the catalog with the last environment used according to the yaml terminus
  # The following is a hack as I can't pass :mode => master in the 2.7 series
  node = Puppet::Face[:node, '0.0.1'].find(node_name, terminus: 'yaml')
  raise "Error retrieving node object from yaml terminus #{node_name}" unless node

  Puppet.debug("Found environment #{node.environment} for node #{node_name}")
  raise "The node retrieved from yaml terminus is a mismatch node returned was (#{node.parameters['clientcert']})" if node.parameters['clientcert'] != node_name

  node.environment
end

#redact_sensitive(data) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 150

def redact_sensitive(data)
  if data.is_a?(Hash) && data.key?('__ptype')
    data[:catalog_diff_hash] = Digest::SHA256.hexdigest Marshal.dump(data['__pvalue'])
    data.reject! { |k| %w[__ptype __pvalue].include?(k) }
  elsif data.is_a? Hash
    data.each do |_k, v|
      redact_sensitive(v) if v.is_a?(Hash) || v.is_a?(Array)
    end
  elsif data.is_a? Array
    data.each do |v|
      redact_sensitive(v) if v.is_a?(Hash) || v.is_a?(Array)
    end
  end
end

#render_pson(catalog) ⇒ Object



123
124
125
126
127
128
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 123

def render_pson(catalog)
  pson = PSON.pretty_generate(catalog, allow_nan: true, max_nesting: false)
  raise "Could not render catalog as pson, #{catalog}" unless pson

  pson
end

#save_catalog_to_disk(save_directory, node_name, catalog, extention) ⇒ Object



165
166
167
168
169
170
171
# File 'lib/puppet/catalog-diff/compilecatalog.rb', line 165

def save_catalog_to_disk(save_directory, node_name, catalog, extention)
  File.open("#{save_directory}/#{node_name}.#{extention}", 'w') do |f|
    f.write(catalog)
  end
rescue Exception => e
  raise "Failed to save catalog for #{node_name} in #{save_directory}: #{e.message}"
end