diff options
| author | Luke Kanies <luke@madstop.com> | 2005-08-23 16:09:14 +0000 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2005-08-23 16:09:14 +0000 |
| commit | 6029ef7812765775306ff8394005c326e359d886 (patch) | |
| tree | 32cbe5ea68e0e9fbdc0935d0b41e58fdfcba9e3d /lib/puppet | |
| parent | e87eb58ce8dc40ba8c66233bf17cea61094e7647 (diff) | |
| download | puppet-6029ef7812765775306ff8394005c326e359d886.tar.gz puppet-6029ef7812765775306ff8394005c326e359d886.tar.xz puppet-6029ef7812765775306ff8394005c326e359d886.zip | |
Moving all files into a consolidated trunk. All tests pass except the known-failing certificate test, but there appear to be some errors that are incorrectly not resulting in failurs. I will track those down ASAP.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@576 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet')
| -rw-r--r-- | lib/puppet/ca.rb | 155 | ||||
| -rw-r--r-- | lib/puppet/client.rb | 18 | ||||
| -rwxr-xr-x | lib/puppet/fileserver.rb | 260 | ||||
| -rw-r--r-- | lib/puppet/master.rb | 88 | ||||
| -rw-r--r-- | lib/puppet/metric.rb | 4 | ||||
| -rw-r--r-- | lib/puppet/parser/ast.rb | 1238 | ||||
| -rw-r--r-- | lib/puppet/parser/grammar.ra | 672 | ||||
| -rw-r--r-- | lib/puppet/parser/interpreter.rb | 135 | ||||
| -rw-r--r-- | lib/puppet/parser/lexer.rb | 225 | ||||
| -rw-r--r-- | lib/puppet/parser/makefile | 5 | ||||
| -rw-r--r-- | lib/puppet/parser/parser.rb | 1264 | ||||
| -rw-r--r-- | lib/puppet/parser/scope.rb | 427 | ||||
| -rw-r--r-- | lib/puppet/server.rb | 129 | ||||
| -rw-r--r-- | lib/puppet/servlet.rb | 110 | ||||
| -rw-r--r-- | lib/puppet/type.rb | 9 | ||||
| -rw-r--r-- | lib/puppet/type/pfile.rb | 660 |
16 files changed, 5212 insertions, 187 deletions
diff --git a/lib/puppet/ca.rb b/lib/puppet/ca.rb new file mode 100644 index 000000000..9a6f8aeda --- /dev/null +++ b/lib/puppet/ca.rb @@ -0,0 +1,155 @@ +require 'openssl' +require 'puppet' +require 'puppet/sslcertificates' +require 'xmlrpc/server' + +# Much of this was taken from QuickCert: +# http://segment7.net/projects/ruby/QuickCert/ + +module Puppet + class CAError < Puppet::Error; end + class CA + attr_reader :ca + + def self.interface + XMLRPC::Service::Interface.new("puppetca") { |iface| + iface.add_method("array getcert(csr)") + } + end + + def autosign?(hostname) + # simple values are easy + asign = Puppet[:autosign] + if asign == true or asign == false + return asign + end + + # we only otherwise know how to handle files + unless asign =~ /^\// + raise Puppet::Error, "Invalid autosign value %s" % + asign + end + + unless FileTest.exists?(asign) + Puppet.warning "Autosign is enabled but %s is missing" % asign + return false + end + File.open(asign) { |f| + f.each { |line| + line.chomp! + if line =~ /^[.\w-]+$/ and line == hostname + Puppet.info "%s exactly matched %s" % [hostname, line] + return true + else + begin + rx = Regexp.new(line) + rescue => detail + Puppet.err( + "Could not create regexp out of autosign line %s: %s" % + [line, detail] + ) + next + end + + if hostname =~ rx + Puppet.info "%s matched %s" % [hostname, line] + return true + end + end + } + } + + return false + end + + def initialize(hash = {}) + @ca = Puppet::SSLCertificates::CA.new() + end + + # our client sends us a csr, and we either store it for later signing, + # or we sign it right away + def getcert(csrtext, request = nil) + # okay, i need to retrieve the hostname from the csr, and then + # verify that i get the same hostname through reverse lookup or + # something + + Puppet.info "Someone's trying for a cert" + csr = OpenSSL::X509::Request.new(csrtext) + + subject = csr.subject + + nameary = subject.to_a.find { |ary| + ary[0] == "CN" + } + + if nameary.nil? + Puppet.err "Invalid certificate request" + return "invalid" + end + + hostname = nameary[1] + + unless @ca + Puppet.notice "Host %s asked for signing from non-CA master" % hostname + return "" + end + + # okay, we're now going to store the public key if we don't already + # have it + public_key = csr.public_key + unless FileTest.directory?(Puppet[:publickeydir]) + Puppet.recmkdir(Puppet[:publickeydir]) + end + pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.')) + + if FileTest.exists?(pkeyfile) + currentkey = File.open(pkeyfile) { |k| k.read } + unless currentkey == public_key.to_s + raise Puppet::Error, "public keys for %s differ" % hostname + end + else + File.open(pkeyfile, "w", 0644) { |f| + f.print public_key.to_s + } + end + unless FileTest.directory?(Puppet[:certdir]) + Puppet.recmkdir(Puppet[:certdir], 0770) + end + certfile = File.join(Puppet[:certdir], [hostname, "pem"].join(".")) + + #puts hostname + #puts certfile + + unless FileTest.directory?(Puppet[:csrdir]) + Puppet.recmkdir(Puppet[:csrdir], 0770) + end + # first check to see if we already have a signed cert for the host + cert, cacert = ca.getclientcert(hostname) + if cert and cacert + Puppet.info "Retrieving existing certificate for %s" % hostname + Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class] + return [cert.to_pem, cacert.to_pem] + elsif @ca + if self.autosign?(hostname) + # okay, we don't have a signed cert + # if we're a CA and autosign is turned on, then go ahead and sign + # the csr and return the results + Puppet.info "Signing certificate for %s" % hostname + cert, cacert = @ca.sign(csr) + Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class] + return [cert.to_pem, cacert.to_pem] + else # just write out the csr for later signing + if @ca.getclientcsr(hostname) + Puppet.info "Not replacing existing request from %s" % hostname + else + Puppet.info "Storing certificate request for %s" % hostname + @ca.storeclientcsr(csr) + end + return ["", ""] + end + else + raise "huh?" + end + end + end +end diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb index 6f9b21cbf..1282a5900 100644 --- a/lib/puppet/client.rb +++ b/lib/puppet/client.rb @@ -5,6 +5,7 @@ require 'puppet' require 'puppet/sslcertificates' require 'puppet/type' +require 'puppet/server' require 'facter' require 'openssl' require 'puppet/transaction' @@ -32,22 +33,23 @@ module Puppet class NetworkClient < XMLRPC::Client #include Puppet::Daemon - @@methods = { - "puppetmaster" => [ :getconfig ], - "puppetca" => [ :getcert ] - } + @@handlers = [Puppet::FileServer, Puppet::CA, Puppet::Master] + + @@handlers.each { |handler| + interface = handler.interface + namespace = interface.prefix - @@methods.each { |namespace, methods| - methods.each { |method| + interface.methods.each { |ary| + method = ary[0] self.send(:define_method,method) { |*args| - Puppet.info "peer cert is %s" % @http.peer_cert + #Puppet.info "peer cert is %s" % @http.peer_cert #Puppet.info "cert is %s" % @http.cert begin call("%s.%s" % [namespace, method.to_s],*args) rescue XMLRPC::FaultException => detail Puppet.err "XML Could not call %s.%s: %s" % [namespace, method, detail.faultString] - raise NetworkClientError.new, + raise NetworkClientError, "XMLRPC Error: %s" % detail.faultString rescue => detail Puppet.err "Could not call %s.%s: %s" % diff --git a/lib/puppet/fileserver.rb b/lib/puppet/fileserver.rb new file mode 100755 index 000000000..f04c337af --- /dev/null +++ b/lib/puppet/fileserver.rb @@ -0,0 +1,260 @@ +require 'puppet' +require 'cgi' + +module Puppet + class FileServerError < Puppet::Error; end + class FileServer + attr_accessor :local + + #CHECKPARAMS = %w{checksum type mode owner group} + CHECKPARAMS = [:mode, :type, :owner, :group, :checksum] + + def self.interface + XMLRPC::Service::Interface.new("fileserver") { |iface| + iface.add_method("string describe(string)") + iface.add_method("string list(string, boolean)") + iface.add_method("string retrieve(string)") + } + end + + def check(dir) + unless FileTest.exists?(dir) + Puppet.notice "File source %s does not exist" % dir + return nil + end + + obj = nil + unless obj = Puppet::Type::PFile[dir] + obj = Puppet::Type::PFile.new( + :name => dir, + :check => CHECKPARAMS + ) + end + # we should really have a timeout here -- we don't + # want to actually check on every connection, maybe no more + # than every 60 seconds or something + #@files[mount].evaluate + obj.evaluate + + return obj + end + + def describe(file) + mount, path = splitpath(file) + + subdir = nil + unless subdir = subdir(mount, path) + Puppet.notice "Could not find subdirectory %s" % + "//%s/%s" % [mount, path] + return "" + end + + obj = nil + unless obj = self.check(subdir) + return "" + end + + desc = [] + CHECKPARAMS.each { |check| + if state = obj.state(check) + unless state.is + Puppet.notice "Manually retrieving info for %s" % check + state.retrieve + end + desc << state.is + else + if check == "checksum" and obj.state(:type).is == "file" + Puppet.notice "File %s does not have data for %s" % + [obj.name, check] + end + desc << nil + end + } + + return desc.join("\t") + end + + def initialize(hash = {}) + @mounts = {} + @files = {} + + if hash[:Local] + @local = hash[:Local] + else + @local = false + end + end + + def list(dir, recurse = false, sum = "md5") + mount, path = splitpath(dir) + + subdir = nil + unless subdir = subdir(mount, path) + Puppet.notice "Could not find subdirectory %s" % + "//%s/%s" % [mount, path] + return "" + end + + obj = nil + unless FileTest.exists?(subdir) + return "" + end + + #rmdir = File.dirname(File.join(@mounts[mount], path)) + rmdir = nameswap(dir, mount) + desc = self.reclist(rmdir, subdir, recurse) + + if desc.length == 0 + Puppet.notice "Got no information on //%s/%s" % + [mount, path] + return "" + end + + desc.collect { |sub| + sub.join("\t") + }.join("\n") + end + + def mount(dir, name) + if @mounts.include?(name) + if @mounts[name] != dir + raise FileServerError, "%s is already mounted at %s" % + [@mounts[name], name] + else + # it's already mounted; no problem + return + end + end + + unless name =~ %r{^\w+$} + raise FileServerError, "Invalid name format '%s'" % name + end + + unless FileTest.exists?(dir) + raise FileServerError, "%s does not exist" % dir + end + + if FileTest.directory?(dir) + if FileTest.readable?(dir) + Puppet.info "Mounting %s at %s" % [dir, name] + @mounts[name] = dir + else + raise FileServerError, "%s is not readable" % dir + end + else + raise FileServerError, "%s is not a directory" % dir + end + end + + # recursive listing function + def reclist(root, path, recurse) + #desc = [obj.name.sub(%r{#{root}/?}, '')] + name = path.sub(root, '') + if name == "" + name = "/" + end + + if name == path + raise Puppet::FileServerError, "Could not match %s in %s" % + [root, path] + end + + desc = [name] + ftype = File.stat(path).ftype + + desc << ftype + if recurse.is_a?(Integer) + recurse -= 1 + end + + ary = [desc] + if recurse == true or (recurse.is_a?(Integer) and recurse > -1) + if ftype == "directory" + Dir.entries(path).each { |child| + next if child =~ /^\.\.?$/ + self.reclist(root, File.join(path, child), recurse).each { |cobj| + ary << cobj + } + } + end + end + + return ary.reject { |c| c.nil? } + end + + def retrieve(file) + mount, path = splitpath(file) + + unless (@mounts.include?(mount)) + # FIXME I really need some better way to pass and handle xmlrpc errors + raise FileServerError, "%s not mounted" % mount + end + + fpath = nil + if path + fpath = File.join(@mounts[mount], path) + else + fpath = @mounts[mount] + end + + unless FileTest.exists?(fpath) + return "" + end + + str = File.read(fpath) + + if @local + return str + else + return CGI.escape(str) + end + end + + private + + def nameswap(name, mount) + name.sub(/\/#{mount}/, @mounts[mount]).gsub(%r{//}, '/').sub( + %r{/$}, '' + ) + #Puppet.info "Swapped %s to %s" % [name, newname] + #newname + end + + def splitpath(dir) + # the dir is based on one of the mounts + # so first retrieve the mount path + mount = nil + path = nil + if dir =~ %r{/(\w+)/?} + mount = $1 + path = dir.sub(%r{/#{mount}/?}, '') + + unless @mounts.include?(mount) + raise FileServerError, "%s not mounted" % mount + end + else + raise FileServerError, "Invalid path '%s'" % dir + end + + if path == "" + path = nil + end + return mount, path + end + + def subdir(mount, dir) + basedir = @mounts[mount] + + dirname = nil + if dir + dirname = File.join(basedir, dir.split("/").join(File::SEPARATOR)) + else + dirname = basedir + end + + return dirname + end + end +end + +# $Id$ diff --git a/lib/puppet/master.rb b/lib/puppet/master.rb new file mode 100644 index 000000000..bbfeaf557 --- /dev/null +++ b/lib/puppet/master.rb @@ -0,0 +1,88 @@ +require 'openssl' +require 'puppet' +require 'puppet/parser/interpreter' +require 'puppet/sslcertificates' +require 'xmlrpc/server' + +module Puppet + class MasterError < Puppet::Error; end + class Master + attr_accessor :ast, :local + attr_reader :ca + + def self.interface + XMLRPC::Service::Interface.new("puppetmaster") { |iface| + iface.add_method("string getconfig(string)") + } + end + + def initialize(hash = {}) + + # build our AST + @file = hash[:File] || Puppet[:manifest] + @parser = Puppet::Parser::Parser.new() + @parser.file = @file + @ast = @parser.parse + hash.delete(:File) + + if hash[:Local] + @local = hash[:Local] + else + @local = false + end + + if hash.include?(:CA) and hash[:CA] + @ca = Puppet::SSLCertificates::CA.new() + else + @ca = nil + end + end + + def getconfig(facts, request = nil) + if request + #Puppet.warning request.inspect + end + if @local + # we don't need to do anything, since we should already + # have raw objects + Puppet.debug "Our client is local" + else + Puppet.debug "Our client is remote" + + # XXX this should definitely be done in the protocol, somehow + begin + facts = Marshal::load(CGI.unescape(facts)) + rescue => detail + puts "AAAAA" + puts detail + exit + end + end + + Puppet.debug("Creating interpreter") + + begin + interpreter = Puppet::Parser::Interpreter.new( + :ast => @ast, + :facts => facts + ) + rescue => detail + return detail.to_s + end + + Puppet.debug("Running interpreter") + begin + retobjects = interpreter.run() + rescue => detail + Puppet.err detail.to_s + return "" + end + + if @local + return retobjects + else + return CGI.escape(Marshal::dump(retobjects)) + end + end + end +end diff --git a/lib/puppet/metric.rb b/lib/puppet/metric.rb index 9a4fb46da..f2d754a57 100644 --- a/lib/puppet/metric.rb +++ b/lib/puppet/metric.rb @@ -223,7 +223,7 @@ module Puppet begin RRD.graph(*args) rescue => detail - err "Failed to graph %s: %s" % [self.name,detail] + Puppet.err "Failed to graph %s: %s" % [self.name,detail] end end @@ -241,7 +241,7 @@ module Puppet begin RRD.update(self.path,args.join(":")) rescue => detail - err "Failed to update %s: %s" % [self.name,detail] + Puppet.err "Failed to update %s: %s" % [self.name,detail] end end end diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb new file mode 100644 index 000000000..ff237f62e --- /dev/null +++ b/lib/puppet/parser/ast.rb @@ -0,0 +1,1238 @@ +#/usr/bin/ruby + +# $Id$ +# vim: syntax=ruby + +# the AST tree + +# the parent class for all of our syntactical objects +module Puppet + module Parser + class ASTError < RuntimeError; end + #--------------------------------------------------------------- + class AST + attr_accessor :line, :file, :parent + + @@pink = "[0;31m" + @@green = "[0;32m" + @@yellow = "[0;33m" + @@slate = "[0;34m" + @@reset = "[0m" + + @@indent = " " * 4 + @@indline = @@pink + ("-" * 4) + @@reset + @@midline = @@slate + ("-" * 4) + @@reset + + @@settypes = Hash.new { |hash,key| + hash[key] = Hash.new(0) + } + + def AST.indention + return @@indent * @@indention + end + + def AST.midline + return @@midline + end + + def evaluate(scope) + #Puppet.debug("Evaluating ast %s" % @name) + value = self.collect { |obj| + obj.evaluate(scope) + }.reject { |obj| + obj.nil? + } + end + + def typewrap(string) + #return self.class.to_s.sub(/.+::/,'') + + #"(" + @@green + string.to_s + @@reset + ")" + return @@green + string.to_s + @@reset + + "(" + self.class.to_s.sub(/.+::/,'') + ")" + end + + def initialize(args) + # this has to wait until all of the objects are defined + unless defined? @@klassorder + @@klassorder = [ + AST::VarDef, AST::TypeDefaults, + AST::ObjectDef, AST::StatementArray + ] + end + + args.each { |param,value| + method = param.to_s + "=" + unless self.respond_to?(method) + error = Puppet::ParseError.new( + "Invalid parameter %s to object class %s" % + [method,self.class.to_s] + ) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + + begin + #Puppet.debug("sending %s to %s" % [method, self.class]) + self.send(method,value) + rescue => detail + error = Puppet::DevError.new( + "Could not set parameter %s on class %s: %s" % + [method,self.class.to_s,detail] + ) + error.stack = caller + raise error + end + } + end + + #--------------------------------------------------------------- + # this differentiation is used by the interpreter + # these objects have children + class Branch < AST + include Enumerable + attr_accessor :pin, :children + + def each + @children.each { |child| + yield child + } + end + + def evaluate(scope) + #Puppet.debug("Evaluating branch") +# rets = nil +# if scope.declarative +# # if we're operating declaratively, then we want to get +# # all of our 'setting' operations done first +# rets = @children.sort { |a,b| +# [a,b].each { |i| +# unless @@klassorder.include?(i.class) +# raise "Order not defined for %s" % i.class +# end +# } +# @@klassorder.index(a.class) <=> @@klassorder.index(b.class) +# }.collect { |item| +# Puppet.debug "Decl evaluating %s" % item.class +# item.evaluate(scope) +# } +# else +# rets = @children.collect { |item| +# item.evaluate(scope) +# } +# end + self.collect { |item| + #Puppet.debug "Evaluating %s" % item.class + item.evaluate(scope) + }.reject { |obj| + obj.nil + } + end + + def initialize(arghash) + super(arghash) + + unless defined? @children + @children = [] + end + + #puts "children is '%s'" % [@children] + + self.each { |child| + if child.class == Array + error = Puppet::DevError.new( + "child for %s(%s) is array" % [self.class,self.parent] + ) + error.stack = caller + raise error + end + unless child.nil? + child.parent = self + end + } + end + + def tree(indent = 0) + return ((@@indline * indent) + + self.typewrap(self.pin)) + "\n" + self.collect { |child| + child.tree(indent + 1) + }.join("\n") + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class ASTArray < AST::Branch + include Enumerable + + def [](index) + @children[index] + end + + def evaluate(scope) + rets = nil + if scope.declarative + test = [ + AST::VarDef, AST::TypeDefaults + ] + + # if we're operating declaratively, then we want to get + # all of our 'setting' operations done first + settors = [] + others = [] + @children.each { |child| + if test.include?(child.class) + settors.push child + else + others.push child + end + } + rets = [settors,others].flatten.collect { |child| + child.evaluate(scope) + } + else + rets = @children.collect { |item| + item.evaluate(scope) + } + end + rets = rets.reject { |obj| obj.nil? } + end + + def initialize(hash) + super(hash) + + @children.each { |child| + unless child.is_a?(AST) + Puppet.err("child %s is not an ast" % child) + exit + end + } + return self + end + + def push(*ary) + ary.each { |child| + #Puppet.debug "adding %s(%s) of type %s to %s" % + # [child, child.object_id, child.class.to_s.sub(/.+::/,''), + # self.object_id] + @children.push(child) + } + + return self + end + + def to_s + return "[" + @children.collect { |child| + child.to_s + }.join(", ") + "]" + end + + def tree(indent = 0) + #puts((AST.indent * indent) + self.pin) + self.collect { |child| + if child.class == Array + Puppet.debug "child is array for %s" % self.class + end + child.tree(indent) + }.join("\n" + (AST.midline * (indent+1)) + "\n") + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class StatementArray < ASTArray + def evaluate(scope) + rets = nil + if scope.declarative + # if we're operating declaratively, then we want to get + # all of our 'setting' operations done first + rets = @children.sort { |a,b| + [a,b].each { |i| + unless @@klassorder.include?(i.class) + error = Puppet::DevError.new( + "Order not defined for %s" % i.class + ) + error.stack = caller + raise error + end + } + @@klassorder.index(a.class) <=> @@klassorder.index(b.class) + }.collect { |item| + Puppet.debug "Decl evaluating %s" % item.class + item.evaluate(scope) + }.reject { |obj| obj.nil? } + else + rets = @children.collect { |item| + item.evaluate(scope) + }.reject { |obj| obj.nil? } + end + + return rets + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # and these ones don't + class Leaf < AST + attr_accessor :value, :type + + # this only works if @value has already been evaluated + # otherwise you get AST objects, which you don't likely want... + def evaluate(scope) + return @value + end + + def tree(indent = 0) + return ((@@indent * indent) + self.typewrap(self.value)) + end + + def to_s + return @value + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Boolean < AST::Leaf + def initialize(hash) + super + + unless @value == 'true' or @value == 'false' + error = Puppet::DevError.new( + "'%s' is not a boolean" % @value + ) + error.stack = caller + raise error + end + if @value == 'true' + @value = true + else + @value = false + end + end + + def evaluate(scope) + return @value + end + + def to_s + return @value + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class String < AST::Leaf + def evaluate(scope) + return scope.strinterp(@value) + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Word < AST::Leaf; end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Type < AST::Leaf; end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Name < AST::Leaf; end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Variable < Word + def evaluate(scope) + # look up the variable value in the symbol table + begin + return scope.lookupvar(@value) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::DevError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class ObjectDef < AST::Branch + attr_accessor :name, :type + attr_reader :params + + def []=(index,obj) + @params[index] = obj + end + + def [](index) + return @params[index] + end + + def each + #Puppet.debug("each called on %s" % self) + [@type,@name,@params].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + def evaluate(scope) + hash = {} + + objtype = @type.evaluate(scope) + objnames = @name.evaluate(scope) + + # first, retrieve the defaults + defaults = scope.lookupdefaults(objtype) + defaults.each { |var,value| + Puppet.debug "Found default %s for %s" % + [var,objtype] + + hash[var] = value + } + + # then set all of the specified params + @params.each { |param| + ary = param.evaluate(scope) + hash[ary[0]] = ary[1] + } + + # it's easier to always use an array, even for only one name + unless objnames.is_a?(Array) + objnames = [objnames] + end + + begin + object = scope.lookuptype(objtype) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + + # this is where our implicit iteration takes place; + # if someone passed an array as the name, then we act + # just like the called us many times + objnames.collect { |objname| + # if the type is not defined in our scope, we assume + # that it's a type that the client will understand, so we + # just store it in our objectable + if object.nil? + begin + Puppet.debug("Setting object '%s' with arguments %s" % + [objname, hash.inspect]) + obj = scope.setobject( + objtype, + objname, + hash, + @file, + @line + ) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + else + # but things like components create a new type; if we find + # one of those, evaluate that with our arguments + Puppet.debug("Calling object '%s' with arguments %s" % + [object.name, hash.inspect]) + object.evaluate(scope,hash,objtype,objname) + end + }.reject { |obj| obj.nil? } + end + + def initialize(hash) + super + + Puppet.debug "%s id is %s" % [@name, object_id] + + # we don't have to evaluate because we require bare words + # for types + objtype = @type.value + + if Puppet[:typecheck] + builtin = false + begin + builtin = Puppet::Type.type(objtype) + rescue TypeError + # nothing; we've already set builtin to false + end + if builtin + # we're a builtin type + #Puppet.debug "%s is a builtin type" % objtype + if Puppet[:paramcheck] + @params.each { |param| + pname = param.param.value + next if pname == "name" # always allow these + unless builtin.validarg?(pname) + error = Puppet::ParseError.new( + "Invalid parameter '%s' for type '%s'" % + [pname,objtype] + ) + error.stack = caller + error.line = self.line + error.file = self.file + raise error + end + } + end + # FIXME this should use scoping rules to find the set type, + # not a global list + elsif @@settypes.include?(objtype) + # we've defined it locally + Puppet.debug "%s is a defined type" % objtype + hash = @@settypes[objtype] + @params.each { |param| + # FIXME we might need to do more here eventually... + if Puppet::Type.metaparam?(param.param.value.intern) + next + end + + pname = param.param.value + unless hash.include?(pname) + error = Puppet::ParseError.new( + "Invalid parameter '%s' for type '%s'" % + [pname,objtype] + ) + error.stack = caller + error.line = self.line + error.file = self.file + raise error + end + } + else + # we don't know anything about it + error = Puppet::ParseError.new( + "Unknown type '%s'" % objtype + ) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + end + end + + def params=(params) + if params.is_a?(AST::ASTArray) + @params = params + else + @params = AST::ASTArray.new( + :children => [params] + ) + end + end + + def tree(indent = 0) + return [ + @type.tree(indent + 1), + @name.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @params.collect { |param| + begin + param.tree(indent + 1) + rescue NoMethodError => detail + Puppet.err @params.inspect + error = Puppet::DevError.new( + "failed to tree a %s" % self.class + ) + error.stack = caller + raise error + end + }.join("\n") + ].join("\n") + end + + def to_s + return "%s => { %s }" % [@name, + @params.collect { |param| + param.to_s + }.join("\n") + ] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class ObjectRef < AST::Branch + attr_accessor :name, :type + + def each + #Puppet.debug("each called on %s" % self) + [@type,@name].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + def evaluate(scope) + objtype = @type.evaluate(scope) + objnames = @name.evaluate(scope) + + # it's easier to always use an array, even for only one name + unless objnames.is_a?(Array) + objnames = [objnames] + end + + begin + object = scope.lookuptype(objtype) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + Puppet.debug "ObjectRef returned type %s" % object + + # should we implicitly iterate here? + # yes, i believe that we essentially have to... + objnames.collect { |objname| + if object.is_a?(Component) + objname = "%s[%s]" % [objtype,objname] + objtype = "component" + end + [objtype,objname] + }.reject { |obj| obj.nil? } + end + + def tree(indent = 0) + return [ + @type.tree(indent + 1), + @name.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)) + ].join("\n") + end + + def to_s + return "%s[%s]" % [@name,@type] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class ObjectParam < AST::Branch + attr_accessor :value, :param + + def each + [@param,@value].each { |child| yield child } + end + + def evaluate(scope) + return [@param.evaluate(scope),@value.evaluate(scope)] + end + + def tree(indent = 0) + return [ + @param.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s => %s" % [@param,@value] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Test < AST::Branch + attr_accessor :lhs, :rhs + + # is our test true or false? + def evaluate(scope) + # retrieve our values and make them a touch easier to manage + lvalue = @lhs.evaluate(scope) + rvalue = @rhs.evaluate(scope) + + # FIXME this probably won't work except on strings right now... + retvalue = lvalue.send(@pin, rvalue) + + #Puppet.debug "test '%s' returned %s" % [self.to_s,retvalue] + return retvalue + end + + def tree(indent = 0) + return [ + @lhs.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @rhs.tree(indent + 1) + ].join("\n") + end + + def each + [@lhs,@rhs].each { |child| yield child } + end + + def to_s + return "%s %s %s" % [@lhs,@pin,@rhs] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class If < AST::Branch + attr_accessor :test, :statements, :else, :elsif + + # 'if' is a bit special, since we don't want to continue + # evaluating if a test turns up true + def evaluate(scope) + scope = scope.newscope + retvalue = nil + if @test.evaluate(scope) + Puppet.debug "%s is true" % @test + retvalue = @statements.evaluate(scope) + elsif defined? @elsif + Puppet.debug "%s is false" % @test + elsereturn = nil + @elsif.each { |elsetest| + if elsereturn = @elsif.evaluate(scope) + retvalue = elsereturn + end + } + elsif defined? @else + retvalue = @else.evaluate(scope) + else + Puppet.debug "None of the ifs are true" + end + return retvalue + end + + def tree(indent = 0) + rettree = [ + @test.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @statements.tree(indent + 1) + ] + if defined? @elsif + @elsif.each { |elsetest| + rettree.push(elsetest.tree(indent + 1)) + } + end + + if defined? @else + rettree.push(@else.tree(indent+1)) + end + + return rettree.flatten.join("\n") + end + + def each + list = [@test,@statements] + + if defined? @elsif + @elsif.each { |tmp| + list.push(tmp) + } + end + + if defined? @else + list.push(@else) + end + + list.each { |child| yield child } + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Selector < AST::Branch + attr_accessor :param, :value + + # okay, here's a decision point... + def evaluate(scope) + # retrieve our values and make them a touch easier to manage + hash = Hash[*(@value.evaluate(scope).flatten)] + + retvalue = nil + + paramvalue = @param.evaluate(scope) + + retvalue = hash.detect { |test,value| + # FIXME this will return variables named 'default'... + if paramvalue == test + break value + end + } + if retvalue.nil? + if hash.include?("default") + return hash["default"] + else + error = Puppet::ParseError.new( + "No value for selector param '%s'" % paramvalue + ) + error.line = self.line + error.file = self.file + error.stack = self.stack + raise error + end + end + + return retvalue + end + + def tree(indent = 0) + return [ + @param.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def each + [@param,@value].each { |child| yield child } + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class VarDef < AST::Branch + attr_accessor :name, :value + + def evaluate(scope) + name = @name.evaluate(scope) + value = @value.evaluate(scope) + + Puppet.debug "setting %s to %s" % [name,value] + begin + scope.setvar(name,value) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + end + + def each + [@name,@value].each { |child| yield child } + end + + def tree(indent = 0) + return [ + @name.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s => %s" % [@name,@value] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class TypeDefaults < AST::Branch + attr_accessor :type, :params + + def each + [@type,@params].each { |child| yield child } + end + + def evaluate(scope) + type = @type.evaluate(scope) + params = @params.evaluate(scope) + + #Puppet.info "Params are %s" % params.inspect + #Puppet.debug("evaluating '%s.%s' with values [%s]" % + # [type,name,values]) + # okay, now i need the interpreter's client object thing... + begin + scope.setdefaults(type.downcase,params) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + end + + def tree(indent = 0) + return [ + @type.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap(self.pin)), + @params.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s { %s }" % [@type,@params] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # these are analogous to defining new object types + class CompDef < AST::Branch + attr_accessor :name, :args, :code + + def each + [@name,@args,@code].each { |child| yield child } + end + + def evaluate(scope) + name = @name.evaluate(scope) + + args = @args.evaluate(scope) + + #Puppet.debug("defining '%s' with arguments [%s]" % + # [name,args]) + #p @args + #p args + # okay, now i need to evaluate all of the statements + # within a component and a new lexical scope... + + begin + scope.settype(name, + Component.new( + :name => name, + :args => args, + :code => @code + ) + ) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + end + + def initialize(hash) + super + + Puppet.debug "Defining type %s" % @name.value + + # we need to both mark that a given argument is valid, + # and we need to also store any provided default arguments + hash = @@settypes[@name.value] + if @args.is_a?(AST::ASTArray) + @args.each { |ary| + if ary.is_a?(AST::ASTArray) + arg = ary[0] + hash[arg.value] += 1 + else + hash[ary.value] += 1 + end + } + else + Puppet.warning "got arg %s" % @args.inspect + hash[@args.value] += 1 + end + end + + def tree(indent = 0) + return [ + @name.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap("define")), + @args.tree(indent + 1), + @code.tree(indent + 1), + ].join("\n") + end + + def to_s + return "define %s(%s) {\n%s }" % [@name, @args, @code] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # these are analogous to defining new object types + class ClassDef < AST::CompDef + attr_accessor :parentclass + + def each + [@name,@args,@parentclass,@code].each { |child| yield child } + end + + def evaluate(scope) + name = @name.evaluate(scope) + args = @args.evaluate(scope) + + #Puppet.debug "evaluating parent %s of type %s" % + # [@parent.name, @parent.class] + parent = @parentclass.evaluate(scope) + + Puppet.debug("defining hostclass '%s' with arguments [%s]" % + [name,args]) + + begin + scope.settype(name, + HostClass.new( + :name => name, + :args => args, + :parent => parent, + :code => @code + ) + ) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + end + + def tree(indent = 0) + return [ + @name.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap("class")), + @args.tree(indent + 1), + @parentclass.tree(indent + 1), + @code.tree(indent + 1), + ].join("\n") + end + + def to_s + return "class %s(%s) inherits %s {\n%s }" % + [@name, @args, @parentclass, @code] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # host definitions are special, because they get called when a host + # whose name matches connects + class NodeDef < AST::Branch + attr_accessor :names, :code + + def each + [@names,@code].each { |child| yield child } + end + + def evaluate(scope) + names = @names.evaluate(scope) + + unless names.is_a?(Array) + names = [names] + end + Puppet.debug("defining hosts '%s'" % [names.join(", ")]) + + names.each { |name| + begin + scope.sethost(name, + Host.new( + :name => name, + :code => @code + ) + ) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + } + end + + def tree(indent = 0) + return [ + @names.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap("host")), + @code.tree(indent + 1), + ].join("\n") + end + + def to_s + return "host %s {\n%s }" % [@name, @code] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is not really an AST node; it's just a placeholder + # for a bunch of AST code to evaluate later + class Component < AST::Branch + attr_accessor :name, :args, :code + + def evaluate(scope,hash,objtype,objname) + scope = scope.newscope + scope.type = objtype + scope.name = objname + + # define all of the arguments in our local scope + if self.args + Puppet.debug "args are %s" % self.args.inspect + self.args.each { |arg, default| + unless hash.include?(arg) + if default + hash[arg] = default + else + error = Puppet.ParseError.new( + "Must pass %s to %s of type %s" % + [arg.inspect,name,objtype] + ) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + end + } + + hash.each { |arg,value| + begin + scope.setvar(arg,hash[arg]) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => except + error = Puppet::ParseError.new(except.message) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + } + end + + # now just evaluate the code with our new bindings + self.code.evaluate(scope) + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is not really an AST node; it's just a placeholder + # for a bunch of AST code to evaluate later + class HostClass < AST::Component + attr_accessor :parentclass + + def evaluate(scope,hash,objtype,objname) + if @parentclass + begin + parentobj = scope.lookuptype(@parentclass) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + raise error + end + unless parentobj + error = Puppet::ParseError.new( + "Could not find parent '%s' of '%s'" % [@parentclass,@name]) + error.line = self.line + error.file = self.file + raise error + end + parentobj.evaluate(scope,hash,objtype,objname) + end + + # just use the Component evaluate method, but change the type + # to our own type + super(scope,hash,@name,objname) + end + + def initialize(hash) + @parentclass = nil + super + if self.parent.is_a?(Array) + self.parent = nil + end + end + + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is not really an AST node; it's just a placeholder + # for a bunch of AST code to evaluate later + class Host < AST::Component + attr_accessor :name, :args, :code, :parentclass + + def evaluate(scope,hash,objtype,objname) + if @parentclass + begin + parentobj = scope.lookuptype(@parentclass) + rescue Puppet::ParseError => except + except.line = self.line + except.file = self.file + raise except + rescue => detail + error = Puppet::ParseError.new(detail) + error.line = self.line + error.file = self.file + raise error + end + unless parentobj + error = Puppet::ParseError.new( + "Could not find parent '%s' of '%s'" % [@parentclass,@name]) + error.line = self.line + error.file = self.file + raise error + end + parentobj.evaluate(scope,hash,objtype,objname) + end + + # just use the Component evaluate method, but change the type + # to our own type + super(scope,hash,@name,objname) + end + end + #--------------------------------------------------------------- + end + end +end diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra new file mode 100644 index 000000000..7386a0e15 --- /dev/null +++ b/lib/puppet/parser/grammar.ra @@ -0,0 +1,672 @@ +#/usr/bin/ruby + +# $Id$ +# vim: syntax=ruby + +# the parser + +class Puppet::Parser::Parser + +token LBRACK QTEXT RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE FALSE EQUALS +token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL NOTEQUAL +token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN DOT COLON TYPE +token NAME SEMIC + +rule +program: statements { + if val[0].is_a?(AST::ASTArray) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :children => [val[0]] + ) + end + + # this is mainly so we can test the parser separately from the + # interpreter + if Puppet[:parseonly] + begin + if Puppet[:debug] + puts result.tree(0) + end + rescue NoMethodError => detail + Puppet.err detail + #exit(78) + end + #require 'puppet/parser/interpreter' + #result = Puppet::Server.new(result) + end +} + +statements: statement + | statements statement { + if val[0].is_a?(AST::ASTArray) + val[0].push(val[1]) + result = val[0] + else + result = AST::ASTArray.new( + :file => @lexer.file, + :line => @lexer.line, + :children => [val[0],val[1]] + ) + end +} + +statement: object + | assignment + | selector + | iftest + | import + | definition + | hostclass + +#object: name LBRACE objectname COLON params endcomma RBRACE { +object: name LBRACE objectinstances endsemi RBRACE { + ary = val[2] + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid name" + raise Puppet::ParseError, "Invalid name" + end + if ary[0].is_a?(AST::Leaf) + ary = [ary] + end + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file + ) + ary.each { |instance| + Puppet.debug "Adding %s with name %s" % [val[0].value, instance[0].value] + result.push AST::ObjectDef.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => instance[0], + :params => instance[1] + ) + } +} | name LBRACE params endcomma RBRACE { + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid name" + raise Puppet::ParseError, "Invalid name" + end + # an object but without a name + # this cannot be an instance of a library type + + Puppet.debug "Adding %s" % val[0].value + # make a unique name for bookkeeping purposes + name = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => [val[0].value, "-", val[0].object_id].join('') + ) + + result = AST::ObjectDef.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => name, + :params => val[2] + ) +} | type LBRACE params endcomma RBRACE { + # a template setting for a type + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid type" + raise Puppet::ParseError, "Invalid type" + end + result = AST::TypeDefaults.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :params => val[2] + ) +} + +objectinst: objectname COLON params { + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) +} + +objectinstances: objectinst + | objectinstances SEMIC objectinst { + if val[0][0].is_a?(AST::ASTArray) + val[0].push val[2] + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +endsemi: # nothing + | SEMIC + +name: NAME { + result = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) +} + +type: TYPE { + result = AST::Type.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) +} + +objectname: quotedtext + | name + | selector + | variable + | array + +assignment: VARIABLE EQUALS rvalue { + # this is distinct from referencing a variable + variable = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0].sub(/^\$/,'') + ) + result = AST::VarDef.new( + :pin => "=", + :line => @lexer.line, + :file => @lexer.file, + :name => variable, + :value => val[2] + ) +} + +params: # nothing +{ + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [] + ) +} + | param { result = val[0] } + | params COMMA param { + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +param: NAME FARROW rvalue { + leaf = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :file => @lexer.file, + :param => leaf, + :value => val[2] + ) +} + +rvalues: rvalue + | rvalues comma rvalue { + if val[0].is_a?(AST::ASTArray) + result = val[0].push(val[2]) + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +rvalue: quotedtext + | name + | type + | boolean + | selector + | object + | variable + | array + | objectref + +quotedtext: QTEXT { + result = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) +} + +boolean: BOOLEAN { + result = AST::Boolean.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) +} + +objectref: name LBRACK rvalue RBRACK { + result = AST::ObjectRef.new( + :pin => '[]', + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => val[2] + ) +} + +iftest: IF test LBRACE statements RBRACE { + result = AST::If.new( + :pin => "if", + :test => val[1], + :file => @lexer.file, + :line => @lexer.line, + :statements => val[3] + ) +} + | IF test LBRACE statements RBRACE elsifs ELSE LBRACE statements RBRACE { + # make sure our elsifs are an array, as it will save lots of + # effort later + unless val[5].is_a?(AST::ASTArray) + val[5] = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[5]] + ) + end + + result = AST::If.new( + :pin => "if", + :test => val[1], + :statements => val[3], + :file => @lexer.file, + :line => @lexer.line, + :else => val[8], + :elsif => val[5] + ) + +} + | IF test LBRACE statements RBRACE ELSE LBRACE statements RBRACE { + result = AST::If.new( + :pin => "if", + :test => val[1], + :statements => val[3], + :file => @lexer.file, + :line => @lexer.line, + :else => val[7] + ) + +} + +elsifs: ELSIF test LBRACE statements RBRACE { + result = AST::If.new( + :pin => "elseif", + :test => val[1], + :file => @lexer.file, + :statements => val[3], + :line => @lexer.line + ) +} + | elsifs ELSIF test LBRACE statements RBRACE { + second = AST::If.new( + :pin => "elsif", + :test => val[2], + :statements => val[4], + :file => @lexer.file, + :line => @lexer.line + ) + + if val[0].is_a?(AST::ASTArray) + val[0].push(second) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],second] + ) + end +} + +test: rvalue + | rvalue testop rvalue { + result = AST::Test.new( + :pin => val[1], + :line => @lexer.line, + :file => @lexer.file, + :lhs => val[0], + :rhs => val[2] + ) +} + +testop: ISEQUAL + | GREATEREQUAL + | GREATERTHAN + | LESSTHAN + | LESSEQUAL + | NOTEQUAL + +selector: variable QMARK svalues { + result = AST::Selector.new( + :pin => "?", + :line => @lexer.line, + :file => @lexer.file, + :param => val[0], + :value => val[2] + ) +} + +svalues: selectval + | LBRACE sintvalues RBRACE { result = val[1] } + +sintvalues: selectval + | sintvalues comma selectval { + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +selectval: selectlhand FARROW rvalue { + leaf = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :file => @lexer.file, + :param => leaf, + :value => val[2] + ) +} + +selectlhand: NAME + | TYPE + | QTEXT + +import: IMPORT QTEXT { + # importing files + # yuk, i hate keywords + # we'll probably have to have some kind of search path eventually + # but for now, just use a path relative to the file doing the importing + path = @lexer.file.sub(%r{[^/]+$},val[1]) + parser = Puppet::Parser::Parser.new() + parser.stack = self.stack + Puppet.debug("importing %s" % path) + noimport = false + begin + parser.file = path + rescue Puppet::ImportError + Puppet.warning("Importing %s would result in an import loop" % path) + result = AST::ASTArray.new( + :file => @lexer.file, + :line => @lexer.line + ) + noimport = true + end + unless noimport + result = parser.parse + end +} + +definition: DEFINE NAME argumentlist LBRACE statements RBRACE { + result = AST::CompDef.new( + :name => AST::Name.new(:value => val[1], :line => @lexer.line), + :args => val[2], + :file => @lexer.file, + :line => @lexer.line, + :code => val[4] + ) +} + +hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { + result = AST::ClassDef.new( + :name => AST::Name.new(:value => val[1], :line => @lexer.line), + :args => val[2], + :parentclass => val[3], + :file => @lexer.file, + :line => @lexer.line, + :code => val[5] + ) +} + +#nodedef: NODE words LBRACE statements RBRACE { +# result = AST::NodeDef.new( +# :names => val[1], +# :code => val[3] +# ) +#} + +nothing: { + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [] + ) +} + +argumentlist: nothing + | LPAREN nothing RPAREN { + result = val[1] +} + | LPAREN arguments RPAREN { + if val[1].is_a?(AST::ASTArray) + result = val[1] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0]] + ) + end +} + +arguments: argument + | arguments COMMA argument { + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +argument: name EQUALS rvalue { + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) +} + | name { + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0]] + ) +} + +parent: nothing + | INHERITS NAME { + result = AST::Name.new( + :value => val[1], + :file => @lexer.file, + :line => @lexer.line + ) +} + +variable: VARIABLE { + name = val[0].sub(/^\$/,'') + result = AST::Variable.new( + :line => @lexer.line, + :file => @lexer.file, + :value => name + ) +} + +array: LBRACK rvalues RBRACK { + if val[1].is_a?(AST::ASTArray) + result = val[1] + else + result = AST::ASTArray.new + result.push val[1] + end +} + +comma: FARROW + | COMMA + +endcomma: # nothing + | COMMA { result = nil } + +end +---- header ---- +require 'puppet' +require 'puppet/parser/lexer' +require 'puppet/parser/ast' +#require 'puppet/parser/interpreter' + +module Puppet + # this exception class already has a :stack accessor + class ParseError < Puppet::Error + attr_accessor :line, :file + end + + class ImportError < Racc::ParseError; end +end + +Puppet[:typecheck] = true +Puppet[:paramcheck] = true + +---- inner ---- +attr_writer :stack +attr_reader :file + +def file=(file) + if self.stack.include?(file) + raise Puppet::ImportError.new("Import loop detected") + else + @lexer.file = file + end +end + +def initialize + @lexer = Puppet::Parser::Lexer.new() + if Puppet[:debug] + @yydebut = true + end +end + +def on_error(token,value,stack) + #on '%s' at '%s' in\n'%s'" % [token,value,stack] + #error = "line %s: parse error after '%s'" % + # [@lexer.line,@lexer.last] + error = "an error was found" + + if Puppet[:debug] + puts stack.inspect + puts stack + end + if @lexer.file + error += (" in '%s'" % @lexer.file) + end + + except = Puppet::ParseError.new(error) + except.line = @lexer.line + if @lexer.file + except.file = @lexer.file + end + + raise except +end + +# how should I do error handling here? +def parse + begin + yyparse(@lexer,:scan) + rescue Racc::ParseError => except + error = Puppet::ParseError.new(except) + error.line = @lexer.line + error.file = @lexer.file + error.stack = caller + raise error + rescue Puppet::ParseError => except + except.line ||= @lexer.line + except.file ||= @lexer.file + raise except + rescue Puppet::Error => except + # and this is a framework error + except.line ||= @lexer.line + except.file ||= @lexer.file + except.stack ||= except.stack + raise except + rescue Puppet::DevError => except + except.line ||= @lexer.line + except.file ||= @lexer.file + except.stack ||= caller + if Puppet[:debug] + puts except.stack + end + raise except + rescue => except + error = Puppet::DevError.new(except.message) + error.line = @lexer.line + error.file = @lexer.file + error.stack = caller + if Puppet[:debug] + puts caller + end + raise error + end +end + +def stack + if defined? @stack and ! @stack.nil? + if @lexer.file + return [@stack,@lexer.file].flatten + else + return @stack + end + else + if @lexer.file + return [@lexer.file] + else + return [] + end + end +end + +def string=(string) + @lexer.string = string +end diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb new file mode 100644 index 000000000..88ab5854e --- /dev/null +++ b/lib/puppet/parser/interpreter.rb @@ -0,0 +1,135 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the interpreter +# +# this builds our virtual pinball machine, into which we'll place our host-specific +# information and out of which we'll receive our host-specific configuration + +require 'puppet' +require 'puppet/parser/parser' +require 'puppet/parser/scope' + + +module Puppet + module Parser + #--------------------------------------------------------------- + class Interpreter + attr_accessor :ast, :topscope + # just shorten the constant path a bit, using what amounts to an alias + AST = Puppet::Parser::AST + + #------------------------------------------------------------ + def clear + TransObject.clear + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + #def callfunc(function,*args) + # #Puppet.debug("Calling %s on %s" % [function,@client]) + # @client.callfunc(function,*args) + # #Puppet.debug("Finished %s" % function) + #end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # create our interpreter + def initialize(hash) + unless hash.include?(:ast) + raise ArgumentError.new("Must pass tree and client to Interpreter") + end + @ast = hash[:ast] + #@client = hash[:client] + + @scope = Puppet::Parser::Scope.new() # no parent scope + @topscope = @scope + @scope.interp = self + + if hash.include?(:facts) + facts = hash[:facts] + unless facts.is_a?(Hash) + raise ArgumentError.new("Facts must be a hash") + end + + facts.each { |fact,value| + @scope.setvar(fact,value) + } + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # evaluate our whole tree + def run + # evaluate returns a value, but at the top level we only + # care about its side effects + # i think + unless @ast.is_a?(AST) or @ast.is_a?(AST::ASTArray) + Puppet.err "Received top-level non-ast '%s' of type %s" % + [@ast,@ast.class] + raise TypeError.new("Received non-ast '%s' of type %s" % + [@ast,@ast.class]) + end + + begin + @ast.evaluate(@scope) + rescue Puppet::DevError, Puppet::Error, Puppet::ParseError => except + #Puppet.err "File %s, line %s: %s" % + # [except.file, except.line, except.message] + if Puppet[:debug] + puts except.stack + end + #exit(1) + raise + rescue => except + error = Puppet::DevError.new("%s: %s" % + [except.class, except.message]) + error.stack = caller + if Puppet[:debug] + puts error.stack + end + raise error + end + + # okay, at this point we have a tree of scopes, and we want to + # unzip along that tree, building our structure of objects + # to pass to the client + # this will be heirarchical, and will (at this point) contain + # only TransObjects and TransSettings + @topscope.name = "top" + @topscope.type = "puppet" + begin + topbucket = @topscope.to_trans + rescue => detail + Puppet.warning detail + raise + end + + # add our settings to the front of the array + # at least, for now + #@topscope.typesets.each { |setting| + # topbucket.unshift setting + #} + + # guarantee that settings are at the very top + #topbucket.push settingbucket + #topbucket.push @scope.to_trans + + #retlist = TransObject.list + #Puppet.debug "retobject length is %s" % retlist.length + #TransObject.clear + return topbucket + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def scope + return @scope + end + #------------------------------------------------------------ + end + #--------------------------------------------------------------- + end +end diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb new file mode 100644 index 000000000..d9d9d5f5c --- /dev/null +++ b/lib/puppet/parser/lexer.rb @@ -0,0 +1,225 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the scanner/lexer + +require 'strscan' +require 'puppet' + + +module Puppet + class LexError < RuntimeError; end + module Parser + #--------------------------------------------------------------- + class Lexer + attr_reader :line, :last, :file + + #%r{\w+} => :WORD, + @@tokens = { + %r{#.*} => :COMMENT, + %r{\[} => :LBRACK, + %r{\]} => :RBRACK, + %r{\{} => :LBRACE, + %r{\}} => :RBRACE, + %r{\(} => :LPAREN, + %r{\)} => :RPAREN, + %r{"} => :DQUOTE, + %r{\n} => :RETURN, + %r{'} => :SQUOTE, + %r{=} => :EQUALS, + %r{==} => :ISEQUAL, + %r{>=} => :GREATEREQUAL, + %r{>} => :GREATERTHAN, + %r{<} => :LESSTHAN, + %r{<=} => :LESSEQUAL, + %r{!=} => :NOTEQUAL, + %r{,} => :COMMA, + %r{\.} => :DOT, + %r{:} => :COLON, + %r{;} => :SEMIC, + %r{\?} => :QMARK, + %r{\\} => :BACKSLASH, + %r{=>} => :FARROW, + %r{[a-z]\w*} => :NAME, + %r{[A-Z]\w*} => :TYPE, + %r{[0-9]+} => :NUMBER, + %r{\$\w+} => :VARIABLE + } + + @@keywords = { + "if" => :IF, + "elsif" => :ELSIF, + "else" => :ELSE, + "import" => :IMPORT, + "class" => :CLASS, + "node" => :NODE, + "host" => :NODE, + "true" => :BOOLEAN, + "false" => :BOOLEAN, + "inherits" => :INHERITS, + "define" => :DEFINE + } + + # scan the whole file + # basically just used for testing + def fullscan + array = [] + + self.scan { |token,str| + #Puppet.debug("got token '%s' => '%s'" % [token,str]) + if token.nil? + return array + else + array.push([token,str]) + end + } + return array + end + + # this is probably pretty damned inefficient... + # it'd be nice not to have to load the whole file first... + def file=(file) + @file = file + @line = 1 + File.open(file) { |of| + str = "" + of.each { |line| str += line } + @scanner = StringScanner.new(str) + } + end + + def initialize + @line = 1 + @last = "" + @scanner = nil + @file = nil + # AAARRGGGG! okay, regexes in ruby are bloody annoying + # no one else has "\n" =~ /\s/ + @skip = %r{[ \t]+} + end + + def rest + @scanner.rest + end + + # this is the heart of the lexer + def scan + #Puppet.debug("entering scan") + if @scanner.nil? + raise TypeError.new("Invalid or empty string") + end + + @scanner.skip(@skip) + until @scanner.eos? do + yielded = false + sendbreak = false # gah, this is a nasty hack + stoken = nil + sregex = nil + value = "" + + # first find out which type of token we've got + @@tokens.each { |regex,token| + # we're just checking, which doesn't advance the scan + # pointer + tmp = @scanner.check(regex) + if tmp.nil? + #puppet.debug("did not match %s to '%s'" % + # [regex,@scanner.rest]) + next + end + + # find the longest match + if tmp.length > value.length + value = tmp + stoken = token + sregex = regex + else + # we've already got a longer match + next + end + } + + # error out if we didn't match anything at all + if stoken.nil? + nword = nil + if @scanner.rest =~ /^(\S+)/ + nword = $1 + elsif@scanner.rest =~ /^(\s+)/ + nword = $1 + else + nword = @scanner.rest + end + raise "Could not match '%s'" % nword + end + + value = @scanner.scan(sregex) + + if value == "" + raise "Didn't match regex on token %s" % stoken + end + + # token-specific operations + # if this gets much more complicated, it should + # be moved up to where the tokens themselves are defined + # which will get me about 75% of the way to a lexer generator + case stoken + when :NAME then + wtoken = stoken + # we're looking for keywords here + if @@keywords.include?(value) + wtoken = @@keywords[value] + #Puppet.debug("token '%s'" % wtoken) + end + yield [wtoken,value] + @last = value + when :NUMBER then + yield [:NAME,value] + # just throw comments away + when :COMMENT then + # just throw comments away + when :RETURN then + @line += 1 + @scanner.skip(@skip) + when :DQUOTE then + #Puppet.debug("searching '%s' after '%s'" % [self.rest,value]) + value = self.slurpstring(value) + yield [:QTEXT,value] + @last = value + #stoken = :QTEXT + #Puppet.debug("got string '%s' => '%s'" % [:QTEXT,value]) + else + yield [stoken,value] + @last = value + #Puppet.debug("got token '%s' => '%s'" % [stoken,value]) + end + @scanner.skip(@skip) + end + @scanner = nil + yield [false,false] + end + + # we've encountered an opening quote... + # slurp in the rest of the string and return it + def slurpstring(quote) + #Puppet.debug("searching '%s'" % self.rest) + str = @scanner.scan_until(/[^\\]#{quote}/) + #str = @scanner.scan_until(/"/) + if str.nil? + raise Puppet::LexError.new("Unclosed quote after '%s' in '%s'" % + [self.last,self.rest]) + else + str.sub!(/#{quote}$/,"") + str.gsub!(/\\#{quote}/,quote) + end + + return str + end + + def string=(string) + @scanner = StringScanner.new(string) + end + end + #--------------------------------------------------------------- + end +end diff --git a/lib/puppet/parser/makefile b/lib/puppet/parser/makefile new file mode 100644 index 000000000..9ec6265de --- /dev/null +++ b/lib/puppet/parser/makefile @@ -0,0 +1,5 @@ +#parser.rb: grammar.ry +# ryacc --output parser grammar + +parser.rb: grammar.ra + racc -v -o$@ grammar.ra diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb new file mode 100644 index 000000000..3b42bd8d7 --- /dev/null +++ b/lib/puppet/parser/parser.rb @@ -0,0 +1,1264 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by racc 1.4.4 +# from racc grammer file "grammar.ra". +# + +require 'racc/parser' + + +require 'puppet' +require 'puppet/parser/lexer' +require 'puppet/parser/ast' +#require 'puppet/parser/interpreter' + +module Puppet + # this exception class already has a :stack accessor + class ParseError < Puppet::Error + attr_accessor :line, :file + end + + class ImportError < Racc::ParseError; end +end + +Puppet[:typecheck] = true +Puppet[:paramcheck] = true + + +module Puppet + + module Parser + + class Parser < Racc::Parser + +module_eval <<'..end grammar.ra modeval..ida89b5273aa', 'grammar.ra', 573 +attr_writer :stack +attr_reader :file + +def file=(file) + if self.stack.include?(file) + raise Puppet::ImportError.new("Import loop detected") + else + @lexer.file = file + end +end + +def initialize + @lexer = Puppet::Parser::Lexer.new() + if Puppet[:debug] + @yydebut = true + end +end + +def on_error(token,value,stack) + #on '%s' at '%s' in\n'%s'" % [token,value,stack] + #error = "line %s: parse error after '%s'" % + # [@lexer.line,@lexer.last] + error = "an error was found" + + if Puppet[:debug] + puts stack.inspect + puts stack + end + if @lexer.file + error += (" in '%s'" % @lexer.file) + end + + except = Puppet::ParseError.new(error) + except.line = @lexer.line + if @lexer.file + except.file = @lexer.file + end + + raise except +end + +# how should I do error handling here? +def parse + begin + yyparse(@lexer,:scan) + rescue Racc::ParseError => except + error = Puppet::ParseError.new(except) + error.line = @lexer.line + error.file = @lexer.file + error.stack = caller + raise error + rescue Puppet::ParseError => except + except.line ||= @lexer.line + except.file ||= @lexer.file + raise except + rescue Puppet::Error => except + # and this is a framework error + except.line ||= @lexer.line + except.file ||= @lexer.file + except.stack ||= except.stack + raise except + rescue Puppet::DevError => except + except.line ||= @lexer.line + except.file ||= @lexer.file + except.stack ||= caller + if Puppet[:debug] + puts except.stack + end + raise except + rescue => except + error = Puppet::DevError.new(except.message) + error.line = @lexer.line + error.file = @lexer.file + error.stack = caller + if Puppet[:debug] + puts caller + end + raise error + end +end + +def stack + if defined? @stack and ! @stack.nil? + if @lexer.file + return [@stack,@lexer.file].flatten + else + return @stack + end + else + if @lexer.file + return [@lexer.file] + else + return [] + end + end +end + +def string=(string) + @lexer.string = string +end +..end grammar.ra modeval..ida89b5273aa + +##### racc 1.4.4 generates ### + +racc_reduce_table = [ + 0, 0, :racc_error, + 1, 38, :_reduce_1, + 1, 39, :_reduce_none, + 2, 39, :_reduce_3, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 5, 41, :_reduce_11, + 5, 41, :_reduce_12, + 5, 41, :_reduce_13, + 3, 54, :_reduce_14, + 1, 49, :_reduce_none, + 3, 49, :_reduce_16, + 0, 50, :_reduce_none, + 1, 50, :_reduce_none, + 1, 48, :_reduce_19, + 1, 53, :_reduce_20, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 3, 42, :_reduce_26, + 0, 51, :_reduce_27, + 1, 51, :_reduce_28, + 3, 51, :_reduce_29, + 3, 60, :_reduce_30, + 1, 61, :_reduce_none, + 3, 61, :_reduce_32, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 56, :_reduce_42, + 1, 63, :_reduce_43, + 4, 64, :_reduce_44, + 5, 44, :_reduce_45, + 10, 44, :_reduce_46, + 9, 44, :_reduce_47, + 5, 66, :_reduce_48, + 6, 66, :_reduce_49, + 1, 65, :_reduce_none, + 3, 65, :_reduce_51, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 3, 43, :_reduce_58, + 1, 68, :_reduce_none, + 3, 68, :_reduce_60, + 1, 70, :_reduce_none, + 3, 70, :_reduce_62, + 3, 69, :_reduce_63, + 1, 71, :_reduce_none, + 1, 71, :_reduce_none, + 1, 71, :_reduce_none, + 2, 45, :_reduce_67, + 6, 46, :_reduce_68, + 7, 47, :_reduce_69, + 0, 74, :_reduce_70, + 1, 72, :_reduce_none, + 3, 72, :_reduce_72, + 3, 72, :_reduce_73, + 1, 75, :_reduce_none, + 3, 75, :_reduce_75, + 3, 76, :_reduce_76, + 1, 76, :_reduce_77, + 1, 73, :_reduce_none, + 2, 73, :_reduce_79, + 1, 57, :_reduce_80, + 3, 58, :_reduce_81, + 1, 62, :_reduce_none, + 1, 62, :_reduce_none, + 0, 52, :_reduce_none, + 1, 52, :_reduce_85 ] + +racc_reduce_n = 86 + +racc_shift_n = 153 + +racc_action_table = [ + 22, 24, 22, 24, 1, 54, 5, 9, 39, 12, + 14, 22, 24, 22, 24, 82, 3, 7, 116, 84, + 85, 59, 22, 24, 115, 28, 126, 28, 96, 31, + 108, 31, 3, 7, 3, 7, 28, 59, 28, 62, + 31, 7, 31, 3, 7, 3, 7, 28, 22, 24, + 89, 31, 56, 57, 3, 7, 97, 22, 24, 22, + 24, 22, 24, 128, 22, 24, 129, 113, 56, 57, + 22, 24, 114, 28, 98, 22, 24, 31, 59, 103, + 3, 7, 28, 80, 28, 80, 28, 63, 31, 28, + 7, 3, 7, 31, 69, 28, 3, 7, 133, 31, + 28, 121, 3, 7, 31, 22, 24, 3, 7, 56, + 57, 122, 136, 80, 1, 137, 5, 9, 124, 12, + 14, 125, 111, 109, 84, 85, 3, 7, 102, 102, + 28, 78, 63, 55, 31, 44, 134, 3, 7, 1, + 140, 5, 9, 99, 12, 14, 98, 138, 37, 80, + 37, 3, 7, 141, 152, 44, 1, 42, 5, 9, + 144, 12, 14, 41, 146, 40, 39, 38, 3, 7, + 1, 151, 5, 9, 37, 12, 14, 36, 7, nil, + nil, nil, 3, 7, nil, 149, nil, 1, nil, 5, + 9, nil, 12, 14, nil, nil, nil, nil, nil, 3, + 7, 1, 147, 5, 9, nil, 12, 14, nil, nil, + nil, nil, nil, 3, 7, nil, nil, nil, 1, nil, + 5, 9, nil, 12, 14, nil, nil, nil, nil, nil, + 3, 7, 1, nil, 5, 9, nil, 12, 14, nil, + nil, nil, nil, nil, 3, 7, 1, nil, 5, 9, + nil, 12, 14, nil, nil, nil, nil, nil, 3, 7, + 1, nil, 5, 9, nil, 12, 14, nil, nil, nil, + nil, nil, 3, 7, 1, nil, 5, 9, nil, 12, + 14, nil, nil, nil, nil, nil, 3, 7, 1, nil, + 5, 9, nil, 12, 14, nil, nil, nil, nil, nil, + 3, 7, 1, nil, 5, 9, nil, 12, 14, nil, + nil, nil, nil, nil, 3, 7, 1, nil, 5, 9, + nil, 12, 14, nil, nil, nil, nil, nil, 3, 7, + 1, nil, 5, 9, nil, 12, 14, nil, nil, nil, + nil, nil, 3, 7, 48, 49, 50, 51, 52, 53 ] + +racc_action_check = [ + 1, 1, 83, 83, 96, 27, 96, 96, 27, 96, + 96, 89, 89, 54, 54, 46, 96, 96, 94, 46, + 46, 112, 47, 47, 94, 1, 106, 83, 64, 1, + 87, 83, 1, 1, 83, 83, 89, 37, 54, 37, + 89, 63, 54, 89, 89, 54, 54, 47, 98, 98, + 61, 47, 112, 112, 47, 47, 66, 99, 99, 129, + 129, 39, 39, 109, 113, 113, 109, 92, 37, 37, + 137, 137, 93, 98, 80, 22, 22, 98, 62, 79, + 98, 98, 99, 97, 129, 44, 39, 42, 129, 113, + 99, 129, 129, 113, 39, 137, 113, 113, 117, 137, + 22, 100, 137, 137, 22, 40, 40, 22, 22, 62, + 62, 101, 127, 102, 117, 127, 117, 117, 103, 117, + 117, 104, 91, 88, 91, 91, 117, 117, 81, 74, + 40, 41, 38, 34, 40, 33, 118, 40, 40, 88, + 135, 88, 88, 73, 88, 88, 69, 128, 68, 134, + 23, 88, 88, 136, 150, 18, 135, 14, 135, 135, + 139, 135, 135, 13, 142, 12, 11, 9, 135, 135, + 150, 148, 150, 150, 6, 150, 150, 5, 116, nil, + nil, nil, 150, 150, nil, 145, nil, 148, nil, 148, + 148, nil, 148, 148, nil, nil, nil, nil, nil, 148, + 148, 145, 143, 145, 145, nil, 145, 145, nil, nil, + nil, nil, nil, 145, 145, nil, nil, nil, 143, nil, + 143, 143, nil, 143, 143, nil, nil, nil, nil, nil, + 143, 143, 144, nil, 144, 144, nil, 144, 144, nil, + nil, nil, nil, nil, 144, 144, 141, nil, 141, 141, + nil, 141, 141, nil, nil, nil, nil, nil, 141, 141, + 146, nil, 146, 146, nil, 146, 146, nil, nil, nil, + nil, nil, 146, 146, 55, nil, 55, 55, nil, 55, + 55, nil, nil, nil, nil, nil, 55, 55, 138, nil, + 138, 138, nil, 138, 138, nil, nil, nil, nil, nil, + 138, 138, 15, nil, 15, 15, nil, 15, 15, nil, + nil, nil, nil, nil, 15, 15, 125, nil, 125, 125, + nil, 125, 125, nil, nil, nil, nil, nil, 125, 125, + 0, nil, 0, 0, nil, 0, 0, nil, nil, nil, + nil, nil, 0, 0, 26, 26, 26, 26, 26, 26 ] + +racc_action_pointer = [ + 308, -2, nil, nil, nil, 174, 161, nil, nil, 132, + nil, 161, 153, 163, 122, 280, nil, nil, 150, nil, + nil, nil, 73, 137, nil, nil, 328, 3, nil, nil, + nil, nil, nil, 130, 128, nil, nil, 34, 118, 59, + 103, 131, 73, nil, 50, nil, 11, 20, nil, nil, + nil, nil, nil, nil, 11, 252, nil, nil, nil, nil, + nil, 42, 75, 6, 23, nil, 23, nil, 135, 138, + nil, nil, nil, 107, 120, nil, nil, nil, nil, 50, + 66, 119, nil, 0, nil, nil, nil, 26, 117, 9, + nil, 116, 55, 57, 9, nil, -18, 48, 46, 55, + 95, 105, 78, 83, 116, nil, 20, nil, nil, 40, + nil, nil, 18, 62, nil, nil, 143, 92, 127, nil, + nil, nil, nil, nil, nil, 294, nil, 89, 142, 57, + nil, nil, nil, nil, 114, 134, 148, 68, 266, 155, + nil, 224, 159, 196, 210, 179, 238, nil, 165, nil, + 148, nil, nil ] + +racc_action_default = [ + -86, -86, -7, -20, -8, -86, -86, -19, -9, -86, + -10, -86, -80, -86, -86, -1, -2, -4, -86, -5, + -6, -33, -86, -39, -42, -40, -50, -34, -80, -38, + -36, -43, -41, -35, -86, -37, -67, -86, -70, -27, + -86, -86, -70, -3, -27, -31, -86, -86, -52, -53, + -54, -55, -56, -57, -86, -86, -65, -64, -58, -66, + -59, -86, -86, -70, -86, -71, -86, -21, -24, -19, + -25, -22, -28, -17, -84, -23, -15, -26, 153, -70, + -86, -84, -81, -86, -82, -83, -51, -86, -86, -86, + -61, -86, -77, -86, -86, -74, -86, -27, -86, -18, + -86, -86, -85, -86, -86, -78, -86, -32, -44, -45, + -63, -60, -86, -86, -72, -73, -86, -86, -14, -30, + -16, -11, -12, -29, -79, -86, -13, -86, -86, -86, + -62, -76, -75, -68, -86, -86, -86, -86, -86, -86, + -69, -86, -86, -86, -86, -86, -86, -47, -86, -46, + -86, -48, -49 ] + +racc_goto_table = [ + 15, 29, 76, 33, 60, 70, 23, 34, 67, 123, + 93, 74, 83, 95, 45, 64, 81, 58, 101, 79, + 47, 127, 29, 73, 33, 106, 105, 23, 91, 90, + 100, 104, 77, 46, 43, 94, 13, nil, nil, 86, + 29, 123, 33, nil, 68, 23, 87, 29, nil, 33, + nil, nil, 23, nil, 29, 88, 33, 112, nil, 23, + nil, nil, 120, 35, nil, 70, 132, nil, 67, 118, + nil, nil, nil, nil, nil, 107, nil, nil, nil, 130, + nil, 110, nil, 29, 35, 33, nil, nil, 23, 29, + 119, 33, nil, nil, 23, nil, 117, nil, 29, nil, + 33, 75, 35, 23, 68, 131, nil, 43, nil, 35, + nil, nil, nil, 29, nil, 33, 35, nil, 23, 27, + nil, nil, nil, nil, nil, 135, nil, nil, nil, 29, + nil, 33, nil, nil, 23, 139, 43, 29, 143, 33, + 27, 145, 23, 142, 148, 35, 150, nil, nil, nil, + nil, 35, nil, nil, 43, nil, nil, 71, 27, nil, + 35, 75, 43, nil, 43, 27, nil, 43, nil, 43, + nil, nil, 27, nil, nil, 35, nil, nil, nil, nil, + nil, 92, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 35, nil, nil, nil, nil, nil, nil, nil, 35, + nil, 27, nil, nil, nil, nil, nil, 27, nil, nil, + nil, nil, nil, nil, nil, nil, 27, 71, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 27, nil, nil, 92, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 27, nil, nil, + nil, nil, nil, nil, nil, 27 ] + +racc_goto_check = [ + 2, 4, 17, 16, 32, 21, 20, 28, 19, 23, + 37, 14, 25, 39, 22, 35, 14, 31, 15, 35, + 30, 29, 4, 12, 16, 15, 37, 20, 33, 32, + 13, 36, 22, 24, 3, 38, 1, nil, nil, 22, + 4, 23, 16, nil, 20, 20, 22, 4, nil, 16, + nil, nil, 20, nil, 4, 2, 16, 25, nil, 20, + nil, nil, 17, 6, nil, 21, 39, nil, 19, 14, + nil, nil, nil, nil, nil, 22, nil, nil, nil, 32, + nil, 22, nil, 4, 6, 16, nil, nil, 20, 4, + 22, 16, nil, nil, 20, nil, 2, nil, 4, nil, + 16, 6, 6, 20, 20, 22, nil, 3, nil, 6, + nil, nil, nil, 4, nil, 16, 6, nil, 20, 11, + nil, nil, nil, nil, nil, 2, nil, nil, nil, 4, + nil, 16, nil, nil, 20, 28, 3, 4, 2, 16, + 11, 2, 20, 28, 2, 6, 2, nil, nil, nil, + nil, 6, nil, nil, 3, nil, nil, 11, 11, nil, + 6, 6, 3, nil, 3, 11, nil, 3, nil, 3, + nil, nil, 11, nil, nil, 6, nil, nil, nil, nil, + nil, 11, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 6, nil, nil, nil, nil, nil, nil, nil, 6, + nil, 11, nil, nil, nil, nil, nil, 11, nil, nil, + nil, nil, nil, nil, nil, nil, 11, 11, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 11, nil, nil, 11, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 11, nil, nil, + nil, nil, nil, nil, nil, 11 ] + +racc_goto_pointer = [ + nil, 36, 0, 19, 0, nil, 62, nil, nil, nil, + nil, 118, -16, -43, -28, -56, 2, -37, nil, -31, + 5, -34, -8, -93, 11, -34, nil, nil, 6, -88, + -6, -20, -33, -34, nil, -23, -48, -53, -28, -50 ] + +racc_goto_default = [ + nil, nil, nil, 16, 17, 19, 20, 2, 4, 8, + 10, 11, nil, nil, nil, nil, 18, nil, 66, 21, + 6, 25, 26, 72, nil, nil, 30, 32, nil, nil, + nil, nil, nil, nil, 61, nil, nil, 65, nil, nil ] + +racc_token_table = { + false => 0, + Object.new => 1, + :LBRACK => 2, + :QTEXT => 3, + :RBRACK => 4, + :LBRACE => 5, + :RBRACE => 6, + :SYMBOL => 7, + :FARROW => 8, + :COMMA => 9, + :TRUE => 10, + :FALSE => 11, + :EQUALS => 12, + :QMARK => 13, + :LPAREN => 14, + :RPAREN => 15, + :ISEQUAL => 16, + :GREATEREQUAL => 17, + :GREATERTHAN => 18, + :LESSTHAN => 19, + :LESSEQUAL => 20, + :NOTEQUAL => 21, + :IF => 22, + :ELSE => 23, + :IMPORT => 24, + :DEFINE => 25, + :ELSIF => 26, + :VARIABLE => 27, + :CLASS => 28, + :INHERITS => 29, + :NODE => 30, + :BOOLEAN => 31, + :DOT => 32, + :COLON => 33, + :TYPE => 34, + :NAME => 35, + :SEMIC => 36 } + +racc_use_result_var = true + +racc_nt_base = 37 + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ +'$end', +'error', +'LBRACK', +'QTEXT', +'RBRACK', +'LBRACE', +'RBRACE', +'SYMBOL', +'FARROW', +'COMMA', +'TRUE', +'FALSE', +'EQUALS', +'QMARK', +'LPAREN', +'RPAREN', +'ISEQUAL', +'GREATEREQUAL', +'GREATERTHAN', +'LESSTHAN', +'LESSEQUAL', +'NOTEQUAL', +'IF', +'ELSE', +'IMPORT', +'DEFINE', +'ELSIF', +'VARIABLE', +'CLASS', +'INHERITS', +'NODE', +'BOOLEAN', +'DOT', +'COLON', +'TYPE', +'NAME', +'SEMIC', +'$start', +'program', +'statements', +'statement', +'object', +'assignment', +'selector', +'iftest', +'import', +'definition', +'hostclass', +'name', +'objectinstances', +'endsemi', +'params', +'endcomma', +'type', +'objectinst', +'objectname', +'quotedtext', +'variable', +'array', +'rvalue', +'param', +'rvalues', +'comma', +'boolean', +'objectref', +'test', +'elsifs', +'testop', +'svalues', +'selectval', +'sintvalues', +'selectlhand', +'argumentlist', +'parent', +'nothing', +'arguments', +'argument'] + +Racc_debug_parser = false + +##### racc system variables end ##### + + # reduce 0 omitted + +module_eval <<'.,.,', 'grammar.ra', 40 + def _reduce_1( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :children => [val[0]] + ) + end + + # this is mainly so we can test the parser separately from the + # interpreter + if Puppet[:parseonly] + begin + if Puppet[:debug] + puts result.tree(0) + end + rescue NoMethodError => detail + Puppet.err detail + #exit(78) + end + #require 'puppet/parser/interpreter' + #result = Puppet::Server.new(result) + end + result + end +.,., + + # reduce 2 omitted + +module_eval <<'.,.,', 'grammar.ra', 54 + def _reduce_3( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + val[0].push(val[1]) + result = val[0] + else + result = AST::ASTArray.new( + :file => @lexer.file, + :line => @lexer.line, + :children => [val[0],val[1]] + ) + end + result + end +.,., + + # reduce 4 omitted + + # reduce 5 omitted + + # reduce 6 omitted + + # reduce 7 omitted + + # reduce 8 omitted + + # reduce 9 omitted + + # reduce 10 omitted + +module_eval <<'.,.,', 'grammar.ra', 89 + def _reduce_11( val, _values, result ) + ary = val[2] + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid name" + raise Puppet::ParseError, "Invalid name" + end + if ary[0].is_a?(AST::Leaf) + ary = [ary] + end + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file + ) + ary.each { |instance| + Puppet.debug "Adding %s with name %s" % [val[0].value, instance[0].value] + result.push AST::ObjectDef.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => instance[0], + :params => instance[1] + ) + } + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 113 + def _reduce_12( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid name" + raise Puppet::ParseError, "Invalid name" + end + # an object but without a name + # this cannot be an instance of a library type + + Puppet.debug "Adding %s" % val[0].value + # make a unique name for bookkeeping purposes + name = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => [val[0].value, "-", val[0].object_id].join('') + ) + + result = AST::ObjectDef.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => name, + :params => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 126 + def _reduce_13( val, _values, result ) + # a template setting for a type + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid type" + raise Puppet::ParseError, "Invalid type" + end + result = AST::TypeDefaults.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :params => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 134 + def _reduce_14( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + result + end +.,., + + # reduce 15 omitted + +module_eval <<'.,.,', 'grammar.ra', 148 + def _reduce_16( val, _values, result ) + if val[0][0].is_a?(AST::ASTArray) + val[0].push val[2] + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + + # reduce 17 omitted + + # reduce 18 omitted + +module_eval <<'.,.,', 'grammar.ra', 159 + def _reduce_19( val, _values, result ) + result = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 167 + def _reduce_20( val, _values, result ) + result = AST::Type.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result + end +.,., + + # reduce 21 omitted + + # reduce 22 omitted + + # reduce 23 omitted + + # reduce 24 omitted + + # reduce 25 omitted + +module_eval <<'.,.,', 'grammar.ra', 189 + def _reduce_26( val, _values, result ) + # this is distinct from referencing a variable + variable = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0].sub(/^\$/,'') + ) + result = AST::VarDef.new( + :pin => "=", + :line => @lexer.line, + :file => @lexer.file, + :name => variable, + :value => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 198 + def _reduce_27( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 198 + def _reduce_28( val, _values, result ) + result = val[0] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 211 + def _reduce_29( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 226 + def _reduce_30( val, _values, result ) + leaf = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :file => @lexer.file, + :param => leaf, + :value => val[2] + ) + result + end +.,., + + # reduce 31 omitted + +module_eval <<'.,.,', 'grammar.ra', 239 + def _reduce_32( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + result = val[0].push(val[2]) + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + + # reduce 33 omitted + + # reduce 34 omitted + + # reduce 35 omitted + + # reduce 36 omitted + + # reduce 37 omitted + + # reduce 38 omitted + + # reduce 39 omitted + + # reduce 40 omitted + + # reduce 41 omitted + +module_eval <<'.,.,', 'grammar.ra', 257 + def _reduce_42( val, _values, result ) + result = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 265 + def _reduce_43( val, _values, result ) + result = AST::Boolean.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 275 + def _reduce_44( val, _values, result ) + result = AST::ObjectRef.new( + :pin => '[]', + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 285 + def _reduce_45( val, _values, result ) + result = AST::If.new( + :pin => "if", + :test => val[1], + :file => @lexer.file, + :line => @lexer.line, + :statements => val[3] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 307 + def _reduce_46( val, _values, result ) + # make sure our elsifs are an array, as it will save lots of + # effort later + unless val[5].is_a?(AST::ASTArray) + val[5] = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[5]] + ) + end + + result = AST::If.new( + :pin => "if", + :test => val[1], + :statements => val[3], + :file => @lexer.file, + :line => @lexer.line, + :else => val[8], + :elsif => val[5] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 318 + def _reduce_47( val, _values, result ) + result = AST::If.new( + :pin => "if", + :test => val[1], + :statements => val[3], + :file => @lexer.file, + :line => @lexer.line, + :else => val[7] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 328 + def _reduce_48( val, _values, result ) + result = AST::If.new( + :pin => "elseif", + :test => val[1], + :file => @lexer.file, + :statements => val[3], + :line => @lexer.line + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 348 + def _reduce_49( val, _values, result ) + second = AST::If.new( + :pin => "elsif", + :test => val[2], + :statements => val[4], + :file => @lexer.file, + :line => @lexer.line + ) + + if val[0].is_a?(AST::ASTArray) + val[0].push(second) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],second] + ) + end + result + end +.,., + + # reduce 50 omitted + +module_eval <<'.,.,', 'grammar.ra', 359 + def _reduce_51( val, _values, result ) + result = AST::Test.new( + :pin => val[1], + :line => @lexer.line, + :file => @lexer.file, + :lhs => val[0], + :rhs => val[2] + ) + result + end +.,., + + # reduce 52 omitted + + # reduce 53 omitted + + # reduce 54 omitted + + # reduce 55 omitted + + # reduce 56 omitted + + # reduce 57 omitted + +module_eval <<'.,.,', 'grammar.ra', 376 + def _reduce_58( val, _values, result ) + result = AST::Selector.new( + :pin => "?", + :line => @lexer.line, + :file => @lexer.file, + :param => val[0], + :value => val[2] + ) + result + end +.,., + + # reduce 59 omitted + +module_eval <<'.,.,', 'grammar.ra', 378 + def _reduce_60( val, _values, result ) + result = val[1] + result + end +.,., + + # reduce 61 omitted + +module_eval <<'.,.,', 'grammar.ra', 393 + def _reduce_62( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 408 + def _reduce_63( val, _values, result ) + leaf = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :file => @lexer.file, + :param => leaf, + :value => val[2] + ) + result + end +.,., + + # reduce 64 omitted + + # reduce 65 omitted + + # reduce 66 omitted + +module_eval <<'.,.,', 'grammar.ra', 437 + def _reduce_67( val, _values, result ) + # importing files + # yuk, i hate keywords + # we'll probably have to have some kind of search path eventually + # but for now, just use a path relative to the file doing the importing + path = @lexer.file.sub(%r{[^/]+$},val[1]) + parser = Puppet::Parser::Parser.new() + parser.stack = self.stack + Puppet.debug("importing %s" % path) + noimport = false + begin + parser.file = path + rescue Puppet::ImportError + Puppet.warning("Importing %s would result in an import loop" % path) + result = AST::ASTArray.new( + :file => @lexer.file, + :line => @lexer.line + ) + noimport = true + end + unless noimport + result = parser.parse + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 447 + def _reduce_68( val, _values, result ) + result = AST::CompDef.new( + :name => AST::Name.new(:value => val[1], :line => @lexer.line), + :args => val[2], + :file => @lexer.file, + :line => @lexer.line, + :code => val[4] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 458 + def _reduce_69( val, _values, result ) + result = AST::ClassDef.new( + :name => AST::Name.new(:value => val[1], :line => @lexer.line), + :args => val[2], + :parentclass => val[3], + :file => @lexer.file, + :line => @lexer.line, + :code => val[5] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 473 + def _reduce_70( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [] + ) + result + end +.,., + + # reduce 71 omitted + +module_eval <<'.,.,', 'grammar.ra', 478 + def _reduce_72( val, _values, result ) + result = val[1] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 489 + def _reduce_73( val, _values, result ) + if val[1].is_a?(AST::ASTArray) + result = val[1] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0]] + ) + end + result + end +.,., + + # reduce 74 omitted + +module_eval <<'.,.,', 'grammar.ra', 503 + def _reduce_75( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 511 + def _reduce_76( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 518 + def _reduce_77( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0]] + ) + result + end +.,., + + # reduce 78 omitted + +module_eval <<'.,.,', 'grammar.ra', 527 + def _reduce_79( val, _values, result ) + result = AST::Name.new( + :value => val[1], + :file => @lexer.file, + :line => @lexer.line + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 536 + def _reduce_80( val, _values, result ) + name = val[0].sub(/^\$/,'') + result = AST::Variable.new( + :line => @lexer.line, + :file => @lexer.file, + :value => name + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 545 + def _reduce_81( val, _values, result ) + if val[1].is_a?(AST::ASTArray) + result = val[1] + else + result = AST::ASTArray.new + result.push val[1] + end + result + end +.,., + + # reduce 82 omitted + + # reduce 83 omitted + + # reduce 84 omitted + +module_eval <<'.,.,', 'grammar.ra', 550 + def _reduce_85( val, _values, result ) + result = nil + result + end +.,., + + def _reduce_none( val, _values, result ) + result + end + + end # class Parser + + end # module Parser + +end # module Puppet diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb new file mode 100644 index 000000000..dcefdbcae --- /dev/null +++ b/lib/puppet/parser/scope.rb @@ -0,0 +1,427 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the interpreter +# +# this builds our virtual pinball machine, into which we'll place our host-specific +# information and out of which we'll receive our host-specific configuration + +require 'puppet/transportable' + +module Puppet + module Parser + class ScopeError < RuntimeError + attr_accessor :line, :file + end + #--------------------------------------------------------------- + class Scope + + attr_accessor :symtable, :objectable, :parent, :level, :interp + attr_accessor :name, :type + + # i don't really know how to deal with a global scope yet, so + # i'm leaving it disabled + @@global = nil + + @@hosttable = {} + @@settingtable = [] + @@declarative = true + + #------------------------------------------------------------ + def Scope.declarative + return @@declarative + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def Scope.declarative=(val) + @@declarative = val + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def Scope.global + return @@global + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def child=(scope) + @children.push(scope) + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def declarative + return @@declarative + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def initialize(parent = nil, declarative = true) + @parent = parent + if @parent.nil? + @level = 1 + @@declarative = declarative + else + @parent.child = self + @level = @parent.level + 1 + @interp = @parent.interp + end + + @children = [] + + @symtable = Hash.new(nil) + @typetable = Hash.new(nil) + + # the defaultstable is a hash of hashes + @defaultstable = Hash.new { |dhash,type| + dhash[type] = Hash.new(nil) + } + + @objectable = Hash.new { |typehash,typekey| + #hash[key] = TransObject.new(key) + typehash[typekey] = Hash.new { |namehash, namekey| + #Puppet.debug("Creating iobject with name %s and type %s" % + # [namekey,typekey]) + namehash[namekey] = TransObject.new(namekey,typekey) + @children.push namehash[namekey] + + # this has to be last, because the return value of the + # block is the actual hash + namehash[namekey] + } + } + @map = { + "variable" => @symtable, + "type" => @typetable, + "object" => @objectable, + "defaults" => @defaultstable + } + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # this method just abstracts the upwards-recursive nature of + # name resolution + # because different tables are different depths (e.g., flat, or + # hash of hashes), we pass in a code snippet that gets passed + # the table. It is assumed that the code snippet already has + # the name in it + def lookup(type,sub) + table = @map[type] + if table.nil? + error = Puppet::ParseError.new( + "Could not retrieve %s table at level %s" % [type,self.level] + ) + error.stack = caller + raise error + end + + if sub.is_a?(Proc) and obj = sub.call(table) + return obj + elsif table.include?(sub) + return table[sub] + elsif ! @parent.nil? + return @parent.lookup(type,sub) + else + return :undefined + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def lookuphost(name) + if @@hosttable.include?(name) + return @@hosttable[name] + else + return nil + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # collect all of the defaults set at any higher scopes + # this is a different type of lookup because it's additive -- + # it collects all of the defaults, with defaults in closer scopes + # overriding those in later scopes + def lookupdefaults(type) + values = {} + # first collect the values from the parents + unless @parent.nil? + @parent.lookupdefaults(type).each { |var,value| + values[var] = value + } + end + + # then override them with any current values + # this should probably be done differently + if @defaultstable.include?(type) + @defaultstable[type].each { |var,value| + values[var] = value + } + end + Puppet.debug "Got defaults for %s: %s" % + [type,values.inspect] + return values + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def lookuptype(name) + Puppet.debug "Looking up type %s" % name + value = self.lookup("type",name) + if value == :undefined + return nil + else + return value + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # slightly different, because we're looking through a hash of hashes + def lookupobject(name,type) + Puppet.debug "Looking up object %s of type %s" % [name, type] + sub = proc { |table| + if table.include?(type) + if type[type].include?(name) + type[type][name] + end + else + nil + end + } + value = self.lookup("object",sub) + if value == :undefined + return nil + else + return value + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def lookupvar(name) + Puppet.debug "Looking up variable %s" % name + value = self.lookup("variable", name) + if value == :undefined + error = Puppet::ParseError.new( + "Undefined variable '%s'" % name + ) + error.stack = caller + raise error + else + #Puppet.debug "Value of '%s' is '%s'" % [name,value] + return value + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def newscope + Puppet.debug "Creating new scope, level %s" % [self.level + 1] + return Puppet::Parser::Scope.new(self) + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def setdefaults(type,params) + table = @defaultstable[type] + + # if we got a single param, it'll be in its own array + unless params[0].is_a?(Array) + params = [params] + end + + params.each { |ary| + Puppet.debug "Default for %s is %s => %s" % + [type,ary[0].inspect,ary[1].inspect] + if @@declarative + if table.include?(ary[0]) + error = Puppet::ParseError.new( + "Default already defined for %s { %s }" % + [type,ary[0]] + ) + error.stack = caller + raise error + end + else + if table.include?(ary[0]) + # we should maybe allow this warning to be turned off... + Puppet.warning "Replacing default for %s { %s }" % + [type,ary[0]] + end + end + table[ary[0]] = ary[1] + } + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def sethost(name,host) + @@hosttable[name] = host + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def settype(name,ltype) + @typetable[name] = ltype + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # when we have an 'eval' function, we should do that instead + # for now, we only support variables in strings + def strinterp(string) + newstring = string.dup + regex = Regexp.new('\$\{(\w+)\}|\$(\w+)') + #Puppet.debug("interpreting '%s'" % string) + while match = regex.match(newstring) do + if match[1] + newstring.sub!(regex,self.lookupvar(match[1])) + elsif match[2] + newstring.sub!(regex,self.lookupvar(match[2])) + else + raise 'fuh?' + end + end + #Puppet.debug("result is '%s'" % newstring) + return newstring + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # this is kind of quirky, because it doesn't differentiate between + # creating a new object and adding params to an existing object + # it doesn't solve the real problem, though: cases like file recursion, + # where one statement explicitly modifies an object, and another + # statement modifies it because of recursion + def setobject(type, name, params, file, line) + obj = self.lookupobject(name,type) + if obj == :undefined or obj.nil? + obj = @objectable[type][name] + + # only set these if we've created the object, which is the + # most common case + obj.file = file + obj.line = line + end + + # now add the params to whatever object we've found, whether + # it was in a higher scope or we just created it + # it will not be obvious where these parameters are from, that is, + # which file they're in or whatever + params.each { |var,value| + obj[var] = value + } + return obj + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def setvar(name,value) + Puppet.debug "Setting '%s' to '%s' at level %s" % + [name.inspect,value,self.level] + if @@declarative and @symtable.include?(name) + error = Puppet::ParseError.new( + "Cannot reassign variable %s" % name + ) + error.stack = caller + raise error + else + if @symtable.include?(name) + Puppet.warning "Reassigning %s to %s" % [name,value] + end + @symtable[name] = value + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # I'm pretty sure this method could be obviated, but it doesn't + # really seem worth it + def to_trans + Puppet.debug "Translating scope %s at level %s" % + [self.object_id,self.level] + + results = [] + + @children.each { |child| + if child.is_a?(Scope) + cresult = child.to_trans + Puppet.debug "Got %s from scope %s" % + [cresult.class,child.object_id] + + # get rid of the arrayness + unless cresult.is_a?(TransBucket) + cresult.each { |result| + results.push(result) + } + else + results.push(cresult) + end + elsif child.is_a?(TransObject) + results.push(child) + else + error = Puppet::DevError.new( + "Puppet::Parse::Scope cannot handle objects of type %s" % + child.class + ) + error.stack = caller + raise error + end + } + results = results.reject { |child| + # if a scope didn't result in any objects, we get some nils + # just get rid of them + child.nil? + } + + # if we have a name and type, then make a TransBucket, which + # becomes a component + # else, just stack all of the objects into the current bucket + if defined? @name + bucket = TransBucket.new + bucket.name = @name + + # it'd be nice not to have to do this... + results.each { |result| + #Puppet.debug "Result type is %s" % result.class + bucket.push(result) + } + if defined? @type + bucket.type = @type + else + error = Puppet::ParseError.new( + "No type for scope %s" % @name + ) + error.stack = caller + raise error + end + Puppet.debug "TransBucket with name %s and type %s in scope %s" % + [@name,@type,self.object_id] + + # now find metaparams + @symtable.each { |var,value| + if Puppet::Type.metaparam?(var.intern) + #Puppet.debug("Adding metaparam %s" % var) + bucket.param(var,value) + else + #Puppet.debug("%s is not a metaparam" % var) + end + } + #Puppet.debug "Returning bucket %s from scope %s" % + # [bucket.name,self.object_id] + return bucket + else + #Puppet.debug "nameless scope; just returning a list" + return results + end + end + #------------------------------------------------------------ + end + #--------------------------------------------------------------- + end +end diff --git a/lib/puppet/server.rb b/lib/puppet/server.rb new file mode 100644 index 000000000..28bb1bd46 --- /dev/null +++ b/lib/puppet/server.rb @@ -0,0 +1,129 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the server +# +# allow things to connect to us and communicate, and stuff + +require 'puppet' +require 'puppet/daemon' +require 'puppet/servlet' +require 'puppet/master' +require 'puppet/ca' + +$noservernetworking = false + +begin + require 'webrick' + require 'webrick/https' + require 'cgi' + require 'xmlrpc/server' + require 'xmlrpc/client' +rescue LoadError => detail + $noservernetworking = detail +end + +module Puppet + class ServerError < RuntimeError; end + #--------------------------------------------------------------- + if $noservernetworking + Puppet.err "Could not create server: %s" % $noservernetworking + else + class ServerStatus + attr_reader :ca + + def self.interface + XMLRPC::Service::Interface.new("status") { |iface| + iface.add_method("int status()") + } + end + + def initialize(hash = {}) + end + + def status(status = nil, request = nil) + Puppet.warning "Returning status" + return 1 + end + end + + class Server < WEBrick::HTTPServer + include Puppet::Daemon + + # a bit of a hack for now, but eh, wadda ya gonna do? + @@handlers = { + :Master => Puppet::Master, + :CA => Puppet::CA, + :Status => Puppet::ServerStatus + } + + def self.inithandler(handler,args) + unless @@handlers.include?(handler) + raise ServerError, "Invalid handler %s" % handler + end + + hclass = @@handlers[handler] + + obj = hclass.new(args) + return obj + end + + def initialize(hash = {}) + hash[:Port] ||= Puppet[:masterport] + hash[:Logger] ||= self.httplog + hash[:AccessLog] ||= [ + [ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ], + [ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ] + ] + + if hash.include?(:Handlers) + unless hash[:Handlers].is_a?(Hash) + raise ServerError, "Handlers must have arguments" + end + + @handlers = hash[:Handlers].collect { |handler, args| + self.class.inithandler(handler, args) + } + else + raise ServerError, "A server must have handlers" + end + + # okay, i need to retrieve my cert and set it up, somehow + # the default case will be that i'm also the ca + if ca = @handlers.find { |handler| handler.is_a?(Puppet::CA) } + @driver = ca + @secureinit = true + self.fqdn + end + + unless self.readcert + unless self.requestcert + raise Puppet::Error, "Cannot start without certificates" + end + end + + hash[:SSLCertificate] = @cert + hash[:SSLPrivateKey] = @key + hash[:SSLStartImmediately] = true + hash[:SSLEnable] = true + hash[:SSLCACertificateFile] = @cacertfile + hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_NONE + hash[:SSLCertName] = nil + + super(hash) + + # this creates a new servlet for every connection, + # but all servlets have the same list of handlers + # thus, the servlets can have their own state -- passing + # around the requests and such -- but the handlers + # have a global state + + # mount has to be called after the server is initialized + self.mount("/RPC2", Puppet::Servlet, @handlers) + end + end + end + + #--------------------------------------------------------------- +end diff --git a/lib/puppet/servlet.rb b/lib/puppet/servlet.rb new file mode 100644 index 000000000..3fa965fa3 --- /dev/null +++ b/lib/puppet/servlet.rb @@ -0,0 +1,110 @@ +require 'xmlrpc/server' + +module Puppet + class ServletError < RuntimeError; end + class Servlet < XMLRPC::WEBrickServlet + attr_accessor :request + + # this is just a duplicate of the normal method; it's here for + # debugging when i need it + def self.get_instance(server, *options) + self.new(server, *options) + end + + def initialize(server, handlers) + #Puppet.info server.inspect + + # the servlet base class does not consume any arguments + # and its BasicServer base class only accepts a 'class_delim' + # option which won't change in Puppet at all + # thus, we don't need to pass any args to our base class, + # and we can consume them all ourselves + super() + + handlers.each { |handler| + Puppet.debug "adding handler for %s" % handler.class + self.add_handler(handler.class.interface, handler) + } + + @request = nil + self.set_service_hook { |obj, *args| + #raise "crap!" + if @request + args.push @request + #obj.call(args, @request) + end + begin + obj.call(*args) + rescue => detail + Puppet.warning obj.inspect + Puppet.err "Could not call: %s" % detail.to_s + end + } + end + + def service(request, response) + @request = request + if @request.client_cert + Puppet.info "client cert is %s" % @request.client_cert + end + if @request.server_cert + Puppet.info "server cert is %s" % @request.server_cert + end + #p @request + begin + super + rescue => detail + Puppet.err "Could not service request: %s: %s" % + [detail.class, detail] + end + @request = nil + end + + private + + # this is pretty much just a copy of the original method but with more + # feedback + def dispatch(methodname, *args) + #Puppet.warning "dispatch on %s called with %s" % + # [methodname, args.inspect] + for name, obj in @handler + if obj.kind_of? Proc + unless methodname == name + Puppet.debug "obj is proc but %s != %s" % + [methodname, name] + next + end + else + unless methodname =~ /^#{name}(.+)$/ + Puppet.debug "methodname did not match" + next + end + unless obj.respond_to? $1 + Puppet.debug "methodname does not respond to %s" % $1 + next + end + obj = obj.method($1) + end + + if check_arity(obj, args.size) + if @service_hook.nil? + return obj.call(*args) + else + return @service_hook.call(obj, *args) + end + else + Puppet.debug "arity is incorrect" + end + end + + if @default_handler.nil? + raise XMLRPC::FaultException.new( + ERR_METHOD_MISSING, + "Method #{methodname} missing or wrong number of parameters!" + ) + else + @default_handler.call(methodname, *args) + end + end + end +end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index d77e9017b..107e9e906 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -409,9 +409,13 @@ class Type < Puppet::Element # does the name reflect a valid parameter? def self.validparameter?(name) unless defined? @parameters - raise "Class %s has not defined parameters" % self + raise Puppet::DevError, "Class %s has not defined parameters" % self + end + if @parameters.include?(name) or @@metaparams.include?(name) + return true + else + return false end - return @parameters.include?(name) end #--------------------------------------------------------------- @@ -474,6 +478,7 @@ class Type < Puppet::Element raise Puppet::Error.new("Got nil value for %s" % name) end if Puppet::Type.metaparam?(name) + @parameters[name] = value # call the metaparam method self.send(("meta" + name.id2name + "="),value) elsif stateklass = self.class.validstate?(name) diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index c0e05f5a8..4eab8ca5b 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -4,14 +4,42 @@ require 'digest/md5' require 'etc' +require 'uri' require 'fileutils' require 'puppet/type/state' +require 'puppet/fileserver' module Puppet # we first define all of the state that our file will use # because the objects must be defined for us to use them in our # definition of the file object class State + class PFileType < Puppet::State + require 'etc' + @doc = "A read-only state to check the file type." + @name = :type + + def should=(value) + raise Puppet::Error, ":type is read-only" + end + + def retrieve + if stat = @parent.stat(true) + @is = stat.ftype + else + @is = -1 + end + + # so this state is never marked out of sync + @should = @is + end + + + def sync + raise Puppet::Error, ":type is read-only" + end + end + class PFileCreate < Puppet::State require 'etc' @doc = "Whether to create files that don't currently exist. @@ -209,7 +237,7 @@ module Puppet if @is == -1 # if they're copying, then we won't worry about the file # not existing yet - unless @parent.state(:copy) or @parent.state(:create) + unless @parent.state(:source) or @parent.state(:create) Puppet.warning "File %s does not exist -- cannot checksum" % @parent.name end @@ -280,16 +308,16 @@ module Puppet def retrieve # if we're not root, then we can't chown anyway - unless Process.uid == 0 - @parent.delete(self.name) - @should = nil - @is = nil - unless defined? @@notified - Puppet.notice "Cannot manage ownership unless running as root" - @@notified = true - return - end - end +# unless Process.uid == 0 +# @parent.delete(self.name) +# @should = nil +# @is = nil +# unless defined? @@notified +# Puppet.notice "Cannot manage ownership unless running as root" +# @@notified = true +# return +# end +# end unless stat = @parent.stat(true) @is = -1 @@ -307,7 +335,9 @@ module Puppet Puppet.notice "Cannot manage ownership unless running as root" #@parent.delete(self.name) @@notified = true - return + end + if @parent.state(:owner) + @parent.delete(:owner) end raise Puppet::Error.new( "Cannot manage ownership unless running as root" @@ -588,54 +618,172 @@ module Puppet end end - class PFileCopy < Puppet::State + class PFileSource < Puppet::State attr_accessor :source, :local @doc = "Copy a file over the current file. Uses `checksum` to determine when a file should be copied. This is largely a support state for the `source` parameter, which is what should generally be used instead of `copy`." - @name = :copy + @name = :source + + def desc2hash(line, params) + args = {} + + values = line.split("\t").collect { |value| + if value =~ /^[0-9]+$/ + value.to_i + else + value + end + } - def retrieve - sum = nil - if sum = @parent.state(:checksum) - if sum.is - if sum.is == -1 - sum.retrieve + params.zip(values).each { |param, value| + args[param] = value + } + + if args[:type] == "directory" + args.delete(:checksum) + end + args + end + + def hash2child(hash, source, recurse) + # "/" == self + if hash[:name] == "/" + if hash[:type] == "directory" + else + self[:source] = source + end + hash.each { |param, value| + next if param == :name + next if param == :type + + unless self.state(param) + self[param] = value end - @is = sum.is + } + if source =~ /Filesourcetest/ + p self + end + # we can now skip this object, and the rest is + # pretty much related to children + return + end + + name = File.join(self.name, hash[:name]) + if child = @children.find { |child| + child.name == name } + else # the child does not yet exist + #hash.delete(:name) + sum = nil + if hash[:type] == "directory" + hash[:create] = "directory" + hash[:source] = @parameters[:source] + hash[:name] + hash[:recurse] = recurse else - @is = -1 + hash[:source] = source + hash[:name] + #sum = hash[:checksum] + #hash.delete(:checksum) end - else - @is = -1 + hash.delete(:type) + + name = hash[:name].sub(/^\//, '') # rm leading / + hash.delete(:name) + + Puppet.warning "Found new file %s under %s" % + [name.inspect, self.name] + self.newchild(name, hash) end end - def should=(source) - @local = true # redundant for now - @source = source - type = Puppet::Type.type(:file) + def describe + source = @source + + sourceobj, path = @parent.uri2obj(source) + + server = sourceobj.server + + desc = server.describe(path) + + args = {} + Puppet::Type::PFile::PINPARAMS.zip(desc.split("\t")).each { |param, value| + if value =~ /^[0-9]+$/ + value = value.to_i + end + args[param] = value + } + + return args + end + + def retrieve + sum = nil + + @stats = self.describe + + @stats.each { |stat, value| + next if stat == :checksum + next if stat == :type - sourcesum = nil - stat = File.stat(@source) - case stat.ftype + unless @parent.argument?(stat) + if state = @parent.state(stat) + state.should = value + else + @parent[stat] = value + end + end + } + case @stats[:type] when "file": - unless sourcesum = type[@source].state(:checksum).is - raise Puppet::Error.new( - "Could not retrieve checksum of source %s" % - @source - ) + if sum = @parent.state(:checksum) + if sum.is + if sum.is == -1 + sum.retrieve + end + @is = sum.is + else + @is = -1 + end + else + @is = -1 + end + + @should = @stats[:checksum] + + if state = @parent.state(:create) + unless state.should == "file" + Puppet.notice( + "File %s had both create and source enabled" % + @parent.name + ) + @parent.delete(:create) + end end when "directory": - raise Puppet::Error.new( - "Somehow got told to copy dir %s" % @parent.name) + if state = @parent.state(:create) + unless state.should == "directory" + state.should = "directory" + end + else + @parent[:create] = "directory" + @parent.state(:create).retrieve + end + # we'll let the :create state do our work + @should = true + @is = true else - raise Puppet::Error.new( - "Cannot use files of type %s as source" % stat.ftype) + Puppet.err "Cannot use files of type %s as sources" % + @stats[:type] + @should = true + @is = true end + end - @should = sourcesum + def should=(source) + @source = source + + # stupid hack for now; it'll get overriden + @should = source end def sync @@ -673,64 +821,140 @@ module Puppet @backed = true end + unless @stats[:type] == "file" + raise Puppet::DevError, "Got told to copy non-file %s" % + @parent.name + end + + sourceobj, path = @parent.uri2obj(@source) + + contents = sourceobj.server.retrieve(path) + + unless sourceobj.server.local + contents = CGI.unescape(contents) + end + + if contents == "" + Puppet.notice "Could not retrieve contents for %s" % + @source + end + + begin + if FileTest.exists?(@parent.name) + if FileTest.exists?(@parent.name + bak) + Puppet.warning "Deleting backup of %s" % + @parent.name + File.unlink(@parent.name + bak) + end + + # rename the existing one + File.rename( + @parent.name, + @parent.name + ".puppet-bak" + ) + # move the new file into place + args = [@parent.name, + File::CREAT | File::WRONLY | File::TRUNC] + + # try to create it with the correct modes to start + # we should also be changing our effective uid/gid, but... + if @parent[:mode] + args.push @parent[:mode] + end + File.open(*args) { |f| + f.print contents + } + # if we've made a backup, then delete the old file + if @backed + #Puppet.err "Unlinking backup" + File.unlink(@parent.name + bak) + #else + #Puppet.err "Not unlinking backup" + end + else + # the easy case + args = [@parent.name, + File::CREAT | File::WRONLY | File::TRUNC] + + # try to create it with the correct modes to start + # we should also be changing our effective uid/gid, but... + if @parent[:mode] + args.push @parent[:mode] + end + File.open(*args) { |f| + f.print contents + } + end + rescue => detail + # since they said they want a backup, let's error out + # if we couldn't make one + error = Puppet::Error.new("Could not copy %s to %s: %s" % + [@source, @parent.name, detail.message]) + raise error + end + #Puppet.notice "@is: %s; @should: %s" % [@is,@should] #Puppet.err "@is: %s; @should: %s" % [@is,@should] # okay, we've now got whatever backing up done we might need # so just copy the files over - if @local - stat = File.stat(@source) - case stat.ftype - when "file": - begin - if FileTest.exists?(@parent.name) - # get the file here - FileUtils.cp(@source, @parent.name + ".tmp") - if FileTest.exists?(@parent.name + bak) - Puppet.warning "Deleting backup of %s" % + + f = false + if f + if @local + stat = File.stat(@source) + case stat.ftype + when "file": + begin + if FileTest.exists?(@parent.name) + # get the file here + FileUtils.cp(@source, @parent.name + ".tmp") + if FileTest.exists?(@parent.name + bak) + Puppet.warning "Deleting backup of %s" % + @parent.name + File.unlink(@parent.name + bak) + end + # rename the existing one + File.rename( + @parent.name, + @parent.name + ".puppet-bak" + ) + # move the new file into place + File.rename( + @parent.name + ".tmp", @parent.name - File.unlink(@parent.name + bak) - end - # rename the existing one - File.rename( - @parent.name, - @parent.name + ".puppet-bak" - ) - # move the new file into place - File.rename( - @parent.name + ".tmp", - @parent.name - ) - # if we've made a backup, then delete the old file - if @backed - #Puppet.err "Unlinking backup" - File.unlink(@parent.name + bak) - #else - #Puppet.err "Not unlinking backup" + ) + # if we've made a backup, then delete the old file + if @backed + #Puppet.err "Unlinking backup" + File.unlink(@parent.name + bak) + #else + #Puppet.err "Not unlinking backup" + end + else + # the easy case + FileUtils.cp(@source, @parent.name) end - else - # the easy case - FileUtils.cp(@source, @parent.name) + rescue => detail + # since they said they want a backup, let's error out + # if we couldn't make one + error = Puppet::Error.new("Could not copy %s to %s: %s" % + [@source, @parent.name, detail.message]) + raise error end - rescue => detail - # since they said they want a backup, let's error out - # if we couldn't make one - error = Puppet::Error.new("Could not copy %s to %s: %s" % - [@source, @parent.name, detail.message]) - raise error + when "directory": + raise Puppet::Error.new( + "Somehow got told to copy directory %s" % + @parent.name) + when "link": + dest = File.readlink(@source) + Puppet::State::PFileLink.create(@dest,@parent.path) + else + raise Puppet::Error.new( + "Cannot use files of type %s as source" % stat.ftype) end - when "directory": - raise Puppet::Error.new( - "Somehow got told to copy directory %s" % - @parent.name) - when "link": - dest = File.readlink(@source) - Puppet::State::PFileLink.create(@dest,@parent.path) else - raise Puppet::Error.new( - "Cannot use files of type %s as source" % stat.ftype) + raise Puppet::Error.new("Somehow got a non-local source") end - else - raise Puppet::Error.new("Somehow got a non-local source") end return :file_changed end @@ -746,18 +970,19 @@ module Puppet @states = [ Puppet::State::PFileCreate, Puppet::State::PFileChecksum, - Puppet::State::PFileCopy, + Puppet::State::PFileSource, Puppet::State::PFileUID, Puppet::State::PFileGroup, - Puppet::State::PFileMode + Puppet::State::PFileMode, + Puppet::State::PFileType ] @parameters = [ :path, :backup, :linkmaker, - :recurse, :source, + :recurse, :filebucket ] @@ -782,7 +1007,7 @@ module Puppet *file* is supported as a protocol)." @paramdoc[:filebucket] = "A repository for backing up files, including - over the network. Argument must the name of an existing + over the network. Argument must be the name of an existing filebucket." @name = :file @@ -790,11 +1015,12 @@ module Puppet @depthfirst = false - if Process.uid == 0 - @@pinparams = [:mode, :owner, :group, :checksum] - else - @@pinparams = [:mode, :group, :checksum] - end + #if Process.uid == 0 + @@pinparams = [:mode, :type, :owner, :group, :checksum] + PINPARAMS = [:mode, :type, :owner, :group, :checksum] + #else + # @@pinparams = [:mode, :type, :group, :checksum] + #end def self.recursecompare(source,dest) @@ -825,6 +1051,10 @@ module Puppet end end + def argument?(arg) + @arghash.include?(arg) + end + def initialize(hash) @arghash = self.argclean(hash) @arghash.delete(self.class.namevar) @@ -882,13 +1112,13 @@ module Puppet end args = @arghash.dup + #args = {} args[:path] = path unless hash.include?(:source) # it's being manually overridden - if args.include?(:source) - #Puppet.notice "Rewriting source for %s" % path + Puppet.err "Rewriting source for %s" % path name = File.basename(path) dirname = args[:source] #Puppet.notice "Old: %s" % args[:source] @@ -937,16 +1167,26 @@ module Puppet klass = Puppet::Type::PFile end if child = klass[path] - #raise "Ruh-roh" + unless @children.include?(child) + raise Puppet::Error, + "Planned child file %s already exists with parent %s" % + [path, child.parent] + end args.each { |var,value| next if var == :path next if var == :name - child[var] = value + # behave idempotently + unless child[var] == value + #Puppet.warning "%s is %s, not %s" % [var, child[var], value] + child[var] = value + end } else # create it anew #notice "Creating new file with args %s" % args.inspect begin child = klass.new(args) + child.parent = self + @children << child rescue Puppet::Error => detail Puppet.notice( "Cannot manage %s: %s" % @@ -963,9 +1203,6 @@ module Puppet child = nil end end - if child - child.parent = self - end return child end @@ -984,8 +1221,8 @@ module Puppet #Puppet.info "%s is already in memory" % @source if obj.managed? raise Puppet::Error.new( - "You cannot currently use managed files as sources;" + - "%s is managed" % path + "You cannot currently use managed file %s as a source" % + path.inspect ) else # verify they're looking up the correct info @@ -1031,18 +1268,29 @@ module Puppet return obj end - # pinning is like recursion, except that it's recursion across - # the pinned file's tree, instead of our own - # if recursion is turned off, then this whole thing is pretty easy - def paramsource=(source) + def disabledparamsource=(source) @parameters[:source] = source @arghash.delete(:source) @source = source # verify we support the proto - if @source =~ /^file:\/\/(\/.+)/ - @source = $1 - elsif @source =~ /(\w+):\/\/(\/.+)/ + if @source =~ /^\/(\/.+)/ + @sourcetype = "file" + elsif @source =~ /^(\w):\/\/(\/.+)/ + @sourcetype = $1 + @source = $2 + else + raise Puppet::Error, "Invalid source %s" % @source + end + + + case @sourcetype + when "file": + when "http", "https": + if @parameters[:recurse] + raise Puppet::Error.new("HTTP is not supported recursively") + end + else raise Puppet::Error.new("Protocol %s not supported" % $1) end @@ -1158,9 +1406,7 @@ module Puppet else # they have it but we don't fullname = File.join(self.name, name) if kid = self.newchild(name,:source => child.name) - if @children.include?(kid) - Puppet.notice "Child already included" - else + unless @children.include?(kid) self.push kid end end @@ -1169,19 +1415,12 @@ module Puppet } else - self[:copy] = @sourceobj.name + self[:source] = @sourceobj.name end end - def paramrecurse=(value) - @parameters[:recurse] = value - unless FileTest.exist?(self.name) and self.stat.directory? - #Puppet.info "%s is not a directory; not recursing" % - # self.name - return - end - - recurse = value + def recurse + recurse = @parameters[:recurse] # we might have a string, rather than a number if recurse.is_a?(String) if recurse =~ /^[0-9]+$/ @@ -1194,6 +1433,24 @@ module Puppet # are we at the end of the recursion? if recurse == 0 + Puppet.info "finished recursing" + return + end + + if recurse.is_a?(Integer) + recurse -= 1 + end + + self.localrecurse(recurse) + if @states.include?(:source) + self.sourcerecurse(recurse) + end + end + + def localrecurse(recurse) + unless FileTest.exist?(self.name) and self.stat.directory? + #Puppet.info "%s is not a directory; not recursing" % + # self.name return end @@ -1211,51 +1468,79 @@ module Puppet Dir.foreach(self.name) { |file| next if file =~ /^\.\.?/ # skip . and .. if child = self.newchild(file, :recurse => recurse) - if @children.include?(child) - Puppet.notice "Child already included" - else + unless @children.include?(child) self.push child added.push file end end } + end + + def sourcerecurse(recurse) + source = @states[:source].source + + sourceobj, path = uri2obj(source) - # here's where we handle sources; it's special, because it can - # require us to build a structure of files that don't yet exist - if @parameters.include?(:source) - unless FileTest.directory?(@parameters[:source]) - raise Puppet::Error("Cannot use file %s as a source for a dir" % - @parameters[:source]) + # we'll set this manually as necessary + if @arghash.include?(:create) + @arghash.delete(:create) + end + + # okay, we've got our source object; now we need to + # build up a local file structure to match the remote + # one + + server = sourceobj.server + sum = "md5" + if state = self.state(:checksum) + sum = state.checktype + end + r = false + if recurse + unless recurse == 0 + r = 1 end - Dir.foreach(@parameters[:source]) { |file| - next if file =~ /^\.\.?$/ # skip . and .. - unless added.include?(file) - Puppet.notice "Adding absent source-file %s" % file - if child = self.newchild(file, - :recurse => recurse, - :source => File.join(self.name, file) - ) - if @children.include?(child) - Puppet.notice "Child already included" - else - self.push child - added.push file - end - end - end - } end + + #Puppet.warning "Listing path %s" % path.inspect + desc = server.list(path, r) + + #params = @@pinparams.dup + #params.unshift(:name) + desc.split("\n").each { |line| + file, type = line.split("\t") + next if file == "/" + name = file.sub(/^\//, '') + #Puppet.warning "child name is %s" % name + args = {:source => source + file} + if type == file + args[:recurse] = nil + end + self.newchild(name, args) + #self.newchild(hash, source, recurse) + #hash2child(hash, source, recurse) + } end # a wrapper method to make sure the file exists before doing anything def retrieve + if @states.include?(:source) + @states[:source].retrieve + end + + if @parameters.include?(:recurse) + #self[:recurse] = @parameters[:recurse] + self.recurse + end + unless stat = self.stat(true) - Puppet.debug "File %s does not exist" % self.name + Puppet.debug "rFile %s does not exist" % self.name @states.each { |name,state| state.is = -1 } return end + super end @@ -1264,8 +1549,6 @@ module Puppet begin @stat = File.stat(self.name) rescue Errno::ENOENT => error - #Puppet.debug "Failed to stat %s: No such file or directory" % - # [self.name] @stat = nil rescue => error Puppet.debug "Failed to stat %s: %s" % @@ -1276,30 +1559,57 @@ module Puppet return @stat end - end # Puppet::Type::PFile - end # Puppet::Type - - class PFileSource - attr_accessor :name - @sources = Hash.new(nil) - - def PFileSource.[]=(name,sub) - @sources[name] = sub - end + def uri2obj(source) + sourceobj = FileSource.new + path = nil + if source =~ /^\// + source = "file://localhost/%s" % source + sourceobj.mount = "localhost" + sourceobj.local = true + end + begin + uri = URI.parse(source) + rescue => detail + raise Puppet::Error, "Could not understand source %s: %s" % + [source, detail.to_s] + end - def PFileSource.[](name) - return @sources[name] - end + case uri.scheme + when "file": + sourceobj.server = Puppet::FileServer.new( + :Local => true + ) + sourceobj.server.mount("/", "localhost") + path = "/localhost" + uri.path + when "puppet": + args = { :Server => uri.host } + if uri.port + args[:Port] = uri.port + end + sourceobj.server = Puppet::NetworkClient.new(args) - def initialize(name) - @name = name + tmp = uri.path + if tmp =~ %r{^/(\w+)} + sourceobj.mount = $1 + path = tmp.sub(%r{^/\w+},'') || "/" + else + raise Puppet::Error, "Invalid source path %s" % tmp + end + else + raise Puppet::Error, + "Got other recursive file proto %s" % uri.scheme + return + end - if block_given? - yield self + return [sourceobj, path.sub(/\/\//, '/')] end + end # Puppet::Type::PFile + end # Puppet::Type - PFileSource[name] = self - end + # the filesource class can't include the path, because the path + # changes for every file instance + class FileSource + attr_accessor :mount, :root, :server, :local end end |
