Project

General

Profile

class_scanner.rb

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

 
1
require 'puppet_proxy/puppet_class'
2
require 'yaml'
3

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

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

    
15
        cachefile="/var/lib/foreman-proxy/cache_#{environment}.yaml"
16
        cache = {}
17
        if File.exist?(cachefile) then
18
          cache = YAML::load_file(cachefile)
19
        end
20
        if cache.has_key?(directory) then
21
          lcache = cache[directory]
22
        else
23
          lcache = {}
24
        end
25

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

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

    
44
            lcache[puppetmodule][:timestamp]=Time.new
45
            lcache[puppetmodule][:manifest]=modulemanifest
46
          end
47
          modulemanifest
48
        end.compact.flatten
49

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

    
59
          cache[directory] = lcache
60

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

    
67
        manifest
68
      end
69

    
70
      def scan_manifest manifest, parser, filename = ''
71
        klasses = []
72

    
73
        already_seen = Set.new parser.known_resource_types.hostclasses.keys
74
        already_seen << '' # Prevent the toplevel "main" class from matching
75
        ast = parser.parse manifest
76
                           # Get the parsed representation of the top most objects
77
        hostclasses = ast.respond_to?(:instantiate) ? ast.instantiate('') : ast.hostclasses.values
78
        hostclasses.each do |klass|
79
          # Only look at classes
80
          if klass.type == :hostclass and not already_seen.include? klass.namespace
81
            params = {}
82
            # Get parameters and eventual default values
83
            klass.arguments.each do |name, value|
84
              params[name] = ast_to_value(value) rescue nil
85
            end
86
            klasses << PuppetClass.new(klass.namespace, params)
87
          end
88
        end
89
        klasses
90
      rescue => e
91
        puts "Error while parsing #{filename}: #{e}"
92
        klasses
93
      end
94

    
95
      def ast_to_value value
96
        unless value.class.name.start_with? "Puppet::Parser::AST::"
97
          # Native Ruby types
98
          case value
99
            # Supported with exact JSON equivalent
100
            when NilClass, String, Numeric, Array, Hash, FalseClass, TrueClass
101
              value
102
            when Struct
103
              value.hash
104
            when Enumerable
105
              value.to_a
106
            # Stringified
107
            when Regexp # /(?:stringified)/
108
              "/#{value.to_s}/"
109
            when Symbol # stringified
110
              value.to_s
111
            else
112
              raise TypeError
113
          end
114
        else
115
          # Parser types
116
          case value
117
            # Supported with exact JSON equivalent
118
            when Puppet::Parser::AST::Boolean, Puppet::Parser::AST::String
119
              value.evaluate nil
120
            # Supported with stringification
121
            when Puppet::Parser::AST::Concat
122
              # This is the case when two params are concatenated together ,e.g. "param_${key}_something"
123
              # Note1: only simple content are supported, plus variables whose raw name is taken
124
              # Note2: The variable substitution WON'T be done by Puppet from the ENC YAML output
125
              value.value.map do |v|
126
                case v
127
                  when Puppet::Parser::AST::String
128
                    v.evaluate nil
129
                  when Puppet::Parser::AST::Variable
130
                    "${#{v.value}}"
131
                  else
132
                    raise TypeError
133
                end
134
              end.join rescue nil
135
            when Puppet::Parser::AST::Variable
136
              "${#{value}}"
137
            when Puppet::Parser::AST::Type
138
              value.value
139
            when Puppet::Parser::AST::Name
140
              (Puppet::Parser::Scope.number?(value.value) or value.value)
141
            when Puppet::Parser::AST::Undef # equivalent of nil, but optional
142
              ""
143
            # Depends on content
144
            when Puppet::Parser::AST::ASTArray
145
              value.inject([]) { |arr, v| (arr << ast_to_value(v)) rescue arr }
146
            when Puppet::Parser::AST::ASTHash
147
              Hash[value.value.each.inject([]) { |arr, (k,v)| (arr << [ast_to_value(k), ast_to_value(v)]) rescue arr }]
148
            when Puppet::Parser::AST::Function
149
              value.to_s
150
            # Let's see if a raw evaluation works with no scope for any other type
151
            else
152
              if value.respond_to? :evaluate
153
                # Can probably work for: (depending on the actual content)
154
                # - Puppet::Parser::AST::ArithmeticOperator
155
                # - Puppet::Parser::AST::ComparisonOperator
156
                # - Puppet::Parser::AST::BooleanOperator
157
                # - Puppet::Parser::AST::Minus
158
                # - Puppet::Parser::AST::Not
159
                # May work for:
160
                # - Puppet::Parser::AST::InOperator
161
                # - Puppet::Parser::AST::MatchOperator
162
                # - Puppet::Parser::AST::Selector
163
                # Probably won't work for
164
                # - Puppet::Parser::AST::Variable
165
                # - Puppet::Parser::AST::HashOrArrayAccess
166
                # - Puppet::Parser::AST::ResourceReference
167
                value.evaluate nil
168
              else
169
                raise TypeError
170
              end
171
          end
172
        end
173
      end
174
    end
175
  end
176
end