Project

General

Profile

Feature #8210 » class_scanner.rb

Stefan Julin, 10/30/2014 03:52 AM

 
require 'puppet_proxy/puppet_class'
require 'yaml'

module Proxy::Puppet
class ClassScanner
extend Proxy::Log
class << self
# scans a given directory and its sub directory for puppet classes
# returns an array of PuppetClass objects.
def scan_directory directory, environment
logger.info("Running scan_directory on #{environment}: #{directory}")

parser = Puppet::Parser::Parser.new Puppet::Node::Environment.new

cachefile="/var/lib/foreman-proxy/cache_#{environment}.yaml"
cache = {}
if File.exist?(cachefile) then
cache = YAML::load_file(cachefile)
end
if cache.has_key?(directory) then
lcache = cache[directory]
else
lcache = {}
end

seenmodules=[]
changed = false
manifest = Dir.glob("#{directory}/*").map do |path|
puppetmodule = File.basename(path)
mtime = File.mtime(path)
seenmodules.push(puppetmodule)

lcache[puppetmodule] = {} unless lcache.has_key?(puppetmodule)
if lcache[puppetmodule].has_key?(:timestamp) and lcache[puppetmodule][:timestamp] >= mtime then
logger.debug("Using cached class #{puppetmodule}")
modulemanifest = lcache[puppetmodule][:manifest]
else
changed = true
logger.info("Scanning class #{puppetmodule}")
modulemanifest = Dir.glob("#{path}/manifests/**/*.pp").map do |filename|
scan_manifest File.read(filename), parser, filename
end

lcache[puppetmodule][:timestamp]=Time.new
lcache[puppetmodule][:manifest]=modulemanifest
end
modulemanifest
end.compact.flatten

if changed then
logger.info("Cache file need to be updated for #{environment}: #{directory}")
# Clean obsolete cache modules
oldlength = lcache.length
lcache.delete_if { |key, value|
!seenmodules.include?(key)
}
logger.info("Cleaning #{oldlength - lcache.length } modules from cache") if oldlength - lcache.length > 0

cache[directory] = lcache

tmpfile = cachefile + ".tmp"
File.open(tmpfile, "w") { |file| file.write(cache.to_yaml) }
File.rename(tmpfile, cachefile)
logger.info("Cache file updated for #{environment}: #{directory}")
end

manifest
end

def scan_manifest manifest, parser, filename = ''
klasses = []

already_seen = Set.new parser.known_resource_types.hostclasses.keys
already_seen << '' # Prevent the toplevel "main" class from matching
ast = parser.parse manifest
# Get the parsed representation of the top most objects
hostclasses = ast.respond_to?(:instantiate) ? ast.instantiate('') : ast.hostclasses.values
hostclasses.each do |klass|
# Only look at classes
if klass.type == :hostclass and not already_seen.include? klass.namespace
params = {}
# Get parameters and eventual default values
klass.arguments.each do |name, value|
params[name] = ast_to_value(value) rescue nil
end
klasses << PuppetClass.new(klass.namespace, params)
end
end
klasses
rescue => e
puts "Error while parsing #{filename}: #{e}"
klasses
end

def ast_to_value value
unless value.class.name.start_with? "Puppet::Parser::AST::"
# Native Ruby types
case value
# Supported with exact JSON equivalent
when NilClass, String, Numeric, Array, Hash, FalseClass, TrueClass
value
when Struct
value.hash
when Enumerable
value.to_a
# Stringified
when Regexp # /(?:stringified)/
"/#{value.to_s}/"
when Symbol # stringified
value.to_s
else
raise TypeError
end
else
# Parser types
case value
# Supported with exact JSON equivalent
when Puppet::Parser::AST::Boolean, Puppet::Parser::AST::String
value.evaluate nil
# Supported with stringification
when Puppet::Parser::AST::Concat
# This is the case when two params are concatenated together ,e.g. "param_${key}_something"
# Note1: only simple content are supported, plus variables whose raw name is taken
# Note2: The variable substitution WON'T be done by Puppet from the ENC YAML output
value.value.map do |v|
case v
when Puppet::Parser::AST::String
v.evaluate nil
when Puppet::Parser::AST::Variable
"${#{v.value}}"
else
raise TypeError
end
end.join rescue nil
when Puppet::Parser::AST::Variable
"${#{value}}"
when Puppet::Parser::AST::Type
value.value
when Puppet::Parser::AST::Name
(Puppet::Parser::Scope.number?(value.value) or value.value)
when Puppet::Parser::AST::Undef # equivalent of nil, but optional
""
# Depends on content
when Puppet::Parser::AST::ASTArray
value.inject([]) { |arr, v| (arr << ast_to_value(v)) rescue arr }
when Puppet::Parser::AST::ASTHash
Hash[value.value.each.inject([]) { |arr, (k,v)| (arr << [ast_to_value(k), ast_to_value(v)]) rescue arr }]
when Puppet::Parser::AST::Function
value.to_s
# Let's see if a raw evaluation works with no scope for any other type
else
if value.respond_to? :evaluate
# Can probably work for: (depending on the actual content)
# - Puppet::Parser::AST::ArithmeticOperator
# - Puppet::Parser::AST::ComparisonOperator
# - Puppet::Parser::AST::BooleanOperator
# - Puppet::Parser::AST::Minus
# - Puppet::Parser::AST::Not
# May work for:
# - Puppet::Parser::AST::InOperator
# - Puppet::Parser::AST::MatchOperator
# - Puppet::Parser::AST::Selector
# Probably won't work for
# - Puppet::Parser::AST::Variable
# - Puppet::Parser::AST::HashOrArrayAccess
# - Puppet::Parser::AST::ResourceReference
value.evaluate nil
else
raise TypeError
end
end
end
end
end
end
end
(2-2/3)