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 | |
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
81 files changed, 9415 insertions, 211 deletions
diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..b3701c91b --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +This entire repository is under the GNU Public License. See www.gnu.org +for a copy. @@ -0,0 +1,22 @@ +$Id$ + +This is what you need to get puppet running: + +-- Check you have Ruby version 1.8.2 or later + You can download it from + ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.8.2.tar.gz + Un tar it, then: ./configure; make; make install + +-- http://reductivelabs.com/downloads/facter/facter-1.0.tgz + A library that puppet needs to run. Un tar it, and calling + 'ruby install.rb' file should successfully install it; let + me know if it doesn't. Otherwise, you can just set RUBYLIB + to contain its lib directory. + +-- Install puppet + Run 'ruby install.rb' or add the 'lib/' directory to your RUBYLIB path. + Documentation can be found online at + http://reductivelabs.com/projects/puppet/documentation/ + +-- After that, you should be able to go into test/ and run ./test, + or run 'bin/puppet' on whichever puppet config files you want. @@ -0,0 +1,4 @@ +$Id$ + +* Syntax for configuration of types (e.g., search path for Services) +* Get file parsing working in the language diff --git a/bin/certmgr.rb b/bin/certmgr.rb new file mode 100755 index 000000000..37f4a406c --- /dev/null +++ b/bin/certmgr.rb @@ -0,0 +1,44 @@ +#!/usr/bin/ruby -w + +#-------------------- +# the puppet client +# +# $Id$ + + +require 'puppet' +require 'puppet/openssl' +require 'getoptlong' + +result = GetoptLong.new( + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ] +) + +check = false + +result.each { |opt,arg| + case opt + when "--debug": + when "--check": + when "--help": + puts "There is no help yet" + exit + else + puts "Invalid option '#{opt}'" + exit(10) + end +} + +Puppet[:logdest] = :console +Puppet[:loglevel] = :info + +rootcert = Puppet[:rootcert] +rootkey = Puppet[:rootkey] +rootkey = Puppet[:rootkey] + +unless rootcert + raise "config unset" +end + +#mkcertsdir(File.basename(rootcert)) diff --git a/bin/filebucketd b/bin/filebucketd new file mode 100755 index 000000000..2e7177d6a --- /dev/null +++ b/bin/filebucketd @@ -0,0 +1,40 @@ +#!/usr/bin/ruby -w + +#-------------------- +# accept and serve files +# +# $Id$ + +require 'getoptlong' +require 'puppet/filebucket' + + #[ "--size", "-s", GetoptLong::REQUIRED_ARGUMENT ], +result = GetoptLong.new( + [ "--help", "-h", GetoptLong::NO_ARGUMENT ] +) + +result.each { |opt,arg| + case opt + when "--help" + puts "There is no help yet" + exit + else + raise "Invalid option '#{opt}'" + end +} + +begin + server = FileBucket::BucketWebserver.new( + :Bucket => File.expand_path("~/.puppet/bucket"), + :Debug => true, + :Port => 8080 + ) + trap(:INT) { server.shutdown } + server.start + #server = FileBucket::BucketServer.new( + # :Bucket => File.expand_path("~/.puppet/bucket") + #) +rescue => detail + $stderr.puts detail + exit(1) +end diff --git a/bin/puppet b/bin/puppet new file mode 100755 index 000000000..094cefd0a --- /dev/null +++ b/bin/puppet @@ -0,0 +1,129 @@ +#!/usr/bin/ruby -w + +# +# = Synopsis +# +# Run a stand-alone +puppet+ script. +# +# = Usage +# +# puppet [-V|--version] [-d|--debug] [-v|--verbose] [-l|--logfile <file>] [-h|--help] <file> +# +# = Description +# +# This is the standalone +puppet+ execution script; use it to execute +# individual scripts that you write. If you need to execute site-wide +# scripts, use +puppetd+ and +puppetmasterd+. +# +# = Options +# +# debug:: +# Enable full debugging. +# +# help:: +# Print this help message +# +# logfile:: +# Where to send messages. Defaults to sending messages to the console. +# +# = Example +# +# puppet -l /tmp/script.log script.pp +# +# = Author +# +# Luke Kanies +# +# = Copyright +# +# Copyright (c) 2005 Reductive Labs, LLC +# Licensed under the Gnu Public License + +require 'puppet' +require 'puppet/server' +require 'puppet/client' +require 'getoptlong' + +$haveusage = true + +begin + require 'rdoc/usage' +rescue + $haveusage = false +end + + result = GetoptLong.new( + [ "--version", "-V", GetoptLong::NO_ARGUMENT ], + [ "--logfile", "-l", GetoptLong::REQUIRED_ARGUMENT ], + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ] + ) + +debug = false +verbose = false +logfile = false + +begin + result.each { |opt,arg| + case opt + when "--version" + puts "%s" % Puppet.version + exit + when "--help" + if $haveusage + RDoc::usage && exit + else + puts "No help available unless you have RDoc::usage installed" + exit + end + when "--verbose" + verbose = true + when "--debug" + debug = true + when "--logfile" + logfile = arg + else + $stderr.puts "Invalid option '#{opt}'" + Rdoc::usage(1,'usage') + end + } +rescue GetoptLong::InvalidOption => detail + RDoc::usage(1,'usage') +end + +if debug + Puppet[:loglevel] = :debug +elsif verbose + Puppet[:loglevel] = :info +end + +if logfile + Puppet[:logdest] = logfile +end + +begin + server = Puppet::Master.new( + :File => ARGV.shift, + :Local => true + ) +rescue => detail + $stderr.puts detail + exit(1) +end + +begin + client = Puppet::Client.new( + :Server => server + ) +rescue => detail + $stderr.puts detail + exit(1) +end + +begin + client.getconfig +rescue => detail + $stderr.puts detail + exit(1) +end diff --git a/bin/puppetca b/bin/puppetca new file mode 100755 index 000000000..358f721b1 --- /dev/null +++ b/bin/puppetca @@ -0,0 +1,127 @@ +#!/usr/bin/ruby -w + +#-------------------- +# the puppet client +# +# $Id$ + + +require 'puppet' +require 'puppet/sslcertificates' +require 'getoptlong' + +result = GetoptLong.new( + [ "--ssldir", GetoptLong::REQUIRED_ARGUMENT ], + [ "--list", "-l", GetoptLong::NO_ARGUMENT ], + [ "--sign", "-s", GetoptLong::NO_ARGUMENT ], + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], + [ "--all", "-a", GetoptLong::NO_ARGUMENT ], + [ "--cadir", GetoptLong::REQUIRED_ARGUMENT ], + [ "--generate", "-g", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ] +) + +mode = nil +all = false +generate = nil + +result.each { |opt,arg| + case opt + when "--help" + puts "There is no help yet" + exit + when "--list" + mode = :list + when "--sign" + mode = :sign + when "--all" + all = true + when "--verbose" + Puppet[:loglevel] = :info + when "--debug" + Puppet[:loglevel] = :debug + when "--generate" + generate = arg + mode = :generate + when "--cadir" + Puppet[:cadir] = arg + when "--ssldir" + Puppet[:ssldir] = arg + else + puts "Invalid option '#{opt}'" + exit(10) + end +} + +ca = Puppet::SSLCertificates::CA.new() + +unless mode + $stderr.puts "You must specify --list or --sign" + exit(12) +end + +hosts = ca.list +unless hosts.length > 0 or mode == :generate + Puppet.info "No waiting requests" + exit(0) +end + +case mode +when :list + puts hosts.join("\n") +when :sign + unless ARGV.length > 0 or all + $stderr.puts( + "You must specify to sign all certificates or you must specify hostnames" + ) + exit(24) + end + + unless all + hosts = hosts.find_all { |host| + ARGV.include?(host) + } + end + + hosts.each { |host| + begin + csr = ca.getclientcsr(host) + rescue => detail + $stderr.puts "Could not retrieve request for %s: %s" % [host, detail] + end + + begin + ca.sign(csr) + rescue => detail + $stderr.puts "Could not sign request for %s: %s" % [host, detail] + end + + begin + ca.removeclientcsr(host) + rescue => detail + $stderr.puts "Could not remove request for %s: %s" % [host, detail] + end + } +when :generate + # we need to generate a certificate for a host + unless ARGV.length > 0 + $stderr.puts "You must specify hosts to generate certs for" + exit(84) + end + ARGV.each { |host| + puts "Generating certificate for %s" % host + cert = Puppet::SSLCertificates::Certificate.new( + :name => host + ) + cert.mkcsr + signedcert, cacert = ca.sign(cert.csr) + + cert.cert = signedcert + cert.cacert = cacert + cert.write + } +else + $stderr.puts "Invalid mode %s" % mode + exit(42) +end diff --git a/bin/puppetd b/bin/puppetd new file mode 100755 index 000000000..7b5184948 --- /dev/null +++ b/bin/puppetd @@ -0,0 +1,102 @@ +#!/usr/bin/ruby + +#-------------------- +# the puppet client +# +# $Id$ + + +$:.unshift '../lib' + +require 'puppet' +require 'puppet/server' +require 'puppet/client' +require 'getoptlong' + +result = GetoptLong.new( + [ "--logfile", "-l", GetoptLong::REQUIRED_ARGUMENT ], + [ "--ssldir", GetoptLong::REQUIRED_ARGUMENT ], + [ "--fqdn", "-f", GetoptLong::REQUIRED_ARGUMENT ], + [ "--server", "-s", GetoptLong::REQUIRED_ARGUMENT ], + [ "--secure", GetoptLong::REQUIRED_ARGUMENT ], + [ "--port", "-p", GetoptLong::REQUIRED_ARGUMENT ], + [ "--noinit", "-n", GetoptLong::NO_ARGUMENT ], + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ] +) + +noinit = false +server = "localhost" +proto = "http" +fqdn = nil + +result.each { |opt,arg| + case opt + when "--help" + puts "There is no help yet" + exit + when "--verbose" + Puppet[:loglevel] = :info + when "--debug" + Puppet[:loglevel] = :debug + when "--ssldir" + Puppet[:ssldir] = arg + when "--secure" + proto = "https" + when "--noinit" + noinit = true + when "--fqdn" + fqdn = arg + when "--server" + server = arg + when "--port" + Puppet[:masterport] = arg + when "--logfile" + Puppet[:logfile] = arg + else + puts "Invalid option '#{opt}'" + exit(10) + end +} + +bg = false + +unless Puppet[:loglevel] == :debug or Puppet[:loglevel] == :info + bg = true +end + +args = {:Server => server} +if fqdn + args[:FQDN] = fqdn +end +client = Puppet::Client.new(args) + +unless client.readcert + begin + while ! client.requestcert do + Puppet.notice "Could not request certificate" + sleep 5 + end + rescue => detail + Puppet.err "Could not request certificate: %s" % detail.to_s + exit(23) + end +end + +if bg + Puppet[:logdest] = Puppet[:logfile] + client.daemonize +end +#client.start +client.getconfig + +#threads = [] +#threads << Thread.new { +# trap(:INT) { +# client.shutdown +# } +# client.start +#} +# +#client.getconfig diff --git a/bin/puppetdoc b/bin/puppetdoc new file mode 100755 index 000000000..e64ca0624 --- /dev/null +++ b/bin/puppetdoc @@ -0,0 +1,129 @@ +#!/usr/local/bin/ruby +#!/usr/bin/ruby -w + +#-------------------- +# produce documentation on all of the puppet types +# +# $Id$ + + +$:.unshift '../lib' + +require 'puppet' +require 'getoptlong' + +def tab(num) + return $tab * num +end + +result = GetoptLong.new( + [ "--logfile", "-l", GetoptLong::REQUIRED_ARGUMENT ], + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ] +) + +debug = false +logfile = false + +$tab = " " + +result.each { |opt,arg| + case opt + when "--help" + puts "There is no help yet" + exit + when "--debug" + debug = true + when "--logfile" + logfile = arg + else + raise "Invalid option '#{opt}'" + end +} + +puts %{ +============== +Type Reference +============== + +} + +types = {} +Puppet::Type.eachtype { |type| + types[type.name] = type +} +puts %{ +--------------- +Meta-Parameters +--------------- + +} +Puppet::Type.eachmetaparam { |param| + puts "- **" + param.to_s + "**" + puts tab(1) + Puppet::Type.metaparamdoc(param).gsub(/\n\s*/,' ') +} + +puts %{ +----- +Types +----- + +- *namevar* is the parameter used to uniquely identify a type instance. + This is the parameter that gets assigned when a string is provided before + the colon in a type declaration. +- *states* are the aspects of a type that can be changed. +- *params* control how a type implements the state changes. + + +} + +types.sort { |a,b| + a.to_s <=> b.to_s +}.each { |name,type| + next if name == :puppet + next if name == :component + + puts " + +---------------- + +" + + puts " +%s +%s" % [name, "=" * (name.to_s.length + 4)] + #String.new('n%s\n') % name.to_s + #puts "**" + type.doc.gsub(/\n\s*/, ' ') + "**\n\n" + puts type.doc.gsub(/\n\s*/, ' ') + "\n\n" + type.buildstatehash + #puts tab(1) + "* namevar: %s" % type.namevar + puts "%s States\n'''''''''''''''''''''''''''''''" % name.to_s.capitalize + type.validstates.sort { |a,b| + a.to_s <=> b.to_s + }.each { |sname,state| + puts "- **%s**" % sname + puts tab(1) + state.doc.gsub(/\n\s*/,' ') + } + + puts "\n%s Parameters\n''''''''''''''''''''''''''''''" % name.to_s.capitalize + type.parameters.sort { |a,b| + a.to_s <=> b.to_s + }.each { |name,param| + print "- **%s**" % name + if type.namevar == name and name != :name + puts " (*namevar*)" + else + puts "" + end + puts tab(1) + type.paramdoc(name).gsub(/\n\s*/,' ') + } + puts "\n" +} + +puts " + +---------------- + +" + +puts "\n*This page autogenerated on %s*" % Time.now diff --git a/bin/puppetmasterd b/bin/puppetmasterd new file mode 100755 index 000000000..af5da3ee5 --- /dev/null +++ b/bin/puppetmasterd @@ -0,0 +1,89 @@ +#!/usr/bin/ruby -w + +#-------------------- +# the central puppet server +# +# $Id$ + +require 'getoptlong' +require 'puppet' +require 'puppet/server' + +result = GetoptLong.new( + [ "--logfile", "-l", GetoptLong::REQUIRED_ARGUMENT ], + [ "--manifest", "-m", GetoptLong::REQUIRED_ARGUMENT ], + [ "--ssldir", "-s", GetoptLong::REQUIRED_ARGUMENT ], + [ "--noinit", "-n", GetoptLong::NO_ARGUMENT ], + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], + [ "--noca", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ] +) + +noinit = false +ca = true + +result.each { |opt,arg| + case opt + when "--help" + puts "There is no help yet" + exit + when "--verbose" + Puppet[:loglevel] = :info + when "--debug" + Puppet[:debug] = true + when "--noca" + ca = false + when "--ssldir" + Puppet[:ssldir] = arg + when "--manifest" + Puppet[:manifest] = arg + when "--noinit" + noinit = true + when "--logfile" + Puppet[:masterlog] = arg + else + raise "Invalid option '#{opt}'" + end +} + +bg = false + +Puppet[:autosign] = true + +unless Puppet[:loglevel] == :debug or Puppet[:loglevel] == :info + bg = true +end + +if bg + Puppet[:logdest] = Puppet[:masterlog] +end + +begin + # use the default, um, everything + #server = Puppet::Server.new(:CA => ca) + server = Puppet::Server.new( + :Handlers => { + :CA => {}, # so that certs autogenerate + :Master => {}, + :Status => {} + } + ) +rescue => detail + $stderr.puts detail + exit(1) +end + +if bg + server.daemonize +end + +trap(:INT) { + server.shutdown +} +begin + server.start +rescue => detail + Puppet.err "Could not start puppetmaster: %s" % detail + exit(1) +end diff --git a/examples/code/allatonce b/examples/code/allatonce new file mode 100644 index 000000000..2857b9b62 --- /dev/null +++ b/examples/code/allatonce @@ -0,0 +1,13 @@ +# $Id$ + +define thingie { + file { "/tmp/classtest": create => true, mode => 755 } + #testing {} +} + +class testing { + thingie { "componentname": } +} + +#component {} +testing { "testingname": } diff --git a/examples/code/assignments b/examples/code/assignments new file mode 100644 index 000000000..3edcef84e --- /dev/null +++ b/examples/code/assignments @@ -0,0 +1,11 @@ +# $Id$ + +$goodness = sunos + +$subvariable = $goodness + +$yayness = "this is a string of text" + +#$sleeper = service { sleeper: +# running => "1" +#} diff --git a/examples/code/classing b/examples/code/classing new file mode 100644 index 000000000..8f9477721 --- /dev/null +++ b/examples/code/classing @@ -0,0 +1,35 @@ +# $Id$ + +# define the server as a class + +import "components" + +class base() { + # how do i handle components that don't take arguments? do they still + # require a name? + sudo { } +} + +class server inherits base { + file { "/tmp/puppetfiletest": + create => true + } +} + +class webserver(docroot) inherits server { + apache { + php => false, + docroot => $docroot, + user => http, + group => http + } +} + +class sleepserver(path) inherits server { + sleeper { + path => $path, + mode => 644 + } +} + +# see 'nodes' for how to handle nodes diff --git a/examples/code/components b/examples/code/components new file mode 100644 index 000000000..efedf75e3 --- /dev/null +++ b/examples/code/components @@ -0,0 +1,73 @@ +# $Id$ + +# i still have no 'require'-like functionality, and i should also +# have 'recommend'-like functionality... +define apache(php,docroot,user,group) { + package { apache: + version => "2.0.53" + } + service { apache: + running => true + } + + + # this definitely won't parse + if $php == "true" { + # this needs to do two things: + # - mark a dependency + # - cause this apache component to receive refresh events generated by php + #require("php") + $var = value + } + + #file { "../examples/root/etc/configfile": + # owner => $user + #} +} + +define sudo() { + package { sudo: + version => "1.6.8p7" + } + file { "/etc/sudoers": + owner => root, + group => root, + mode => "440" + } +} + +define ssh { + package { ssh: + version => "3.4.4.4" + } + service { "sshd": + running => true + } +} + +define sleeper(path,mode) { + Service { + path => "../examples/root/etc/init.d" + } + + service { sleeper: + running => true, + path => "../examples/root/etc/init.d" + } + file { $path: + mode => $mode + } + $files = ["/tmp/testness","/tmp/funtest"] + file { $files: + create => true + } +} + +#apache { "test": +# php => false, +# docroot => "/export/html", +# user => "www-data", +# group => "www-data" +#} + +#ssh { "yucko":} diff --git a/examples/code/execs b/examples/code/execs new file mode 100644 index 000000000..44f133098 --- /dev/null +++ b/examples/code/execs @@ -0,0 +1,16 @@ +$path = "/usr/bin:/bin" + +exec { "mkdir -p /tmp/fakedir": + path => $path +} + +exec { "rm -rf /tmp/fakedir": + path => $path +} + +exec { "touch /this/directory/does/not/exist": + path => $path, + returns => 1 +} + + diff --git a/examples/code/facts b/examples/code/facts new file mode 100644 index 000000000..fcfec2c0d --- /dev/null +++ b/examples/code/facts @@ -0,0 +1,22 @@ +# $Id$ + +# DISABLED + +# facts are now added to the top scope (e.g., operatingsystem and macaddress) + +# these facts have to get defined by the server onto the client before +# we can retrieve their values +#fact { "videocard": +# interpreter => "/bin/sh", +# code => "lspci | grep VGA", +# os => "Linux" +#} +$testing = "value" +$operatingsystem = fact("operatingsystem") +$fact = addfact( + name => "videocard", + interpreter => "/bin/sh", + code => "lspci | grep VGA", + os => "Linux" +) +$card = fact("videocard") diff --git a/examples/code/failers/badclassnoparam b/examples/code/failers/badclassnoparam new file mode 100644 index 000000000..a0397aacc --- /dev/null +++ b/examples/code/failers/badclassnoparam @@ -0,0 +1,10 @@ +class comp() { + file { "/etc/passwd": + mode => 644 + } +} + +# this argument is invalid, thus we should get a falure +comp { + fakearg => "yay" +} diff --git a/examples/code/failers/badclassparam b/examples/code/failers/badclassparam new file mode 100644 index 000000000..4c9ff6199 --- /dev/null +++ b/examples/code/failers/badclassparam @@ -0,0 +1,10 @@ +class comp(arg1) { + file { "/etc/passwd": + mode => 644 + } +} + +# i've specified an arg but it's an invalid one +comp { + fakearg => "yay" +} diff --git a/examples/code/failers/badcompnoparam b/examples/code/failers/badcompnoparam new file mode 100644 index 000000000..fd25c9445 --- /dev/null +++ b/examples/code/failers/badcompnoparam @@ -0,0 +1,9 @@ +define comp() { + file { "/etc/passwd": + mode => 644 + } +} + +comp { + fakearg => "yay" +} diff --git a/examples/code/failers/badcompparam b/examples/code/failers/badcompparam new file mode 100644 index 000000000..283d54014 --- /dev/null +++ b/examples/code/failers/badcompparam @@ -0,0 +1,9 @@ +define comp(arg1) { + file { "/etc/passwd": + mode => 644 + } +} + +comp { + fakearg => "yay" +} diff --git a/examples/code/failers/badtypeparam b/examples/code/failers/badtypeparam new file mode 100644 index 000000000..4634f2052 --- /dev/null +++ b/examples/code/failers/badtypeparam @@ -0,0 +1,3 @@ +file { "/etc/passwd": + fakeparam => 644 +} diff --git a/examples/code/file.bl b/examples/code/file.bl new file mode 100644 index 000000000..41d80b53d --- /dev/null +++ b/examples/code/file.bl @@ -0,0 +1,11 @@ +# $Id$ + +file { + "/tmp/atest": create => true, mode => 755; + "/tmp/btest": create => true, mode => 755 +} + +file { + "/tmp/ctest": create => true; + "/tmp/dtest": create => true; +} diff --git a/examples/code/filedefaults b/examples/code/filedefaults new file mode 100644 index 000000000..cb005c093 --- /dev/null +++ b/examples/code/filedefaults @@ -0,0 +1,10 @@ +# $Id$ + +File { + mode => 755, + recurse => true +} + +file { "/tmp/filedefaultstest": + create => true +} diff --git a/examples/code/fileparsing b/examples/code/fileparsing new file mode 100644 index 000000000..f9766b9f6 --- /dev/null +++ b/examples/code/fileparsing @@ -0,0 +1,116 @@ +# $Id$ + +# this will eventually parse different config files + +# this creates the 'passwd' type, but it does not create any instances +filetype { "passwd": + linesplit => "\n", + escapednewlines => false +} + + +# this creates the 'PasswdUser' type, but again, no instances +filerecord { "user": + filetype => passwd, + fields => [name, password, uid, gid, gcos, home, shell], + namevar => name, + splitchar => ":" + +} + +filetype { ini: + linesplit => "\n\n" +} + +# ini files are different because we don't really care about validating fields +# or at least, we can't do it for most files... +filerecord { "initrecord": + filetype => ini, + fields => [name, password, uid, gid, gcos, home, shell], + namevar => name, + splitchar => ":" + +} + +# this won't work for multiple record types, will it? +# or at least, it requires that we specify multiple times +# ah, and it doesn't specify which of the available record types +# it works for... +passwd { user: + complete => true, # manage the whole file + path => "/etc/passwd" +} + +user { yaytest: + password => x, + uid => 10000, + gid => 10000, + home => "/home/yaytest", + gcos => "The Yaytest", + shell => "/bin/sh" +} + # there seems to be an intrinsic problem here -- i've got subtypes that only + # make sense when an instance of the super type already exists, and i need + # to associate the instances of the subtype with the instances of the supertype + # even if i created the parsers manually, I'd have the same problem + +# this is the crux of it -- i want to be able to say 'user' here without having +# to specify the file, which leaves two options: +# 1) associate the record type with a filetype instance (BAD) +# 2) once the filetype and record type are created, have another command +# that specifically creates a filetype instance and gives names for instances +# of its record types + +define syslog { + + # create a new type, with all defaults + filetype { "syslog": + escapednewlines => true + } + + filerecord { "log": + filetype => syslog, + regex => "^([^#\s]+)\s+(\S+)$", + joinchar => "\t", + fields => [logs, dest] + } + + # these two should just be supported within the filetypes + filerecord { "comment": + filetype => syslog, + regex => "^(#.*)$", + joinchar => "s", + fields => [comment] + } + + filerecord { "blank": + filetype => syslog, + regex => "^(\s*)$", + joinchar => "s", + fields => blank + } +} + +define cron { + filetype { "usercrontab": + } + + # this won't actually work, of course + filerecord { "cronjob": + filetype => crontab, + regex => "^([^#\s]+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$", + joinchar => " ", + fields => [minute, hour, day, month, weekday, command], + defaults => ["*", "*", "*", "*", "*", nil], + optional => [minute, hour, day, month, weekday] + } + + crontab { "luke": + } +} + +# XXX this doesn't work in the slightest +define crontab(name,path) { + usercrontab { "${path}/${name}": + } +} diff --git a/examples/code/filerecursion b/examples/code/filerecursion new file mode 100644 index 000000000..b7d8278c2 --- /dev/null +++ b/examples/code/filerecursion @@ -0,0 +1,15 @@ +# $Id$ + +file { "/tmp/dirtest/b/a": + mode => 755, +} + +file { "/tmp/dirtest": + mode => 755, + recurse => true, +} + +file { "/tmp/dirtest/b/b": + mode => 644, +} + diff --git a/examples/code/functions b/examples/code/functions new file mode 100644 index 000000000..8e95c3a72 --- /dev/null +++ b/examples/code/functions @@ -0,0 +1,3 @@ +# $Id$ + +$yaytest = fact("operatingsystem") diff --git a/examples/code/groups b/examples/code/groups new file mode 100644 index 000000000..35505a2eb --- /dev/null +++ b/examples/code/groups @@ -0,0 +1,7 @@ +# $Id$ + +# there need to be two forms of adding to groups: +# add the current host to a group, and add a list of hosts to a +# group by name + +$group = "crap" diff --git a/examples/code/head b/examples/code/head new file mode 100644 index 000000000..0b7a6c288 --- /dev/null +++ b/examples/code/head @@ -0,0 +1,30 @@ +# $Id$ + +# this file is responsible for importing all of the files we want to actually test + +# these are all of the simple tests +import "simpletests" +import "assignments" +import "selectors" +import "iftest" +import "importing" +import "execs" +import "filedefaults" + +# facts are now imported into the top of the namespace +#import "facts" + +# obsoleted +#import "functions" + +# files we no longer need to import directly, or at all in some cases +#import "one" +#import "classing" +#import "components" +#import "file.bl" +#import "fileparsing.disabled" +#import "groups" + +# this imports the more complex files +import "allatonce" # imports classing and components +import "nodes" # imports classing and components diff --git a/examples/code/iftest b/examples/code/iftest new file mode 100644 index 000000000..bc9d9cff1 --- /dev/null +++ b/examples/code/iftest @@ -0,0 +1,21 @@ +# $Id$ +$variable = "value" + +$cooltest = $variable ? { + other => yayness, + value => goodtest +} + +if $variable == "other" { + $cooltest = "yayness" + $platform = $operatingsystem +} elsif $variable == "value" { + $cooltest = "goodtest" + $platform = $operatingsystem +} else { + $goodness = $operatingsystemrelease +} + +if "value" == "value" { + $booyah = "value" +} diff --git a/examples/code/importing b/examples/code/importing new file mode 100644 index 000000000..f02604109 --- /dev/null +++ b/examples/code/importing @@ -0,0 +1,8 @@ +# $Id$ + +#import "groups" +# testing import loops +import "importing" + +$name = "value" +$system = $operatingsystem diff --git a/examples/code/nodes b/examples/code/nodes new file mode 100644 index 000000000..42488e689 --- /dev/null +++ b/examples/code/nodes @@ -0,0 +1,20 @@ +# $Id$ + +# define nodes + +#service.setpath("../examples/root/etc/init.d") + +Service { + path => "../examples/root/etc/init.d" +} + +import "classing" + +sleepserver { + path => $operatingsystem ? { + sunos => "../examples/root/etc/configfile", + hpux => "../examples/other/etc/configfile", + default => "../examples/root/etc/configfile" + }, + schedule => true +} diff --git a/examples/code/one b/examples/code/one new file mode 100644 index 000000000..452d32f3e --- /dev/null +++ b/examples/code/one @@ -0,0 +1,8 @@ +# $Id$ + +# this service doesn't actually exist, so we noop it +# and this way, we can test noop :) +service { "funtest": + running => "0", + noop => true +} diff --git a/examples/code/relationships b/examples/code/relationships new file mode 100644 index 000000000..f2319d5e4 --- /dev/null +++ b/examples/code/relationships @@ -0,0 +1,34 @@ +# $Id$ + +#service.setpath("../examples/root/etc/init.d") +#puppet.statefile("/tmp/puppetstate") +$path = "../examples/root/etc/configfile" + path => "../examples/root/etc/init.d" + + +define files { + file { "/tmp/yaytest": + create => true, + mode => 755 + } + file { "/tmp/exists": + checksum => md5 + } +} + +define sleeper { + file { $path: + mode => 755 + } + service { sleeper: + path => "../examples/root/etc/init.d", + running => 1 + } +} + +files { } + +sleeper { + require => files["yay"], + schedule => true +} diff --git a/examples/code/selectors b/examples/code/selectors new file mode 100644 index 000000000..b1f1c63ec --- /dev/null +++ b/examples/code/selectors @@ -0,0 +1,28 @@ +# $Id$ +# + +$platform = sunos + +$funtest = $platform ? { + sunos => yayness, + aix => goodness, + default => badness +} + +# this is a comment + +$filename = "/tmp/yayness" + +$sleeper = file { $filename: + mode => $platform ? { + sunos => 644, + default => 755 + }, + create => $platform ? sunos => true +} + +# i guess it has to be solved this way... + +$platform ? sunos => file { $filename: + mode => 644 +} diff --git a/examples/code/simpletests b/examples/code/simpletests new file mode 100644 index 000000000..6a2d5c5ff --- /dev/null +++ b/examples/code/simpletests @@ -0,0 +1,11 @@ +# $Id$ + +file { + "/tmp/atest": create => true; + "/tmp/btest": create => true +} + +file { + "/tmp/ctest": create => true; + "/tmp/dtest": create => true; +} diff --git a/examples/code/snippets/argumentdefaults b/examples/code/snippets/argumentdefaults new file mode 100644 index 000000000..b4081e9b0 --- /dev/null +++ b/examples/code/snippets/argumentdefaults @@ -0,0 +1,14 @@ +# $Id$ + +define testargs(file, mode = 755) { + file { $file: create => true, mode => $mode } +} + +testargs { "testingname": + file => "/tmp/argumenttest1" +} + +testargs { "testingother": + file => "/tmp/argumenttest2", + mode => 644 +} diff --git a/examples/code/snippets/classpathtest b/examples/code/snippets/classpathtest new file mode 100644 index 000000000..609b3bef5 --- /dev/null +++ b/examples/code/snippets/classpathtest @@ -0,0 +1,13 @@ +# $Id$ + +define component { + file { "/tmp/classtest": create => true, mode => 755 } + #testing {} +} + +class testing { + component { "componentname": } +} + +#component {} +testing { "testingname": } diff --git a/examples/code/snippets/dirchmod b/examples/code/snippets/dirchmod new file mode 100644 index 000000000..0a8268fa8 --- /dev/null +++ b/examples/code/snippets/dirchmod @@ -0,0 +1,19 @@ +# $Id$ + +file { + "/tmp/dirchmodtesta": create => directory; + "/tmp/dirchmodtesta/testing": create => true +} + +file { "/tmp/dirchmodtesta": + mode => 644, recurse => true +} + +file { + "/tmp/dirchmodtestb": create => directory; + "/tmp/dirchmodtestb/testing": create => true +} + +file { "/tmp/dirchmodtestb": + mode => 600, recurse => true +} diff --git a/examples/code/snippets/filecreate b/examples/code/snippets/filecreate new file mode 100644 index 000000000..7a4cdbb1e --- /dev/null +++ b/examples/code/snippets/filecreate @@ -0,0 +1,11 @@ +# $Id$ + +file { + "/tmp/createatest": create => true, mode => 755; + "/tmp/createbtest": create => true, mode => 755 +} + +file { + "/tmp/createctest": create => true; + "/tmp/createdtest": create => true; +} diff --git a/examples/code/snippets/simpledefaults b/examples/code/snippets/simpledefaults new file mode 100644 index 000000000..a8f6190b1 --- /dev/null +++ b/examples/code/snippets/simpledefaults @@ -0,0 +1,5 @@ +# $Id$ + +File { mode => 755 } + +file { "/tmp/defaulttest": create => true } diff --git a/examples/code/snippets/simpleselector b/examples/code/snippets/simpleselector new file mode 100644 index 000000000..52a06c773 --- /dev/null +++ b/examples/code/snippets/simpleselector @@ -0,0 +1,38 @@ +# $Id$ + +$var = "value" + +file { "/tmp/snippetselectatest": + create => true, + mode => $var ? { + nottrue => 641, + value => 755 + } +} + +file { "/tmp/snippetselectbtest": + create => true, + mode => $var ? { + nottrue => 644, + default => 755 + } +} + +$othervar = "complex value" + +file { "/tmp/snippetselectctest": + create => true, + mode => $othervar ? { + "complex value" => 755, + default => 644 + } +} +$anothervar = Yayness + +file { "/tmp/snippetselectdtest": + create => true, + mode => $anothervar ? { + Yayness => 755, + default => 644 + } +} diff --git a/examples/code/svncommit b/examples/code/svncommit new file mode 100644 index 000000000..350cd8580 --- /dev/null +++ b/examples/code/svncommit @@ -0,0 +1,13 @@ +$path = "/usr/bin:/bin" + +file { "/tmp/svntests": + recurse => true, + checksum => md5 +} + +exec { "echo 'files have been updated'": + cwd => "/tmp/svntests", + refreshonly => true, + require => file["/tmp/svntests"], + path => $path +} diff --git a/examples/root/bin/sleeper b/examples/root/bin/sleeper new file mode 100755 index 000000000..ca38c3e53 --- /dev/null +++ b/examples/root/bin/sleeper @@ -0,0 +1,69 @@ +#!/usr/bin/ruby -w + +### +# sleep indefinitely as a debug + +require 'getoptlong' + +#----------------------------------------------------------------- +def daemonize + outfile = "/tmp/sleeperout" + if pid = fork() + Process.detach(pid) + sleep 1 + # verify that we didn't have any problems starting the daemon + if FileTest.exists?(outfile) + $stderr.puts "Sleeper failed: %s" % File.read(outfile) + File.unlink(outfile) + exit(14) + else + exit(0) + end + end + Process.setsid + Dir.chdir("/") + begin + $stdin.reopen "/dev/null" + $stdout.reopen "/dev/null", "a" + $stderr.reopen $stdin + rescue => detail + File.open(outfile, "w") { |f| + f.puts detail + } + exit(12) + end +end +#----------------------------------------------------------------- + +debug = false + +result = GetoptLong.new( + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ] +) + +result.each { |opt,arg| + case opt + when "--help" + puts "There is no help yet" + exit + when "--debug" + debug = true + else + raise "Invalid option '#{opt}'" + end +} + +trap(:INT) { + exit +} + +unless debug + daemonize() +end + +var = true + +while var do + sleep 600 +end diff --git a/examples/root/etc/configfile b/examples/root/etc/configfile new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/examples/root/etc/configfile diff --git a/examples/root/etc/debian-passwd b/examples/root/etc/debian-passwd new file mode 100644 index 000000000..59cdf4acf --- /dev/null +++ b/examples/root/etc/debian-passwd @@ -0,0 +1,29 @@ +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/bin/sh +bin:x:2:2:bin:/bin:/bin/sh +sys:x:3:3:sys:/dev:/bin/sh +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/bin/sh +man:x:6:12:man:/var/cache/man:/bin/sh +lp:x:7:7:lp:/var/spool/lpd:/bin/sh +mail:x:8:8:mail:/var/mail:/bin/sh +news:x:9:9:news:/var/spool/news:/bin/sh +uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh +proxy:x:13:13:proxy:/bin:/bin/sh +postgres:x:31:32:postgres:/var/lib/postgres:/bin/sh +www-data:x:33:33:www-data:/var/www:/bin/sh +backup:x:34:34:backup:/var/backups:/bin/sh +operator:x:37:37:Operator:/var:/bin/sh +list:x:38:38:Mailing List Manager:/var/list:/bin/sh +irc:x:39:39:ircd:/var/run/ircd:/bin/sh +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh +nobody:x:65534:65534:nobody:/nonexistent:/bin/sh +sshd:x:102:65534::/var/run/sshd:/bin/false +gdm:x:101:101:Gnome Display Manager:/var/lib/gdm:/bin/false +telnetd:x:103:103::/usr/lib/telnetd:/bin/false +nagios:x:1000:1001::/home/nagios: +messagebus:x:104:107::/var/run/dbus:/bin/false +saned:x:109:109::/home/saned:/bin/false +ganglia:x:105:110:Ganglia Monitor:/var/lib/ganglia:/bin/false +zope:x:106:111::/var/lib/zope2.7/var:/bin/false +fbgetty:x:112:112::/home/fbgetty:/bin/false diff --git a/examples/root/etc/debian-syslog.conf b/examples/root/etc/debian-syslog.conf new file mode 100644 index 000000000..8f2925960 --- /dev/null +++ b/examples/root/etc/debian-syslog.conf @@ -0,0 +1,71 @@ +# /etc/syslog.conf Configuration file for syslogd. +# +# For more information see syslog.conf(5) +# manpage. + +# +# First some standard logfiles. Log by facility. +# + +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none -/var/log/syslog +#cron.* /var/log/cron.log +daemon.* -/var/log/daemon.log +kern.* -/var/log/kern.log +lpr.* -/var/log/lpr.log +mail.* -/var/log/mail.log +user.* -/var/log/user.log +uucp.* /var/log/uucp.log + +# +# Logging for the mail system. Split it up so that +# it is easy to write scripts to parse these files. +# +mail.info -/var/log/mail.info +mail.warn -/var/log/mail.warn +mail.err /var/log/mail.err + +# Logging for INN news system +# +news.crit /var/log/news/news.crit +news.err /var/log/news/news.err +news.notice -/var/log/news/news.notice + +# +# Some `catch-all' logfiles. +# +*.=debug;\ + auth,authpriv.none;\ + news.none;mail.none -/var/log/debug +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, +# you must invoke `xconsole' with the `-file' option: +# +# $ xconsole -file /dev/xconsole [...] +# +# NOTE: adjust the list below, or you'll go crazy if you have a reasonably +# busy site.. +# +daemon.*;mail.*;\ + news.crit;news.err;news.notice;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole + diff --git a/examples/root/etc/init.d/sleeper b/examples/root/etc/init.d/sleeper new file mode 100755 index 000000000..e7f5d3e30 --- /dev/null +++ b/examples/root/etc/init.d/sleeper @@ -0,0 +1,65 @@ +#!/bin/bash + +# $Id$ + +script=$0 +path=`echo $script | sed 's/etc..*/bin/'` + +PATH=$PATH:$path + +ps=`facter ps | cut -d ' ' -f3-` + +if [ -z "$ps" ]; then + ps="ps -ef" +fi + +function start +{ + sleeper +} + +function stop +{ + #if [ -n `which pgrep` ]; then + # pid=`pgrep sleeper` + #else + pid=`$ps | grep -v grep | grep sleeper | grep ruby | awk '{print $2}'` + #fi + if [ -n "$pid" ]; then + kill $pid + fi +} + +function status +{ + #if [ -n `which pgrep` ]; then + # cmd="pgrep sleeper" + #else + #cmd="$ps | grep -v grep | grep sleeper | grep ruby | awk '{print $2}'" + #fi + #$cmd + $ps | grep -v grep | grep sleeper | grep ruby +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop; start + ;; + status) + output=`status` + #status + exit $? + ;; + *) + echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/examples/root/etc/otherfile b/examples/root/etc/otherfile new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/examples/root/etc/otherfile 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 diff --git a/test/certmgr/tc_certmgr.rb b/test/certmgr/tc_certmgr.rb new file mode 100755 index 000000000..e00acd067 --- /dev/null +++ b/test/certmgr/tc_certmgr.rb @@ -0,0 +1,275 @@ +#!/usr/bin/ruby + +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/sslcertificates.rb' +require 'test/unit' +require 'puppettest' + +# so, what kind of things do we want to test? + +# we don't need to test function, since we're confident in the +# library tests. We do, however, need to test how things are actually +# working in the language. + +# so really, we want to do things like test that our ast is correct +# and test whether we've got things in the right scopes + +class TestCertMgr < Test::Unit::TestCase + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + #@dir = File.join(Puppet[:certdir], "testing") + @dir = "/tmp/puppetcertestingdir" + Puppet[:ssldir] = @dir + system("mkdir -p %s" % @dir) + @@tmpfiles = [@dir] + end + + def mkPassFile() + keyfile = File.join(@dir, "tmpkeyfile") + @@tmpfiles << keyfile + unless FileTest.exists?(@dir) + system("mkdir -p %s" % @dir) + end + File.open(keyfile, "w", 0600) { |f| + f.print "as;dklj23rlkjzdflij23wr" + } + + return keyfile + end + + def mkCA + ca = nil + Puppet[:ssldir] = @dir + assert_nothing_raised { + ca = Puppet::SSLCertificates::CA.new() + } + + return ca + end + + def teardown + @@tmpfiles.each { |f| + if FileTest.exists?(f) + system("rm -rf %s" % f) + end + } + end + + def testCreateSelfSignedCertificate + cert = nil + name = "testing" + newcert = proc { + Puppet::SSLCertificates::Certificate.new( + :name => name, + :selfsign => true + ) + } + assert_nothing_raised { + cert = newcert.call() + } + assert_nothing_raised { + cert.mkselfsigned + } + + assert_raise(Puppet::Error) { + cert.mkselfsigned + } + + assert_nothing_raised { + cert.write + } + + assert(FileTest.exists?(cert.certfile)) + + assert_nothing_raised { + cert.delete + } + + assert_nothing_raised { + cert = newcert.call() + } + assert_nothing_raised { + cert.mkselfsigned + } + + assert_nothing_raised { + cert.delete + } + + end + + def disabled_testCreateEncryptedSelfSignedCertificate + cert = nil + name = "testing" + keyfile = mkPassFile + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new( + :name => name, + :selfsign => true, + :capass => keyfile + ) + } + assert_nothing_raised { + cert.mkselfsigned + } + assert_nothing_raised { + cert.mkhash + } + + assert_raise(Puppet::Error) { + cert.mkselfsigned + } + + assert(FileTest.exists?(cert.certfile)) + assert(FileTest.exists?(cert.hash)) + + assert_nothing_raised { + cert.delete + } + + assert_nothing_raised { + cert.mkselfsigned + } + + assert_nothing_raised { + cert.delete + } + + end + + def testCreateCA + ca = nil + assert_nothing_raised { + ca = Puppet::SSLCertificates::CA.new() + } + + # make the CA again and verify it doesn't fail because everything + # still exists + assert_nothing_raised { + ca = Puppet::SSLCertificates::CA.new() + } + + end + + def testSignCert + ca = mkCA() + + cert = nil + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new( + :name => "signedcertest", + :state => "TN", + :city => "Nashville", + :country => "US", + :email => "luke@madstop.com", + :org => "Reductive", + :ou => "Development", + :encrypt => mkPassFile() + ) + + } + + assert_nothing_raised { + cert.mkcsr + } + + signedcert = nil + cacert = nil + + assert_nothing_raised { + signedcert, cacert = ca.sign(cert.csr) + } + + assert_instance_of(OpenSSL::X509::Certificate, signedcert) + assert_instance_of(OpenSSL::X509::Certificate, cacert) + + assert_nothing_raised { + cert.cert = signedcert + cert.cacert = cacert + cert.write + } + #system("find %s" % Puppet[:ssldir]) + + output = nil + assert_nothing_raised { + output = %x{openssl verify -CApath #{Puppet[:certdir]} -purpose sslserver #{cert.certfile}} + } + + assert_equal($?,0) + assert_equal("\n", output) + end + + def mkcert(hostname) + cert = nil + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new(:name => hostname) + cert.mkcsr + } + + return cert + end + + + def test_interactiveca + ca = nil + Puppet[:ssldir] = "/tmp/puppetinteractivecatest" + @@tmpfiles.push Puppet[:ssldir] + + assert_nothing_raised { + ca = Puppet::SSLCertificates::CA.new + } + + # basic initialization + hostname = "test.hostname.com" + cert = mkcert(hostname) + + # create the csr + csr = nil + assert_nothing_raised { + csr = cert.mkcsr + } + + assert_nothing_raised { + ca.storeclientcsr(csr) + } + + # store it + pulledcsr = nil + assert_nothing_raised { + pulledcsr = ca.getclientcsr(hostname) + } + + assert_equal(csr.to_pem, pulledcsr.to_pem) + + signedcert = nil + assert_nothing_raised { + signedcert, cacert = ca.sign(csr) + } + + assert_instance_of(OpenSSL::X509::Certificate, signedcert) + newsignedcert = nil + assert_nothing_raised { + newsignedcert, cacert = ca.getclientcert(hostname) + } + + assert(newsignedcert) + + assert_equal(signedcert.to_pem, newsignedcert.to_pem) + end + + def test_cafailures + ca = mkCA() + cert = cacert = nil + assert_nothing_raised { + cert, cacert = ca.getclientcert("nohost") + } + assert_nil(cert) + end +end diff --git a/test/executables/tc_puppetbin.rb b/test/executables/tc_puppetbin.rb new file mode 100755 index 000000000..b42f5aa6a --- /dev/null +++ b/test/executables/tc_puppetbin.rb @@ -0,0 +1,39 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +# $ID: $ + +require 'puppet' +require 'puppet/server' +require 'puppet/sslcertificates' +require 'test/unit' +require 'puppettest.rb' + +# add the bin directory to our search path +ENV["PATH"] += ":" + File.join($puppetbase, "bin") + +# and then the library directories +libdirs = $:.find_all { |dir| + dir =~ /puppet/ or dir =~ /\.\./ +} +ENV["RUBYLIB"] = libdirs.join(":") + +class TestPuppetBin < Test::Unit::TestCase + def setup + end + + def teardown + end + + def test_version + output = nil + assert_nothing_raised { + output = %x{puppet --version}.chomp + } + assert(output == Puppet.version) + end +end diff --git a/test/executables/tc_puppetca.rb b/test/executables/tc_puppetca.rb new file mode 100755 index 000000000..e657de483 --- /dev/null +++ b/test/executables/tc_puppetca.rb @@ -0,0 +1,90 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/server' +require 'puppet/sslcertificates' +require 'test/unit' +require 'puppettest.rb' + +# $Id$ + +# ok, we have to add the bin directory to our search path +ENV["PATH"] += ":" + File.join($puppetbase, "bin") + +# and then the library directories +libdirs = $:.find_all { |dir| + dir =~ /puppet/ or dir =~ /\.\./ +} +ENV["RUBYLIB"] = libdirs.join(":") + +class TestPuppetCA < Test::Unit::TestCase + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + @@tmpfiles = [] + end + + def teardown + @@tmpfiles.flatten.each { |file| + if File.exists? file + system("rm -rf %s" % file) + end + } + end + + def mkcert(hostname) + cert = nil + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new( + :name => hostname + ) + cert.mkcsr + } + + return cert + end + + def test_signing + ca = nil + Puppet[:ssldir] = "/tmp/puppetcatest" + @@tmpfiles << Puppet[:ssldir] + Puppet[:autosign] = false + assert_nothing_raised { + ca = Puppet::CA.new() + } + #Puppet.warning "SSLDir is %s" % Puppet[:ssldir] + #system("find %s" % Puppet[:ssldir]) + + cert = mkcert("host.test.com") + resp = nil + assert_nothing_raised { + resp = ca.getcert(cert.csr.to_pem) + } + assert_equal(["",""], resp) + #Puppet.warning "SSLDir is %s" % Puppet[:ssldir] + #system("find %s" % Puppet[:ssldir]) + + output = nil + assert_nothing_raised { + output = %x{puppetca --list --ssldir=#{Puppet[:ssldir]} 2>&1}.chomp.split("\n") + } + #Puppet.warning "SSLDir is %s" % Puppet[:ssldir] + #system("find %s" % Puppet[:ssldir]) + assert_equal($?,0) + assert_equal(%w{host.test.com}, output) + assert_nothing_raised { + output = %x{puppetca --sign -a --ssldir=#{Puppet[:ssldir]}}.chomp.split("\n") + } + assert_equal($?,0) + assert_equal([], output) + assert_nothing_raised { + output = %x{puppetca --list --ssldir=#{Puppet[:ssldir]}}.chomp.split("\n") + } + assert_equal($?,0) + assert_equal([], output) + end +end diff --git a/test/executables/tc_puppetd.rb b/test/executables/tc_puppetd.rb new file mode 100755 index 000000000..38467332d --- /dev/null +++ b/test/executables/tc_puppetd.rb @@ -0,0 +1,88 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/server' +require 'test/unit' +require 'puppettest.rb' +require 'socket' +require 'facter' + +# $Id$ + +# ok, we have to add the bin directory to our search path +ENV["PATH"] += ":" + File.join($puppetbase, "bin") + +# and then the library directories +libdirs = $:.find_all { |dir| + dir =~ /puppet/ or dir =~ /\.\./ +} +ENV["RUBYLIB"] = libdirs.join(":") + +class TestPuppetDExe < Test::Unit::TestCase + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + @@tmpfiles = [] + @@tmppids = [] + end + + def teardown + @@tmpfiles.flatten.each { |file| + if File.exists? file + system("rm -rf %s" % file) + end + } + + @@tmppids.each { |pid| + %x{kill -INT #{pid} 2>/dev/null} + } + end + + def startmaster + file = File.join($puppetbase, "examples", "code", "head") + output = nil + assert_nothing_raised { + output = %x{puppetmasterd --manifest #{file}}.chomp + } + assert($? == 0) + @@tmppids << $?.pid + assert_equal("", output) + end + + def stopmaster + ps = Facter["ps"].value || "ps -ef" + + pid = nil + %x{#{ps}}.chomp.split(/\n/).each { |line| + if line =~ /puppetmasterd/ + ary = line.split(" ") + pid = ary[1].to_i + end + } + assert(pid) + + assert_nothing_raised { + Process.kill("-INT", pid) + } + end + + def test_normalstart + startmaster + output = nil + assert_nothing_raised { + output = %x{puppetd --server localhost}.chomp + } + assert($? == 0) + assert_equal("", output) + + assert_nothing_raised { + socket = TCPSocket.new("127.0.0.1", Puppet[:masterport]) + socket.close + } + stopmaster + end +end diff --git a/test/executables/tc_puppetmasterd.rb b/test/executables/tc_puppetmasterd.rb new file mode 100755 index 000000000..5ef802151 --- /dev/null +++ b/test/executables/tc_puppetmasterd.rb @@ -0,0 +1,171 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/server' +require 'puppet/daemon' +require 'test/unit' +require 'puppettest.rb' +require 'socket' +require 'facter' + +# $Id$ + +# ok, we have to add the bin directory to our search path +ENV["PATH"] += ":" + File.join($puppetbase, "bin") + +# and then the library directories +libdirs = $:.find_all { |dir| + dir =~ /puppet/ or dir =~ /\.\./ +} +ENV["RUBYLIB"] = libdirs.join(":") + +class TestPuppetMasterD < Test::Unit::TestCase + def getcerts + include Puppet::Daemon + if self.readcerts + return [@cert, @key, @cacert, @cacertfile] + else + raise "Couldn't read certs" + end + end + + def setup + if __FILE__ == $0 + Puppet[:loglevel] = :debug + end + @@tmpfiles = [] + end + + def startmasterd(args) + output = nil + cmd = "puppetmasterd %s" % args + #if Puppet[:debug] + # Puppet.debug "turning daemon debugging on" + # cmd += " --debug" + #end + assert_nothing_raised { + output = %x{puppetmasterd #{args}}.chomp + } + assert($? == 0) + assert_equal("", output) + end + + def stopmasterd(running = true) + ps = Facter["ps"].value || "ps -ef" + + pid = nil + %x{#{ps}}.chomp.split(/\n/).each { |line| + if line =~ /puppetmasterd --manifest/ + ary = line.split(" ") + pid = ary[1].to_i + end + } + + # we default to mandating that it's running, but teardown + # doesn't require that + if running or pid + assert(pid) + + assert_nothing_raised { + Process.kill("-INT", pid) + } + end + end + + def teardown + @@tmpfiles.flatten.each { |file| + if File.exists?(file) + system("rm -rf %s" % file) + end + } + + stopmasterd(false) + end + + def test_normalstart + file = File.join($puppetbase, "examples", "code", "head") + startmasterd("--manifest #{file}") + + assert_nothing_raised { + socket = TCPSocket.new("127.0.0.1", Puppet[:masterport]) + socket.close + } + + client = nil + assert_nothing_raised() { + client = XMLRPC::Client.new("localhost", "/RPC2", Puppet[:masterport], + nil, nil, nil, nil, true, 5) + } + retval = nil + + assert_nothing_raised() { + retval = client.call("status.status", "") + } + assert_equal(1, retval) + facts = {} + Facter.each { |p,v| + facts[p] = v + } + textfacts = CGI.escape(Marshal::dump(facts)) + assert_nothing_raised() { + #Puppet.notice "calling status" + #retval = client.call("status.status", "") + retval = client.call("puppetmaster.getconfig", textfacts) + } + + objects = nil + assert_nothing_raised { + Marshal::load(CGI.unescape(retval)) + } + #stopmasterd + end + + def disabled_test_sslconnection + #file = File.join($puppetbase, "examples", "code", "head") + #startmasterd("--manifest #{file}") + + #assert_nothing_raised { + # socket = TCPSocket.new("127.0.0.1", Puppet[:masterport]) + # socket.close + #} + + client = nil + cert, key, cacert, cacertfile = getcerts() + + assert_nothing_raised() { + client = Net::HTTP.new("localhost", Puppet[:masterport]) + client.cert = cert + client.key = key + client.ca_file = cacertfile + client.use_ssl = true + client.start_immediately = true + } + retval = nil + + assert_nothing_raised() { + retval = client.nothing + } + assert_equal(1, retval) + facts = {} + Facter.each { |p,v| + facts[p] = v + } + textfacts = CGI.escape(Marshal::dump(facts)) + assert_nothing_raised() { + #Puppet.notice "calling status" + #retval = client.call("status.status", "") + retval = client.call("puppetmaster.getconfig", textfacts) + } + + objects = nil + assert_nothing_raised { + Marshal::load(CGI.unescape(retval)) + } + #stopmasterd + end +end diff --git a/test/language/tc_scope.rb b/test/language/tc_scope.rb new file mode 100755 index 000000000..ece621629 --- /dev/null +++ b/test/language/tc_scope.rb @@ -0,0 +1,264 @@ +#!/usr/bin/ruby + +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/parser/interpreter' +require 'puppet/parser/parser' +require 'puppet/client' +require 'test/unit' +require 'puppettest' + +# so, what kind of things do we want to test? + +# we don't need to test function, since we're confident in the +# library tests. We do, however, need to test how things are actually +# working in the language. + +# so really, we want to do things like test that our ast is correct +# and test whether we've got things in the right scopes + +class TestScope < Test::Unit::TestCase + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + end + + def to_ary(hash) + hash.collect { |key,value| + [key,value] + } + end + + def test_variables + scope = nil + over = "over" + + scopes = [] + vars = [] + values = {} + ovalues = [] + + 10.times { |index| + # slap some recursion in there + scope = Puppet::Parser::Scope.new(scope) + scopes.push scope + + var = "var%s" % index + value = rand(1000) + ovalue = rand(1000) + + ovalues.push ovalue + + vars.push var + values[var] = value + + # set the variable in the current scope + assert_nothing_raised { + scope.setvar(var,value) + } + + # this should override previous values + assert_nothing_raised { + scope.setvar(over,ovalue) + } + + assert_equal(value,scope.lookupvar(var)) + + #puts "%s vars, %s scopes" % [vars.length,scopes.length] + i = 0 + vars.zip(scopes) { |v,s| + # this recurses all the way up the tree as necessary + val = nil + oval = nil + + # look up the values using the bottom scope + assert_nothing_raised { + val = scope.lookupvar(v) + oval = scope.lookupvar(over) + } + + # verify they're correct + assert_equal(values[v],val) + assert_equal(ovalue,oval) + + # verify that we get the most recent value + assert_equal(ovalue,scope.lookupvar(over)) + + # verify that they aren't available in upper scopes + if parent = s.parent + assert_raise(Puppet::ParseError) { + parent.lookupvar(v) + } + + # and verify that the parent sees its correct value + assert_equal(ovalues[i - 1],parent.lookupvar(over)) + end + i += 1 + } + } + end + + def test_declarative + # set to declarative + top = Puppet::Parser::Scope.new(nil,true) + sub = Puppet::Parser::Scope.new(top) + + assert_nothing_raised { + top.setvar("test","value") + } + assert_raise(Puppet::ParseError) { + top.setvar("test","other") + } + assert_nothing_raised { + sub.setvar("test","later") + } + assert_raise(Puppet::ParseError) { + top.setvar("test","yeehaw") + } + end + + def test_notdeclarative + # set to not declarative + top = Puppet::Parser::Scope.new(nil,false) + sub = Puppet::Parser::Scope.new(top) + + assert_nothing_raised { + top.setvar("test","value") + } + assert_nothing_raised { + top.setvar("test","other") + } + assert_nothing_raised { + sub.setvar("test","later") + } + assert_nothing_raised { + sub.setvar("test","yayness") + } + end + + def test_defaults + scope = nil + over = "over" + + scopes = [] + vars = [] + values = {} + ovalues = [] + + defs = Hash.new { |hash,key| + hash[key] = Hash.new(nil) + } + + prevdefs = Hash.new { |hash,key| + hash[key] = Hash.new(nil) + } + + params = %w{a list of parameters that could be used for defaults} + + types = %w{a set of types that could be used to set defaults} + + 10.times { |index| + scope = Puppet::Parser::Scope.new(scope) + scopes.push scope + + tmptypes = [] + + # randomly create defaults for a random set of types + tnum = rand(5) + tnum.times { |t| + # pick a type + #Puppet.debug "Type length is %s" % types.length + #s = rand(types.length) + #Puppet.debug "Type num is %s" % s + #type = types[s] + #Puppet.debug "Type is %s" % s + type = types[rand(types.length)] + if tmptypes.include?(type) + Puppet.debug "Duplicate type %s" % type + redo + else + tmptypes.push type + end + + Puppet.debug "type is %s" % type + + d = {} + + # randomly assign some parameters + num = rand(4) + num.times { |n| + param = params[rand(params.length)] + if d.include?(param) + Puppet.debug "Duplicate param %s" % param + redo + else + d[param] = rand(1000) + end + } + + # and then add a consistent type + d["always"] = rand(1000) + + d.each { |var,val| + defs[type][var] = val + } + + assert_nothing_raised { + scope.setdefaults(type,to_ary(d)) + } + fdefs = nil + assert_nothing_raised { + fdefs = scope.lookupdefaults(type) + } + + # now, make sure that reassignment fails if we're + # in declarative mode + assert_raise(Puppet::ParseError) { + scope.setdefaults(type,[%w{always funtest}]) + } + + # assert that we have collected the same values + assert_equal(defs[type],fdefs) + + # now assert that our parent still finds the same defaults + # it got last time + if parent = scope.parent + unless prevdefs[type].nil? + assert_equal(prevdefs[type],parent.lookupdefaults(type)) + end + end + d.each { |var,val| + prevdefs[type][var] = val + } + } + } + end + + def test_strinterp + scope = Puppet::Parser::Scope.new(nil) + + assert_nothing_raised { + scope.setvar("test","value") + } + val = nil + assert_nothing_raised { + val = scope.strinterp("string ${test}") + } + assert_equal("string value", val) + + assert_nothing_raised { + val = scope.strinterp("string ${test} ${test} ${test}") + } + assert_equal("string value value value", val) + + assert_nothing_raised { + val = scope.strinterp("string $test ${test} $test") + } + assert_equal("string value value value", val) + end +end diff --git a/test/language/tc_snippets.rb b/test/language/tc_snippets.rb new file mode 100755 index 000000000..da1172f63 --- /dev/null +++ b/test/language/tc_snippets.rb @@ -0,0 +1,317 @@ +#!/usr/bin/ruby -w + +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '..' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/parser/interpreter' +require 'puppet/parser/parser' +require 'puppet/client' +require 'puppet/server' +require 'test/unit' +require 'puppettest' + +# so, what kind of things do we want to test? + +# we don't need to test function, since we're confident in the +# library tests. We do, however, need to test how things are actually +# working in the language. + +# so really, we want to do things like test that our ast is correct +# and test whether we've got things in the right scopes + +class TestSnippets < Test::Unit::TestCase + $snippetbase = File.join($puppetbase, "examples", "code", "snippets") + + def file2ast(file) + parser = Puppet::Parser::Parser.new() + parser.file = file + ast = parser.parse + + return ast + end + + def snippet2ast(text) + parser = Puppet::Parser::Parser.new() + parser.string = text + ast = parser.parse + + return ast + end + + def client + args = { + :Listen => false + } + Puppet::Client.new(args) + end + + def ast2scope(ast) + interp = Puppet::Parser::Interpreter.new( + :ast => ast, + :client => client() + ) + scope = Puppet::Parser::Scope.new() + ast.evaluate(scope) + + return scope + end + + def scope2objs(scope) + objs = scope.to_trans + end + + def snippet2scope(snippet) + ast = snippet2ast(snippet) + scope = ast2scope(ast) + end + + def snippet2objs(snippet) + ast = snippet2ast(snippet) + scope = ast2scope(ast) + objs = scope2objs(scope) + end + + def states(type) + states = [] + + type.buildstatehash + type.validstates.each { |name,state| + states.push name + } + + #if states.length == 0 + # raise "%s has no states" % type + #end + + states + end + + def metaparams(type) + mparams = [] + Puppet::Type.eachmetaparam { |param| + mparams.push param + } + + mparams + end + + def params(type) + params = [] + type.parameters.each { |name,state| + params.push name + } + + params + end + + def randthing(thing,type) + list = self.send(thing,type) + list[rand(list.length)] + end + + def randeach(type) + [:states, :metaparams, :params].collect { |thing| + randthing(thing,type) + } + end + + @@snippets = { + true => [ + %{File { mode => 755 }} + ], + } + + def disabled_test_defaults + Puppet::Type.eachtype { |type| + next if type.name == :puppet or type.name == :component + + rands = randeach(type) + + name = type.name.to_s.capitalize + + [0..1, 0..2].each { |range| + params = rands[range] + paramstr = params.collect { |param| + "%s => fake" % param + }.join(", ") + + str = "%s { %s }" % [name, paramstr] + + scope = nil + assert_nothing_raised { + scope = snippet2scope(str) + } + + defaults = nil + assert_nothing_raised { + defaults = scope.lookupdefaults(name) + } + + p defaults + + params.each { |param| + puts "%s => '%s'" % [name,param] + assert(defaults.include?(param)) + } + } + } + end + + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + @@tmpfiles = [] + end + + def teardown + @@tmpfiles.flatten.each { |file| + if FileTest.exists?(file) + File.unlink(file) + end + } + Puppet::Type.allclear + end + + # this is here in case no tests get defined; otherwise we get a warning + def test_nothing + end + + def snippet_filecreate(trans) + %w{a b c d}.each { |letter| + file = "/tmp/create%stest" % letter + Puppet.info "testing %s" % file + assert(Puppet::Type::PFile[file]) + assert(FileTest.exists?(file)) + @@tmpfiles << file + } + %w{a b}.each { |letter| + file = "/tmp/create%stest" % letter + assert(File.stat(file).mode & 007777 == 0755) + } + + assert_nothing_raised { + trans.rollback + } + %w{a b c d}.each { |letter| + file = "/tmp/create%stest" % letter + assert(! FileTest.exists?(file)) + } + end + + def snippet_simpledefaults(trans) + file = "/tmp/defaulttest" + @@tmpfiles << file + assert(FileTest.exists?(file)) + assert(File.stat(file).mode & 007777 == 0755) + + assert_nothing_raised { + trans.rollback + } + assert(! FileTest.exists?(file)) + end + + def snippet_simpleselector(trans) + files = %w{a b c d}.collect { |letter| + "/tmp/snippetselect%stest" % letter + } + @@tmpfiles << files + + files.each { |file| + assert(FileTest.exists?(file)) + assert(File.stat(file).mode & 007777 == 0755) + @@tmpfiles << file + } + + assert_nothing_raised { + trans.rollback + } + files.each { |file| + assert(! FileTest.exists?(file)) + } + end + + def snippet_classpathtest(trans) + file = "/tmp/classtest" + @@tmpfiles << file + + assert(FileTest.exists?(file)) + + obj = nil + assert_nothing_raised { + obj = Puppet::Type::PFile[file] + } + + assert_nothing_raised { + assert_equal(%w{puppet[top] testing[testingname] component[componentname] /tmp/classtest}, obj.path) + #Puppet.err obj.path + } + + assert_nothing_raised { + trans.rollback + } + assert(! FileTest.exists?(file)) + end + + def snippet_argumentdefaults(trans) + file1 = "/tmp/argumenttest1" + file2 = "/tmp/argumenttest2" + #@@tmpfiles << file + + assert(FileTest.exists?(file1)) + assert(File.stat(file1).mode & 007777 == 0755) + + assert(FileTest.exists?(file2)) + assert(File.stat(file2).mode & 007777 == 0644) + end + + def disabled_snippet_dirchmod(trans) + dirs = %w{a b}.collect { |letter| + "/tmp/dirchmodtest%s" % letter + } + + @@tmpfiles << dirs + + dirs.each { |dir| + assert(FileTest.directory?(dir)) + } + + assert(File.stat("/tmp/dirchmodtesta").mode & 007777 == 0755) + assert(File.stat("/tmp/dirchmodtestb").mode & 007777 == 0700) + + assert_nothing_raised { + trans.rollback + } + end + + # XXX this is the answer + Dir.entries($snippetbase).sort.each { |file| + next if file =~ /^\./ + + mname = "snippet_" + file + if self.method_defined?(mname) + #eval("alias %s %s" % [testname, mname]) + testname = ("test_" + mname).intern + self.send(:define_method, testname) { + # first parse the file + server = Puppet::Master.new( + :File => File.join($snippetbase, file), + :Local => true + ) + client = Puppet::Client.new( + :Server => server, + :Cache => false + ) + + assert(client.local) + client.getconfig() + trans = client.config() + self.send(mname, trans) + } + mname = mname.intern + #eval("alias %s %s" % [testname, mname]) + end + } +end diff --git a/test/other/tc_metrics.rb b/test/other/tc_metrics.rb index 7ba03d554..9db8683e9 100644 --- a/test/other/tc_metrics.rb +++ b/test/other/tc_metrics.rb @@ -1,7 +1,7 @@ if __FILE__ == $0 $:.unshift '..' $:.unshift '../../lib' - $puppetbase = "../../../../language/trunk/" + $puppetbase = "../.." end require 'puppet/metric' diff --git a/test/parser/tc_lexer.rb b/test/parser/tc_lexer.rb new file mode 100644 index 000000000..b8fd3e23b --- /dev/null +++ b/test/parser/tc_lexer.rb @@ -0,0 +1,112 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/parser/lexer' +require 'test/unit' +require 'puppettest.rb' + +# $Id$ + +#%q{service("telnet") = \{ +# port => "23", +# protocol => "tcp", +# name => "telnet", +#\} +#} => [[:NAME, "service"], [:LPAREN, "("], [:DQUOTE, "\""], [:NAME, "telnet"], [:DQUOTE, "\""], [:RPAREN, ")"], [:EQUALS, "="], [:lbrace, "{"], [:NAME, "port"], [:FARROW, "=>"], [:DQUOTE, "\""], [:NAME, "23"], [:DQUOTE, "\""], [:COMMA, ","], [:NAME, "protocol"], [:FARROW, "=>"], [:DQUOTE, "\""], [:NAME, "tcp"], [:DQUOTE, "\""], [:COMMA, ","], [:NAME, "name"], [:FARROW, "=>"], [:DQUOTE, "\""], [:NAME, "telnet"], [:DQUOTE, "\""], [:COMMA, ","], [:RBRACE, "}"]] + +class TestLexer < Test::Unit::TestCase + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + @lexer = Puppet::Parser::Lexer.new() + end + + def test_simple_lex + strings = { +%q{\\} => [[:BACKSLASH,"\\"],[false,false]], +%q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"],[false,false]], +%q{returned scanner test +} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"],[false,false]] + } + strings.each { |str,ary| + @lexer.string = str + assert_equal( + ary, + @lexer.fullscan() + ) + } + end + + def test_quoted_strings + strings = { +%q{a simple "scanner" test +} => [[:NAME,"a"],[:NAME,"simple"],[:QTEXT,"scanner"],[:NAME,"test"],[false,false]], +%q{a harder "scanner test" +} => [[:NAME,"a"],[:NAME,"harder"],[:QTEXT,"scanner test"],[false,false]], +%q{a hardest "scanner \"test\"" +} => [[:NAME,"a"],[:NAME,"hardest"],[:QTEXT,'scanner "test"'],[false,false]], +%q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:QTEXT,'call'],[:RPAREN,")"],[false,false]] +} + strings.each { |str,array| + @lexer.string = str + assert_equal( + array, + @lexer.fullscan() + ) + } + end + + def test_errors + strings = %w{ + ^ + @ + } + strings.each { |str| + @lexer.string = str + assert_raise(RuntimeError) { + @lexer.fullscan() + } + } + end + + def test_more_error + assert_raise(TypeError) { + @lexer.fullscan() + } + end + + def test_files + textfiles() { |file| + @lexer.file = file + assert_nothing_raised() { + @lexer.fullscan() + } + Puppet::Type.allclear + } + end + + def test_strings + names = %w{this is a bunch of names} + types = %w{Many Different Words A Word} + words = %w{differently Cased words A a} + + names.each { |t| + @lexer.string = t + assert_equal( + [[:NAME,t],[false,false]], + @lexer.fullscan + ) + } + types.each { |t| + @lexer.string = t + assert_equal( + [[:TYPE,t],[false,false]], + @lexer.fullscan + ) + } + end +end diff --git a/test/parser/tc_parser.rb b/test/parser/tc_parser.rb new file mode 100644 index 000000000..b88bd8116 --- /dev/null +++ b/test/parser/tc_parser.rb @@ -0,0 +1,55 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/parser/parser' +require 'test/unit' +require 'puppettest' + +# $Id$ + +class TestParser < Test::Unit::TestCase + # hmmm + # this is complicated, because we store references to the created + # objects in a central store + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + Puppet[:parseonly] = true + #@lexer = Puppet::Parser::Lexer.new() + @parser = Puppet::Parser::Parser.new() + end + + def test_each_file + textfiles { |file| + Puppet.debug("parsing %s" % file) if __FILE__ == $0 + assert_nothing_raised() { + @parser.file = file + @parser.parse + } + + Puppet::Type.eachtype { |type| + type.each { |obj| + assert(obj.file) + assert(obj.name) + assert(obj.line) + } + } + Puppet::Type.allclear + } + end + + def test_failers + failers { |file| + Puppet.debug("parsing failer %s" % file) if __FILE__ == $0 + assert_raise(Puppet::ParseError) { + @parser.file = file + @parser.parse + } + Puppet::Type.allclear + } + end +end diff --git a/test/puppet/tc_defaults.rb b/test/puppet/tc_defaults.rb index 1bb7e9afa..e1d0124d2 100755 --- a/test/puppet/tc_defaults.rb +++ b/test/puppet/tc_defaults.rb @@ -1,7 +1,7 @@ if __FILE__ == $0 $:.unshift '..' $:.unshift '../../lib' - $puppetbase = "../../../../language/trunk/" + $puppetbase = ".." end require 'puppet' @@ -11,8 +11,7 @@ require 'test/unit' class TestPuppetDefaults < Test::Unit::TestCase @@dirs = %w{rrddir puppetconf puppetvar logdir statedir} - @@files = %w{logfile checksumfile - manifest masterlog} + @@files = %w{logfile checksumfile manifest masterlog} @@normals = %w{puppetport masterport server} @@booleans = %w{rrdgraph noop} @@ -40,17 +39,19 @@ class TestPuppetDefaults < Test::Unit::TestCase } end - def testContained - confdir = Regexp.new(Puppet[:puppetconf]) - vardir = Regexp.new(Puppet[:puppetvar]) - [@@dirs,@@files].flatten.each { |param| - value = Puppet[param] + if __FILE__ == $0 + def disabled_testContained + confdir = Regexp.new(Puppet[:puppetconf]) + vardir = Regexp.new(Puppet[:puppetvar]) + [@@dirs,@@files].flatten.each { |param| + value = Puppet[param] - unless value =~ confdir or value =~ vardir - assert_nothing_raised { raise "%s is in wrong dir: %s" % - [param,value] } - end - } + unless value =~ confdir or value =~ vardir + assert_nothing_raised { raise "%s is in wrong dir: %s" % + [param,value] } + end + } + end end def testArgumentTypes diff --git a/test/server/tc_ca.rb b/test/server/tc_ca.rb new file mode 100644 index 000000000..9fb579638 --- /dev/null +++ b/test/server/tc_ca.rb @@ -0,0 +1,225 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/ca' +require 'puppet/sslcertificates' +require 'openssl' +require 'test/unit' +require 'puppettest.rb' + +# $Id$ + +if ARGV.length > 0 and ARGV[0] == "short" + $short = true +else + $short = false +end + +class TestCA < Test::Unit::TestCase + def setup + if __FILE__ == $0 + Puppet[:loglevel] = :debug + #paths = Puppet::Type.type(:service).searchpath + #paths.push "%s/examples/root/etc/init.d" % $puppetbase + #Puppet::Type.type(:service).setpath(paths) + end + + @@tmpfiles = [] + end + + def teardown + Puppet::Type.allclear + print "\n\n" if Puppet[:debug] + + @@tmpfiles.each { |file| + if FileTest.exists?(file) + system("rm -rf %s" % file) + end + } + end + + def test_autocertgeneration + ssldir = "/tmp/testcertdir" + @@tmpfiles.push ssldir + assert_nothing_raised { + Puppet[:autosign] = true + Puppet[:ssldir] = ssldir + } + file = File.join($puppetbase, "examples", "code", "head") + ca = nil + + assert_nothing_raised { + ca = Puppet::CA.new() + } + + key = nil + csr = nil + cert = nil + hostname = "test.domain.com" + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new( + :name => "test.domain.com" + ) + } + assert_nothing_raised { + cert.mkcsr + } + + certtext = nil + cacerttext = nil + assert_nothing_raised { + certtext, cacerttext = ca.getcert(cert.csr.to_s) + } + + assert_instance_of(String, certtext) + assert_instance_of(String, cacerttext) + x509 = nil + assert_nothing_raised { + x509 = OpenSSL::X509::Certificate.new(certtext) + } + assert_nothing_raised { + OpenSSL::X509::Certificate.new(cacerttext) + } + + # and pull it again, just to make sure we're getting the same thing + newtext = nil + assert_nothing_raised { + newtext, cacerttext = ca.getcert(cert.csr.to_s) + } + + assert_equal(certtext,newtext) + end + + def test_storeAndSign + ssldir = "/tmp/testcertdir" + @@tmpfiles.push ssldir + assert_nothing_raised { + Puppet[:ssldir] = ssldir + Puppet[:autosign] = false + } + file = File.join($puppetbase, "examples", "code", "head") + ca = nil + caserv = nil + assert_nothing_raised { + caserv = Puppet::CA.new() + } + assert_nothing_raised { + ca = caserv.ca + } + + key = nil + csr = nil + cert = nil + hostname = "test.domain.com" + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new( + :name => "anothertest.domain.com" + ) + } + assert_nothing_raised { + cert.mkcsr + } + + certtext = nil + assert_nothing_raised { + certtext, cacerttext = caserv.getcert(cert.csr.to_s) + } + + assert_equal("", certtext) + + x509 = nil + assert_nothing_raised { + x509, cacert = ca.sign(cert.csr) + } + cert.cert = x509 + assert_nothing_raised { + cert.write + } + + assert(File.exists?(cert.certfile)) + + newtext = nil + assert_nothing_raised { + newtext, cacerttext = caserv.getcert(cert.csr.to_s) + } + + assert(newtext) + end + + def cycleautosign + ssldir = "/tmp/testcertdir" + autosign = "/tmp/autosign" + @@tmpfiles.push ssldir + @@tmpfiles.push autosign + assert_nothing_raised { + Puppet[:ssldir] = ssldir + } + file = File.join($puppetbase, "examples", "code", "head") + caserv = nil + + assert_nothing_raised { + caserv = Puppet::CA.new() + } + + key = nil + csr = nil + cert = nil + hostname = "test.domain.com" + assert_nothing_raised { + cert = Puppet::SSLCertificates::Certificate.new( + :name => "test.domain.com" + ) + } + assert_nothing_raised { + cert.mkcsr + } + + certtext = nil + assert_nothing_raised { + certtext = caserv.getcert(cert.csr.to_s) + } + + x509 = nil + assert_nothing_raised { + x509 = OpenSSL::X509::Certificate.new(certtext) + } + + assert(File.exists?(cert.certfile)) + + newtext = nil + assert_nothing_raised { + newtext = caserv.getcert(cert.csr.to_s) + } + + assert_equal(certtext,newtext) + end + + def test_autosign + autosign = "/tmp/autosign" + Puppet[:autosign] = "/tmp/autosign" + @@tmpfiles << autosign + File.open(autosign, "w") { |f| + f.puts "hostmatch.domain.com" + f.puts ".+.other.com" + f.puts "hostname.+" + } + + caserv = nil + file = File.join($puppetbase, "examples", "code", "head") + assert_nothing_raised { + caserv = Puppet::CA.new() + } + + assert(caserv.autosign?("hostmatch.domain.com")) + assert(caserv.autosign?("fakehost.other.com")) + assert(caserv.autosign?("hostname.rahtest.boo")) + assert(caserv.autosign?("hostname.com")) # a tricky one + assert(!caserv.autosign?("kirby.reductivelabs.com")) + assert(!caserv.autosign?("culain.domain.com")) + end +end diff --git a/test/server/tc_fileserver.rb b/test/server/tc_fileserver.rb new file mode 100755 index 000000000..ad9b6298c --- /dev/null +++ b/test/server/tc_fileserver.rb @@ -0,0 +1,386 @@ +if __FILE__ == $0 + if Dir.getwd =~ /test\/server$/ + Dir.chdir("..") + end + + $:.unshift '../lib' + $:.unshift '../../../library/trunk/lib/' + $:.unshift '../../../library/trunk/test/' + $puppetbase = ".." + +end + +require 'puppet' +require 'puppet/fileserver' +require 'test/unit' +require 'puppettest.rb' + +class TestFileServer < TestPuppet + def setup + if __FILE__ == $0 + Puppet[:loglevel] = :debug + end + + super + end + + def mkrandomdirs(dir, depth, width) + assert_nothing_raised { + Dir.mkdir(dir) + } + + end + + def test_namefailures + server = nil + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + assert_raise(Puppet::FileServerError) { + server.mount("/tmp", "invalid+name") + } + + assert_raise(Puppet::FileServerError) { + server.mount("/tmp", "invalid-name") + } + + assert_raise(Puppet::FileServerError) { + server.mount("/tmp", "invalid name") + } + + assert_raise(Puppet::FileServerError) { + server.mount("/tmp", "") + } + end + + def test_listroot + server = nil + testdir = "/tmp/remotefilecopying" + tmpfile = File.join(testdir, "tmpfile") + assert_nothing_raised { + Dir.mkdir(testdir) + File.open(tmpfile, "w") { |f| + 3.times { f.puts rand(100) } + } + @@tmpfiles << testdir + } + + file = nil + checks = Puppet::FileServer::CHECKPARAMS + + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + assert_nothing_raised { + server.mount(testdir, "test") + } + + list = nil + assert_nothing_raised { + list = server.list("/test/", true) + } + + assert(list =~ /tmpfile/) + + assert_nothing_raised { + list = server.list("/test", true) + } + assert(list =~ /tmpfile/) + + end + + def test_getfilelist + server = nil + testdir = "/tmp/remotefilecopying" + #subdir = "testingyo" + #subpath = File.join(testdir, "testingyo") + #dir = File.join(testdir, subdir) + tmpfile = File.join(testdir, "tmpfile") + assert_nothing_raised { + Dir.mkdir(testdir) + #Dir.mkdir(subpath) + File.open(tmpfile, "w") { |f| + 3.times { f.puts rand(100) } + } + @@tmpfiles << testdir + } + + file = nil + + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + assert_nothing_raised { + server.mount(testdir, "test") + } + + list = nil + sfile = "/test/tmpfile" + assert_nothing_raised { + list = server.list(sfile, true) + } + + assert_nothing_raised { + file = Puppet::Type::PFile[tmpfile] + } + + output = "/\tfile" + + assert_equal(output, list) + assert(list !~ /\t\t/) + + list.split("\n").each { |line| + assert(line !~ %r{remotefile}) + } + contents = File.read(tmpfile) + + ret = nil + assert_nothing_raised { + ret = server.retrieve(sfile) + } + + assert_equal(contents, ret) + end + + def test_seenewfiles + server = nil + testdir = "/tmp/remotefilecopying" + oldfile = File.join(testdir, "oldfile") + newfile = File.join(testdir, "newfile") + assert_nothing_raised { + Dir.mkdir(testdir) + File.open(oldfile, "w") { |f| + 3.times { f.puts rand(100) } + } + @@tmpfiles << testdir + } + + file = nil + checks = Puppet::FileServer::CHECKPARAMS + + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + assert_nothing_raised { + server.mount(testdir, "test") + } + + list = nil + sfile = "/test/" + assert_nothing_raised { + list = server.list(sfile, true) + } + + File.open(newfile, "w") { |f| + 3.times { f.puts rand(100) } + } + + newlist = nil + assert_nothing_raised { + newlist = server.list(sfile, true) + } + + assert(list != newlist) + + assert(newlist =~ /newfile/) + end + + def test_mountroot + server = nil + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + assert_nothing_raised { + server.mount("/", "root") + } + + testdir = "/tmp/remotefilecopying" + oldfile = File.join(testdir, "oldfile") + assert_nothing_raised { + Dir.mkdir(testdir) + File.open(oldfile, "w") { |f| + 3.times { f.puts rand(100) } + } + @@tmpfiles << testdir + } + + list = nil + assert_nothing_raised { + list = server.list("/root/" + testdir, true) + } + + assert(list =~ /oldfile/) + end + + def test_recursionlevels + server = nil + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + basedir = "/tmp/remotefilecopying" + testdir = "%s/with/some/sub/directories/for/the/purposes/of/testing" % basedir + oldfile = File.join(testdir, "oldfile") + assert_nothing_raised { + system("mkdir -p %s" % testdir) + File.open(oldfile, "w") { |f| + 3.times { f.puts rand(100) } + } + @@tmpfiles << basedir + } + + assert_nothing_raised { + server.mount(basedir, "test") + } + + list = nil + assert_nothing_raised { + list = server.list("/test/with", false) + } + + assert(list !~ /\n/) + + [0, 1, 2].each { |num| + assert_nothing_raised { + list = server.list("/test/with", num) + } + + count = 0 + #p list + while list =~ /\n/ + list.sub!(/\n/, '') + count += 1 + end + assert_equal(num, count) + } + end + + def test_listedpath + server = nil + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + basedir = "/tmp/remotefilecopying" + testdir = "%s/with/some/sub/directories/for/testing" % basedir + oldfile = File.join(testdir, "oldfile") + assert_nothing_raised { + system("mkdir -p %s" % testdir) + File.open(oldfile, "w") { |f| + 3.times { f.puts rand(100) } + } + @@tmpfiles << basedir + } + + assert_nothing_raised { + server.mount(basedir, "localhost") + } + + list = nil + assert_nothing_raised { + list = server.list("/localhost/with", false) + } + + assert(list !~ /with/) + + assert_nothing_raised { + list = server.list("/localhost/with/some/sub", true) + } + + assert(list !~ /sub/) + end + + def test_widelists + server = nil + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + basedir = "/tmp/remotefilecopying" + dirs = %w{a set of directories} + assert_nothing_raised { + Dir.mkdir(basedir) + dirs.each { |dir| + Dir.mkdir(File.join(basedir, dir)) + } + @@tmpfiles << basedir + } + + assert_nothing_raised { + server.mount(basedir, "localhost") + } + + list = nil + assert_nothing_raised { + list = server.list("/localhost/", 1).split("\n") + } + + assert_equal(dirs.length + 1, list.length) + end + + def test_describe + server = nil + testdir = "/tmp/remotefilecopying" + assert_nothing_raised { + Dir.mkdir(testdir) + @@tmpfiles << testdir + %w{a b c d e}.each { |l| + name = File.join(testdir, "file%s" % name) + File.open(name, "w") { |f| + f.puts rand(100) + } + } + } + + file = nil + checks = Puppet::FileServer::CHECKPARAMS + + assert_nothing_raised { + server = Puppet::FileServer.new( + :Local => true + ) + } + + assert_nothing_raised { + server.mount(testdir, "test") + } + + list = nil + sfile = "/test/" + assert_nothing_raised { + list = server.list(sfile, true) + } + + assert_nothing_raised { + list.split("\n").each { |line| + file, type = line.split("\t") + + desc = server.describe(sfile + file) + } + } + end +end + +# $Id$ + diff --git a/test/server/tc_master.rb b/test/server/tc_master.rb new file mode 100644 index 000000000..be61b232b --- /dev/null +++ b/test/server/tc_master.rb @@ -0,0 +1,135 @@ +if __FILE__ == $0 + if Dir.getwd =~ /test\/server$/ + Dir.chdir("..") + #puts "Unfortunately, you must be in the test dir to run this test." + #puts "Yes, I know it's different than all of the others." + #exit + end + + $:.unshift '../lib' + $:.unshift '../../../library/trunk/lib/' + $:.unshift '../../../library/trunk/test/' + $puppetbase = ".." + +end + +#if __FILE__ == $0 +# $:.unshift '../../lib' +# $:.unshift '../../../../library/trunk/lib/' +# $:.unshift '../../../../library/trunk/test/' +# $puppetbase = "../.." +#end + +require 'puppet' +require 'puppet/master' +require 'puppet/client' +require 'test/unit' +require 'puppettest.rb' + +class TestMaster < Test::Unit::TestCase + def setup + if __FILE__ == $0 + Puppet[:loglevel] = :debug + end + + @@tmpfiles = [] + end + + def stopservices + if stype = Puppet::Type.type(:service) + stype.each { |service| + service[:running] = false + service.sync + } + end + end + + def teardown + Puppet::Type.allclear + print "\n\n\n\n" if Puppet[:debug] + + @@tmpfiles.each { |file| + if FileTest.exists?(file) + system("rm -rf %s" % file) + end + } + end + + def test_files + Puppet[:debug] = true if __FILE__ == $0 + Puppet[:puppetconf] = "/tmp/servertestingdir" + @@tmpfiles << Puppet[:puppetconf] + textfiles { |file| + Puppet.debug("parsing %s" % file) + server = nil + client = nil + threads = [] + port = 8080 + master = nil + assert_nothing_raised() { + # this is the default server setup + master = Puppet::Master.new( + :File => file, + :Local => true + ) + } + assert_nothing_raised() { + client = Puppet::Client.new( + :Server => master + ) + } + + # pull our configuration + assert_nothing_raised() { + client.getconfig + stopservices + Puppet::Type.allclear + } + assert_nothing_raised() { + client.getconfig + stopservices + Puppet::Type.allclear + } + assert_nothing_raised() { + client.getconfig + stopservices + Puppet::Type.allclear + } + } + end + + def test_defaultmanifest + Puppet[:debug] = true if __FILE__ == $0 + Puppet[:puppetconf] = "/tmp/servertestingdir" + @@tmpfiles << Puppet[:puppetconf] + textfiles { |file| + Puppet[:manifest] = file + client = nil + master = nil + assert_nothing_raised() { + # this is the default server setup + master = Puppet::Master.new( + :File => file, + :Local => true + ) + } + assert_nothing_raised() { + client = Puppet::Client.new( + :Server => master + ) + } + + # pull our configuration + assert_nothing_raised() { + client.getconfig + stopservices + Puppet::Type.allclear + } + + break + } + end +end + +# $Id$ + diff --git a/test/server/tc_server.rb b/test/server/tc_server.rb new file mode 100644 index 000000000..d3a4ea305 --- /dev/null +++ b/test/server/tc_server.rb @@ -0,0 +1,364 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '../../../../library/trunk/lib/' + $:.unshift '../../../../library/trunk/test/' + $puppetbase = "../.." +end +#if __FILE__ == $0 +# $:.unshift '../lib' +# $:.unshift '../../../library/trunk/lib/' +# $:.unshift '../../../library/trunk/test/' +# $puppetbase = ".." +# +# +# if Dir.getwd !~ /test$/ +# puts "Unfortunately, you must be in the test dir to run this test." +# puts "Yes, I know it's different than all of the others." +# exit +# end +#end + +require 'puppet' +require 'cgi' +require 'puppet/server' +require 'facter' +require 'puppet/client' +require 'xmlrpc/client' +require 'test/unit' +require 'puppettest.rb' + +# $Id$ + +if ARGV.length > 0 and ARGV[0] == "short" + $short = true +else + $short = false +end + +class TestServer < Test::Unit::TestCase + def setup + if __FILE__ == $0 + Puppet[:loglevel] = :debug + #paths = Puppet::Type.type(:service).searchpath + #paths.push "%s/examples/root/etc/init.d" % $puppetbase + #Puppet::Type.type(:service).setpath(paths) + end + + @@tmpfiles = [] + @@tmppids = [] + end + + def stopservices + if stype = Puppet::Type.type(:service) + stype.each { |service| + service[:running] = false + service.sync + } + end + end + + def teardown + Puppet::Type.allclear + print "\n\n\n\n" if Puppet[:debug] + + @@tmpfiles.each { |file| + if FileTest.exists?(file) + system("rm -rf %s" % file) + end + } + @@tmppids.each { |pid| + system("kill -INT %s" % pid) + } + end + + def test_start + server = nil + Puppet[:ssldir] = "/tmp/serverstarttesting" + Puppet[:autosign] = true + @@tmpfiles << "/tmp/serverstarttesting" + port = 8081 + file = File.join($puppetbase, "examples", "code", "head") + assert_nothing_raised() { + server = Puppet::Server.new( + :Port => port, + :Handlers => { + :CA => {}, # so that certs autogenerate + :Master => { + :File => file, + }, + :Status => nil + } + ) + + } + sthread = nil + assert_nothing_raised() { + trap(:INT) { server.shutdown } + sthread = Thread.new { + server.start + } + } + sleep 1 + assert_nothing_raised { + server.shutdown + } + assert_nothing_raised { + sthread.join + } + end + + # disabled because i can't find a good way to test client connecting + # i'll have to test the external executables + def disabled_test_connect_with_threading + server = nil + Puppet[:ssldir] = "/tmp/serverconnecttesting" + Puppet[:autosign] = true + @@tmpfiles << "/tmp/serverconnecttesting" + threads = [] + port = 8080 + server = nil + Thread.abort_on_exception = true + assert_nothing_raised() { + server = Puppet::Server.new( + :Port => port, + :Handlers => { + :CA => {}, # so that certs autogenerate + :Status => nil + } + ) + + } + sthread = Thread.new { + assert_nothing_raised() { + #trap(:INT) { server.shutdown; Kernel.exit! } + trap(:INT) { server.shutdown } + server.start + } + } + + sleep(3) + client = nil + assert_nothing_raised() { + client = XMLRPC::Client.new("localhost", "/RPC2", port, nil, nil, + nil, nil, true, 3) + } + retval = nil + + clthread = Thread.new { + assert_nothing_raised() { + Puppet.notice "calling status" + retval = client.call("status.status", "") + } + } + assert_not_nil(clthread.join(5)) + + assert_equal(1, retval) + assert_nothing_raised { + #system("kill -INT %s" % serverpid) + server.shutdown + } + + assert_not_nil(sthread.join(5)) + + #Process.wait + end + + # disabled because i can't find a good way to test client connecting + # i'll have to test the external executables + def test_connect_with_fork + server = nil + Puppet[:ssldir] = "/tmp/serverconnecttesting" + Puppet[:autosign] = true + @@tmpfiles << "/tmp/serverconnecttesting" + serverpid = nil + port = 8080 + assert_nothing_raised() { + server = Puppet::Server.new( + :Port => port, + :Handlers => { + :CA => {}, # so that certs autogenerate + :Status => nil + } + ) + + } + serverpid = fork { + assert_nothing_raised() { + #trap(:INT) { server.shutdown; Kernel.exit! } + trap(:INT) { server.shutdown } + server.start + } + } + @@tmppids << serverpid + + sleep(3) + client = nil + assert_nothing_raised() { + client = XMLRPC::Client.new("localhost", "/RPC2", port, nil, nil, + nil, nil, true, 3) + } + retval = nil + + assert_nothing_raised() { + Puppet.notice "calling status" + retval = client.call("status.status") + } + + assert_equal(1, retval) + #assert_nothing_raised { + # system("kill -INT %s" % serverpid) + # #server.shutdown + #} + + #Process.wait + end + + # disabled because i can't find a good way to test client connecting + # i'll have to test the external executables + def test_zzgetconfig_with_fork + server = nil + Puppet[:ssldir] = "/tmp/serverconfigtesting" + Puppet[:autosign] = true + @@tmpfiles << "/tmp/serverconfigtesting" + serverpid = nil + port = 8082 + file = File.join($puppetbase, "examples", "code", "head") + assert_nothing_raised() { + server = Puppet::Server.new( + :Port => port, + :Handlers => { + :CA => {}, # so that certs autogenerate + :Master => { + :File => file + }, + :Status => nil + } + ) + + } + serverpid = fork { + assert_nothing_raised() { + #trap(:INT) { server.shutdown; Kernel.exit! } + trap(:INT) { server.shutdown } + server.start + } + } + @@tmppids << serverpid + + sleep(3) + client = nil + + # first use a puppet client object + assert_nothing_raised() { + client = Puppet::Client.new( + :Server => "localhost", + :Port => port + ) + } + retval = nil + + assert_nothing_raised() { + Puppet.notice "calling status" + retval = client.getconfig + } + + # then use a raw rpc client + assert_nothing_raised() { + client = XMLRPC::Client.new("localhost", "/RPC2", port, nil, nil, + nil, nil, true, 3) + } + retval = nil + + facts = CGI.escape(Marshal.dump(Puppet::Client.facts)) + assert_nothing_raised() { + Puppet.notice "calling status" + retval = client.call("puppetmaster.getconfig", facts) + } + + #assert_equal(1, retval) + end + + # disabled because clients can't seem to connect from in the same process + def disabled_test_files + Puppet[:debug] = true if __FILE__ == $0 + Puppet[:puppetconf] = "/tmp/servertestingdir" + Puppet[:autosign] = true + @@tmpfiles << Puppet[:puppetconf] + textfiles { |file| + Puppet.debug("parsing %s" % file) + server = nil + client = nil + threads = [] + port = 8080 + assert_nothing_raised() { + # this is the default server setup + server = Puppet::Server.new( + :Port => port, + :Handlers => { + :CA => {}, # so that certs autogenerate + :Master => { + :File => file, + }, + } + ) + } + assert_nothing_raised() { + client = Puppet::Client.new( + :Server => "localhost", + :Port => port + ) + } + + # start the server + assert_nothing_raised() { + trap(:INT) { server.shutdown } + threads << Thread.new { + server.start + } + } + + # start the client + #assert_nothing_raised() { + # threads << Thread.new { + # client.start + # } + #} + + sleep(1) + # pull our configuration + assert_nothing_raised() { + client.getconfig + stopservices + Puppet::Type.allclear + } + assert_nothing_raised() { + client.getconfig + stopservices + Puppet::Type.allclear + } + assert_nothing_raised() { + client.getconfig + stopservices + Puppet::Type.allclear + } + + # and shut them both down + assert_nothing_raised() { + [server].each { |thing| + thing.shutdown + } + } + + # make sure everything's complete before we stop + assert_nothing_raised() { + threads.each { |thr| + thr.join + } + } + assert_nothing_raised() { + stopservices + } + Puppet::Type.allclear + } + end +end @@ -7,14 +7,13 @@ $:.unshift '.' $:.unshift '../lib' -$:.unshift '../../../language/trunk/lib/' # if we're not in the library trunk, then add that to the lib search path -if Dir.getwd !~ %r{puppet/library} - $:.unshift '../../../library/trunk/lib/' - $:.unshift '../../../language/trunk/lib/' - $:.unshift '../../../library/trunk/test/' -end +#if Dir.getwd !~ %r{puppet/library} +# $:.unshift '../../../library/trunk/lib/' +# $:.unshift '../../../language/trunk/lib/' +# $:.unshift '../../../library/trunk/test/' +#end require 'puppettest.rb' @@ -37,7 +36,7 @@ result.each { |opt,arg| end } -$puppetbase = "../../../language/trunk" +$puppetbase = ".." suites = nil diff --git a/test/types/tc_file.rb b/test/types/tc_file.rb index 8c7316403..730bcfc8f 100644 --- a/test/types/tc_file.rb +++ b/test/types/tc_file.rb @@ -1,6 +1,7 @@ if __FILE__ == $0 $:.unshift '..' $:.unshift '../../lib' + $:.unshift "../../../../language/trunk/lib" $puppetbase = "../../../../language/trunk" end @@ -67,7 +68,7 @@ class TestFile < Test::Unit::TestCase Puppet::Storage.clear end - def test_zzowner + def test_owner file = mktestfile() users = {} @@ -138,6 +139,9 @@ class TestFile < Test::Unit::TestCase users.each { |uid, name| # just make sure we don't try to manage users assert_nothing_raised() { + file.sync + } + assert_nothing_raised() { file[:owner] = name } assert_nothing_raised() { @@ -151,7 +155,7 @@ class TestFile < Test::Unit::TestCase end end - def test_zzgroup + def test_group file = mktestfile() [%x{groups}.chomp.split(/ /), Process.groups].flatten.each { |group| assert_nothing_raised() { @@ -493,6 +497,8 @@ class TestFile < Test::Unit::TestCase trans.evaluate } + assert(FileTest.exists?(todir)) + clearstorage Puppet::Type.allclear end @@ -518,7 +524,7 @@ class TestFile < Test::Unit::TestCase return [fromdir,todir] end - def test_complex_sources_twice + def test_zzzcomplex_sources_twice fromdir, todir = run_complex_sources assert_trees_equal(fromdir,todir) recursive_source_test(fromdir, todir) @@ -528,6 +534,7 @@ class TestFile < Test::Unit::TestCase def test_sources_with_deleted_destfiles fromdir, todir = run_complex_sources # then delete some files + assert(FileTest.exists?(todir)) delete_random_files(todir) # and run @@ -539,6 +546,7 @@ class TestFile < Test::Unit::TestCase def test_sources_with_readonly_destfiles fromdir, todir = run_complex_sources + assert(FileTest.exists?(todir)) readonly_random_files(todir) recursive_source_test(fromdir, todir) @@ -549,6 +557,7 @@ class TestFile < Test::Unit::TestCase def test_sources_with_modified_dest_files fromdir, todir = run_complex_sources + assert(FileTest.exists?(todir)) # then modify some files modify_random_files(todir) @@ -560,6 +569,7 @@ class TestFile < Test::Unit::TestCase def test_sources_with_added_destfiles fromdir, todir = run_complex_sources + assert(FileTest.exists?(todir)) # and finally, add some new files add_random_files(todir) @@ -582,4 +592,84 @@ class TestFile < Test::Unit::TestCase # and make sure they're still equal assert_trees_equal(fromdir,todir) end + + def test_filetype_retrieval + file = nil + assert_nothing_raised { + file = Puppet::Type::PFile.new( + :name => "/tmp", + :check => :type + ) + } + + assert_nothing_raised { + file.evaluate + } + + assert_equal("directory", file.state(:type).is) + + assert_nothing_raised { + file = Puppet::Type::PFile.new( + :name => "/etc/passwd", + :check => :type + ) + } + + assert_nothing_raised { + file.evaluate + } + + assert_equal("file", file.state(:type).is) + + assert_raise(Puppet::Error) { + file[:type] = "directory" + } + + assert(file.insync?) + + assert_raise(Puppet::Error) { + file.sync + } + end + + def test_RecursionWithAddedFiles + basedir = "/tmp/recursionplussaddedfiles" + Dir.mkdir(basedir) + @@tmpfiles << basedir + file1 = File.join(basedir, "file1") + file2 = File.join(basedir, "file2") + subdir1 = File.join(basedir, "subdir1") + file3 = File.join(subdir1, "file") + File.open(file1, "w") { |f| 3.times { f.print rand(100) } } + rootobj = nil + assert_nothing_raised { + rootobj = Puppet::Type::PFile.new( + :name => basedir, + :recurse => true, + :check => %w{type owner} + ) + + rootobj.evaluate + } + + klass = Puppet::Type::PFile + assert(klass[basedir]) + assert(klass[file1]) + assert_nil(klass[file2]) + + File.open(file2, "w") { |f| 3.times { f.print rand(100) } } + + assert_nothing_raised { + rootobj.evaluate + } + assert(klass[file2]) + + Dir.mkdir(subdir1) + File.open(file3, "w") { |f| 3.times { f.print rand(100) } } + + assert_nothing_raised { + rootobj.evaluate + } + assert(klass[file3]) + end end |